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

    DescID and DescLevel for Shader Layers and properties

    Cinema 4D SDK
    2025 python windows
    2
    11
    217
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • ferdinandF
      ferdinand @alcol68
      last edited by

      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

      MAXON SDK Specialist
      developers.maxon.net

      1 Reply Last reply Reply Quote 0
      • A
        alcol68
        last edited by

        I have previously looked at the “How to Create and Populate a Layer Shader” post. The shader that I am referencing above, I can easily create using those same methods. Below is the code for creating the Shader I am working with:

        Field1 = doc.SearchObject("Shader Field")
        Shader = c4d.LayerShader()
        
        ShaderLayer1 = Shader.AddLayer(c4d.TypeShader)
        GradientShader1 = c4d.BaseList2D(c4d.Xgradient)
        GradientShader1[c4d.ID_BASELIST_NAME] = "Gradient Circular"
        GradientShader1[c4d.SLA_GRADIENT_TYPE] = c4d.SLA_GRADIENT_TYPE_2D_CIRC
        GradientShader1[c4d.SLA_GRADIENT_CYCLE] = False
        GradientShader1[c4d.SLA_GRADIENT_ANGLE] = 3.14159
        Gradient1 = GradientShader1[c4d.SLA_GRADIENT_GRADIENT]
        Gradient1.FlushKnots()
        Gradient1.InsertKnot(c4d.Vector(0,0,0), 1, 0, 0.5, 0)
        Gradient1.InsertKnot(c4d.Vector(0,0,0), 1, 1, 0.5, 1)
        GradientShader1[c4d.SLA_GRADIENT_GRADIENT] = Gradient1
        GradientShader1.InsertUnder(Shader)
        ShaderLayer1.SetParameter(c4d.LAYER_S_PARAM_SHADER_LINK, GradientShader1)
        
        ShaderLayer2 = Shader.AddLayer(c4d.TypeShader)
        GradientShader2 = c4d.BaseList2D(c4d.Xgradient)
        GradientShader2[c4d.ID_BASELIST_NAME] = "Gradient U"
        GradientShader2[c4d.SLA_GRADIENT_CYCLE] = False
        GradientShader2[c4d.SLA_GRADIENT_ANGLE] = 3.14159
        Gradient2 = GradientShader1[c4d.SLA_GRADIENT_GRADIENT]
        Gradient2.FlushKnots()
        Gradient2.InsertKnot(c4d.Vector(0,0,0), 1, 0.125, 0.5, 0)
        Gradient2.InsertKnot(c4d.Vector(1,1,1), 1, 1, 0.5, 1)
        GradientShader2[c4d.SLA_GRADIENT_GRADIENT] = Gradient2
        GradientShader2.InsertUnder(Shader)
        ShaderLayer2.SetParameter(c4d.LAYER_S_PARAM_SHADER_LINK, GradientShader2)
        
        ShaderLayer3 = Shader.AddLayer(c4d.TypeShader)
        NoiseShader = c4d.BaseList2D(c4d.Xnoise)
        NoiseShader[c4d.SLA_NOISE_COLOR1] = c4d.Vector(1,1,1)
        NoiseShader[c4d.SLA_NOISE_COLOR2] = c4d.Vector(0,0,0)
        NoiseShader[c4d.SLA_NOISE_SEED] = 666
        NoiseShader[c4d.SLA_NOISE_GLOBAL_SCALE] = 2
        NoiseShader[c4d.SLA_NOISE_ANI_SPEED] = 1
        NoiseShader[c4d.SLA_NOISE_CONTRAST] = 1
        NoiseShader.InsertUnder(Shader)
        ShaderLayer3.SetParameter(c4d.LAYER_S_PARAM_SHADER_LINK, NoiseShader)
        ShaderLayer3.SetParameter(c4d.LAYER_S_PARAM_SHADER_MODE, 11)
        
        ShaderLayer4 = Shader.AddLayer(c4d.TypeTransform)
        ShaderLayer4.SetParameter(c4d.LAYER_S_PARAM_TRANS_ANGLE, 3.14159)
        Field1.InsertShader(Shader)
        Field1[c4d.ID_MG_SHADER_SHADER] = Shader
        

        Using the port-creation example I gave before, for an object operator, I use the DescLevel to get the ID of the property I want to create a port for. In the above example, I use the integer “c4d.ID_BASEOBJECT_GLOBAL_POSITION.” Then if I want to use the specific Z parameter, I use the second level integer of “c4d.VECTOR_Z.”

        I assume there is a similar level system to grab the angle parameter of the Transform layer in my Shader. The Shader itself is linked to the object operator, so accessing its layers should be the first level.

        ObjectNode.AddPort(c4d.GV_PORT_INPUT, c4d.DescID(c4d.DescLevel(c4d.SLA_LAYER_BLEND), c4d.DescLevel(c4d.LAYER_S_PARAM_TRANS_ANGLE)), flag=c4d.GV_PORT_FLAG_IS_VISIBLE, message=False)
        

        If I use just the single level integer of “c4d. LAYER_S_PARAM_TRANS_ANGLE” the port is not created. So, I assume there is another level above this one that needs to be called as well, namely the actual Transform layer itself. But I don’t know how to call that layer as an integer in the DescLevel method, because I cannot pass an object (“ShaderLayer4” from my above creation code) in the integer parameter of DescLevel. I used the placeholder of “c4d.SLA_LAYER_BLEND” as that first level, but it creates an empty port, not the angle parameter port. Using “c4d.TypeTransform” or “c4d. LAYER_S_PARAM_ALL_ACTIVE” do not work either.

        This is what I am trying to figure out: What is the integer that I need to use in order to access the Transform Layer itself to use in the DescLevel method, to use in the AddPort method? Unless there is a different way to add a port, I cannot find any information on this particular issue.

        1 Reply Last reply Reply Quote 0
        • A
          alcol68
          last edited by

          I guess another way to do this is to create a Python node, rather than an object node, and access the Shader Layer parameters that way.

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

            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

            MAXON SDK Specialist
            developers.maxon.net

            1 Reply Last reply Reply Quote 0
            • A
              alcol68
              last edited by

              I appreciate your help, Ferdinand. I'm in no rush for this. I am now going through the C++ header files to try to get a better understanding of this and the code you posted.

              I saw an older post where you noted that A LayerShaderLayer is not a scene element, but stored in the data container of a LayerShader. If that is true, is there a way to return a list of all the data stored within that LayerShader data container, like you can with the "GetUserDataContainer" function?

              1 Reply Last reply Reply Quote 0
              • A
                alcol68
                last edited by alcol68

                To slightly answer my own, most recent question and to complete my second initial question about adding keyframes to an animation track for the Gradient in a Gradient Layer of the Shader:

                As shown in the picture below, if you right-click on the “Layer” parameter of the Shader, navigate over User Interface, and click ‘Show Subchannels,” you can then see all the parameters of the Shader Layers. You can then drag the Transform Angle parameter into the console and get its description ID. Doing this, I was able to get that the dynamic DescID of the Transform Angle parameter (like you showed before): “Layer[c4d.SLA_LAYER_BLEND, 10120].”
                dev response1.PNG
                Putting this into the correct DescID format, I can create an animation track for any of the Layer parameters. I can create an animation track for this specific Transform Angle parameter with the above DescID.

                c4d.DescID(c4d.DescLevel(c4d.SLA_LAYER_BLEND), c4d.DescLevel(10120, c4d.DTYPE_REAL))
                

                HOWEVER, when I use this same DescID to add a port into the Layer/Object operator that I am working with, an empty port is created, not the Transform Angle port I want. But, if I go to manually add the correct port I want on this same operator, it is renamed to “Color Profile” and greyed out, as if it is currently being used (shown in the picture below). So, there is something missing in how I’m adding this port, either another level or a declared type with the first “c4d.SLA_LAYER_BLEND” level. I’m not sure what exactly is incorrect or missing with the adding of this port.
                dev response2.PNG
                We are getting closer to the answer, though.

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

                  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

                  MAXON SDK Specialist
                  developers.maxon.net

                  1 Reply Last reply Reply Quote 0
                  • A
                    alcol68
                    last edited by alcol68

                    hahaha I apreciate your help. Yea this has been wild trying to figure this out, but the creator id note gives me even more direction. Thank you.

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

                      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()
                      

                      MAXON SDK Specialist
                      developers.maxon.net

                      1 Reply Last reply Reply Quote 1
                      • A
                        alcol68
                        last edited by

                        @ferdinand Thank you for your in-depth analysis. It would have taken me way too long to figure this out lol, especially finding "CUSTOMDATA_BLEND_LIST". The first example seems to work for me, but like you said, it may not be reliable. The Python node example also works great. Again, thank you for your time.

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