Group Details Private

administrators

  • RE: Hide RenderPost settings

    @Dunhou said in Hide RenderPost settings:

    I think you need to override RenderEngineCheck to false, the document said:

    Bool MyRenderer::RenderEngineCheck(const BaseVideoPost* node, Int32 id) const
    {
      switch (id)
      {
        case RENDERSETTING_STATICTAB_MULTIPASS:
        case RENDERSETTING_STATICTAB_ANTIALIASING:
        case RENDERSETTING_STATICTAB_OPTIONS:
        case RENDERSETTING_STATICTAB_STEREO:
          return false;
      }
     
      return true;
    }
    

    Cheers~
    DunHou

    Thanks that's indeed the correct way to do it

    Cheers,
    Maxime.

    posted in Cinema 4D SDK
  • RE: Hide RenderPost settings

    Hi @npwouters I'm sorry I've very limited time, right now I will try to have a look at it tomorrow (just that you do not think we have forgotten you) or in worse case Wednesday.

    Cheers,
    Maxime.

    posted in Cinema 4D SDK
  • RE: How can I add the secondary greyed-out text in the Object Manager?

    Hey you need to react to MSG_GETCUSTOM_NAME_ADDITION and return a dict with the expected string with the "res" key.

    You need to register your object with c4d.OBJECT_CUSTOM_NAME_ADDITION to have the additional name displayed in the Object Manager.

    Cheers,
    Maxime.

    posted in Cinema 4D SDK
  • 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
  • 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
  • RE: Maxon One Fall SDK Release

    Thanks for pointing it out, changed it 🙂

    posted in News & Information
  • 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
  • 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
  • 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
  • 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

    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