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
    • Unread
    • Recent
    • Tags
    • Users
    • Login
    1. Maxon Developers Forum
    2. ferdinand
    3. Posts
    • Profile
    • Following 0
    • Followers 15
    • Topics 54
    • Posts 3,086
    • Best 749
    • Controversial 1
    • Groups 2

    Posts made by ferdinand

    • RE: exporting usd with python

      Hey @lionlion44,

      Thank you for reaching out to us. I doubt that this will not work in Python, it is just that we have not documented the symbols there yet. But you can find them in the C++ docs:

      6e23eada-2198-4c34-84bf-a576bbdd0ef9-image.png

      I.e., it will be c4d.FORMAT_USDIMPORT and c4d.FORMAT_USDEXPORT.

      Cheers,
      Ferdinand

      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: DescID and DescLevel for Shader Layers and properties

      Hey @alcol68,

      so, I spent some time with this today, and both the Xpresso and Layer Shader API drive me nuts every time I have to deal with them. The TLDR is here that both Xpresso and the Layer Shader are irregular in their implementations, especially in regards to how they store and access parameters.

      Find below some narrative code which guides you through all of this.

      Cheers,
      Ferdinand

      Result

      The setup expects a material to be selected which has a layer shader in its color channel with a singular transform layer in it. It will then create this Xpresso setup which drives the angle of the transform layer with a user data field in the null object holding the Xpresso setup.

      595958de-534d-40fe-a45e-5d1d973c8ad8-image.png

      edit: lol, now I see that also the first example is working in my own screenshot. I have absolutely no idea why. I would still stay away from this. The layer shader internals are a mess.

      Code

      """Explores concepts around driving layer shader parameters from XPresso.
      
      This script assumes that the active material has a layer shader in its color channel. As explained 
      below, this code assumes the ID of the first parameter of the first layer in the shader, which does 
      not seem to be entirely deterministic.
      
      When push comes to shove, you might have to comment out #Solution.InternalLayerShaderAccess(...) in
      #Solution.main() when it throws errors on your system.
      """
      
      import c4d
      import mxutils
      
      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`.
      
      class Solution:
          """Provides a dummy class so that we can organize our code in a more readable way.
          """
          @staticmethod
          def main() -> None:
              """Called by Cinema 4D when the script is being executed.
              """
              # Get the first material and get its color channel shader, assuming it is a layer shader.
              material: c4d.BaseMaterial = CheckType(doc.GetActiveMaterial())
              shader: c4d.BaseShader = CheckType(material[c4d.MATERIAL_COLOR_SHADER], c4d.LayerShader)
      
              # Create the XPresso setup used by both examples below and also already add the user data
              # field to the null object used by the second example.
              null: c4d.BaseObject = CheckType(c4d.BaseObject(c4d.Onull))
              doc.InsertObject(null)
      
              # Create the "Angle" user data field on the null object.
              bc: c4d.BaseContainer = c4d.GetCustomDataTypeDefault(c4d.DTYPE_REAL)
              bc[c4d.DESC_NAME] = "Angle"
              bc[c4d.DESC_UNIT] = c4d.DESC_UNIT_DEGREE
              null.AddUserData(bc)
              null[c4d.ID_USERDATA, 1] = c4d.utils.DegToRad(42.0) # Initial value.
      
              # Create the XPresso setup.
              tag: XPressoTag = CheckType(null.MakeTag(c4d.Texpresso))
              master: GvNodeMaster = CheckType(tag.GetNodeMaster())
      
              # Call this first example, you might have to comment this out, as it relies on the discussed
              # magic numbers.
              Solution.InternalLayerShaderAccess(shader, master)
              # Call the second example, which uses a Python node to access the layer shader. This will
              # always work.
              Solution.PythonNodeLayerShaderAccess(shader, null, master)
      
              c4d.EventAdd()
      
          @staticmethod
          def InternalLayerShaderAccess(
                  shader: c4d.BaseShader, master: GvNodeMaster) -> None:
              """Explores the internal structure of the layer shader data type and how it interfaces with 
              XPresso.
      
              Args:
                  shader: The layer shader to inspect.
                  master: The node master of the XPresso tag.
              """
              # You claimed on the forum that this ID worked for you for animating the transform angle of
              # a layer shader.
              #
              # c4d.DescID(c4d.DescLevel(c4d.SLA_LAYER_BLEND), c4d.DescLevel(10120, c4d.DTYPE_REAL))
      
              # Even when we ignore the concrete numeric offset and replace it with our own, it still does
              # not work for me. Could it be that you used abstracted GeListNode.__set/getitem__ access 
              # instead, i.e., what the console drag and drop generates? Because there this works for me.
              print(shader[c4d.SLA_LAYER_BLEND, 10060])
      
              # The problem with these IDs is that they are not deterministic from a public API point of
              # view. What holds true, is that data will placed with a stride of 20 (i.e., with 
              # BLEND_DATA_STEP_SIZE) and starts in theory at 10000 (BLEND_DATA_OFFSET). 
              # 
              # But the elements in a layer then do not follow the symbols exposed in the C++ API for the 
              # layer abstraction but are rather just sequentially numbered on a per layer type basis. E.g. 
              # for the transform layer, a parameter set event looks like this:
              #
              # void BlendEffectTransform::SetParameter(Int32 lItem, const DescID &id, ...)
              # {
              #     if (lItem == 0)
              #     {
              #         m_rAngle = t_data.GetFloat();
              #         flags |= DESCFLAGS_SET::PARAM_SET;
              #     }
              #     else if (lItem == 1)
              #     {
              #         m_bMirror = t_data.GetInt32() != 0;
              #         flags |= DESCFLAGS_SET::PARAM_SET;
              #     }
              #     ...
              #
              # Where in the API the angle has actually the id LAYER_S_PARAM_TRANS_ANGLE (2000) and the 
              # mirror has the id LAYER_S_PARAM_TRANS_MIRROR (2001). So, the internal values 0 and 1 are 
              # hardcoded and absolutely ignore the public API symbols. We can of course make here the 
              # observation that PUBLIC_SYMBOL - 2000 == INTERNAL_INDEX, but there is no guarantee at all 
              # that this will always hold true.
              #
              # The bigger problem is that the the layer start index seems to be somewhat arbitrary. You 
              # normally would expect that the first layer starts at 10000 (because BLEND_DATA_OFFSET and 
              # that is what the code suggests), but this does not hold true. When I added a single 
              # transform layer, its angle parameter had most of the time the ID 10060, i.e., this first 
              # layer seems to start at what would be the third slot. Once I have also seen it starting at
              # 10040 without having a good explanation why.
      
              # So, internally, the layer shader is a bit irregular, or at least more complex than it looks
              # like. But let's continue for now and accept these magic numbers.
      
              # To more formally define the DescID for this parameter, we need to define its DescID. For 
              # that we need the data type of SLA_LAYER_BLEND. It is not exposed as a symbol in the public
              # API (but you could look up its numeric value in the description of a layer shader). So, we 
              # define it here.
              CUSTOMDATA_BLEND_LIST: int = 1011131
      
              # Now we can define the DescID for the transform angle parameter of the first layer. This 
              # assumes that it uses the magic number 10060 for the first property of the first layer and 
              # that it is of type float. If it is a transform layer or not does not really matter.
              did: c4d.DescID = c4d.DescID(
                  # First desc level is the datatype unexposed to the public API (both C++ and Python)
                  c4d.DescLevel(    
                      c4d.SLA_LAYER_BLEND,   # The parameter ID for the layer blend list.
                      CUSTOMDATA_BLEND_LIST, # Its data type.
                      shader.GetType()),     # And the owner type of the parameter, i.e., the layer shader.
                  # The sub-channel of the parameter, i.e., we reach here into the internals of the 
                  # unexposed data type.
                  c4d.DescLevel(             
                      10060,                 # Our magic number for the first property of the first layer.
                      c4d.DTYPE_REAL,        # The angle is a real number, you got that absolutely right.
                      0))                    # For sub-channels, the owner type is usually 0.
              
              # With this DescID, we can now access the parameter value formally.
              if not shader.SetParameter(did, c4d.utils.DegToRad(30), c4d.DESCFLAGS_SET_NONE):
                  raise RuntimeError("Could not set parameter value.")
              
              angle: float | None = shader.GetParameter(did, c4d.DESCFLAGS_GET_NONE)
              if angle is None:
                  raise RuntimeError("Could not get parameter value.")
      
              print(f"{angle = }")
      
              # The bad news is that all this will not work in XPresso. The code below will run and create 
              # a port but the port is absolutely garbage, as it is a "color profile" port. But I had a 
              # look at how the C++ XPresso code creates the ports for #ID_OPERATOR_OBJECT nodes and there
              # is custom logic also covering layer shaders that is not replicable in the public API
              # (neither C++ nor Python). Which is likely why this fails with a nonsense port.
      
              # edit: okay, sometimes it seems to work. I would still avoid all this layer shader internals mess.
      
              root: GvNode = CheckType(master.GetRoot())
              objNode: GvNode = CheckType(master.CreateNode(root, c4d.ID_OPERATOR_OBJECT, x=50, y=50))
              objNode[c4d.GV_OBJECT_OBJECT_ID] = shader
              brokenAnglePort: GvPort | None = objNode.AddPort(c4d.GV_PORT_INPUT, did)
              if not brokenAnglePort:
                  raise RuntimeError("Could not add port to node.")
              print(f"{brokenAnglePort = }")
      
          @staticmethod
          def PythonNodeLayerShaderAccess(
              shader: c4d.BaseShader, null: c4d.BaseObject,master: GvNodeMaster) -> None:
              """Creates a Python XPresso node that accesses the transform angle parameter of the first
              layer in the given layer shader.
      
              Args:
                  shader: The layer shader to manipulate.
                  null: The null object that hosts the XPresso tag and the 'Angle' user data field.
                  master: The node master of the XPresso tag.
              """
              # As you said yourself, an easy way out is to use a Python node. So, let's start by creating
              # a Python node.
              root: GvNode = CheckType(master.GetRoot())
              pyNode: GvNode = CheckType(master.CreateNode(root, c4d.ID_OPERATOR_PYTHON, x=200, y=200))
      
              # Create a parameter description for a link box named "Shader Link" and add it as a user 
              # data field to the Python node. There are other ways to get "in" a BaseList2D into a Python
              # node, but it does not have a BaseLink port type, so we always have get a bit creative.
              # Another way could be to pass the UUID of the node, but this then always requires us 
              # searching the node which costs performance.
              bc: c4d.BaseContainer = c4d.GetCustomDataTypeDefault(c4d.DTYPE_BASELISTLINK)
              bc[c4d.DESC_NAME] = "Shader Link"
              pyNode.AddUserData(bc)
      
              # Now set the user data to our shader and the code to the code we define below.
              pyNode[c4d.ID_USERDATA, 1] = shader 
              pyNode[c4d.GV_PYTHON_CODE] = Solution.CODE
      
              # Now remove all the default ports from the node.
              ports: list[GvPort] = CheckType((pyNode.GetInPorts() or []) + (pyNode.GetOutPorts() or []))
              for p in ports:
                  pyNode.RemovePort(p)
      
              # Now we are going to add in and output ports to the Python node. This works a bit differently
              # than for other nodes, see https://developers.maxon.net/forum/topic/16302/ for a more in 
              # depth discussion of the subject.
              angleInPort: GvPort = CheckType(pyNode.AddPort(
                  c4d.GV_PORT_INPUT,
                  c4d.DescID(
                      c4d.DescLevel(c4d.IN_REAL, c4d.DTYPE_SUBCONTAINER, pyNode.GetType()),
                      c4d.DescLevel(1000, c4d.DTYPE_REAL, 0)
                  )))
              angleInPort.SetName("angle_in")
              
              angleOutPort: GvPort = CheckType(pyNode.AddPort(
                  c4d.GV_PORT_OUTPUT,
                  c4d.DescID(
                      c4d.DescLevel(c4d.OUT_REAL, c4d.DTYPE_SUBCONTAINER, pyNode.GetType()),
                      c4d.DescLevel(1000, c4d.DTYPE_REAL, 0)
                  )))
              angleOutPort.SetName("angle_out")
      
              # Now we create nodes for the null object and a result node and connect them to the Python.
              objNode: GvNode = CheckType(master.CreateNode(root, c4d.ID_OPERATOR_OBJECT, x=50, y=100))
              resNode: GvNode = CheckType(master.CreateNode(root, c4d.ID_OPERATOR_RESULT, x=350, y=200))
      
              # The output port for our "Angle" user data field on the null object.
              objOutPort: GvPort | None = objNode.AddPort(
                  c4d.GV_PORT_OUTPUT,
                  c4d.DescID(
                      c4d.DescLevel(c4d.ID_USERDATA, c4d.DTYPE_SUBCONTAINER, objNode.GetType()),
                      c4d.DescLevel(1, c4d.DTYPE_REAL, 0)
                  ))
              if not objOutPort:
                  raise RuntimeError("Could add user data port to object node.")
              
              # A result node can only have one input port, so we can also just do this without any danger
              # that future versions of Cinema 4D might break this code. We could also be more verbose.
              resInPort: GvPort | None = resNode.GetInPorts()[0] if resNode.GetInPorts() else None
              if not resInPort:
                  raise RuntimeError("Could not get input port from result node.")
              
              # And we wire everything up and are done, yay!
              objOutPort.Connect(angleInPort)
              angleOutPort.Connect(resInPort)
      
      
          # The Python code used by the second, Python node based example, to set the code of the Python
          # node.
          CODE: str = '''import c4d
      
      op: c4d.modules.graphview.GvNode # The Python Xpresso node containing this code.
      
      def main() -> None:
          """Sets the value of the transform angle parameter of the first layer in the linked layer 
          shader and outputs the value to the output port.
          """
          global angle_out
      
          try:
              shader: c4d.BaseShader = op[c4d.ID_USERDATA, 1] # The shader linked in the user data field.
              if not isinstance(shader, c4d.LayerShader):
                  angle_out = 0.0
                  print("The linked shader is not a layer shader.")
                  return
              
              # It is of course up to you to define the details of this. My code does not make much sense.
              layer: c4d.LayerShaderLayer = shader.GetFirstLayer()
              if layer.GetType() != c4d.TypeTransform:
                  angle_out = 0.0
                  print("The first layer is not a transform layer.")
                  return
      
              if not layer.SetParameter(c4d.LAYER_S_PARAM_TRANS_ANGLE, angle_in):
                  angle_out = 0.0
                  print("Could not set angle parameter.")
                  return
      
              angle_out = angle_in
          except:
              angle_out = 0.0
      '''
      
      
      if __name__ == '__main__':
          Solution.main()
      
      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: Maxon One Fall SDK Release

      Thanks for pointing it out, changed it 🙂

      posted in News & Information
      ferdinandF
      ferdinand
    • RE: DescID and DescLevel for Shader Layers and properties

      Hey @alcol68,

      sorry, I have not forgotten you, but I was busy with the fall release. I will have a look tomorrow. What you are doing c4d.DescLevel(10120, ...) (i.e., BLEND_DATA_OFFSET + X) is the right direction.

      Xpresso is an unholy hellhole when it comes to its operator IDs and things can fail when you are not ultra precise in your DescID definitions (your DescLevel is there for example missing the creator, and Xpresso can be picky about that - everywhere else the creator usually does not matter).

      Cheers,
      Ferdinand

      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: Broken Push Notifications

      Hey @mogh,

      without details from your console, this is hard to answer. The dangling sessions are well known NodeBB problem and the NodeBB team has never fully addressed it. The general consensus is that this is caused by NodeBB locking onto outdated service workers, which when you have still the site open while doing so, might even survive a cache reset.

      What I would do:

      1. Check if the problem exists on multiple devices and/or browsers.
      2. Log out.
      3. Close developers.maxon.net.
      4. Remove the whole cache of developers.maxon.net.

      When this does not help, I would have to see your console output to see if there are any errors being raised.

      Cheers,
      Ferdinand

      posted in News & Information
      ferdinandF
      ferdinand
    • RE: Cineware as dll

      Hey,

      well, the reason for the "compiler" restrictions mentioned in the docs, is that the SDK is shipped with code example projects and precompiled static libraries for these targets. But the SDK also comes with its full source (the 'includes'), so nothing is really preventing you from using another IDE/compiler. The VS 2019 projects should also be relatively safe to upgrade to 2022. With XCode things are more complicated, and I would there recommend creating your own project from scratch instead of updating the existing one.

      In the bigger picture, we probably should update the Cineware SDK to also using CMake as the Cinema 4D SDK does these days, and with that offer more flexibility. I have this on my bucket list, but it is not very high in priority, because the Cineware SDK is our least requested SDK. When you have concrete problems with creating a build system for the Cineware SDK for your desired compiler (and IDE), just post the questions here, and I will try to help you.

      Cheers,
      Ferdinand

      posted in Cineware SDK
      ferdinandF
      ferdinand
    • Maxon One Fall SDK Release

      Dear development community,

      On September the 10th, 2025, Maxon Computer unveiled the Maxon One fall release. For an overview of the new features please refer to the video.

      Alongside this release, existing APIs have been updated and new APIs have been introduced.

      Cinema 4D

      For a detailed overview, see the C++ SDK change notes and Python SDK change notes.

      C++ API

      • The biggest change for retuning developers is the all new CMake build system which has been introduced within the 2025 release cycle.
      • Added Migrating to the 2026.0 API manual, providing a brief overview of the changes for returning developers.

      Python API

      • Added more debugging and development features in mxutils.
      • Various bugfixes.

      ZBrush

      Python API

      • Introduced new Python API for ZBrush.
      • Introduced ZBrush Python online documentation and its SDK counterpart.

      Head to our download section to grab the newest SDK downloads.

      Happy rendering and coding,
      the Maxon SDK Team

      ℹ Cloudflare unfortunately still does interfere with our server cache. You might have to refresh your cache manually to see new data when you read this posting within 24 hours of its release.

      posted in News & Information news cinema 4d zbrush c++ python sdk
      ferdinandF
      ferdinand
    • RE: GetRad() / GetMp() update issue

      Hey @datamilch,

      Thank you for reaching out to us. GetRad and GetMp express the locally aligned bounding box of an object. I.e., the bounding box in the coordinate system of the object. For a world space aligned bounding box we once had this thread, the Cinema API does not offer this out of the box.

      So, transforming an object will never change its bounding box. What will change the bounding box of an object, is changing its size or changing the parameters of a generator object. When you change the parameters of a generator or when your instantiate a new object, you will have to execute the passes (BaseDocument.ExecutePasses) on a document that contains the object to see the correct bounding box - or wait for the next scene update.

      PS: Your script is also running illegal code. You cannot modify the active document off-main-thread. I assume the is a script for a Python generator object. Its main function runs off-main-thread and you call there your csto which in turn calls SendModelingCommand on the active document. This will all sooner or later crash.

      https://developers.maxon.net/docs/py/2025_3_1/manuals/manual_threading.html#threading-information

      Cheers,
      Ferdinand

      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: DescID and DescLevel for Shader Layers and properties

      Hey @alcol68,

      We cannot always answer right away.

      A layer shader stores its layer in a data type unexposed to the Python and public C++ API (BlendDataType) in its parameter SLA_LAYER_BLEND. Which is why this abstraction with LayerShaderLayer exists.

      Your code went into the right direction, as you probably have to use id decomposition ((param, sub_channel)) when you want to access such value without the layer abstraction. The issue with your code is that you do not take into account that this must be a dynamic description, i.e., you have to encode which layer you mean.

      I currently do not have much time due to approaching releases and would probably only find time next week to have a closer look. Here is how the data type reroutes parameter set events internally. As you can see, it encodes both the layer and the item into a desc level. How this works exactly and if this translates to Python, I would have to find out myself.

      static const Int32 BLEND_DATA_OFFSET = 10000;
      static const Int32 BLEND_DATA_STEP_SIZE = 20;
      
      Bool Foo::GetParameter(...)
      {
        iBlendDataType *s = (iBlendDataType*)data;
        if (!s)
          return false;
      
        Int32 lID = id[0].id;
        lID -= BLEND_DATA_OFFSET;
      
        DescID idNew = id << 1; // push out id[0] 
      
        Int32 lItem = lID % BLEND_DATA_STEP_SIZE;
        Int32 lLayer = lID / BLEND_DATA_STEP_SIZE;
      
        //BlendLayer is what you know as LayerShaderLayer
        BlendLayer* pLayer = s->m_BlendLayers.SearchLayer(lLayer); 
        if (!pLayer)
          return false;
      
        pLayer->GetParameter(lItem, idNew, t_data, flags);
      
        return CustomDataTypeClass::GetParameter(data, id, t_data, flags);
      }
      

      Cheers,
      Ferdinand

      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: DescID and DescLevel for Shader Layers and properties

      Hey @alcol68,

      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

      Your question is a little bit ambiguous in what DescID you are looking for a layer shader, you probably mean the properties of one of its children and not the layer shader itself. I would recommend to have a look at BaseShader: Access and Structure (C++) to understand how shaders are nested. You can then check out this posting for how to manipulate a layer shader in Python where I also documented its more fringe parameters.

      Cheers,
      Ferdinand

      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: Can I get keyboard input mode?

      Hey @Dunhou,

      I have edited your posting. It is fine to mention the beta forum, but we would rather not see its name written here.

      When you are talking about 'C++', I assume you are talking here about the Windows SDK/API, as for example GetGUIThreadInfo. I would doubt that you get very far with that here. Cinema 4D is very OS agnostic and we have our whole GUI decoupled from the OS. The little rename window in our tree view control is not an OS rename dialog. I am not even sure if this is something with an HWND handle which you could address so that you could intercept keyboard events to it, or if this is just a virtual window within our API.

      For this IMM thing to work, we would have to use the IME API in Cinema 4D, so that our text edit gadgets support it. I also do not quite understand what you are trying to do. The missing piece is here probably that our text edit gadget does not send its current content to the IME API (at a glance, via ImmSetCompositionStringW as shown here). And as a third party, you cannot really fix that. Because you (a) do not own the implementation and you (b) are not informed about an edit event, so you cannot just write a plugin which sends the object name to the IME API.

      Cheers,
      Ferdinand

      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: Cineware as dll

      Hey @megamau,

      are you referring to the Cineware libraries shipped with Cinema 4D (e.g., as a dll under Windows)? This not the same Cineware as the SDK Cineware (Melange). There are also currently no plans for converting the Cineware (Melange) SDK into a dynamic library.

      I also do not quite understand the inherint benefit of that? You want to bind with other languages than C++ to Cineware or what? C++ is currently unfourntely the only language we support. And if you REALLY want a Cineware (Melange) dynamic library, you could also just create it yourself. But then you will still need a static library (the declarations) to load and link against that DLL. So, I do not really understand what would be won with that.

      Cheers,
      Ferdinand

      posted in Cineware SDK
      ferdinandF
      ferdinand
    • RE: Can I get keyboard input mode?

      Hey @Dunhou,

      Thank you for reaching out to us. I think I do not have to tell you that there is c4d.gui.GetInputEvent and .GetInputState. The event variant literally grabs the last item from the input event queue and the state variant just polls for the current state again. Often they are the same, but in some scenarios Cinema 4D injects events which are then not visible as a state.

      When you have for example a tool which the user (or your plugin) set to have the shortcut "CTRL+P". Then, when the tool is invoked, you can usually still poll for CTRL or C being pressed with GetInputState because the user is likely still pressing these keys when the tool is activated. But GetInputEvent will likely not return C with the modifier CTRL being pressed, because that event likely has already been consumed.

      If an event has been consumed, is determined by returning either False (not consumed) or True (consumed) in the implementing message method such GeDialog.Message. Usually, events are consumed once they are handled by their rightful owner (but developers can make mistakes or deliberately ignore this).

      Options

      All GUI is sealed in the Cinema 4D API, i.e., we intentionally do not expose things like the object manager dialog. You therefore cannot send it messages or hook into its message stream. What you can technically do, is have a plugin or dialog which registers a timer and then regularly polls the keyboard. But that would neither tell you where one semantically connected input event starts and ends (e.g., the user is typing in a renaming field) nor who the owner is (e.g., the object manager). When you own a dialog, there is also BFM_RENAMEWINDOW but that won't help you much here.

      This is not any different in C++, as all this is an intentional design choice (to not expose GUI entities). What you would need is a core message which is emitted on renaming events but something like this does not exist at the moment.

      What you can technically also do, at least for scene elements, is hook into their message stream via BaseList2D.AddEventNotification etc., so that you get called when they receive messages, including a MSG_DESCRIPTION_POSTSETPARAMETER message when their name has been written. The issue with this is that you will only receive the messages for the exact scene element your registered for. And these methods are also for a reason internal, as you can quite easily crash Cinema 4D with them. But you will find some examples on the forum. Maybee you could just hook into the stream of the currently selected object or something like that. But you should REALLY avoid registering callbacks for countless scene elements, as this will make Cinema 4D very slow.

      Bottomline

      Cinema 4D's event system is VERY coarse, internally we are talking about this for a while and we might change this at some point. But right now you have to effectively track yourself what is happening in a scene (which is not very performant when every plugin does that and why we are inclined to change it at least to some degree).

      The easiest solution at the moment to track name changes is unfortunately to hook into EVMSG_CHANGE and build the data yourself.

      Cheers,
      Ferdinand

      Result

      9444964f-7cf6-4de7-a03c-63a8a83ef184-image.png

      Code

      """Demonstrates how to hash all objects in a scene to stores their names overt that hash, to then
      later detect name changes with that.
      
      Just create a scene, run this script, rename an object, and then run it again. This just scans for 
      objects but it would be easy to extend to other scene elements.
      """
      
      import c4d
      import mxutils
      
      doc: c4d.documents.BaseDocument  # The currently active document.
      
      # An attribute to inject data under into the c4d module. I am just doing this here so that a script
      # can have a persistent state and remembers what it did on the last execution. PLEASE DO NOT DO THIS
      # IN PRODUCTION CODE!
      NAME_STORAGE: str = "my_name_storage"
      
      def main() -> None:
          """Called by Cinema 4D when the script is being executed.
          """
          # You would either bind this code to EVMSG_CHANGE messages or better (because not as wasteful) 
          # to a specific event that makes sense in the context of your plugin. E.g., the user clicking the
          # button X in your plugin.
      
          # Get the dictionary we monkey patch into the c4d module. Please REALLY do not do this in 
          # production code, and instead use your own modules to store data! If you ignore this, it is
          # bound to lead to serious problems sooner or later!
          data: dict = getattr(c4d, NAME_STORAGE, {})
          if not data:
              print("No previous data found. Initializing new storage.")
          else:
              print("Found previous data:")
              for key, value in data.items():
                  print(f"    {key}: {value}")
      
          # Scan the scene for all objects.
          obj: c4d.BaseList2D
          noUpdates: bool = True
          for obj in mxutils.IterateTree(doc.GetFirstObject(), True, True, True):
              # Hash the object into an integer. This is reasonably collision resistant and possible since
              # Cinema 4D 2023.2 (before there was not C4DAtom.__hash__).
              hid: int = hash(obj)
              name: str = obj.GetName()
              # We found a new object.
              if hid not in data:
                  print(f"Found new object: {name} (hash: {hid})")
                  data[hid] = name
                  noUpdates = False
              # We found an already previously seen object AND its name changed.
              elif hid in data and data[hid] != name:
                  print(f"Object renamed: {data[hid]} -> {name} (hash: {hid})")
                  data[hid] = name
                  noUpdates = False
      
          if noUpdates:
              print("No changes detected.")
      
          # Write the data back into the c4d module.
          print("Storing data for next execution.")
          setattr(c4d, NAME_STORAGE, data)
      
      if __name__ == '__main__':
          main()
      
      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: [GeUserArea] Click detection selects wrong item when clicking on lower part of cell (image + text area)

      Hey,

      thanks for the extended code, but I still cannot run this, as it is only a fragment 😉 So, I only have a very rough understanding of what is going wrong.

      A likely issue apart from you just having a bug in your hit logic, is that you use the wrong coordinate system.

      if msg[c4d.BFM_INPUT_DEVICE] == c4d.BFM_INPUT_MOUSE \
          and msg[c4d.BFM_INPUT_CHANNEL] == c4d.BFM_INPUT_MOUSELEFT \
          and msg[c4d.BFM_INPUT_VALUE]:
      
          local_x = msg[c4d.BFM_INPUT_X]
          local_y = msg[c4d.BFM_INPUT_Y]
      
          scroll_y = self.dialog.get_scroll_offset()
      

      I would have to check myself, but there is some inconsistency with which dialogs send mouse input messages regarding the used coordinate system. Sometimes they send messages in the coordinate system of the dialog and sometimes messages in the coordinate system of the gadget. Just print out your (local_x and local_y) and check if the values make sense as local coordinates as you seem to treat them. On GeUserArea are multiple coordinate conversion methods. I think BFM_INPUT in this context is in local user area coordinates but I am not sure, it could also be that you have to convert them.

      The other thing is of course that you add this self.dialog.get_scroll_offset() on top of things. I assume this is sourced by something like GeDialog.GetVisibleArea? There you could also unintentionally mix coordinate systems.

      Cheers,
      Ferdinand

      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: [GeUserArea] Click detection selects wrong item when clicking on lower part of cell (image + text area)

      Hey @Amazing_iKe,

      thank you for reaching out to us. I cannot execute your code example like this. Please provide an executable example. And as far as I can see, that also seems to be your own code which fails there and not one of our API calls? We cannot debug your code for you. Or is there a specific API call which is failing for you?

      My recommendation would be more abstraction, all this function shuffling especially in the context of GUIs gets really confusing really quick. I would write myself a Grid and a GridItem class which encapsulate the items and their hit logic. What matters also is how you feed def on_click(self, x, y). There are local (e.g. within a user area) and global (e.g., within a dialog or screen) coordinate systems.

      Cheers,
      Ferdinand

      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: Finding, Renaming, and Updating all References to Assets

      Hey @d_keith,

      Thank you for reaching out to us. Please file this request (which I assume is not private) internally, the forum is not the right place for this.

      I am not quite sure what the exact question of yours is :D. GetAllAssets(New) is the middleware for the Project Asset Inspector (PAI) and the backend messages MSG_GETALLASSETS and MSG_RENAMETEXTURES. The latter is not wrapped at all for Python and the former only as an outgoing message. So, GetAllAssets is the 'helper' method used by the PAI to realize all its functionalities you are looking for.

      There was once the thread Get all textures for a material which was in scope very similar to yours. We very recently also had this thread where we talked about how you can abstract away some stuff.

      As always, we cannot debug code for users, so I did not run your code to find potential problems. At first glance it looks okay. I would however recommend to use the flag ASSETDATA_FLAG_MULTIPLEUSE as you will otherwise step over nodes using an already yielded asset. Which you probably do not want to do in your case.

      You are also very broad in your terminology - ' finding & updating all references to assets in a Cinema 4D document'. The rest of your posting seems to be however very focused on textures, while your code then again does not really make an attempt to distinguish what kind of asset you are handling. Documents can contain media (textures, movies, audio), scene data (documents, volumes, xrefs, substances, ies, ...) and font assets. There is also some super exotic internal data you sometimes come along. Not filtering at all what asset data you handle does not seem to be a good idea.

      Cheers,
      Ferdinand

      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: [API Question] How to List All Parameters in RS Shader Graph Material?

      Hello @writing-qi,

      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 familiarize yourself with our support guidelines, your first question is composed out of multiple explicit and implicit questions and also lacks executable code, which makes it rather hard to answer.

      GetDescription() returns parameters but all show "Access Failed"

      There is no such error in our API. I assume you mean an error like this instead: AttributeError: Parameter value not accessible (object unknown in Python).

      The Cinema 4D API is primarily a C++ API and therefore exist data types that simply have not been ported to Python. For example, many objects have the parameters ID_BASELIST_ICON_PRESET_BUTTONS and ID_BASELIST_NODESPACE_ADD_REMOVE_BUTTONS (which are usually hidden) and when you try to access them, it will raise an error, because the respective data types simply have not been wrapped, so the Python API has no idea what to do with the data held by these parameters.

      In Redshift, which started out as a third party plugin and is still an optional feature of Cinema 4D, many data types have never been ported to Python which makes them simply inaccessible. But Redshift usually decomposes their data via channel decomposition. I.e., the parameter for a "Texture" (CUSTOMDATATYPE_RSFILE) itself is not exposed, but its sub channels such as the path, tiling, etc. are. This has been discussed quite a few times before on the forum, for example here.

      What is the correct API to enumerate all parameters in RS Shader Graph?

      Well, descriptions are the tool for that, but descriptions also contain things where you cannot get/set a parameter, either because the data type is not exposed to Python or simply because there is nothing to get and set. The next SDK release will also contain a function called mxutils.GetParameterTreeString which allows you to visualize the sensible parameters of a scene element. But neither it nor c4d.Description.__iter__ will automatically unfold sub-channels. E.g., when you come along a node which has a parameter of type DYTPE_VECTOR at ID_MY_VECTOR_PRAM, the description will yield you a parameter at ID_MY_VECTOR_PRAM but not its sub-channels (ID_MY_VECTOR_PRAM, VECTOR_X), (ID_MY_VECTOR_PRAM, VECTOR_Y), and (ID_MY_VECTOR_PRAM, VECTOR_Z). The same goes for Redshift datatypes, when you come across a parameter of type CUSTOMDATATYPE_RSFILE, e.g., for the 'Image > File' (REDSHIFT_SHADER_TEXTURESAMPLER_TEX0) parameter of an RS Texture node. The description will list the parameter for REDSHIFT_SHADER_TEXTURESAMPLER_TEX0 for that node but not its nested parameters, such as [c4d.REDSHIFT_SHADER_TEXTURESAMPLER_TEX0,c4d.REDSHIFT_FILE_PATH], [..,c4d.REDSHIFT_FILE_LAYER], [...,c4d.REDSHIFT_FILE_COLORSPACE], etc. will not be listed.

      I am afraid there is no abstracted way to solve this in Python. You would need access to the datatype registries which you do not have in Python. And even in the public C++ API, I currently do not see a way to do this. You need access to our internal API to be able to query a data type for the sub-channels it does register.

      But you could of course just hardcode the cases, I have given a small example below for CUSTOMDATATYPE_RSFILE, it is not that much work for one data type. But Redshift did use a lot of custom data types in its old Xpresso system, so wrapping them all could be a bit of work (but should still be doable).

      Cheers,
      Ferdinand

      Output

      For this graph:
      72414249-863b-4c13-8acd-5247941e64b3-image.png

      RS Xpresso Material: <c4d.BaseMaterial object called RS C4D Shader/RS Shader Graph with ID 1036224 at 1819322820416>
      	Found Xpresso node: <c4d.modules.graphview.GvNode object called Shader Graph/XGroup with ID 1001102 at 1818547015360>
      		Parameter 'Icon File / ID'(c4d.ID_BASELIST_ICON_FILE) =
      		Parameter 'Icon Color'(c4d.None) = 0
      		Parameter 'Color'(c4d.ID_BASELIST_ICON_COLOR) = Vector(0, 0, 0)
      		Parameter ''(c4d.ID_BASELIST_ICON_PRESET_BUTTONS) = Inaccessible in Python
      		Parameter 'Name'(c4d.ID_BASELIST_NAME) = Shader Graph
      		Parameter 'Layer'(c4d.ID_LAYER_LINK) = None
      		Parameter ''(c4d.ID_BASELIST_NODESPACE_ADD_REMOVE_BUTTONS) = Inaccessible in Python
      		Parameter 'Enabled'(c4d.ID_GVBASE_ENABLE) = 1
      		Parameter 'Title Color'(c4d.ID_GVBASE_COLOR) = Vector(0.38, 0.384, 0.392)
      		Parameter 'Remark'(c4d.ID_GVBASE_REMARK) =
      		Parameter 'Inputs First'(c4d.GV_GROUP_INPUTS_FIRST) = 0
      		Parameter 'Active'(c4d.GV_GROUP_ACTIVE) = 0
      	Found Xpresso node: <c4d.modules.graphview.GvNode object called Output/Output with ID 1001101 at 1819032444352>
      		Parameter 'Icon File / ID'(c4d.ID_BASELIST_ICON_FILE) =
      		Parameter 'Icon Color'(c4d.None) = 0
      		Parameter 'Color'(c4d.ID_BASELIST_ICON_COLOR) = Vector(0, 0, 0)
      		Parameter ''(c4d.ID_BASELIST_ICON_PRESET_BUTTONS) = Inaccessible in Python
      		Parameter 'Name'(c4d.ID_BASELIST_NAME) = Output
      		Parameter 'Layer'(c4d.ID_LAYER_LINK) = None
      		Parameter ''(c4d.ID_BASELIST_NODESPACE_ADD_REMOVE_BUTTONS) = Inaccessible in Python
      		Parameter 'Enabled'(c4d.ID_GVBASE_ENABLE) = 1
      		Parameter 'Title Color'(c4d.ID_GVBASE_COLOR) = Vector(0.286, 0.329, 0.463)
      		Parameter 'Remark'(c4d.ID_GVBASE_REMARK) =
      		Parameter 'Material ID'(c4d.GV_REDSHIFT_OUTPUT_MATERIAL_ID) = 0
      	Found Xpresso node: <c4d.modules.graphview.GvNode object called RS Material/RS Shader with ID 1001101 at 1819032542400>
      		Parameter 'Icon File / ID'(c4d.ID_BASELIST_ICON_FILE) =
      		Parameter 'Icon Color'(c4d.None) = 0
      		Parameter 'Color'(c4d.ID_BASELIST_ICON_COLOR) = Vector(0, 0, 0)
      		Parameter ''(c4d.ID_BASELIST_ICON_PRESET_BUTTONS) = Inaccessible in Python
      		Parameter 'Name'(c4d.ID_BASELIST_NAME) = RS Material
      		Parameter 'Layer'(c4d.ID_LAYER_LINK) = None
      		Parameter ''(c4d.ID_BASELIST_NODESPACE_ADD_REMOVE_BUTTONS) = Inaccessible in Python
      		Parameter 'Enabled'(c4d.ID_GVBASE_ENABLE) = 1
      		Parameter 'Title Color'(c4d.ID_GVBASE_COLOR) = Vector(0.529, 0.345, 0.333)
      		Parameter 'Remark'(c4d.ID_GVBASE_REMARK) =
      		Parameter 'Version'(c4d.GV_REDSHIFT_SHADER_META_VERSION) = 17
      		Parameter 'Shader Class'(c4d.GV_REDSHIFT_SHADER_META_CLASSNAME) = Material
      		Parameter 'preset'(c4d.REDSHIFT_SHADER_MATERIAL_PRESET) = 65536
      		Parameter 'diffuse_color'(c4d.REDSHIFT_SHADER_MATERIAL_DIFFUSE_COLOR) = Vector(0.5, 0.5, 0.5)
      		Parameter 'diffuse_weight'(c4d.REDSHIFT_SHADER_MATERIAL_DIFFUSE_WEIGHT) = 1.0
      		Parameter 'diffuse_roughness'(c4d.REDSHIFT_SHADER_MATERIAL_DIFFUSE_ROUGHNESS) = 0.0
      		Parameter 'transl_color'(c4d.REDSHIFT_SHADER_MATERIAL_TRANSL_COLOR) = Vector(0.5, 0.5, 0.5)
      		Parameter 'transl_weight'(c4d.REDSHIFT_SHADER_MATERIAL_TRANSL_WEIGHT) = 0.0
      		Parameter 'refl_color'(c4d.REDSHIFT_SHADER_MATERIAL_REFL_COLOR) = Vector(1, 1, 1)
      		Parameter 'refl_weight'(c4d.REDSHIFT_SHADER_MATERIAL_REFL_WEIGHT) = 1.0
      		Parameter 'refl_roughness'(c4d.REDSHIFT_SHADER_MATERIAL_REFL_ROUGHNESS) = 0.0
      		Parameter 'refl_samples'(c4d.REDSHIFT_SHADER_MATERIAL_REFL_SAMPLES) = 16
      		Parameter 'refl_brdf'(c4d.REDSHIFT_SHADER_MATERIAL_REFL_BRDF) = 0
      		Parameter 'refl_aniso'(c4d.REDSHIFT_SHADER_MATERIAL_REFL_ANISO) = 0.0
      		Parameter 'refl_aniso_rotation'(c4d.REDSHIFT_SHADER_MATERIAL_REFL_ANISO_ROTATION) = 0.0
      		Parameter 'refl_fresnel_mode'(c4d.REDSHIFT_SHADER_MATERIAL_REFL_FRESNEL_MODE) = 3
      		Parameter 'refl_reflectivity'(c4d.REDSHIFT_SHADER_MATERIAL_REFL_REFLECTIVITY) = Vector(0.04, 0.04, 0.04)
      		Parameter 'refl_edge_tint'(c4d.REDSHIFT_SHADER_MATERIAL_REFL_EDGE_TINT) = Vector(0, 0, 0)
      		Parameter 'refl_ior3'(c4d.REDSHIFT_SHADER_MATERIAL_REFL_IOR3) = Vector(1.5, 1.5, 1.5)
      		Parameter 'refl_k3'(c4d.REDSHIFT_SHADER_MATERIAL_REFL_K3) = Vector(0, 0, 0)
      		Parameter 'refl_metalness'(c4d.REDSHIFT_SHADER_MATERIAL_REFL_METALNESS) = 0.0
      		Parameter 'refl_ior'(c4d.REDSHIFT_SHADER_MATERIAL_REFL_IOR) = 1.5
      		Parameter 'sheen_color'(c4d.REDSHIFT_SHADER_MATERIAL_SHEEN_COLOR) = Vector(1, 1, 1)
      		Parameter 'sheen_weight'(c4d.REDSHIFT_SHADER_MATERIAL_SHEEN_WEIGHT) = 0.0
      		Parameter 'sheen_roughness'(c4d.REDSHIFT_SHADER_MATERIAL_SHEEN_ROUGHNESS) = 0.30000001192092896
      		Parameter 'sheen_samples'(c4d.REDSHIFT_SHADER_MATERIAL_SHEEN_SAMPLES) = 64
      		Parameter 'refr_color'(c4d.REDSHIFT_SHADER_MATERIAL_REFR_COLOR) = Vector(1, 1, 1)
      		Parameter 'refr_weight'(c4d.REDSHIFT_SHADER_MATERIAL_REFR_WEIGHT) = 0.0
      		Parameter 'refr_roughness'(c4d.REDSHIFT_SHADER_MATERIAL_REFR_ROUGHNESS) = 0.0
      		Parameter 'refr_samples'(c4d.REDSHIFT_SHADER_MATERIAL_REFR_SAMPLES) = 8
      		Parameter 'refr_ior'(c4d.REDSHIFT_SHADER_MATERIAL_REFR_IOR) = 1.5
      		Parameter 'refr_use_base_IOR'(c4d.REDSHIFT_SHADER_MATERIAL_REFR_USE_BASE_IOR) = 1
      		Parameter 'refr_abbe'(c4d.REDSHIFT_SHADER_MATERIAL_REFR_ABBE) = 0.0
      		Parameter 'refr_thin_walled'(c4d.REDSHIFT_SHADER_MATERIAL_REFR_THIN_WALLED) = 0
      		Parameter 'ss_unitsMode'(c4d.REDSHIFT_SHADER_MATERIAL_SS_UNITSMODE) = 0
      		Parameter 'refr_transmittance'(c4d.REDSHIFT_SHADER_MATERIAL_REFR_TRANSMITTANCE) = Vector(1, 1, 1)
      		Parameter 'refr_absorption_scale'(c4d.REDSHIFT_SHADER_MATERIAL_REFR_ABSORPTION_SCALE) = 0.009999999776482582
      		Parameter 'ss_extinction_coeff'(c4d.REDSHIFT_SHADER_MATERIAL_SS_EXTINCTION_COEFF) = Vector(0, 0, 0)
      		Parameter 'ss_extinction_scale'(c4d.REDSHIFT_SHADER_MATERIAL_SS_EXTINCTION_SCALE) = 0.009999999776482582
      		Parameter 'ss_scatter_coeff'(c4d.REDSHIFT_SHADER_MATERIAL_SS_SCATTER_COEFF) = Vector(1, 1, 1)
      		Parameter 'ss_amount'(c4d.REDSHIFT_SHADER_MATERIAL_SS_AMOUNT) = 0.0
      		Parameter 'ss_phase'(c4d.REDSHIFT_SHADER_MATERIAL_SS_PHASE) = 0.0
      		Parameter 'ss_samples'(c4d.REDSHIFT_SHADER_MATERIAL_SS_SAMPLES) = 16
      		Parameter 'ms_amount'(c4d.REDSHIFT_SHADER_MATERIAL_MS_AMOUNT) = 0.0
      		Parameter 'ms_radius_scale'(c4d.REDSHIFT_SHADER_MATERIAL_MS_RADIUS_SCALE) = 100.0
      		Parameter 'ms_mode'(c4d.REDSHIFT_SHADER_MATERIAL_MS_MODE) = 1
      		Parameter 'ms_samples'(c4d.REDSHIFT_SHADER_MATERIAL_MS_SAMPLES) = 64
      		Parameter 'ms_include_mode'(c4d.REDSHIFT_SHADER_MATERIAL_MS_INCLUDE_MODE) = 1
      		Parameter 'ms_color0'(c4d.REDSHIFT_SHADER_MATERIAL_MS_COLOR0) = Vector(1, 0.9, 0.7)
      		Parameter 'ms_weight0'(c4d.REDSHIFT_SHADER_MATERIAL_MS_WEIGHT0) = 1.0
      		Parameter 'ms_radius0'(c4d.REDSHIFT_SHADER_MATERIAL_MS_RADIUS0) = 1.0
      		Parameter 'ms_color1'(c4d.REDSHIFT_SHADER_MATERIAL_MS_COLOR1) = Vector(1, 0.9, 0.7)
      		Parameter 'ms_weight1'(c4d.REDSHIFT_SHADER_MATERIAL_MS_WEIGHT1) = 0.0
      		Parameter 'ms_radius1'(c4d.REDSHIFT_SHADER_MATERIAL_MS_RADIUS1) = 1.0
      		Parameter 'ms_color2'(c4d.REDSHIFT_SHADER_MATERIAL_MS_COLOR2) = Vector(1, 0.9, 0.7)
      		Parameter 'ms_weight2'(c4d.REDSHIFT_SHADER_MATERIAL_MS_WEIGHT2) = 0.0
      		Parameter 'ms_radius2'(c4d.REDSHIFT_SHADER_MATERIAL_MS_RADIUS2) = 1.0
      		Parameter 'coat_color'(c4d.REDSHIFT_SHADER_MATERIAL_COAT_COLOR) = Vector(1, 1, 1)
      		Parameter 'coat_weight'(c4d.REDSHIFT_SHADER_MATERIAL_COAT_WEIGHT) = 0.0
      		Parameter 'coat_roughness'(c4d.REDSHIFT_SHADER_MATERIAL_COAT_ROUGHNESS) = 0.0
      		Parameter 'coat_samples'(c4d.REDSHIFT_SHADER_MATERIAL_COAT_SAMPLES) = 16
      		Parameter 'coat_brdf'(c4d.REDSHIFT_SHADER_MATERIAL_COAT_BRDF) = 0
      		Parameter 'coat_fresnel_mode'(c4d.REDSHIFT_SHADER_MATERIAL_COAT_FRESNEL_MODE) = 3
      		Parameter 'coat_reflectivity'(c4d.REDSHIFT_SHADER_MATERIAL_COAT_REFLECTIVITY) = Vector(0.027, 0.027, 0.027)
      		Parameter 'coat_ior3'(c4d.REDSHIFT_SHADER_MATERIAL_COAT_IOR3) = Vector(1.4, 1.4, 1.4)
      		Parameter 'coat_ior'(c4d.REDSHIFT_SHADER_MATERIAL_COAT_IOR) = 1.399999976158142
      		Parameter 'coat_transmittance'(c4d.REDSHIFT_SHADER_MATERIAL_COAT_TRANSMITTANCE) = Vector(1, 1, 1)
      		Parameter 'coat_thickness'(c4d.REDSHIFT_SHADER_MATERIAL_COAT_THICKNESS) = 0.10000000149011612
      		Parameter 'coat_bump_input'(c4d.REDSHIFT_SHADER_MATERIAL_COAT_BUMP_INPUT) = Vector(0, 0, 0)
      		Parameter 'overall_color'(c4d.REDSHIFT_SHADER_MATERIAL_OVERALL_COLOR) = Vector(1, 1, 1)
      		Parameter 'opacity_color'(c4d.REDSHIFT_SHADER_MATERIAL_OPACITY_COLOR) = Vector(1, 1, 1)
      		Parameter 'emission_color'(c4d.REDSHIFT_SHADER_MATERIAL_EMISSION_COLOR) = Vector(0, 0, 0)
      		Parameter 'emission_weight'(c4d.REDSHIFT_SHADER_MATERIAL_EMISSION_WEIGHT) = 0.0
      		Parameter 'bump_input'(c4d.REDSHIFT_SHADER_MATERIAL_BUMP_INPUT) = Vector(0, 0, 0)
      		Parameter 'depth_override'(c4d.REDSHIFT_SHADER_MATERIAL_DEPTH_OVERRIDE) = 0
      		Parameter 'refl_depth'(c4d.REDSHIFT_SHADER_MATERIAL_REFL_DEPTH) = 3
      		Parameter 'refl_enablecutoff'(c4d.REDSHIFT_SHADER_MATERIAL_REFL_ENABLECUTOFF) = 0
      		Parameter 'refl_cutoff'(c4d.REDSHIFT_SHADER_MATERIAL_REFL_CUTOFF) = 0.009999999776482582
      		Parameter 'skip_inside_refl'(c4d.REDSHIFT_SHADER_MATERIAL_SKIP_INSIDE_REFL) = 1
      		Parameter 'refr_depth'(c4d.REDSHIFT_SHADER_MATERIAL_REFR_DEPTH) = 5
      		Parameter 'refr_enablecutoff'(c4d.REDSHIFT_SHADER_MATERIAL_REFR_ENABLECUTOFF) = 0
      		Parameter 'refr_cutoff'(c4d.REDSHIFT_SHADER_MATERIAL_REFR_CUTOFF) = 0.009999999776482582
      		Parameter 'combined_depth'(c4d.REDSHIFT_SHADER_MATERIAL_COMBINED_DEPTH) = 6
      		Parameter 'diffuse_direct'(c4d.REDSHIFT_SHADER_MATERIAL_DIFFUSE_DIRECT) = 1.0
      		Parameter 'diffuse_indirect'(c4d.REDSHIFT_SHADER_MATERIAL_DIFFUSE_INDIRECT) = 1.0
      		Parameter 'refl_direct'(c4d.REDSHIFT_SHADER_MATERIAL_REFL_DIRECT) = 1.0
      		Parameter 'refl_indirect'(c4d.REDSHIFT_SHADER_MATERIAL_REFL_INDIRECT) = 1.0
      		Parameter 'refl_isGlossiness'(c4d.REDSHIFT_SHADER_MATERIAL_REFL_ISGLOSSINESS) = 0
      		Parameter 'coat_direct'(c4d.REDSHIFT_SHADER_MATERIAL_COAT_DIRECT) = 1.0
      		Parameter 'coat_indirect'(c4d.REDSHIFT_SHADER_MATERIAL_COAT_INDIRECT) = 1.0
      		Parameter 'coat_isGlossiness'(c4d.REDSHIFT_SHADER_MATERIAL_COAT_ISGLOSSINESS) = 0
      		Parameter 'sheen_direct'(c4d.REDSHIFT_SHADER_MATERIAL_SHEEN_DIRECT) = 1.0
      		Parameter 'sheen_indirect'(c4d.REDSHIFT_SHADER_MATERIAL_SHEEN_INDIRECT) = 1.0
      		Parameter 'sheen_isGlossiness'(c4d.REDSHIFT_SHADER_MATERIAL_SHEEN_ISGLOSSINESS) = 0
      		Parameter 'refr_isGlossiness'(c4d.REDSHIFT_SHADER_MATERIAL_REFR_ISGLOSSINESS) = 0
      		Parameter 'decoupleIORFromRoughness'(c4d.REDSHIFT_SHADER_MATERIAL_DECOUPLEIORFROMROUGHNESS) = 0
      		Parameter 'shadow_opacity'(c4d.REDSHIFT_SHADER_MATERIAL_SHADOW_OPACITY) = 0.0
      		Parameter 'affects_alpha'(c4d.REDSHIFT_SHADER_MATERIAL_AFFECTS_ALPHA) = 1
      		Parameter 'block_volumes'(c4d.REDSHIFT_SHADER_MATERIAL_BLOCK_VOLUMES) = 1
      		Parameter 'energyCompMode'(c4d.REDSHIFT_SHADER_MATERIAL_ENERGYCOMPMODE) = 1
      		Parameter 'overallAffectsEmission'(c4d.REDSHIFT_SHADER_MATERIAL_OVERALLAFFECTSEMISSION) = 0
      		Parameter 'refl_endmode'(c4d.REDSHIFT_SHADER_MATERIAL_REFL_ENDMODE) = 1
      		Parameter 'anisotropy_orientation'(c4d.REDSHIFT_SHADER_MATERIAL_ANISOTROPY_ORIENTATION) = 2
      		Parameter 'anisotropy_uvChannel'(c4d.REDSHIFT_SHADER_MATERIAL_ANISOTROPY_UVCHANNEL) =
      		Parameter 'anisotropy_tangentChannel'(c4d.REDSHIFT_SHADER_MATERIAL_ANISOTROPY_TANGENTCHANNEL) =
      	Found Xpresso node: <c4d.modules.graphview.GvNode object called Baked Texture/RS Shader with ID 1001101 at 1819592565952>
      		Parameter 'Icon File / ID'(c4d.ID_BASELIST_ICON_FILE) =
      		Parameter 'Icon Color'(c4d.None) = 0
      		Parameter 'Color'(c4d.ID_BASELIST_ICON_COLOR) = Vector(0, 0, 0)
      		Parameter ''(c4d.ID_BASELIST_ICON_PRESET_BUTTONS) = Inaccessible in Python
      		Parameter 'Name'(c4d.ID_BASELIST_NAME) = Baked Texture
      		Parameter 'Layer'(c4d.ID_LAYER_LINK) = None
      		Parameter ''(c4d.ID_BASELIST_NODESPACE_ADD_REMOVE_BUTTONS) = Inaccessible in Python
      		Parameter 'Enabled'(c4d.ID_GVBASE_ENABLE) = 1
      		Parameter 'Title Color'(c4d.ID_GVBASE_COLOR) = Vector(0.663, 0.624, 0.424)
      		Parameter 'Remark'(c4d.ID_GVBASE_REMARK) =
      		Parameter 'Version'(c4d.GV_REDSHIFT_SHADER_META_VERSION) = 17
      		Parameter 'Shader Class'(c4d.GV_REDSHIFT_SHADER_META_CLASSNAME) = TextureSampler
      		Parameter 'tex0'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_TEX0) = Inaccessible in Python
      			Sub-channel parameter (REDSHIFT_SHADER_TEXTURESAMPLER_TEX0, 1000) =
      			Sub-channel parameter (REDSHIFT_SHADER_TEXTURESAMPLER_TEX0, 1010) =
      			Sub-channel parameter (REDSHIFT_SHADER_TEXTURESAMPLER_TEX0, 1011) =
      			Sub-channel parameter (REDSHIFT_SHADER_TEXTURESAMPLER_TEX0, 1001) = 0
      			Sub-channel parameter (REDSHIFT_SHADER_TEXTURESAMPLER_TEX0, 1006) = <c4d.BaseTime object at 0x000001A85FC0EDC0>
      			Sub-channel parameter (REDSHIFT_SHADER_TEXTURESAMPLER_TEX0, 1007) = <c4d.BaseTime object at 0x000001A85FC1F440>
      			Sub-channel parameter (REDSHIFT_SHADER_TEXTURESAMPLER_TEX0, 1009) = 0
      			Sub-channel parameter (REDSHIFT_SHADER_TEXTURESAMPLER_TEX0, 1003) = 0
      			Sub-channel parameter (REDSHIFT_SHADER_TEXTURESAMPLER_TEX0, 1004) = 0
      			Sub-channel parameter (REDSHIFT_SHADER_TEXTURESAMPLER_TEX0, 1005) = 30
      			Sub-channel parameter (REDSHIFT_SHADER_TEXTURESAMPLER_TEX0, 1008) = 0
      			Sub-channel parameter (REDSHIFT_SHADER_TEXTURESAMPLER_TEX0, 1002) = 0
      		Parameter 'tex0_layername'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_TEX0_LAYERNAME) =
      		Parameter 'tex0_colorSpace'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_TEX0_COLORSPACE) =
      		Parameter 'tex0_gamma'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_TEX0_GAMMA) = 1.0
      		Parameter 'tspace_id'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_TSPACE_ID) =
      		Parameter 'mirrorU'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_MIRRORU) = 0
      		Parameter 'mirrorV'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_MIRRORV) = 0
      		Parameter 'wrapU'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_WRAPU) = 1
      		Parameter 'wrapV'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_WRAPV) = 1
      		Parameter 'scale'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_SCALE) = Vector(1, 1, 0)
      		Parameter 'offset'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_OFFSET) = Vector(0, 0, 0)
      		Parameter 'rotate'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_ROTATE) = 0.0
      		Parameter 'color_multiplier'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_COLOR_MULTIPLIER) = Vector(1, 1, 1)
      		Parameter 'color_offset'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_COLOR_OFFSET) = Vector(0, 0, 0)
      		Parameter 'alpha_multiplier'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_ALPHA_MULTIPLIER) = 1.0
      		Parameter 'alpha_offset'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_ALPHA_OFFSET) = 0.0
      		Parameter 'alpha_is_luminance'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_ALPHA_IS_LUMINANCE) = 0
      		Parameter 'invalid_color'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_INVALID_COLOR) = Inaccessible in Python
      		Parameter 'filter_enable_type'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_FILTER_ENABLE_TYPE) = 2
      		Parameter 'filter_bicubic'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_FILTER_BICUBIC) = 0
      		Parameter 'prefer_sharp'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_PREFER_SHARP) = 1
      		Parameter 'mip_bias'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_MIP_BIAS) = 0.0
      		Parameter 'tone_map_enable'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_TONE_MAP_ENABLE) = 0
      		Parameter 'default_uv_if_invalid'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_DEFAULT_UV_IF_INVALID) = 1
      	Found Xpresso node: <c4d.modules.graphview.GvNode object called C4D Brick Shader/C4D Shader with ID 1001101 at 1819592570688>
      		Parameter 'Icon File / ID'(c4d.ID_BASELIST_ICON_FILE) =
      		Parameter 'Icon Color'(c4d.None) = 0
      		Parameter 'Color'(c4d.ID_BASELIST_ICON_COLOR) = Vector(0, 0, 0)
      		Parameter ''(c4d.ID_BASELIST_ICON_PRESET_BUTTONS) = Inaccessible in Python
      		Parameter 'Name'(c4d.ID_BASELIST_NAME) = C4D Brick Shader
      		Parameter 'Layer'(c4d.ID_LAYER_LINK) = None
      		Parameter ''(c4d.ID_BASELIST_NODESPACE_ADD_REMOVE_BUTTONS) = Inaccessible in Python
      		Parameter 'Enabled'(c4d.ID_GVBASE_ENABLE) = 1
      		Parameter 'Title Color'(c4d.ID_GVBASE_COLOR) = Vector(0.424, 0.392, 0.541)
      		Parameter 'Remark'(c4d.ID_GVBASE_REMARK) =
      		Parameter 'Width'(c4d.GV_REDSHIFT_BAKER_TEXTURE_WIDTH) = 128
      		Parameter 'Height'(c4d.GV_REDSHIFT_BAKER_TEXTURE_HEIGHT) = 128
      		Parameter 'Depth'(c4d.GV_REDSHIFT_BAKER_TEXTURE_DEPTH) = 0
      		Parameter 'Shader'(c4d.GV_REDSHIFT_BAKER_SHADER) = <c4d.BaseShader object called Brick/Brick with ID 5804 at 1822672588480>
      	Found Xpresso node: <c4d.modules.graphview.GvNode object called file_730884e9656be535~/RS Shader with ID 1001101 at 1819592599488>
      		Parameter 'Icon File / ID'(c4d.ID_BASELIST_ICON_FILE) =
      		Parameter 'Icon Color'(c4d.None) = 0
      		Parameter 'Color'(c4d.ID_BASELIST_ICON_COLOR) = Vector(0, 0, 0)
      		Parameter ''(c4d.ID_BASELIST_ICON_PRESET_BUTTONS) = Inaccessible in Python
      		Parameter 'Name'(c4d.ID_BASELIST_NAME) = file_730884e9656be535~
      		Parameter 'Layer'(c4d.ID_LAYER_LINK) = None
      		Parameter ''(c4d.ID_BASELIST_NODESPACE_ADD_REMOVE_BUTTONS) = Inaccessible in Python
      		Parameter 'Enabled'(c4d.ID_GVBASE_ENABLE) = 1
      		Parameter 'Title Color'(c4d.ID_GVBASE_COLOR) = Vector(0.663, 0.624, 0.424)
      		Parameter 'Remark'(c4d.ID_GVBASE_REMARK) =
      		Parameter 'Version'(c4d.GV_REDSHIFT_SHADER_META_VERSION) = 17
      		Parameter 'Shader Class'(c4d.GV_REDSHIFT_SHADER_META_CLASSNAME) = TextureSampler
      		Parameter 'tex0'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_TEX0) = Inaccessible in Python
      			Sub-channel parameter (REDSHIFT_SHADER_TEXTURESAMPLER_TEX0, 1000) = asset:///file_730884e9656be535~.jpg?name=back-2.jpg&db=MaxonAssets.db (assets.maxon.net)
      			Sub-channel parameter (REDSHIFT_SHADER_TEXTURESAMPLER_TEX0, 1010) =
      			Sub-channel parameter (REDSHIFT_SHADER_TEXTURESAMPLER_TEX0, 1011) =
      			Sub-channel parameter (REDSHIFT_SHADER_TEXTURESAMPLER_TEX0, 1001) = 0
      			Sub-channel parameter (REDSHIFT_SHADER_TEXTURESAMPLER_TEX0, 1006) = <c4d.BaseTime object at 0x000001A797214AC0>
      			Sub-channel parameter (REDSHIFT_SHADER_TEXTURESAMPLER_TEX0, 1007) = <c4d.BaseTime object at 0x000001A79721BFC0>
      			Sub-channel parameter (REDSHIFT_SHADER_TEXTURESAMPLER_TEX0, 1009) = 0
      			Sub-channel parameter (REDSHIFT_SHADER_TEXTURESAMPLER_TEX0, 1003) = 0
      			Sub-channel parameter (REDSHIFT_SHADER_TEXTURESAMPLER_TEX0, 1004) = 0
      			Sub-channel parameter (REDSHIFT_SHADER_TEXTURESAMPLER_TEX0, 1005) = 30
      			Sub-channel parameter (REDSHIFT_SHADER_TEXTURESAMPLER_TEX0, 1008) = 0
      			Sub-channel parameter (REDSHIFT_SHADER_TEXTURESAMPLER_TEX0, 1002) = 0
      		Parameter 'tex0_layername'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_TEX0_LAYERNAME) =
      		Parameter 'tex0_colorSpace'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_TEX0_COLORSPACE) =
      		Parameter 'tex0_gamma'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_TEX0_GAMMA) = 1.0
      		Parameter 'tspace_id'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_TSPACE_ID) =
      		Parameter 'mirrorU'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_MIRRORU) = 0
      		Parameter 'mirrorV'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_MIRRORV) = 0
      		Parameter 'wrapU'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_WRAPU) = 1
      		Parameter 'wrapV'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_WRAPV) = 1
      		Parameter 'scale'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_SCALE) = Vector(1, 1, 0)
      		Parameter 'offset'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_OFFSET) = Vector(0, 0, 0)
      		Parameter 'rotate'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_ROTATE) = 0.0
      		Parameter 'color_multiplier'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_COLOR_MULTIPLIER) = Vector(1, 1, 1)
      		Parameter 'color_offset'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_COLOR_OFFSET) = Vector(0, 0, 0)
      		Parameter 'alpha_multiplier'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_ALPHA_MULTIPLIER) = 1.0
      		Parameter 'alpha_offset'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_ALPHA_OFFSET) = 0.0
      		Parameter 'alpha_is_luminance'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_ALPHA_IS_LUMINANCE) = 0
      		Parameter 'invalid_color'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_INVALID_COLOR) = Inaccessible in Python
      		Parameter 'filter_enable_type'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_FILTER_ENABLE_TYPE) = 2
      		Parameter 'filter_bicubic'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_FILTER_BICUBIC) = 0
      		Parameter 'prefer_sharp'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_PREFER_SHARP) = 1
      		Parameter 'mip_bias'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_MIP_BIAS) = 0.0
      		Parameter 'tone_map_enable'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_TONE_MAP_ENABLE) = 0
      		Parameter 'default_uv_if_invalid'(c4d.REDSHIFT_SHADER_TEXTURESAMPLER_DEFAULT_UV_IF_INVALID) = 1
      

      Code

      """Prints the parameter values of all Xpresso nodes attached to the materials of a document.
      
      Adds custom handling for the Redshift data type `CUSTOMDATATYPE_RSFILE` to unfold its sub-channels.
      
      Note:
          This script requires Cinema 4D 2025.0.0 or higher to run because it uses mxutils.RecurseGraph to
          find all Xpresso nodes in a material. The general approach of this script can however be applied
          to much older versions of Cinema 4D (up to R21 guaranteed, maybe even older versions).
      """
      
      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`.
      
      # A bunch of data types to step over in descriptions because we do not care about them.
      NO_VALUE_TYPES: list[int] = [
              c4d.DTYPE_BUTTON, c4d.DTYPE_CHILDREN, c4d.DTYPE_CHILDS, c4d.DTYPE_DYNAMIC,
              c4d.DTYPE_GROUP, c4d.DTYPE_MULTIPLEDATA, c4d.DTYPE_NONE,
              c4d.DTYPE_SEPARATOR, c4d.DTYPE_SUBCONTAINER, c4d.DTYPE_POPUP
      ]
      
      def main() -> None:
          """Called by Cinema 4D when the script is being executed.
          """
          # Iterate over all materials in the document, stepping over everything that is not an
           # RS Xpresso material.
          for material in doc.GetMaterials():
              if not material.CheckType(c4d.Mrsgraph):
                  continue
              
              # Iterate over all Xpresso nodes owned by this material. #RecurseGraph is a 2025.0.0+ feature. 
              # You can achieve the same effect with the Redshift API in earlier versions of Cinema 4D. But
              # since you did not share your code, I took this shortcut instead of writing some lengthy code 
              # to find all RS Xpresso nodes for this material.
              print(f"RS Xpresso Material: {material}")
              for node in mxutils.RecurseGraph(material, yieldBranches=True, nodeFilter=[c4d.GVbase]):
                  print(f"\tFound Xpresso node: {node}")
      
                  # Iterate over the description of the node, stepping over all irrelevant data types.
                  description: c4d.Description = node.GetDescription(c4d.DESCFLAGS_DESC_0)
                  data: c4d.BaseContainer
                  pid: c4d.DescID
                  for data, pid, _ in description:
                      if pid[0].dtype in NO_VALUE_TYPES:
                          continue
      
                      # Now we try to access the data at #pid. This can fail because the data type of #pid
                      # might not be accessible in Python.
                      value: any | None = None
                      try:
                          value = node[pid]
                      except:
                          value = "Inaccessible in Python"
                      
                      print(f"\t\tParameter '{data[c4d.DESC_NAME]}'(c4d.{data[c4d.DESC_IDENT]}) = {value}")
      
                      # Hardcode one of the data types (CUSTOMDATATYPE_RSFILE) and unfold its sub-channels.
                      if pid[0].dtype == c4d.CUSTOMDATATYPE_RSFILE and len(pid) == 1:
                          for subChannel in  (c4d.REDSHIFT_FILE_PATH, 
                                              c4d.REDSHIFT_FILE_LAYER, 
                                              c4d.REDSHIFT_FILE_COLORSPACE,
                                              c4d.REDSHIFT_FILE_ANIMATION_MODE, 
                                              c4d.REDSHIFT_FILE_ANIMATION_RANGE_START,
                                              c4d.REDSHIFT_FILE_ANIMATION_RANGE_END, 
                                              c4d.REDSHIFT_FILE_ANIMATION_LOOP_COUNT,
                                              c4d.REDSHIFT_FILE_ANIMATION_FRAME_START,
                                              c4d.REDSHIFT_FILE_ANIMATION_FRAME_END,
                                              c4d.REDSHIFT_FILE_ANIMATION_FRAME_RATE,
                                              c4d.REDSHIFT_FILE_ANIMATION_FRAME_OFFSET,
                                              c4d.REDSHIFT_FILE_ANIMATION_TIMING_MODE):
                              try:
                                  value = node[pid[0].id, subChannel]
                              except:
                                  # This, that the sub-chanel is inaccessible too, can also happen, as the 
                                  # sub-channel could be as much an unwrapped data type as its parent 
                                  # parameter. Technically, a DescID can be composed out of three 
                                  # DescLevel, i.e., you could have a parameter X with N sub-channels
                                  # where each of the sub-channels could also have N sub-channels, e.g.,
                                  # (ID_FOO, ID_SUB_1, ID_SUB_1_1). None of these sub-channels has to use
                                  # a data type that is wrapped in Python. But something like this is 
                                  # rather rare. Parameters where the first desc level is not some kind of
                                  # container (e.g., DTYPE_SUBCONTAINER), usually only decomposes into 
                                  # atomic data types that can also be read from Python. But there is no
                                  # hard guarantee that this is the case.
                                  value = "Sub-channel is inaccessible in Python"
      
                              print(f"\t\t\tSub-channel parameter ({data[c4d.DESC_IDENT]}, "
                                    f"{subChannel}) = {value}")
      
      if __name__ == '__main__':
          main()
      
      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: Motion Blur, ExecutePasses in VideoPostData

      Hello @Aaron,

      Thank you for reaching out to us. Please follow our support procedures.

      • You should consolidate your postings until we answer, i.e., update your last posting instead of creating new ones. I have consolidated your postings for you.
      • You should also spend more time on making your problems reproducible for us. I know it can be easy to overlook in the heat of the battle but your question(s) are not as obvious to me as you probably think they are. As lined out in our support procedures, example code and/or scene data is usually required.

      I cannot answer every possible angle and interpretation this topic of yours could take, but I can state three things which come to mind when reading your two postings.

      What's the way to get several steps of motion blur transformation matrices for BaseObjects of the BaseDocument.

      That is simply not possible. A document has no notion of motion blur as it is a render effects implemented by a render engine, not something documents implement. Cinema's time system BaseTime allows for sub-frame accuracy, e.g., you could set a 10fps document to BaseTime(i, 30) and with that step with three sub-frames through the document. Simple things like position animations or other interpolated values will also work doing this. But simulations might yield different results than stepping with the natural frame rate through the document.

      And the most important thing is that there is absolutely no guarantee that this approach will result in 'matrices', i.e., transforms, that will match with what a render engine produces when you add some kind of sub-frame-based motion blur. Using this BaseTime approach is one way how the render engine could do it, but since render engines always have their own scene format, they could also do the calculation there, yielding different results than what Cinema's animation system would yield.

      When we want to add Motion Blur and call document->ExecutePasses(...) for some time then this POC is flushed and has no polygons or vertices, so it's not collected and can't be rendered

      It is too opaque for me what you are doing there to give a real answer, but:

      • There is no guarantee that the caches of an object are built at any point except for when a render engine gets a scene state to render or inside GetVirtualsObjects when you manually build your dependent objcts. Usually, caches are built but depending on where you are, the cache of another object might not have been built or updated yet, resulting in either an empty cache or you looking at the cache output from the previous scene update.
      • ExecutePasses has become a bit too popular tool recently. I guess that is partially my fault, because I have shown its usage in some examples. But executing the passes on a document is a very expensive operation and should only be carried out when absolutely necessary.
      • I would absolutely not be surprised if something like liquids do not support stepping through a document with a frame rate lower than the documents frame rate. But without concrete data and example code I am just guessing.

      Cheers,
      Ferdinand

      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: How to Modify Button Parameters?

      Hey @Ross,

      Thank you for reaching out to us. You are right, we cannot support third party APIs, but questions about our API do not automatically become moot or out of scope of support, just because they happen in the context of a plugin. We will of course still answer such questions. We will just never write code for a third-party API or test user code that requires a plugin.

      Your question '[I] need help modifying the "Import Type" button parameters of an ImageTexture node' is however ambiguous. A parameter is Cinema 4D slang for an element in a description, one of the ways of how Cinema 4D defines UIs. This includes things that users would naturally call a parameter such as a textbox holding a string, a number edit holding a float, or a combo box holding a selection, i.e., things that hold a value. But it also includes things that are just part of the UI of a node that cannot hold a value, e.g., a group, a separator, or a button.

      • When someone talks about 'modifying' a parameter I would usually assume that they talk about changing the description of a node, specifically at that parameter. E.g., changing the label of a button, adding or removing elements to/from a drop down, disabling an element, etc. You can only do this for nodes you own, i.e., implement yourself. You cannot change the UI of alien elements, because for you their description is read only.
      • But I assume you are actually talking here about getting or setting the parameter of an element, e.g., set a float slider to the value '4.13'.

      What gets a bit in the way here in your case, is that Cinema 4D summarizes everything under the label of paramater, even things that do not naturally have a value which could be gotten or set. So, I assume you want to press this button, for that you have two options:

      • Call c4d.CallButton
      • Send the message c4d.MSG_DESCRIPTION_COMMAND

      Please note that both approaches are restricted to being called from the main thread only when the scene element upon which they are called is part of a loaded scene. E.g., calling one of them on a node in the active document from outside of the main thread: forbidden. Calling them from inside a GetVirtualObjects (or any other off-main thread context) on a scene element in a dummy document which has been constructed just for this purpose and is not loaded by Cinema 4D: absolutely fine.

      Cheers,
      Ferdinand

      posted in Cinema 4D SDK
      ferdinandF
      ferdinand
    • RE: Script to change parameters across all similar nodes material

      Hey @annoyedUser,

      Thank you for reaching out to us. Your code looks very much like generated by a chatbot or generated with the help of a chatbot. Please note our Support Procedures: Scope of Support regrading the usage of AI. Please also post into the correct forum for future topics, I have moved your topic into the Cinema 4D SDK forum.

      Your code does not make too much sense, I cannot dissect all the issues there. It is possible to do with the Nodes API what you want to do but it is more an API for experts. You can find here some code examples for it. Novice users should rather use graph descriptions which is a less powerful but also simpler API.

      Cheers,
      Ferdinand

      To change the external thickness of each Contour node in a document, you could write something like using a graph description query:

      """Sets the external thickness of all 'Contour' nodes in Redshift material graphs of the active 
      document to 2.
      """
      
      import c4d
      import maxon
      
      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.
          """
          # Iterate over the material graphs of all Redshift materials in the document.
          for graph in maxon.GraphDescription.GetMaterialGraphs(
              doc, maxon.NodeSpaceIdentifiers.RedshiftMaterial):
              # Apply a graph description to each of them which ...
              maxon.GraphDescription.ApplyDescription(graph, 
              {
                  # ... queries all nodes of type 'Contour' in the graph ...
                  "$query": {
                      "$qmode": maxon.GraphDescription.QUERY_FLAGS.MATCH_ALL | 
                                maxon.GraphDescription.QUERY_FLAGS.MATCH_MAYBE,
                      "$type": "Contour",
                  },
                  # ... so that we can set the thickness value of all matching nodes to 2.
                  "External/Thickness": 2
              })
      
      
      if __name__ == '__main__':
          main()
      
      posted in Cinema 4D SDK
      ferdinandF
      ferdinand