Notifications
Clear all

[Closed] Getting the Parent Root of a hierarchy

Hi everyone,

I’m trying to find a way to select an object in a hierarchy and find the root node of the hierarchy that node is part of.

Example: Box01 child of Box02 that is a child of Box03, that’s a child of Box04

By using Box01, how can I find Box04 (pretending i don’t know that Box04 is the parent of it all)

The .parent property only looks for the direct parent of the node. I’d like to be able to find the top node of the hierarchy.

Thanks and have a good day

17 Replies

Hi Guibou, here is a function to get the root of a node hierarchy. Feed it with a node and it returns the root, the node itself if it has no parent.

function getNodeRoot theNode =
(
    if (isValidNode theNode) do
    (
        local theParent = theNode.parent

        while (theParent != undefined) do
        (
            theNode = theParent
            theParent = theNode.parent
        )
    )
    
    return theNode
)
  • Enrico

What Enrico writes is absolutely right. It can be a little shorter/faster though:

function getNodeRoot theNode =
(
    local theParent = theNode;
    while (isValidNode theParent.parent) do
    (
       theParent = theParent.parent;
    )
    theParent; --return value;
)

Hey Pier, I like your optimization. I’m always intrigued by compact code. I run some performance tests on our functions, with a structure I wrote some time ago, and even if yours saves some typing, they provide the same results in terms of execution speed, milliseconds difference is irrelevant and variable.

Test Structure:

struct PerformanceTester
(
    iStartTime = 0,
    iStartMem = 0,

    iStopTime = 0,
    iStopMem = 0,
    iStopMemDelta = 0,

    iCycle = 0,

    iResTime = 0,
    iResMem = 0,
    iResMemDelta = 0,

    function start =
    (
        gc()
        iCycle += 1
        iStartTime = timeStamp()
        iStartMem = heapSize - heapFree
    ),

    function stop =
    (
        iStopTime = timeStamp()
        iResTime += (iStopTime - iStartTime) / 1000.0

        iStopMem = heapSize - heapFree
        iResMem += (iStopMem - iStartMem) / 1024.0

        gc()
        iStopMemDelta = heapSize - heapFree
        iResMemDelta += (iStopMemDelta - iStartMem) / 1024.0
    ),

    function report =
    (
        format "Average processing on % iteration(s) took:
" iCycle
        format "% seconds
" (iResTime / iCycle)
        format "% Kbytes used
" (iResMem / iCycle)
        format "% Kbytes delta

" (iResMemDelta / iCycle)

        iCycle = 0
        iResTime = 0
        iResMem = 0
        iResMemClean = 0
    )
)

Tests:

(
    function getNodeRoot1 theNode =
    (
        if (isValidNode theNode) do
        (
            local theParent = theNode.parent

            while (theParent != undefined) do
            (
                theNode = theParent
                theParent = theNode.parent
            )
        )

        return theNode
    )

    function getNodeRoot2 theNode =
    (
        local theParent = theNode;
        while (isValidNode theParent.parent) do
        (
           theParent = theParent.parent;
        )
        theParent; --return value;
    )

    local theWatch = PerformanceTester()


    local theSpheres = #()
    local iNumSpheres = 2000

    for i = 1 to iNumSpheres do
        append theSpheres (Sphere pos:[i*10, 0, 0])

    for i = iNumSpheres to 2 by -1 do
        theSpheres[i].parent = theSpheres[i-1]


    for i = 1 to 100 do
    (
        theWatch.start()
        getNodeRoot1 theSpheres[iNumSpheres]
        theWatch.stop()
    )
    theWatch.report()

    for i = 1 to 100 do
    (
        theWatch.start()
        getNodeRoot2 theSpheres[iNumSpheres]
        theWatch.stop()
    )
    theWatch.report()


    for s in theSpheres do
        delete s
)

Results:

Average processing on 100 iteration(s) took:
0.00263 seconds
0.226563 Kbytes used
-0.105469 Kbytes delta

Average processing on 100 iteration(s) took:
0.0028 seconds
0.226563 Kbytes used
-0.213125 Kbytes delta
  • Enrico

p.s.
I’m eagerly waiting your Outliner 2.0!

edit: nm

If you write the code out over multiple lines it may become more clear:

fn getRoot node = (
  --Check if the node passed to the function is a valid node.
  if isvalidnode node do 
  (
    --Loop through the hierarchy until the current node's parent is undefined (i.e. rootnode)
    while node.parent != undefined do node = node.parent; 
    --Return the rootnode
    node
  )
)

This just declares a function, which gives the ‘OK’ you mentioned. To call it, just do something like:

getRoot $Sphere01
3 Replies
(@denist)
Joined: 11 months ago

Posts: 0

thank you, Pier… using phone keypad is problematic to right multyline code.

(@papigiulio)
Joined: 11 months ago

Posts: 0

Have been trying to get this to work but am about to give up. No matter what I do, how I edit the code, it doesnt do anything.

However thank you for the explanation, it was way more clear what the piece of code was doing.

In the meantime im gonna bust my head on this piece of code again and try get it to work.

(@pjanssen)
Joined: 11 months ago

Posts: 0

If you like recursion you should give functional programming a go, for example with Haskell or Erlang. It’s lovely!

Maybe you should show the exact code you’re trying to run, so we can provide some help.

fn getRoot node = if isvalidnode node.parent then getRoot node.parent else node

i like recursion

1 Reply
(@denist)
Joined: 11 months ago

Posts: 0

everyone likes recursion but recursion is not so friendly… compare my function and yours for memory use. but before that please add the check for node validity:

fn getRoot node = if isvalidnode node and isvalidnode node.parent then getRoot node.parent else node

I also like your way with the exception of the validation part

1 Reply
(@denist)
Joined: 11 months ago

Posts: 0

the validation is necessary. you have to do it anyway before calling the function or inside the function. the asking of node’s parent where the node is undefined or deleted will break the function.

you probably have to know that any recursive function has a limit by the stack of recursions. usually the size of this stack defined by the system (compiler, etc.) . i don’t very well know about the limits but in MXS the stack is about ~500 recursions. it might be less or more for different situations, but never less than 200 and never more than 1000…

here is a sample where my method without recursion works well, but the recursive function crashes the max:


(
	delete objects
	count = 400 -- (400 still works, but 800 crashes)
	local node 
	for k=1 to count do in node (node = box name:(k as string))
	gc()
)

fn getRoot node = if isvalidnode node do 
(
	while node.parent != undefined do node = node.parent
	node
)
 
fn getRootRecursive node = if isvalidnode node and isvalidnode node.parent then getRootRecursive node.parent else node
	
/*

getRoot objects[objects.count]

getRootRecursive objects[objects.count]

*/

1 Reply
(@dokdan85)
Joined: 11 months ago

Posts: 0

yep you’re right, recursion has problems and programmer should always care about depth of recursion and think that it can make stack overflow.

but some problems (like this with finding parent of parent of parent…) so classic for exercising recursion solution even if it’s not the best.

well, as you’re veteran and i continue to learn new things from you, (from this thread for example, i see that the limit of recursion is more lower than i expected), so somehow cannot imagine that you’re not aware about the evil behind this practice.
(no hidden offence to you here, i whish to know)

so i’ll post some brief explanation about, not to you, but for the all folks who are new to the programming at all.

1st to say that the good programming practice rules are too complex, they’re linked to each other and intersect each other. so any isolated explanation of concrete rule is not easy, nor yet very correct. for example, in our case, to not using validation of passed params/arguments within the function is just a small part of more bigger rule saying “do not make mixin responsibility inside reusable code”. skipping pre-validation (outside of the function) is an other bad habits, and so on.

well, unlikely i’m good in such explanations, but lets i try anyway…

so if we pre-validate already, that come as redundant code and redundant calculation. but if we plan to omit pre-validation, what happens then when invalid argument come? the function will not fail, but will return invalid result. its not something local to the concrete function. i’m pretty sure no one will inclide validaion in simple function like: “fn add n1 n2 = (n1 + n2)”. but if we do that, how this will improve the output result? what it will return in case n1 or n2 are not numeric values? how we’ll use that result after all?

ok, back to GetRoot example where we expect object as result. in maxscript no void functions (w/o result), and maybe in case the result of the function is not important… but once we start to make exceptions…

ok, no extra scopes. here we need the result, and can we use it without post-validation (outside of the function)? if doing this may put well hidden bug. shortly, post-validation is bad style (or bad idea, if this sound better). and omission of pre-validation is bad habit as well even in simple stay alone solutions.

i hope all this make sense (in the way i write it)

Page 1 / 2