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 Generator - Linking ports to xpresso node

    Cinema 4D SDK
    3
    14
    2.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.
    • M
      moGRR
      last edited by

      Hi all.

      I’m wanting to use a python generator to create an xpresso tag that then links user data to an object’s attribute.

      I’ve got as far as being able to add the xpresso tag and have added the object node in the xpresso tag, but I’m having no joy with adding ports and linking them.

      Could anyone show me a simple example that does this?

      Many thanks,
      Jamie

      1 Reply Last reply Reply Quote 0
      • ferdinandF
        ferdinand
        last edited by ferdinand

        Hi,

        I am not quite sure if I do understand your question correctly, but I assume you want to to know how to connect two c4d.modules.graphview.GvPorts?

        """An example for creating and connecting graphview nodes and ports. The
         script is meant to be run form the Script Manager and has no prerequisites.
        """
        
        import c4d
        
        def main():
            """
            """
            # Create a null object and add an Xpresso tag to it.
            null = c4d.BaseList2D(c4d.Onull)
            xpresso_tag = c4d.BaseList2D(c4d.Texpresso)
            null.InsertTag(xpresso_tag)
            
            # Get the node master and the root noode of that graphview.
            master = xpresso_tag.GetNodeMaster()
            root = master.GetRoot()
        
            # Create and add a math and constant node to that graphview
            const_node = master.CreateNode(root, c4d.ID_OPERATOR_CONST, x=10, y=10)
            math_node = master.CreateNode(root, c4d.ID_OPERATOR_MATH,  x=200, y=10)
            
            # Get all out ports of the constant node and select the first one.
            const_node_out = const_node.GetOutPorts()
            const_node_out = const_node_out[0] if const_node_out else None
        
            # Get all in ports of the math node and select the first one.
            math_node_in = math_node.GetInPorts()
            math_node_in = math_node_in[0] if math_node_in else None
            
            # Connect the constant node to the math node.
            if const_node_out and math_node_in:
                const_node_out.Connect(math_node_in)
        
            # Add our object to the scene graph and let Cinema update.
            doc.InsertObject(null)
            c4d.EventAdd()
            
        if __name__=='__main__':
            main()
        

        Cheers,
        zipit

        MAXON SDK Specialist
        developers.maxon.net

        M 1 Reply Last reply Reply Quote 0
        • M
          moGRR @ferdinand
          last edited by

          Thanks @zipit - that's helped me get a better understanding for connecting ports (please forgive me, I'm still very new to python). However, I'm still stuck with trying to add a 'User Data' port to the node.

          The Node I have created is c4d.ID_OPERATOR_OBJECT and then I use the following code to make sure it's referring to the right object:

          parentNode[c4d.GV_OBJECT_PATH_TYPE] = 0
          parentNode[c4d.GV_OBJECT_START_TYPE_ID] = 1
          

          But then I try and use the AddPort function to add an output port to the node but I can't get it working.

          1 Reply Last reply Reply Quote 0
          • ferdinandF
            ferdinand
            last edited by ferdinand

            Hi,

            ah, okay now I do understand. Unfortunately GvNode.AddPort has been broken for years now and apparently has not been fixed yet.

            See here: https://developers.maxon.net/forum/topic/11423/gv-node-addport-fail/3

            edit: Whoops, wrong link.

            Cheers,
            zipit

            MAXON SDK Specialist
            developers.maxon.net

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

              Thanks again @zipit

              From the link you sent it looks like it's fixed for R21. So I'm downloading the trial version now to see if I can get it working.

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

                Hi all. I've installed the trial version of R21 (I'm looking to upgrade at some point anyway). However, I'm still struggling to get some python code that successfully adds ports to a GV node and then connects them.

                Any help would be massively appreciated.

                Jamie

                1 Reply Last reply Reply Quote 1
                • ferdinandF
                  ferdinand
                  last edited by ferdinand

                  Hi again 😉

                  You should provide your code, as it would make it easier to help you.

                  1. What is the return value of your AddPort call?
                  2. What does a matching call to AddPortIsOK return for your arguments?
                  3. Did you follow the special way in which a user data DescID has to be constructed, in order to be recognised by this method?

                  Cheers,
                  zipit

                  MAXON SDK Specialist
                  developers.maxon.net

                  M 1 Reply Last reply Reply Quote 0
                  • M
                    moGRR @ferdinand
                    last edited by

                    Thanks again @zipit

                    To make things a bit clearer, here is what I am trying to do:

                    Use a Python Generator to:

                    • Create a Connector object
                    • Create an Xpresso tag and assign it to the Connector object
                    • Create a Deformer object (eg Bend) that is a child of the Connector object
                    • Create a Node in the GV for the Connector Object
                    • Create output ports for all of the Connector object's User Data.
                    • Connect these ports to attributes of the Deformer object.

                    I'm afraid I've got so confused with trying to get this right, that my code has become really messy with lots of things commented out as I try to figure what is working and what isn't.

                    So I've created a new Python Generator with this code which is pretty much where I'm up to:

                    def main():
                        # Create Connector Object and insert an Xpresso tag to it
                        connector_obj      = c4d.BaseObject(c4d.Oconnector)
                        tag_xpresso        = c4d.BaseTag(c4d.Texpresso)
                        connector_obj.InsertTag(tag_xpresso)
                    
                        # Get the node master and the root noode of that graphview.
                        gvNode_master      = tag_xpresso.GetNodeMaster()
                        gvNode_root        = gvNode_master.GetRoot()
                        
                        # Create a GV Node in the node Master 
                        gvNode_controller  = gvNode_master.CreateNode(gvNode_root, c4d.ID_OPERATOR_OBJECT) 
                        
                        # Add port to GV Node
                        USERDATA_NUMBER = 1
                        gvNode_controller.AddPort(c4d.GV_PORT_OUTPUT, c4d.DescID(c4d.DescLevel(c4d.ID_USERDATA, c4d.DTYPE_SUBCONTAINER, 0), c4d.DescLevel(USERDATA_NUMBER)), message=True)
                        
                        return connector_obj
                    
                    1 Reply Last reply Reply Quote 0
                    • ferdinandF
                      ferdinand
                      last edited by ferdinand

                      Hi,

                      I should have probably red your postings more carefully, but I am just now seeing that you are trying to do that in a generator object.

                      A python generator object's main function is very similar to the GetVirtualObjects method in an ObjectData plugin, as it is being polled by Cinema to build the cache of your object. Cache means here that Cinema will not evaluate the content of your returned node hierarchy, which also means that it won't evaluate any expressions, including your Xpresso tag. So your approach is generally flawed unless you are not actually interested in the Xpresso graph updating your returned node hierarchy, but only interested in providing the user with a "ready to go" rig when converting your generator object into its cache representation via Current State to Object.

                      Aside form that:

                      1. Your code looks okay.
                      2. Print out the return value of your GvNode.AddPort call, it should be a GvPort or None when something went wrong.
                      3. Use GvNode.AddPortIsOK to test if Cinema thinks that adding a port here is okay. It should return True.

                      Also noteworthy: The bug regarding GvNode.AddPort was that it always returned None and did not add any port, while a matching call to GvNode.AddPortIsOK would return True. The bug is really, really, ..., really old, there was a myriad of threads on it on the old forum. It also has been reported as fixed before and then either broke again or actually had not been fixed if my memory serves me right. So I would not be to shocked if it is actually still broken.

                      Cheers,
                      zipit

                      MAXON SDK Specialist
                      developers.maxon.net

                      M 1 Reply Last reply Reply Quote 0
                      • M
                        moGRR @ferdinand
                        last edited by

                        @zipit Ah I did wonder if the behaviour was slightly different due to it being a Python Generator.

                        I think I understand most of what you have said there, but being new to Python (and coding really) it can be quite tricky for me grasp. From the sounds of it, I might be trying to do TOO MUCH of it within a Python Generator? I'm open to better/alternative pipelines.

                        Here is a bit more detail about what I'm trying to to achieve overall:

                        • Create a Python Generator that:
                          • Has a series of User Data values which it then uses to create a hierarchy with a Connect Object at the top (root) (tick)
                          • When collapsing the Python Generator by pressing 'C', the Connect object also inherits the same User Data from the Python Generator (tick - it does this by default)
                          • The Connect object also has an xpresso tag that links certain attributes within the hierarchy to the Connect object's User Data. (this is where I am stuck)

                        So overall, what I would like to have is a Python Generator which allows me to change certain User Data values so that it generates the correct hierarchy of Cloners, Objects and Deformers. (This all works fine) , but then what I would also like is the Python Generator to create an Xpresso tag that dynamically links certain attributes to the root object's User Data so that even after I have collapsed the Python Generator into the hierarchy, I can still change the User Data on the root object (The Connect object) and Deformer objects within the hierarchy will change accordingly.

                        I hope that makes sense?

                        Does this sound like something that is possible?

                        Cheers,
                        Jamie

                        ferdinandF 1 Reply Last reply Reply Quote 0
                        • ferdinandF
                          ferdinand @moGRR
                          last edited by ferdinand

                          @moGRR said:

                          but then what I would also like is the Python Generator to create an Xpresso tag that dynamically links certain attributes to the root object's User Data so that even after I have collapsed the Python Generator into the hierarchy ...

                          I assume with root object you are referring to the object returned by your main function and not the generator object (which is kind of the root of your cache). Technically you can build anything and it will work after you have collapsed the object, but not even after, i.e. not while it has not been collapsed. Think of caches as of as parts of your scene graph Cinema is completely ignoring regarding the execution of its passes. This little example will demonstrate the effect (see the docstring for details).

                          """Put this into a Python Generator Object. 
                          
                          Playing back the document will not change the output of the Generator, even 
                          when 'Optimize Cache' is turned off (i.e. forcing the generator to be 
                          evaluated each frame), because Cinema will not evaluate caches.
                          
                          But when the generator object is converted into its cache state via 'Current 
                          State to Object', the Python Tag sitting on the spline object will start to 
                          drive the radius of the circle when the document is being played back.
                          """ 
                          
                          import c4d
                          
                          # Code for a python tag that drives the radius of the circle spline object
                          # it is being attached to with the current document time.
                          PYTHON_TAG_SCRIPT = """
                          import c4d
                          
                          def main():
                              obj = op.GetObject()
                              t = (doc.GetTime().Get() % 2.) * 100
                              obj[c4d.PRIM_CIRCLE_RADIUS] = t
                          """
                          
                          def main():
                              """
                              """
                              circle_obj = c4d.BaseList2D(c4d.Osplinecircle)
                              python_tag = c4d.BaseList2D(c4d.Tpython)
                              circle_obj.InsertTag(python_tag)
                              python_tag[c4d.TPYTHON_CODE] = PYTHON_TAG_SCRIPT
                          
                              return circle_obj
                          
                          

                          And just as a friendly reminder, when you do not want to wait for MAXON responding to the AddPort situation - you can always replace Xpresso with a Python tag.

                          Cheers,
                          zipit

                          MAXON SDK Specialist
                          developers.maxon.net

                          M 1 Reply Last reply Reply Quote 0
                          • M
                            moGRR @ferdinand
                            last edited by

                            Hi @zipit - Thanks again for all your help and guidance with this.

                            That last code you sent was really beneficial in helping me get a better understanding of how Python Generators work. I've got my Python Generator code working now just as I intended.

                            Also, creating python tags rather than xpresso is a much more elegant way to achieve what I'm trying to do. I'm guessing that Python Generators might not be the best way to introduce yourself to Python in C4D?

                            Having used AE for years and utilising Javascript expressions, I'm really excited about what python can open up to me with regards to C4D work. 🙂

                            ferdinandF 1 Reply Last reply Reply Quote 1
                            • ferdinandF
                              ferdinand @moGRR
                              last edited by ferdinand

                              Hi,

                              I'm guessing that Python Generators might not be the best way to introduce yourself to Python in C4D?

                              Well, that is a question to which only very subjective answers do exist. But I would say that the Python scripting environment of Cinema (i.e Python Script Manager scripts, Python generator objects, Python tags and the Python Xpresso node) are probably the easiest way to get to know the Cinema 4D SDK.

                              There will be some hoops you will have to jump through, some mistakes you will make, no matter if you are starting with C++ plugins, Python plugins or the scripting environment. But the scripting environment is very powerful while considerably cutting down the fluff that usually comes with developing a full blown plugin. A nice side effect is probably also that the scripting environment is arguably the most rapid development environment.

                              So I would actually say the Python generator object and the Python tag are just the right place to start.

                              Cheers,
                              zipit

                              MAXON SDK Specialist
                              developers.maxon.net

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

                                Hi sorry to jump on this topic so late, and I agree that if you could do what you want in a Python Scripting tag its preferred, the mains issue with xpresso is that its a whole system that assumes certain things and was really not designed to be embedded within a generator.

                                So the issue about the AddNode, not working is because user data things, as an object xpresso node, is something dynamic (they will change according to the linked object) and I found no way except via the UI (by asking a UI Redraw or a whole scene execution) to refresh these data so the GvPort can be created.

                                But since you are in a Generator you don't have access to the GUI since all GUI operation should be done in the Main thread see Threading Information.

                                Cheers,
                                Maxime.

                                MAXON SDK Specialist

                                Development Blog, MAXON Registered Developer

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