Execute python tag only when hierarchy changes

Hi, I have a python tag on an object that sets the objects children to the same layer as the object. But there is no necessity to execute the python tag every frame during playback. I was wondering how to tell the tag to only execute the main part of the code, when there is a change in the overall hierarchy or better yet: only if the children of the object changes.

It feels like there is a simple solution to this that I am not seeing. I am grateful for any help!

import c4d
from c4d import gui

##################################################
#    This tag sets all children of the object it is on
#    to the layer that object is on.
##################################################

def main():
    global layer
    
    obj = op.GetObject()

    # This somehow always return 0, regardless of childrens hierarchy or parameter changes.
    print ("children: " + str( obj.GetDirty(c4d.DIRTYFLAGS_CHILDREN) ))
    
    # This also always return 0, regardless of childrens hierarchy or parameter changes.
    print ("description: " + str( obj.GetDirty(c4d.DIRTYFLAGS_DESCRIPTION) ))
    
    # This increments by one on basically every click anywhere.
    print ("data: " + str(  obj.GetDirty(c4d.DIRTYFLAGS_DATA) ))


    layer = obj.GetLayerObject(doc)

    allchildren(obj, obj.GetNext())

def allchildren(obj,next): # Scan obj hierarchy and set childrens layer
    while obj and obj != next:
        if obj:
            obj.SetLayerObject(layer)
        allchildren(obj.GetDown(),next)
        obj = obj.GetNext()

    return True

Hi @robotcopatrick unfortunately this is one of the biggest flaws in Cinema 4D and there is no way to prevent this. (And that's why we are moving to a new Node Based approach with clear dependencies and Observable for each notification).

So regarding your question, the best approach would be to return as soon as possible.
Unfortunately, there is nothing really magic you can do here, maybe you can build a hierarchy of checksum for each object. But you will still need to iterate over the hierarchy (which is the slow part of your code).

So another idea could be to execute your script each 3/4 each scene execution and also preventing it to be executed while animation is played. So something like that:

import c4d

global exeCount, oldFrame
exeCount = 0
oldFrame = None


def GetCurrentFrame():
    doc = op.GetDocument()
    fps = doc.GetFps()
    currentFrame = doc.GetTime().GetFrame(fps)

    return currentFrame

def main():
    global exeCount, oldFrame

    # Disable Based on execution count
    if exeCount != 3:
        exeCount += 1
        return

    exeCount = 0

    # Disable based on animation
    bd = doc.GetActiveBaseDraw()
    if bd is None:
        return

    currentFrame = GetCurrentFrame()
    if currentFrame != oldFrame:
        oldFrame = currentFrame
        return

    print('exe')

If you have any questions, please let me know.
Cheers,
Maxime.

@m_adam Thank you for your idea and for even providing a sample code. Simple but brilliant idea to check for the frame difference to see if cinema is playing back. This will help optimize some other scripts of mine and be useful in the future. Side note: I didn't quite understand why in this case i would use the exeCount but it may be helpful in other scripts. I ended up with this and am quite happy with the solution. Thanks again!

import c4d
from c4d import gui

##################################################
#    This tag sets all children of the object it is on
#    to the layer that object is on.
##################################################

global oldFrame
oldFrame = None

def GetCurrentFrame():
    doc = op.GetDocument()
    fps = doc.GetFps()
    currentFrame = doc.GetTime().GetFrame(fps)
    return currentFrame

def main():
    global layer, oldFrame

    # dont execute if there is no base draw
    bd = doc.GetActiveBaseDraw()
    if bd is None:
        return

    # dont execute during playback
    currentFrame = GetCurrentFrame()
    if currentFrame != oldFrame:
        oldFrame = currentFrame
        return

    # ELSE: Run the rest of the script normally
    obj = op.GetObject()
    layer = obj.GetLayerObject(doc)
    useName = obj[c4d.ID_USERDATA,1]

    # option: set objects name to layer name
    if layer and useName:
        layerName = layer.GetName()
        obj.SetName(layerName)

    # get all children and sets layer to objects layer
    allchildren(obj, obj.GetNext())

def allchildren(obj,next):
    while obj and obj != next:
        if obj:
            obj.SetLayerObject(layer)
        allchildren(obj.GetDown(),next)
        obj = obj.GetNext()
    return True

This is 2 separated "optimization", one to prevent running the script when an animation is played.
The first one about the exeCount prevents running the script for each scene evaluation (aka each action, e.g user click on the object manager, select one object than another then another one, its pointless to execute your script each time)

It's a pretty dumb approach, I admit but its one 😛

Cheers,
Maxime.