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

    Python tag initalization?

    Cinema 4D SDK
    python 2024
    3
    7
    1.1k
    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.
    • gaschkaG
      gaschka
      last edited by

      Hello everyone,

      I'm new here 🙂

      I've a python script that creates a Python tag and injects some code into it. This Python tag is then searching in the hierarchy for objects to link to. This search is in the main() so I guess it executing every frame/interaction, which doesn't sound very performant, especially as it will relink the same object over and over again.

      Is there a way to initialize the python tag somehow, so in the example below, crtl_01 gets assigned just a single time, and then keeps its value without updating it?

      Is initialization the correct term for this?

      The code looks similar to this:

      import c4d
      
      def get_object_from_python_tag(tag):
          if tag and tag.GetType() == c4d.Tpython:
              linked_object = tag.GetMain()
              if linked_object:
                  return linked_object
          return None
      
      def get_parent_object(obj):
          return obj.GetUp()
      
      def find_object_in_hierarchy_by_name(current_obj, target_name):
          if current_obj.GetName() == target_name:
              return current_obj
      
          child = current_obj.GetDown()
          while child:
              result = find_object_in_hierarchy_by_name(child, target_name)
              if result:
                  return result
              child = child.GetNext()
      
          return None
      
      def main():
          python_null = get_object_from_python_tag(op)
      
          ctrl_01 = find_object_in_hierarchy_by_name(get_parent_object(python_null), "Helper_01")
      
      # Execution
      if __name__ == '__main__':
          main()
      
      1 Reply Last reply Reply Quote 0
      • M
        m_adam
        last edited by m_adam

        Hi @gaschka, there is multiple way to set it up.

        1. Do as you do currently, in each execution, check if the object are the same if not update it. I would say there is nothing really wrong about it.
        2. If you want the user to have the ability to change it, just create a link user data.
          Then either your tag or the script that create the tag, have the duty to define it and within your execute method you do as you do currently and check if the link from the user data should be updated or not.
        3. The name is hardcoded, you can react to the MSG_MENUPREPARE in the message function of a tag like so:
        import c4d
        
        CTRL_NAME = "Helper_01"
        
        def find_object_in_hierarchy_by_name(current_obj, target_name):
            if current_obj.GetName() == target_name:
                return current_obj
        
            child = current_obj.GetDown()
            while child:
                result = find_object_in_hierarchy_by_name(child, target_name)
                if result:
                    return result
                child = child.GetNext()
        
            return None
        
        def cache_ctrl_01(target_name):
            python_null = op.GetObject()
            ctrl_01 = find_object_in_hierarchy_by_name(python_null.GetUp(), target_name)
            op[10001] = ctrl_01
        
        def message(id, data):
            if id == c4d.MSG_MENUPREPARE:
                cache_ctrl_01(CTRL_NAME)
        
        def main():
            ctrl_01 = op[10001]
            # Check if the object exist and is not deleted retrieve it
            if ctrl_01 is None or not ctrl_01.IsAlive():
                cache_ctrl_01(CTRL_NAME)
                ctrl_01 = op[10001]
        
                # If its still None, then something is wrong raise an error in the Python Console
                if ctrl_01 is None or not ctrl_01.IsAlive():
                    raise ValueError(f"Unable to find {CTRL_NAME}")
        
            print(op[10001])
        

        op[10001] will write into the basecontainer of op (the tag). A BaseContainer is like a dict in python, except that it's native to Cinema 4D and used all over the place in Cinema 4D. For example the parameter of an object,tags,material,document are stored in a BaseContainer. When you pass some settings to execute a command, you pass a BaseContainer with the appropriate value in it. I've used the id 10001 as any ID above 10000 are safe to use and will not conflict with internal stuff, but if you really want to be safe, you should register a plugin id in plugincafe we did not yet migrated pluginId to this new website so you will need an old account, the migration will happen in the upcoming weeks/months.

        Then the code to create your tag should be:

        import c4d
        
        SOME_PYTHON_TAG_CODE = """import c4d
        
        CTRL_NAME = "Helper_01"
        
        def find_object_in_hierarchy_by_name(current_obj, target_name):
            if current_obj.GetName() == target_name:
                return current_obj
        
            child = current_obj.GetDown()
            while child:
                result = find_object_in_hierarchy_by_name(child, target_name)
                if result:
                    return result
                child = child.GetNext()
        
            return None
        
        def cache_ctrl_01(target_name):
            python_null = op.GetObject()
            ctrl_01 = find_object_in_hierarchy_by_name(python_null.GetUp(), target_name)
            op[10001] = ctrl_01
        
        def message(id, data):
            if id == c4d.MSG_MENUPREPARE:
                cache_ctrl_01(CTRL_NAME)
        
        def main():
            ctrl_01 = op[10001]
            # Check if the object exist and is not deleted retrieve it
            if ctrl_01 is None or not ctrl_01.IsAlive():
                cache_ctrl_01(CTRL_NAME)
                ctrl_01 = op[10001]
        
                # If its still None, then something is wrong raise an error in the Python Console
                if ctrl_01 is None or not ctrl_01.IsAlive():
                    raise ValueError(f"Unable to find {CTRL_NAME}")
        
            print(op[10001])
        """
        
        def add_python_tag(obj):
            if not isinstance(obj, c4d.BaseObject):
                return None
        
            python_tag = obj.MakeTag(c4d.Tpython)
            doc.AddUndo(c4d.UNDOTYPE_NEWOBJ, python_tag)
            python_tag[c4d.TPYTHON_CODE] = SOME_PYTHON_TAG_CODE
            python_tag.Message(c4d.MSG_MENUPREPARE)
        
            return python_tag
        
        # Execution
        if __name__ == '__main__':
            doc.StartUndo()
            add_python_tag(op)
            doc.EndUndo()
            c4d.EventAdd()
        

        Cheers,
        Maxime.

        MAXON SDK Specialist

        Development Blog, MAXON Registered Developer

        gaschkaG 1 Reply Last reply Reply Quote 3
        • M m_adam referenced this topic on
        • gaschkaG
          gaschka
          last edited by

          @m_adam thanks a lot for the explaination and the code. Messages is a new concept for me and something I'll definitely explore.

          Referring to point 1: So checking if the object is changed, even with the OM traversal, is something I can ignore performance wise? I'm asking, because I want the tag to control a custom piece of a character rig, perhaps with multiple copies of it. Rigs tend to be quite performance critical and running single threaded in C4D due to the OM so I wonder if this kind of search in the OM can have a significant impact?

          bacaB 1 Reply Last reply Reply Quote 0
          • bacaB
            baca @gaschka
            last edited by

            @gaschka
            I can think like hundreds of operations are nothing for the Python
            When it counts as thousands — you'll notice that.

            Optimizations requires some knowledge of Cinema tech side, to listen events, understand execution context, and so on.
            Because there might be multiple scenarios, which you're not expecting in your "optimal" logic.
            Main issue is the user — who will delete your key tags/objects, move objects within hierarchy, wrap objects into a null, and so on

            1 Reply Last reply Reply Quote 0
            • gaschkaG
              gaschka
              last edited by

              @baca thanks for the insights! I guess I sit too long next to game devs who have a tendency for premature optimizations and got infected 😂
              But I can second that: I know so many C4D artists who don't have a clue, neither they care, about how to keep theirs scene performant.

              1 Reply Last reply Reply Quote 0
              • M
                m_adam
                last edited by

                OM is not single threaded, see Threading Manual all operations of a scene execution are not done in the same thread / main thread. The main bottleneck will be the execution of Python by itself. A Xpresso setup may be faster but is single threaded and I don't think will let you do the same thing.

                With that's said if you know that the object will change a lot and you will need to perform the search over and over then yes you may rethink your approach and probably use a link field, so the user can rewire the object instead of you searching for it but what you are doing currently is fine.

                MAXON SDK Specialist

                Development Blog, MAXON Registered Developer

                1 Reply Last reply Reply Quote 0
                • gaschkaG
                  gaschka @m_adam
                  last edited by

                  @m_adam

                  @m_adam said in Python tag initalization?:

                  OM is not single threaded,

                  Oh, that's an interesting insight for me. Perhaps I was misinformed, as Character Rigs had the tendency to get slow quite easily in C4D, and Animators look envy over to Maya, as they do have a parallel evaluation (and caching). Though I'm aware that there are changes/improvements to C4D performance lately. Thanks for the link. A lot of new information to learn an absorb 🙂

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