It’s the exact method fromthis postin this thread but with your diagnostic scene inserted. For the 50 teapots your code creates I’m seeing the 200 resulting elements as a result.
My machine is a few years old and definitely not the fastest.
Here’s the complete code to run this test, including my detach algorithm
/*
diagnostic by denisT
http://forums.cgsociety.org/showpost.php?p=7825173&postcount=9
*/
fn fastAttach nodes =
(
k = 1
attach = polyop.attach
while nodes.count > 1 do
(
k += 1
attach nodes[k-1] nodes[k]
deleteItem nodes k
if k >= nodes.count do k = 1
)
nodes[1]
)
fn firstBit arr =
(
local b
for n in arr while
(
b = n
false
) do ()
b
)
function fn_detachElementsByDeleting arrObject &collection theName:"Klaas"=
(
/*<FUNCTION>
Description
detaches elements as separate objects from a poly, retains object name, material and uv coordinates
splits the object in half recursively until elements can't be detached anymore
Arguments
<array> arrObject: the array of objects we're splitting
<array by reference> collection: if an object can't be split anymore, add it to this array.
Return
<FUNCTION>*/
--store some methods
local polyop_getElementsUsingFace = polyop.getElementsUsingFace
local polyop_deleteFaces = polyop.deleteFaces
setCommandPanelTaskMode #create
with undo off with redraw off
(
for theNode in arrObject where iskindof theNode Editable_Poly do
(
local numfaces = polyop.getNumFaces theNode
local faceList = #{1..numfaces} --initialize the facelist
local arrElementFaceIndices = #() --an array of bitarrays. Each bitarray represents faces of a single element
--get all elements, biggest memory issue arises here
while NOT facelist.isEmpty do
(
local elementFaces = polyop_getElementsUsingFace theNode (firstBit faceList)
append arrElementFaceIndices elementFaces
facelist -= elementFaces
)
if arrElementFaceIndices.count > 1 then --if the object has more than one element we want to split it
(
--get half of the elements
local halfOfTheElements = #{1..numfaces}
for n = 1 to arrElementFaceIndices.count by 2 do halfOfTheElements -= arrElementFaceIndices[n]
--create two halves from one object
local theHalf = copy theNode
polyop_deleteFaces theHalf -halfOfTheElements delIsoVerts:true --delete unneeded faces from theHalf
polyop_deleteFaces theNode halfOfTheElements delIsoVerts:true --delete the processed faces from the original object
--set some properties
theHalf.name = theName--theNode.name
theHalf.wirecolor = random blue yellow
theNode.layer.addNode theHalf
--recurse
fn_detachElementsByDeleting #(theNode,theHalf) &collection theName:theName
)else --if there's one element in the object, collect it
(
converttopoly theNode --idea by polytools3d at least returns the memory taken http://forums.cgsociety.org/showpost.php?p=7825138&postcount=8
append collection theNode
)
)
)
)
gc()
max create mode
delete objects
(
count = 50
seed 0
node = fastAttach \
(
for k=1 to count collect
(
converttopoly (teapot radius:(random 1 10) segments:(random 10 24) pos:(random -[20,20,20] [20,20,20]))
)
)
numfaces = node.numfaces
clearListener()
gc()
st = timeStamp()
mem = heapFree
collection = #()
fn_detachElementsByDeleting #(node) &collection theName:node.name
format "Time: % ms, memory: %
Faces: %
" (timestamp()-st) (mem-heapfree) numfaces
)
ok. now i see what is going on.
the advantage you have is because of using deletefaces method with undo off. with undo off after deletefaces the poly makes a simple (geo and topology) update only. which makes everything faster. with undo ON the same method is about two time slower (what actually i was expecting)
well. the code below is what i used. which is very similar to yours but slightly polished during many years of using.
colored in red was just recently added as a result of the performance investigation. but it keeps the function undoable.
here is a complete solution with test scene setup:
fn fastAttach nodes =
(
k = 1
attach = polyop.attach
while nodes.count > 1 do
(
k += 1
attach nodes[k-1] nodes[k]
deleteItem nodes k
if k >= nodes.count do k = 1
)
nodes[1]
)
fn firstBit arr =
(
local b
for k in arr while (b = k) == 0 do ()
b
)
mapped fn splitNode node nodes:#() = if iskindof node Editable_Poly do
(
appendifunique nodes node
local faces = node.faces as bitarray
local half = node.numfaces/2
getelementsusingface = polyop.getelementsusingface
deletefaces = polyop.deletefaces
elements = #{}
while elements.numberset < half and (f = firstBit faces) != undefined do
(
ff = getelementsusingface node f
faces -= ff
if not faces.isempty do join elements ff
)
if not elements.isempty do
(
n = copy (converttopoly node) wirecolor:node.wirecolor
undo off
(
deletefaces n -elements
deletefaces node elements
)
splitNode #(node, n) nodes:nodes
)
nodes
)
gc()
max create mode
delete objects
(
count = 30
seed 0
submats = 24
mat = MultiMaterial numsubs:submats
for m in mat.materiallist do m.diffuse = random orange green
node = fastAttach (for k=1 to count collect
(
n = converttopoly (teapot radius:(random 1 10) segments:(random 10 24) pos:(random -[20,20,20] [20,20,20]))
polyop.setfacematid n #all (random 1 submats)
n
)
)
node.material = mat
numfaces = node.numfaces
gc light:on
format "detaching... %
" numfaces
t1 = timestamp()
m1 = heapfree
nodes = splitNode node --detachElements node
format "faces:%(%)
time:%
memory:%
" numfaces objects.count (timestamp() - t1) (m1 - heapfree)
select nodes
)
the main difference you can see that on every splitting call i collect just only a half of elements of a processing node. which in case of nodes made with many small elements gives a real advantage.
Aren’t you collecting the elements of half of the faces in a node? Possibly these faces could get you all elements or only one with some bad luck, isn’t it? Collecting half of the faces doesn’t guarantee half of the elements. Have you ever had issues with this? Or is it not so big a deal?
Anyway, a big thanks for the help and insights, yet again.
there is no any problem there. i’m just trying to collect a half faces in elements. but of course it’s possible the only one element in mesh, or last picked element takes all rest faces. but as you see i double-check this situation.
the idea is very similar to yours: leave a half with source node and another half with a copy. i just spot collecting elements when a half was reached.
Denis,
The place where you put convertopoly doesn’t help too much, and in case of in case of large scenes you might run out of RAM. Try with 150 nodes and watch the system memory.
You would need to gc() to reclaim the memory, otherwise it keeps leaking every time you run the function.
convertopoly is used there to make the function undoable. if the node is already Editable_Poly it shouldn’t take any time or extra memory usage.
i usually call gc light:on after using of any function that needs a memory (or leaks)
Isn’t your test scene using editable_poly? If so, then the place where you put “converttopoly” does exactly the contrary to what you say, it makes the function slower and it leaks memory.
Also, gc light:on is of no help here.
At list on my end.
Or let me rephrase it, is not that the function leaks because of “converttopoly”, but the use of it was supposed to be to prevent the memory leaking, and in the place it is now it is not doing the job.
using of converttopoly doesn’t and can’t prevent any leaking, but it also doesn’t cause extra leaking.
as i said i add it just to make the function undoable. yes, it drops the performance (~15% on my machine), but this is the price we have to pay for ‘undoability’. i couldn’t find a cheaper solution.
i’ve found…
it’s enough to do ‘converttopoly’ just for only fist (source) node… no extra leaking, no slow-down, undoable:
mapped fn splitNode node nodes:#() convert:on = if iskindof node Editable_Poly do
(
if convert do converttopoly node
appendifunique nodes node
local faces = node.faces as bitarray
local half = node.numfaces/2
getelementsusingface = polyop.getelementsusingface
deletefaces = polyop.deletefaces
elements = #{}
while elements.numberset < half and (f = firstBit faces) != undefined do
(
ff = getelementsusingface node f
faces -= ff
if not faces.isempty do join elements ff
)
if not elements.isempty do
(
n = copy node wirecolor:node.wirecolor
undo off
(
deletefaces n -elements
deletefaces node elements
)
splitNode #(node, n) nodes:nodes convert:off
)
nodes
)
You’ve found… Finally!
Faster yes, by ±30%, but the memory leaking is still there. The only way to reclaim that memory is by gc(). The optional light:on doesn’t work
Try count=500 or run the code multiple times without gc()
BTW, what do you mean by undoable? If I undo the operation I get a clean scene, it just deletes everything, but does not restore the original node.
the function leaks without ‘converttopoly’ as well. it needs some investigation about what mostly causes that.
undoable… you are running probably ‘complete’ sample. just split it on creation and execution parts, and you will see that the split function is undoable:
gc()
max create mode
delete objects
_node =
(
count = 30
seed 0
submats = 24
mat = MultiMaterial numsubs:submats
for m in mat.materiallist do m.diffuse = random orange green
node = fastAttach (for k=1 to count collect
(
n = converttopoly (teapot radius:(random 1 10) segments:(random 10 24) pos:(random -[20,20,20] [20,20,20]))
polyop.setfacematid n #all (random 1 submats)
n
)
)
node.material = mat
numfaces = node.numfaces
gc light:on
format "detaching... %
" numfaces
node
)
/*
(
t1 = timestamp()
m1 = heapfree
nodes = splitNode _node
format "faces:%(%)
time:%
memory:%
" numfaces objects.count (timestamp() - t1) (m1 - heapfree)
select nodes
)
*/
Yes, that was the OP problem, and converttopoly was used to fix it not to cause it, although I might have expressed myself wrong in some previous post, my intention was not to suggest that the memory leaking was caused by convertopoly, but that it was not working in reclaiming the memory, as it does in Klaas code.
Yes Denis, that’s the source of the problem and probably deleting the faces is not doing the job correctly neither.
Perhaps there is another function that forces the editable_poly to update, that also update the collected instances?
Or perhaps a different approach can do the work in a different way.
the main leaking is caused by ‘copy node’ and ‘collect elements’. i don’t think we can do anything with that.