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 theexeCount
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.