Maxon Developers Maxon Developers
    • Documentation
      • Cinema 4D Python API
      • Cinema 4D C++ API
      • Cineware API
      • ZBrush GoZ API
      • Code Examples on Github
    • Forum
    • Downloads
    • Support
      • Support Procedures
      • Registered Developer Program
      • Plugin IDs
      • Contact Us
    • Categories
      • Overview
      • News & Information
      • Cinema 4D SDK Support
      • Cineware SDK Support
      • ZBrush 4D SDK Support
      • Bugs
      • General Talk
    • Unread
    • Recent
    • Tags
    • Users
    • Login

    Execute python tag only when hierarchy changes

    Cinema 4D SDK
    2
    4
    607
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • R
      robocopatrick
      last edited by

      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
      
      1 Reply Last reply Reply Quote 0
      • M
        m_adam
        last edited by

        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.

        MAXON SDK Specialist

        Development Blog, MAXON Registered Developer

        R 1 Reply Last reply Reply Quote 0
        • R
          robocopatrick @m_adam
          last edited by

          @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
          
          1 Reply Last reply Reply Quote 0
          • M
            m_adam
            last edited by

            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.

            MAXON SDK Specialist

            Development Blog, MAXON Registered Developer

            1 Reply Last reply Reply Quote 1
            • First post
              Last post