Maxon Developers Maxon Developers
    • Documentation
      • Cinema 4D Python API
      • Cinema 4D C++ API
      • Cineware API
      • ZBrush Python 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
    • Recent
    • Tags
    • Users
    • Login
    1. Maxon Developers Forum
    2. ferdinand
    3. Posts
    Offline
    • Profile
    • Following 0
    • Followers 15
    • Topics 54
    • Posts 3,134
    • Groups 2

    Posts

    Recent Best Controversial
    • RE: VertexMap Display (Behavior Equivalent to Double-Click)

      Hey @ymoon,

      Thank you for your question. First of all, your code is more complicated than it has to be. You can just call BaseObject.GetTag to get the first tag of a specific type on an object. Also, your code already does what you are asking for, it selects the tag and therefore causes Cinema 4D to draw the vertex map in the viewport (equivalent to single clicking the tag).

      To have the Paint Tool enabled (i.e., what happens when you double click a vertex map), you could either activate the tool yourself, or just send MSG_EDIT to the tag, the message which is sent to scene elements when they are double clicked.

      Cheers,
      Ferdinand

      Code

      import c4d
      
      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.
          """
          if not op:
              return c4d.gui.MessageDialog("Please select an object.")
          
          tag: c4d.BaseTag | None = op.GetTag(c4d.Tvertexmap)
          if not tag:
              return c4d.gui.MessageDialog("The selected object has no Vertex Map tag.")
          
          doc.SetActiveTag(tag, c4d.SELECTION_NEW)
          tag.Message(c4d.MSG_EDIT)
          
          c4d.EventAdd()
      
      if __name__ == '__main__':
          main()
      
      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: BaseLink across documents

      Hey @WickedP,

      Thank you for your question. A BaseLink always requires a document within which it shall be resolved, when you do not provide one, it just takes the active document.

      5a0f8cfd-a94b-4c0f-b3ef-449841104a35-image.png

      So, your question is a bit ambivalent. Internally, a BaseLink is based on GeMarker which identifies a scene element (C4DAtom) over the mac address of the creating machine, the time of creation, and a random UUID. The value is a constant attached to the scene element expressed as 16 bytes of data.

      So, this value is persistent over reload boundaries and does not care for the document it is in - although copying a scene element will usually cause it to get a new marker, unless you tell Cinema 4D explicitly to also copy the marker. You can easily query multiple documents for containing a given C4DAtom via a BaseLink. But unless you deliberately forced markers to be copied, it is not possible that multiple documents contain the same C4DAtom.

      Cheers,
      Ferdinand

      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: How do I return a list of names and ids for a drop down menu?

      Hey @lionlion44,

      yes, that is the correct answer. The subject comes up from time to time, here is an answer of mine which is about the very case of yours - discovering substance channels.

      Cheers,
      Ferdinand

      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: Viewport depth of field affects the content drawn using drawtexture()

      Yes, it is intentional that the handle draw pass is only being drawn when the object is selected. You can try the box pass, I think it is also drawn when the object is not selected and might not be subject to viewport effects. The highlight pass is also only drawn when an object is selected. The X-Ray pass should be drawn when an object is not selected, but should also be subject to viewport effects. But the box pass is also subject to camera transforms, just as the object pass, which makes it not a very good choice to draw HUD information.

      All statements above are from my recollection and could be wrong, you just have to check yourself.

      But what you are trying to do is also a bit unusual. Typically, objects should not draw persistent HUDs (what I assume you are trying to do) into the viewport. That would be the job of a SceneHookData plugin. But that hook has never been exposed to Python, mostly out of performance concerns.

      Your code is also missing the OCIO handling the C++ example showcases. You will be drawing with incorrect colors (unless the bitmap(s) you draw happen to have the color profile of the render space of the document - which is very unlikely).

      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: Viewport depth of field affects the content drawn using drawtexture()

      Hey @chuanzhen,

      Thank you for reaching out to us. Please remember our support procedures. It is very difficult to help you without seeing the relevant code.

      My guess would be that you are drawing into a draw pass which intentionally is affected by viewport effects such as depth of field, e.g., DRAWPASS:OBJECTS. The C++ code examples contain the Drawing with OCIO Colors in a Viewport example; which is likely very close to what you want to do. The example draws textures in both the object and handle pass, where the object pass will be affected by DOF while the handle pass will not.

      4cba6810-c75f-4622-bacf-8186b19ea3ed-image.png

      Cheers,
      Ferdinand

      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: How to measure Viewport performance? -> Troubleshooting and optimization.

      Hey @mogh,

      I agree that the tool is not so greatly documented but I told you that the options at the bottom (i.e. also 'saving') all only concern animation tests šŸ˜‰

      I guess your grievance is this one: Live readout 97ms, 11 FPS; Workstation: RTX 3090, i9-10980XE? As far as I understand, you are dealing with this outside of any plugins you have developed? Which would make this an end user issue, I really cannot help you there.

      What you can do, is disable all drawing operations (Anim Without View, No Ui Update), to see how fast your scene is then. I.e., you then mostly only pay the price for Cinema 4D building the scene for each update event (caches, expressions, animations, etc.). Because it could very well be that you just created there a scene which is very inefficient or are using there plugins which are. So, the slow viewport is just an effect of scene evaluation being so expensive. Which is often the cause when scenes are slow or very slow.

      edit: Okay, now I see it. 10,000 objects and 120 million polygons. Yeah, Cinema 4D won't like that. Just combine all objects into one, and your numbers should go up. What makes the scene likely slow, is the sheer number of objects. 12 million polygons (10% of 120 million) is something Cinema 4D can handle with such GPU. But it makes really a difference if all these polygons are in one object, or as in your case, scattered over 10,000 objects.

      edit 2: okay, now I saw also your comment about the 'old' laptop compared to a (presumably new) workstation. I would not rule out that you have here multiple issues. That 10k + 12kk scene very likely performs not so great on many machines. In addition, you might have a defective GPU or outdated drivers. Did you update your drivers an ran a GPU stress test to ensure that your GPU pulls the numbers it should be able to pull? You could also use Cinebench.

      Cheers,
      Ferdinand

      posted in General Talk
      ferdinandF
      ferdinand
    • RE: How to measure Viewport performance? -> Troubleshooting and optimization.

      Hey,

      The opposite is true, Calculate Fps and the systems below it are very old. This is a developer tool, so you could say it naturally is not documented šŸ˜‰ . Here is a rough explanation:

      • Start/Stop: Runs a procedural camera animation of the scene. I think mostly the viewport team uses it. Just outputs FPS and a timing.
      • Anim Test: The actual tool, this just runs your active document.
      • Run JSON: I have no idea, never used it.
      • Active View: No idea, I think its is broken. You have always to enable it.
      • Reverse: If to play the scene in reverse.
      • Texture View: Never used it, no idea.
      • Without View: Disable viewport redraws while profiling.
      • Without UI: Disable UI redraws while profiling.
      • Profile Animation/Spinlocks/Save Profile Summary: They all concern animation tests and what is put into the report.

      The summary will automatically be opened once the animation test finished (you must have an app associated with txt files but it is almost impossible to not have at least one app associated with that file format). The data in a report is fairly technical and likely not that useful for normal users. The main public purpose of the tool is to provide accurate FPS numbers in the dialog itself. There are also command line arguments with with you can run animation tests automatically, but they are just as most command line arguments for Cinema 4D not public as they are intended to be developer tools only.

      8ea84b48-339b-4b57-8052-6def2381a9b8-image.png

      Cheers,
      Ferdinand

      posted in General Talk
      ferdinandF
      ferdinand
    • RE: How to measure Viewport performance? -> Troubleshooting and optimization.

      Hey @mogh,

      Thank you for your question. Although development related, I would more put this into the domain of end user support (thank you for posting in the correct forum).

      I would point out that Cinema 4D already has a tool to measure FPS ('Calculate FPS') but when you were in contact with end user support, I guess they already mentioned it and you concluded that it is not enough.

      d0e95062-1036-4fce-aef8-f3400b3a88c6-image.png
      2e9e77da-af77-446b-9e58-d092a444269c-image.png

      Internally, we developers use either this tool or a full on dedicated profiler (something like superluminal or VTune) but you need the source code or at least the symbols of Cinema 4D for that to make any sense. For Python I added mxutils.TIMEIT a while ago, but that is limited to profiling function calls and is also generally limited as any profiling that you just do 'from within' your source code.

      Cheers,
      Ferdinand

      posted in General Talk
      ferdinandF
      ferdinand
    • RE: Educational Licenses

      Hey @lasselauch,

      I tried your plugin in 2025.3.1 (Build 2025_3_1_dec42f629989_1872501510) Win and 2025.3.0 (Build 2025_3_0_dd53fd5fe589_1867889325) OSX (closest version I got on my Mac) with a student license and it just loads fine.
      Screenshot 2025-11-10 at 22.37.08.png
      Screenshot 2025-11-10 at 22.25.34.png

      GUI Operations in Boot Phase

      I noticed that you are creating dialogs and doing file operations while Cinema 4D is booting (i.e., likely within the __name__ == '__main__' context guard of your pyp file). That is absolutely not allowed.

      The registration phase is for registering hooks only. Installing, querying web servers, opening fancy dialogs or doing other shenanigans is absolutely not allowed and can very well lead to malfunctions or crashes. For Python plugins this is less severe as for C++ plugins as Python plugins live in their own fake boot bubble and most systems should have been brought up at this point, but you are still poking the bear.

      We had not too long ago a similar case but for license validation. Register your plugin hook(s) in the boot phase and nothing else. When you have some simple serial or other logic as shown in the serial example, you can run it also, everything else must be done later. Then once Cinema 4D has fully booted (c4d. C4DPL_STARTACTIVITY has been emitted) or when the user tries to use your plugin, you can do a complex license check using web servers or install dependencies. You can then force a restart with c4d.RestartMe.This could absolutely be the cause for the issues of the user and it working on most systems does not mean anything.
      Screenshot 2025-11-10 at 22.23.09.png
      Fig. I: Doing GUI and file operations while Cinema 4D is loading: this is really bad!

      Nested Plugin Package

      Your download package creates a nested folder, which sort of sets a trap for less experienced users, as linking the outer folder will cause the plugin not to load (due to the limitation of how Python plugins are discovered compared to C++ plugins). I would recommend a flat structure.
      99f1642b-f860-4cb0-a660-70d3a5709732-image.png

      TLDR: I would bet money on the user having linked the wrong folder.

      Cheers,
      Ferdinand

      edit:

      And just for completeness, here is your plugin loaded from {c4d}/plugins.

      b2380289-0ab9-4e4b-bd5c-c7d232cb919a-image.png

      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: How to get the fully resolved file path of an upcoming rendering?

      Hey @BigRoy,

      Thank you for reaching out to us and your question. We do not allow for topics that are a collection of questions from one user. New questions mandate new topics, unless they are clear follow up questions on an existing topic. The reason for this is so that this forum remains a searchable database.

      Please create new topics on your own in the future. I have forked your topic.

      Your topic is also lacking an actual question. Here I can infer your question, but for more complex subjects I might not be able to. It is important to have a literal question close to the start of your topic. See Support Procedures: How to Ask Questions and the examples below for what makes a good technical question.

      From the context I am assuming here the question is:

      Is there an API function that returns the full physical file path of a rendered file for a given render setting and in particular the Save > Name format?

      34d82dba-bf2f-411a-8489-a8dccc5d9f77-image.png

      The answer is unfortunately that no such function exists. When you need this feature, you have to hard code it yourself.

      Cheers,
      Ferdinand

      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: Educational Licenses

      Hey @lasselauch,

      Thank you for reaching out to us. In general, we do not regulate users of EDU licenses being able to load plugins or not. The screenshot you sent us just tells the user you cannot manually mount the path {c4d}/plugins as it is an auto-mounted plugin search path. With 2025 we added some minor sanity checks for how plugin paths can be added, so that users cannot 'double dip' the same plugin path and with that cause conflicts.

      So, when the user created a path {c4d}/plugins, the plugin should simply load from there, no adding as a plugin path required. You can send me your plugin, and I can see if I can load it in an EDU. It is hard to tell with this little information what is going on and where the fault lies.

      In {c4d}/plugins you usually do not have write permissions, so when your plugin tries to write to its own root path, that could be an issue. Or the user did not check the console properly and your plugin actually writes something to the console or throws an error. It could of course also be that there is a bug in Cinema 4D.

      I would ask the user:

      1 - What is the content of the Plugin Manager (does your plugin show up)?
      2 - Ask for a screenshot of a scrolled down Python console.
      3 - Common mistake are also over-nested plugins. When you have your plugin as:

      foo/
      └── bar/
          └── my_plugin/
              ā”œā”€ā”€ res/
              └── my_plugin.pyp
      

      mounting /foo/bar/ as a plugin search path will load your Python plugin, mounting foo will not.

      Cheers,
      Ferdinand

      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: Best place to do version conversion for scenes and assets

      Hey @ECHekman,

      Thank you for your question. This is a tricky one. When NodeData::Read is emitted, Cinema 4D is currently reading a file and in the process of deserializing your node; so it indeed is not yet 'fully instantiated', as that is the whole point of the method. You are here not meant to manipulate the scene graph (the node is not yet attached), but read and store data on your plugin hook. I.e., you are supposed to do something with your MyMaterialData and not the GeListNode/BaseMaterial which is being passed in as the first argument of Read.

      The common workflow is to read data from the HyperFile which is passed in and store it on your NodeData instance. Passed in is also the important version number (which is called for some odd reason disklevel) of your HyperFile container.

      As always, you are bound to the main thread for scene graph manipulations. So, NodeData::Init is not a good candidate when wanting to poke around in the scene graph, as it can be called off main thread and on dettached dummy nodes. A good approach is to hook into NodeData::Message and listen for MSG_DOCUMENTINFO to catch a document just having been loaded. This is for your specific case of wanting to manipulate nodes which have been deserialized. If you want to manipulate node instantiation, MSG_MENUPREPARE is a god choice. But it is only called for nodes which have been instantiated from direct user interactions such as a menu click. So, MSG_MENUPREPARE is not being emitted for nodes loaded from a file.

      The drill to do what you want to do would be:

      1. [optional] Overwrite NodeData::Read/Write/CopyTo to read, write, and copy custom data, and possibly use/detect different disk levels to catch different versions of a node type being loaded. You would have to match this with incrementing the disk level in your RegisterMaterialPlugin call, so that there are old and new versions of your material type out there.
      2. Hook into the document being loaded, check the scene state, and possible data left behind for you by NodeData::Read and ::Init.
      3. Manipulate the scene graph to your liking.

      Cheers,
      Ferdinand

      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: The value of 'progress' during the use of RenderDocument() is greater than 1.0

      Hey, almost forgot: We have fixed this, an upcoming release will contain the fix šŸ™‚ . We, however, went with a fix removing the offending call instead of fixing the incorrect progress handler method (which was the issue).

      So, in other words: The workaround I showed here won't work in future versions, as the number of progress calls will be halved. Before one got two calls per update when rendering with the preview renderer: One for the correctly working image progress update and one for the buggy generic progress update. We have removed the latter.

      Halving the number of calls is more technically correct, since it made no sense to call the progress hook twice per update but will break any plugins that relied on that incorrect behaviour.

      Cheers,
      Ferdinand

      posted in Bugs
      ferdinandF
      ferdinand
    • RE: How to compute Redshift MoGraph index ratios (RSMGIDRatioColor)?

      Hey Bruce, multiple people in your company have access. When you do not, reach out to us, so that we can set you up.

      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: set GvNode value via python

      Hey,

      so, I actually did get it right, I apparently just did not check properly when I tested the common case of second level IDs of dynamic properties starting off at 1000.

      Cheers,
      Ferdinand

      """Demonstrates how to set values on dynamic input ports of a Condition node in an XPresso tag.
      """
      
      import c4d
      
      from c4d.modules.graphview import GvNodeMaster, GvNode, GvPort, 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.
          """
          # Create a new XPresso tag with a Condition node.
          null: c4d.BaseObject = CheckType(c4d.BaseObject(c4d.Onull))
          tag: XPressoTag = CheckType(null.MakeTag(c4d.Texpresso))
          master: GvNodeMaster = CheckType(tag.GetNodeMaster())
          doc.InsertObject(null)
      
          root: GvNode = CheckType(master.GetRoot())
          condNode: GvNode = CheckType(master.CreateNode(root, c4d.ID_OPERATOR_CONDITION, x=200, y=200))
      
          # Count the number of input ports on the Condition node. For our case, a freshly instantiated
          # node we do not really have to do this, since there will always be static "Switch" port and
          # two dynamic input ports. But when we are working on an existing graph, we have to count the
          # ports to know how many dynamic ports there are.
          inputPorts: list[GvPort] = condNode.GetInPorts()
          portCount: int = len(inputPorts)
      
          # There is no predefined symbol for this, the implementation counts just ports, so dynamic ports
          # start at 1001 because the input port GV_CONDITION_SWITCH has the ID 1000.
          idDynamicStart: int = 1001
      
          # Iterate over all dynamic input ports (-1 -> skipping the first static "Switch" port).
          for i in range(portCount - 1): 
              # Define the DescID for the dynamic input port.
              did: c4d.DescID = c4d.DescID(
                  # The first desc level for variadic/dynamic ports is always a sub container. For a 
                  # condition node where the variadic ports can only be inputs and always are of one type,
                  # this is GV_CONDITION_INPUT. Other nodes which can have variadic ports of different 
                  # types (like the Python node) have different sub container IDs for inputs and outputs, 
                  # e.g., IN_REAL for all real inputs, IN_STRING for all string inputs, OUT_LONG for all 
                  # long outputs, etc.
                  c4d.DescLevel(c4d.GV_CONDITION_INPUT, c4d.DTYPE_SUBCONTAINER, condNode.GetType()),
                  # And the second desc level is the dynamic port ID, starting at 1001 for the first dynamic
                  # input port, 1002 for the second dynamic input port, and so on. It unfortunately not very
                  # standardized, where these start.
                  c4d.DescLevel(idDynamicStart + i, c4d.DTYPE_REAL, 0)
              )
              # Set the value of the dynamic input port to some float value.
              result: bool = condNode.SetParameter(did, float((i + 1) * 10), c4d.DESCFLAGS_SET_0)
              if not result:
                  print("Failed to set value on port index:", i)
      
          # Or another common workflow, we want to set a dynamic port by port label (instead of knowing
          # the index in advance).
      
          # Find the first port that starts with "Input".
          targetLabel: str = "Input"
          targetPort: GvPort | None = next((port for port in inputPorts 
                                            if port.GetName(condNode).startswith(targetLabel)), None)
          
          # We found a port with that label.
          if targetPort is not None:
              # Unlike in the Nodes API, we cannot set the value on a port directly, we always have to go
              # through the node and use a DescID to identify the port. So we must find the index of the 
              # port in the input ports list to calculate the dynamic port ID.
              index: int = inputPorts.index(targetPort)
              result: bool = condNode.SetParameter(
                  c4d.DescID(
                      c4d.DescLevel(c4d.GV_CONDITION_INPUT, c4d.DTYPE_SUBCONTAINER, condNode.GetType()),
                      c4d.DescLevel(idDynamicStart + (index - 1), c4d.DTYPE_REAL, 0)
                  ),
                  99.9, # Set the value to 99.9
                  c4d.DESCFLAGS_SET_0
              )
              if not result:
                  print("Failed to set value on port:", targetPort.GetName(condNode))
      
          c4d.EventAdd()
      
      if __name__ == '__main__':
          main()
      
      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: How to compute Redshift MoGraph index ratios (RSMGIDRatioColor)?

      FYI: The RS team just told me that they agree that this is a bug and that they will fix this. Please reach out via our beta forums for details.

      So, the code I showed you has an expiration date.

      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: How to compute Redshift MoGraph index ratios (RSMGIDRatioColor)?

      Hey @BruceC,

      Thank you for your question. I just checked, and what RS calls the index ratio does not really have much to do with indices and is instead based on the uvw coordinate of a particle. This might be a bug, but you would have to talk with the Redshift team for that. Anyway, find below some Python code to emulate computing that 'index ratio'.

      Cheers,
      Ferdinand

      Result

      I emulated your setup:

      dfbeebb4-cce6-4ca1-bf31-76e78d3966c1-image.png

      Code

      import c4d
      import mxutils
      
      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.
          """
          data: c4d.modules.mograph.MoData = mxutils.CheckType(c4d.modules.mograph.GeGetMoData(op))
          uvw: list[c4d.Matrix] = data.GetArray(c4d.MODATA_UVW)
          for i in range(data.GetCount()):
              # That is how the 'index ratio' is computed, it is effectively just the shifted x-component
              # of the uvw coordinate of the particle. The color is then just (ratio, ratio, ratio). Not very
              # 'indexy'.
              ratio: float = 1 - uvw[i].x
              print(f"{i}: {ratio}")
      
      if __name__ == '__main__':
          main()
      
      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: Is it possible to hide the Basic Tab in the attribute viewer?

      Hey @ECHekman,

      it depends a bit on what you do in your description, which bases you include, and what you consider NodeData instances in your case. Are you actually talking about plain NodeData hooks or are you talking about derived types such as ObjectData?

      When we look for example into Ocube.res, we can see it includes Obase:

      CONTAINER Ocube
      {
      	NAME Ocube;
      	INCLUDE Obase;  // includes the description base for all objects Obase.res
      

      Which among other things contains the Obaselist group, which is the group you mean.
      e972d7aa-5434-40d2-b219-25703f65c2d8-image.png

      You could either not include the base description for your node type, or use a modified base which for example hides all elements in that group. But I personally would say that none of these approaches are really a good idea, as Cinema 4D is designed with the assumption that each basic scene element (object, tag, etc.) includes their base description.

      Falling to do so, will either make your plugin outright not work, or - when using more 'clever' approaches such as hiding all parameters, or hiding the whole group via GetDDescription or a modified base - severely impact how users can interact with your scene element.

      When you implement a plain NodeData hook, you should not see any extra tabs, as plain node hooks should not include any bases.

      Cheers,
      Ferdinand

      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: set GvNode value via python

      Hey @Dunhou,

      Thank you for reaching out to us. This is probably possible, but I'll have to check with a debugger myself. These are dynamically generated ports which tend to be a pain in the butt in Xpresso, as pretty much every node type handles them slightly differently, and even by looking at our source code you often do not get any wiser. My guess would have been that it is something like c4d.DescID(c4d.DescLevel(c4d.GV_CONDITION_INPUT, c4d.DTYPE_SUBCONTAINER, condNode.GetType()), c4d.DescLevel(0, c4d.DTYPE_REAL, 0)) or the same but other values for the second desc level such as 1000, 2000, etc. instead of 0. GV_CONDITION_INPUT is defined in the description as the input group.

      I will have to attach a debugger to see what is going on, but where I am currently working from, I have no compiled version of Cinema 4D. Will check in the next days from my Mac, where I do have a compiled version. You can check this thread for context, the function PythonNodeLayerShaderAccess goes into some details (a Python node also has a dynamic/variadic number of in- and output ports).

      Cheers,
      Ferdinand

      """My attempt in setting the dynamic ID_OPERATOR_CONDITION operator in ports (does not work). 
      """
      
      import c4d
      
      from c4d.modules.graphview import GvNodeMaster, GvNode, GvPort, 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.
          """
          null: c4d.BaseObject = CheckType(c4d.BaseObject(c4d.Onull))
          tag: XPressoTag = CheckType(null.MakeTag(c4d.Texpresso))
          master: GvNodeMaster = CheckType(tag.GetNodeMaster())
          doc.InsertObject(null)
      
          root: GvNode = CheckType(master.GetRoot())
          condNode: GvNode = CheckType(master.CreateNode(root, c4d.ID_OPERATOR_CONDITION, x=200, y=200))
      
          # Set values:
          did_1: c4d.DescID = c4d.DescID(
              c4d.DescLevel(c4d.GV_CONDITION_INPUT, c4d.DTYPE_SUBCONTAINER, condNode.GetType()),
              c4d.DescLevel(0, c4d.DTYPE_REAL, 0)
          )
          did_2: c4d.DescID = c4d.DescID(
              c4d.DescLevel(c4d.GV_CONDITION_INPUT, c4d.DTYPE_SUBCONTAINER, condNode.GetType()),
              c4d.DescLevel(1, c4d.DTYPE_REAL, 0)
          )
      
          a = condNode.SetParameter(did_1, 10.0, c4d.DESCFLAGS_SET_0)
          b = condNode.SetParameter(did_2, 20.0, c4d.DESCFLAGS_SET_0)
          print(a, b)
      
          c4d.EventAdd()
      
      
      if __name__ == '__main__':
          main()
      
      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: Python tag or Python node in Xpresso crash Cinema 4D

      FYI, there has been a verified fix for the Booleans crash, it will be included in an upcoming release of Cinema 4D.

      posted in Cinema 4D SDK
      ferdinandF
      ferdinand