Python Generator - Linking ports to xpresso node
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?
I am not quite sure if I do understand your question correctly, but I assume you want to to know how to connect two
"""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 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 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()
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_OBJECTand 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.
ah, okay now I do understand. Unfortunately
GvNode.AddPorthas been broken for years now and apparently has not been fixed yet.
edit: Whoops, wrong link.
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.
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.
Hi again ;)
You should provide your code, as it would make it easier to help you.
- What is the return value of your
- What does a matching call to
AddPortIsOKreturn for your arguments?
- Did you follow the special way in which a user data
DescIDhas to be constructed, in order to be recognised by this method?
- What is the return value of your
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
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
mainfunction is very similar to the
GetVirtualObjectsmethod in an
ObjectDataplugin, 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:
- Your code looks okay.
- Print out the return value of your
GvNode.AddPortcall, it should be a
Nonewhen something went wrong.
GvNode.AddPortIsOKto test if Cinema thinks that adding a port here is okay. It should return
Also noteworthy: The bug regarding
GvNode.AddPortwas that it always returned
Noneand did not add any port, while a matching call to
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.
@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?
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
mainfunction 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
AddPortsituation - you can always replace Xpresso with a Python tag.
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?
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.
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.