Create Xpresso Node with input port Object
-
To whom it may concern,
I am trying to create an Xpresso tag and nodes programmatically with python.
I can create a Sphere with custom user data, like a link where I will drag another object. I can create the sphere object with output ports for its global position and the link.
For my application, I need to obtain the global position of the object that will be linked in the link.
I can create an object node; however, I can not programmatically add the Object Input Port to the object node.
How is the correct way to add this Input Port Object ? Is there any other way to extract the global position of the link?Also, is there a way to set a node name programmatically?
doc = c4d.documents.GetActiveDocument() sphere = c4d.BaseObject(c4d.Osphere) doc.InsertObject(sphere) ud_target = c4d.GetCustomDataTypeDefault(c4d.DTYPE_BASELISTLINK) ud_target[c4d.DESC_NAME] = "Target" ud_target[c4d.DESC_ANIMATE] = c4d.DESC_ANIMATE_ON sphere.AddUserData(ud_target) xtag = c4d.BaseTag(c4d.Texpresso) sphere.InsertTag(xtag) gv = xtag.GetNodeMaster() sphere_node = gv.CreateNode(gv.GetRoot(),c4d.ID_OPERATOR_OBJECT) sphere_node[c4d.GV_OBJECT_OBJECT_ID] = sphere global_pos_port = sphere_node.AddPort(c4d.GV_PORT_OUTPUT, c4d.ID_BASEOBJECT_GLOBAL_POSITION) user_data_container = sphere.GetUserDataContainer() target_desc_id = None target_port = None for desc_id, bc in user_data_container: if bc[c4d.DESC_NAME] == "Target": target_desc_id = desc_id target_port = sphere_node.AddPort(c4d.GV_PORT_OUTPUT, desc_id) target_node = gv.CreateNode(gv.GetRoot(), c4d.ID_OPERATOR_OBJECT) target_object_input = target_node.AddPort(c4d.GV_PORT_INPUT, c4d.GV_OBJECT_OBJECT_ID)
What I get with the above code:
What I need it to do:
To later link the target with the object.
Thank you for your time.
Joel Johera -
Hello @JoelJohera,
Welcome to the Maxon developers forum and its community, it is great to have you with us!
Getting Started
Before creating your next postings, we would recommend making yourself accustomed with our forum and support procedures. You did not do anything wrong, we point all new users to these rules.
- Forum Overview: Provides a broad overview of the fundamental structure and rules of this forum, such as the purpose of the different sub-forums or the fact that we will ban users who engage in hate speech or harassment.
- Support Procedures: Provides a more in detail overview of how we provide technical support for APIs here. This topic will tell you how to ask good questions and limits of our technical support.
- Forum Features: Provides an overview of the technical features of this forum, such as Markdown markup or file uploads.
It is strongly recommended to read the first two topics carefully, especially the section Support Procedures: How to Ask Questions.
About your First Question
Please make sure to post executable code in the future. It was just some imports and a main function missing here, but non-executable code always bears the risk of you being misunderstood and usually means that we do not try to run your code. Your code is mostly correct. Your major fault is that the ID for the input port for an object reference is
GV_OBJECT_OPERATOR_OBJECT_IN
and notGV_OBJECT_OBJECT_ID
(that is the ID for the description parameter).There is also a minor problem with how Xpresso updates the labels of object nodes which had set their wrapped scene element via connection. But this is not an issue of the Python API, but rather a general issue with Xpresso.
Find my cleaned up solution below.
Cheers,
FerdinandResult
Code
import c4d import mxutils from c4d.modules.graphview import GvNode, GvPort, GvNodeMaster, XPressoTag from mxutils import CheckType doc: c4d.documents.BaseDocument # The currently active document. op: c4d.BaseObject | None # The primary selected object in `doc`. Can be `None`. def main() -> None: """Called by Cinema 4D when the script is being executed. """ # All these #CheckType calls make sure that the return value of something is not None. The # allocation of scene elements can fail, and it is advisable to check. See the docs for details. # Create a sphere and a cone object, create an XPresso tag on the sphere, and then create the # parameter description for a baselink named 'Target'. sphere: c4d.BaseObject = CheckType(c4d.BaseObject(c4d.Osphere)) cone: c4d.BaseObject = CheckType(c4d.BaseObject(c4d.Ocone)) tag: XPressoTag = CheckType(sphere.MakeTag(c4d.Texpresso)) settings: c4d.BaseContainer = c4d.GetCustomDataTypeDefault(c4d.DTYPE_BASELISTLINK) settings[c4d.DESC_NAME] = "Target" # Add the baselink parameter to the user data of the sphere object and link the cone object in # it, then insert both objects into the document. pid: c4d.DescID = sphere.AddUserData(settings) sphere[pid] = cone doc.InsertObject(sphere) doc.InsertObject(cone) # Get the Xpresso root node and create two nodes in the graph. One for the sphere object and # one for object linked in the baselink parameter of the sphere object. Finally, link the # sphere object as the reference object of the sphere node. master: GvNodeMaster = tag.GetNodeMaster() root: GvNode = master.GetRoot() sphereNode: GvNode = CheckType(master.CreateNode(root, c4d.ID_OPERATOR_OBJECT)) targetNode: GvNode = CheckType(master.CreateNode(root, c4d.ID_OPERATOR_OBJECT)) sphereNode[c4d.GV_OBJECT_OBJECT_ID] = sphere # Now create an object output port on the sphere node and an object input port on the target # node and connect the two. The caveat is here that unless we first manually set the port # value, Xpresso does not seem to update the label of the node when we set the connection. I.e., # the node will not be labeled 'Cone'. This issue persists when we later manually change the # object in the user data of the sphere object. And it also happens when we create the setup # entirely manually from scratch in the Xpresso editor. So, this seems to be more a general # issue with Xpresso and not its Python API. # # But this does not impact the technical functionality of the setup, it still always affects the # correct node, it is just the label on the node that is misleading. objOutPort: GvPort = CheckType(sphereNode.AddPort(c4d.GV_PORT_OUTPUT, pid)) objInPort: GvPort = CheckType(targetNode.AddPort(c4d.GV_PORT_INPUT, c4d.GV_OBJECT_OPERATOR_OBJECT_IN)) targetNode[c4d.GV_OBJECT_OBJECT_ID] = cone # Preset the port value to update the label. objInPort.Connect(objOutPort) # Connect the two ports. # To demonstrate this, we create a port for the global transform of the object wrapped by the # target node and set the position of the object. transformInPort: GvPort = CheckType(targetNode.AddPort(c4d.GV_PORT_INPUT, c4d.GV_OBJECT_OPERATOR_GLOBAL_IN)) targetNode[c4d.GV_OBJECT_OPERATOR_GLOBAL_IN,c4d.MATRIX_OFF] = c4d.Vector(300, 300, 0) # Push an update event. c4d.EventAdd() if __name__ == '__main__': main()
-
Thank you for the information.
Also where I can find all the GV_ options and ID_ options for creating nodes, and ports ?
-
Hey,
the overview for GV ops can be found here here, accessible from our types and symbols index. For the dedicated resources you would have then to look into our resource index, specifically in the graph view section. There you will then find the parsed resources for operators and could look for example at the Object Operator.
The caveat is that there is a small irregularity with the gv object node as it redefines some of its resource parameters for their input and output port. The only other operator type which does seem to do that is hair collision operator for its velocity port. It has likely something to do with the fact that both these operators wrap scene elements. But the resources document all these ports.
If you just want a plain technical top-down view on symbols you can also look at
/{c4d}/resource/modules/python/libs/python311/c4d/__init__.py
. There you can see it is only a handful (20 to be exact) ports which follow this_IN|_OUT
postfix scheme:Cheers,
Ferdinand -
Thank you so much for the resources. I asked because now I am trying to create a Python node, and I tried to specify the input port as c4d.DTYPE_VECTOR, but it does not work.
-
Hey @JoelJohera,
Please stop asking non-follow-up questions in a thread, when you have a new question, please open a new thread.
With that being said:
# Create a Python node. pythonNode: GvNode = CheckType(master.CreateNode(root, c4d.ID_OPERATOR_PYTHON)) # Create a vector input port on it. The syntax for the dynamic ports of a Python node is a bit # weird and I also only found out how this works by dragging parameters to the console and by # inspecting the description of the node. vectorInPort: GvPort = CheckType(pythonNode.AddPort( c4d.GV_PORT_INPUT, # The plain symbols, e.g., #GV_OBJECT_OPERATOR_OBJECT_IN are only a short hand form for how # parameter IDs are defined under the hood: DescIDs. There are plenty of postings about this # on the forum, just search for 'DescID' and my user name, one is for example here: # https://developers.maxon.net/forum/topic/14342/howto-set-keyframes-for-posxyz-rotxyz-scalexyz-with-a-script/15 c4d.DescID( # The IDs for the dynamic ports are really weird. The first level is of type sub-container, # and has an ID such as IN_VECTOR, OUT_VECTOR, IN_REAL, OUT_REAL, etc. The second level # then describes the the actual data and its ID. The IDs seem to start at 1001, and since # a Python node has two default ports, I am putting mine at 1003. The levels also have # some truly wild owners (the third parameter of a level which set here to 0). I checked # its not the type ID of the op. But as so often, you can also null the owner ID and it # still works. c4d.DescLevel(c4d.IN_VECTOR, c4d.DTYPE_SUBCONTAINER, 0), c4d.DescLevel(1003, c4d.DTYPE_VECTOR, 0) # These IDs are in fact so weird, that our parameter drag and drop misfires and produces # malformed DescIDs (when you press enter on such dragged parameter, it raises an # AttributeError). But dragging them can still be useful to figure these a bit hand-wavy # type symbols such as IN_VECTOR, OUT_VECTOR, etc. ) ))
Cheers,
Ferdinand -
Ok thanks and sorry