<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Cinema 4D SDK]]></title><description><![CDATA[Cinema 4D SDK]]></description><link>http://developers.maxon.net/forum/category/2</link><generator>RSS for Node</generator><lastBuildDate>Sat, 05 Sep 2026 14:58:27 GMT</lastBuildDate><atom:link href="http://developers.maxon.net/forum/category/2.rss" rel="self" type="application/rss+xml"/><pubDate>Mon, 31 Aug 2026 20:46:40 GMT</pubDate><ttl>60</ttl><item><title><![CDATA[c4d.RSCAMERAOBJECT_COLOR_CONTROLS_CURVE_RGB]]></title><description><![CDATA[Hey @lasselauch,
no, that is unfortunately not possible. Unlike for the other big custom data type of Redshift RSFILE, the curve data type RSPFXCURVE does not implement Get/SetParameter to access internals of the data type, as you can for the file type with REDSHIFT_FILE_PATH.
It also stands a bit to question how such lower desc level access should work here? Maybe as a lower level which can read/write the curve as SplineData? But I am not sure if this type would be faithfully convertable to SplineData`.
I cannot fix this as Redshift is not part of the Cinema code base where the Python API lives. I gave one of the RS developers a bump so that they maybe can implement  Get/SetParameter for that custom data type.
Cheers,
Ferdinand
]]></description><link>http://developers.maxon.net/forum/topic/16442/c4d.rscameraobject_color_controls_curve_rgb</link><guid isPermaLink="true">http://developers.maxon.net/forum/topic/16442/c4d.rscameraobject_color_controls_curve_rgb</guid><dc:creator><![CDATA[ferdinand]]></dc:creator><pubDate>Mon, 31 Aug 2026 20:46:40 GMT</pubDate></item><item><title><![CDATA[Change render Space (Color Profile) - how?]]></title><description><![CDATA[You likely have to call UpdateOcioColorSpaces, just follow the example. There is btw no guarantee at all that DOCUMENT_COLOR_MANAGEMENT entails a render space of ACEScg. That is just the default, the user could have changed that.
I am also not sure what all that RDATA_IMAGECOLORPROFILE code is meant to do. It depends a bit on what your plugin does and what self.IsForceLinear is meant to express. Only so much: An OCIO document does not necessarily entail a bitmap with a non-linear color profile, and OCIO also does not mean that the bitmap has to be 32bit. You are however missing UNDOTYPE_PRIVATE_DOCUMENTDATA undo management for setting the color management.
]]></description><link>http://developers.maxon.net/forum/topic/16441/change-render-space-color-profile-how</link><guid isPermaLink="true">http://developers.maxon.net/forum/topic/16441/change-render-space-color-profile-how</guid><dc:creator><![CDATA[ferdinand]]></dc:creator><pubDate>Tue, 25 Aug 2026 14:28:21 GMT</pubDate></item><item><title><![CDATA[MergeDocument() crash C4D]]></title><description><![CDATA[@ferdinand Okay, I will handle the plugins and files at an appropriate time.Thanks reply
]]></description><link>http://developers.maxon.net/forum/topic/16440/mergedocument-crash-c4d</link><guid isPermaLink="true">http://developers.maxon.net/forum/topic/16440/mergedocument-crash-c4d</guid><dc:creator><![CDATA[chuanzhen]]></dc:creator><pubDate>Sat, 15 Aug 2026 08:10:44 GMT</pubDate></item><item><title><![CDATA[Disable all animation in document]]></title><description><![CDATA[Hi,
@ferdinand and @Anlv this is most awesome! All way beyond my capablities so thanks a lot for the scripts!
]]></description><link>http://developers.maxon.net/forum/topic/16439/disable-all-animation-in-document</link><guid isPermaLink="true">http://developers.maxon.net/forum/topic/16439/disable-all-animation-in-document</guid><dc:creator><![CDATA[ceen]]></dc:creator><pubDate>Wed, 12 Aug 2026 18:04:11 GMT</pubDate></item><item><title><![CDATA[Using SetWeightMap() multiple times can lead to inexplicable changes in weight values]]></title><description><![CDATA[@ferdinand Thanks for your detailed explanation.
Although the precision conversion leading to value changes is not an error, it is indeed not what we want in terms of the results. For example, when dealing with the weight of some points, using SetWeightMap() will update the weights of other points, resulting in numerical changes. Only using SetWeight() can at least ensure that the weights of unoperated points do not undergo numerical changes (but using SetWeight() must tolerate the print warnings outputted by the console)
]]></description><link>http://developers.maxon.net/forum/topic/16437/using-setweightmap-multiple-times-can-lead-to-inexplicable-changes-in-weight-values</link><guid isPermaLink="true">http://developers.maxon.net/forum/topic/16437/using-setweightmap-multiple-times-can-lead-to-inexplicable-changes-in-weight-values</guid><dc:creator><![CDATA[chuanzhen]]></dc:creator><pubDate>Fri, 07 Aug 2026 06:49:12 GMT</pubDate></item><item><title><![CDATA[After calling BaseContainer.Sort(), the stored values are lost]]></title><description><![CDATA[@ferdinand Thanks for your detailed explanation and the implementation of the sorting function. It's very helpful！
]]></description><link>http://developers.maxon.net/forum/topic/16436/after-calling-basecontainer.sort-the-stored-values-are-lost</link><guid isPermaLink="true">http://developers.maxon.net/forum/topic/16436/after-calling-basecontainer.sort-the-stored-values-are-lost</guid><dc:creator><![CDATA[chuanzhen]]></dc:creator><pubDate>Wed, 05 Aug 2026 10:00:36 GMT</pubDate></item><item><title><![CDATA[Mesh Emitter CallCommand works in Script Manager, no-op from plugin]]></title><description><![CDATA[Thanks Anlv
It looks like its adding the emitter into a null is what's causing it.
Fixed with deferred CallCommand(1062577) (no sub-id (1067510) silently no-ops). Emitter fields greys if the emitter/group are parented under the setup null or generator. Sibling placement below plugin object + rename works fine.
]]></description><link>http://developers.maxon.net/forum/topic/16435/mesh-emitter-callcommand-works-in-script-manager-no-op-from-plugin</link><guid isPermaLink="true">http://developers.maxon.net/forum/topic/16435/mesh-emitter-callcommand-works-in-script-manager-no-op-from-plugin</guid><dc:creator><![CDATA[atg]]></dc:creator><pubDate>Fri, 31 Jul 2026 23:45:00 GMT</pubDate></item><item><title><![CDATA[Alembic Export to Unreal - RootUVs?]]></title><description><![CDATA[What I meant was to use the Cineware Unreal bindings, but I am not sure if they support it. Going the export route is a big backwards when Cinema has specific bindings.
https://www.maxon.net/en/cineware
I will give the Unreal dev a bump when I see him again.
]]></description><link>http://developers.maxon.net/forum/topic/16432/alembic-export-to-unreal-rootuvs</link><guid isPermaLink="true">http://developers.maxon.net/forum/topic/16432/alembic-export-to-unreal-rootuvs</guid><dc:creator><![CDATA[ferdinand]]></dc:creator><pubDate>Thu, 23 Jul 2026 18:37:15 GMT</pubDate></item><item><title><![CDATA[Custom node-locked licensing: Bypassing checks for Team Renderer, Clients, Server safely?]]></title><description><![CDATA[@ferdinand

Yes, thanks, that indirect answer is enough for me. I was mainly wondering whether a check performed on both the client and the server is robust enough, or if it could be bypassed using Python, for instance. I’m well aware that it can be cracked somehow. I’m also already using ExportLicenses() for my node-locking system in combination.
Thanks a lot for your help.
]]></description><link>http://developers.maxon.net/forum/topic/16431/custom-node-locked-licensing-bypassing-checks-for-team-renderer-clients-server-safely</link><guid isPermaLink="true">http://developers.maxon.net/forum/topic/16431/custom-node-locked-licensing-bypassing-checks-for-team-renderer-clients-server-safely</guid><dc:creator><![CDATA[ThomasB]]></dc:creator><pubDate>Wed, 22 Jul 2026 16:10:01 GMT</pubDate></item><item><title><![CDATA[Accessing the Mixamo Control Rig auto-adjustment logic]]></title><description><![CDATA[Hello @leah.hayes,
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
It is a bit difficult to answer you first question, as it is multiple questions of which some are open ended, please have a look at Support Procedures: How to Ask Questions for future postings.

Where is the Python code of the built-in character templates ...

They are stored in {c4d installation folder}/library.zip/characters.

Is there a supported way to extract or view a built-in template (for example through c4d.modules.character.builder.Template) so I can study how the auto-adjustment is implemented?

I am not quite sure that I follow. All these rigs are just our standard CA component system decorated with some Xpresso and Python. Since there can be literally hundreds of nodes in a rig, I attached below a little helper script to scan for specific Python content or just the scripts in general that are embedded somewhere in the rig.

Is there a public API entry point to trigger or implement this kind of auto-adjustment for other templates?

That auto adjustment seems to be some kind of Python script which the authors of this rig provided. These CA rigs have been created by (technical) artists and not the members of the Maxon development team. See the print out below for details on the artist code for "auto-adjustment".
Cheers,
Ferdinand
Result


====================================================================================================
Node: Character Component (Character Component) | Attribute: (2158, 130, 1022113)

Value:

import c4d
from c4d import gui
import c4d.modules.character as ca
import c4d.utils as utils
#Welcome to the world of Python

def Bl2DIterator(bl2D):
    while bl2D:
        yield bl2D
        for bl2DChild in Bl2DIterator(bl2D.GetDown()):
            yield bl2DChild
        bl2D = bl2D.GetNext()

def SearchInHierarchy(obj, name):
    allChildrenWithName = [bl2D for bl2D in Bl2DIterator(obj.GetDown()) if bl2D.GetName() == name]
    return allChildrenWithName[0]

def ShowLayer(layersList, showName):
    for layer in layersList:
        layerName = layer[c4d.ID_BASELIST_NAME]
        if showName in layerName:
            layer[c4d.ID_LAYER_VIEW] = True
            layer[c4d.ID_LAYER_MANAGER] = True

def HideLayer(layersList, hideName):
    for layer in layersList:
        layerName = layer[c4d.ID_BASELIST_NAME]
        if hideName in layerName:
            layer[c4d.ID_LAYER_VIEW] = False
            layer[c4d.ID_LAYER_MANAGER] = False

def CharacterAnimateMode(characterObject):
    characterObject[c4d.ID_CA_CHARACTER_COMPONENT_HIGHLIGHT_OVER] = 4
    characterObject[c4d.ID_CA_CHARACTER_COMPONENT_MOUSEOVER] = 0
    characterObject[c4d.ID_CA_CHARACTER_COMPONENT_VISIBLE] = 2
    characterObject[c4d.ID_CA_CHARACTER_OM_DISPLAY] = 5
    characterObject[c4d.ID_CA_CHARACTER_LOCK_AM] = False

def CharacterEditMode(characterObject):
    characterObject[c4d.ID_CA_CHARACTER_COMPONENT_HIGHLIGHT_OVER] = 3
    characterObject[c4d.ID_CA_CHARACTER_COMPONENT_MOUSEOVER] = 4
    characterObject[c4d.ID_CA_CHARACTER_COMPONENT_VISIBLE] = 3
    characterObject[c4d.ID_CA_CHARACTER_OM_DISPLAY] = 1
    characterObject[c4d.ID_CA_CHARACTER_LOCK_AM] = True

def CharacterBindMode(characterObject):
    characterObject[c4d.ID_CA_CHARACTER_COMPONENT_HIGHLIGHT_OVER] = 4
    characterObject[c4d.ID_CA_CHARACTER_COMPONENT_MOUSEOVER] = 4
    characterObject[c4d.ID_CA_CHARACTER_COMPONENT_VISIBLE] = 1
    characterObject[c4d.ID_CA_CHARACTER_OM_DISPLAY] = 5
    characterObject[c4d.ID_CA_CHARACTER_LOCK_AM] = True

def CatchEmAll(obj, stop, listy):
    if obj is None: return

    #Actions go here
    if obj.GetType()==1019362:
        listy.append(obj)
    if not stop:
        if obj.GetDown():
            CatchEmAll(obj.GetDown(), stop, listy)
        if obj.GetNext():
            CatchEmAll(obj.GetNext(), stop, listy)

    return listy

def SetGlobalRotation(obj, rot):
    """
    Please remember, Cinema 4D handles rotation in radians.

    Example for H=10, P=20, B=30:

    import c4d
    from c4d import utils
    #...
    hpb = c4d.Vector(utils.Rad(10), utils.Rad(20), utils.Rad(30))
    SetGlobalRotation(obj, hpb) #object's rotation is 10, 20, 30
    """
    m = obj.GetMg()
    pos = m.off
    scale = c4d.Vector( m.v1.GetLength(),
                        m.v2.GetLength(),
                        m.v3.GetLength())

    m = utils.HPBToMatrix(rot)

    m.off = pos
    m.v1 = m.v1.GetNormalized() * scale.x
    m.v2 = m.v2.GetNormalized() * scale.y
    m.v3 = m.v3.GetNormalized() * scale.z

    obj.SetMg(m)

def SetGlobalScale(obj, scale):
    m = obj.GetMg()

    m.v1 = m.v1.GetNormalized() * scale.x
    m.v2 = m.v2.GetNormalized() * scale.y
    m.v3 = m.v3.GetNormalized() * scale.z

    obj.SetMg(m)

def ModeChanged():
    characterObject = charop.GetObject()
    #Turning Off the Annotation
    # Main function

    leftAnnotationTag = SearchInHierarchy(characterObject, "LeftLeg_bind").GetLastTag()
    print(leftAnnotationTag)
    if leftAnnotationTag.GetType() == c4d.Tannotation:
        leftAnnotationTag[c4d.ANNOTATIONTAG_VIEWPORT_SHOW] = False

    #Hiding the Rig's Layers
    root = doc.GetLayerObjectRoot() #Gets the layer manager
    layersList = root.GetChildren() #Get Layer list
    for layer in layersList:
        layerName = layer[c4d.ID_BASELIST_NAME]
        if "Mixamo_Rig" in layerName:
            layer[c4d.ID_LAYER_VIEW] = False
            layer[c4d.ID_LAYER_MANAGER] = False
            mixamoLayers = layer.GetChildren()
            for layer in mixamoLayers:
                layerName = layer[c4d.ID_BASELIST_NAME]
                if "Retarget_Hierarchy" in layerName or \
                "ANIMDATA_Nulls" in layerName or \
                "Control Hierarchy" in layerName:
                    layer[c4d.ID_LAYER_VIEW] = False
                    layer[c4d.ID_LAYER_MANAGER] = False

    masterControl = SearchInHierarchy(characterObject, "Master_con+")
    hips = SearchInHierarchy(characterObject, "Hips")
    if nmode==c4d.ID_CA_CHARACTER_MODE_BUILD or \
    nmode==c4d.ID_CA_CHARACTER_MODE_ADJUST:
        CharacterEditMode(characterObject)

    if nmode==c4d.ID_CA_CHARACTER_MODE_BIND:
        CharacterBindMode(characterObject)

    if nmode==c4d.ID_CA_CHARACTER_MODE_ADJUST and omode==c4d.ID_CA_CHARACTER_MODE_BUILD:
        #Changed search method to only search in Character Object
        #hips=doc.SearchObject("Hips")
        #masterControl=doc.SearchObject("Master_con+")
        #added Namespace Code
        nameSpace=masterControl[c4d.ID_USERDATA,16]

        results=CatchEmAll(hips, hips.GetNext(), [])
        for result in results:
            mixamo=doc.SearchObject(nameSpace+result.GetName())
            if mixamo:
                matR = result.GetMg()
                matM = mixamo.GetMg()
                matR.off = matM.off
                matR.v1 = matM.v1.GetNormalized()
                matR.v2 = matM.v2.GetNormalized()
                matR.v3 = matM.v3.GetNormalized()
                result.SetMg(matR)


            if result.GetName()=='Neck1':
                #neck=doc.SearchObject(nameSpace+'Neck')
                #head=doc.SearchObject(nameSpace+'Head')
                neck = SearchInHierarchy(characterObject, nameSpace+'Neck')
                head = SearchInHierarchy(characterObject, nameSpace+'Head')
                if head and neck:
                    result.SetMg((neck.GetMg()+head.GetMg())/2)

        print("Auto-Adjustment Complete")

        #Turning On the Annotation
        if leftAnnotationTag.GetType() == c4d.Tannotation:
            leftAnnotationTag[c4d.ANNOTATIONTAG_VIEWPORT_SHOW] = True

    if omode==c4d.ID_CA_CHARACTER_MODE_ADJUST and nmode!=c4d.ID_CA_CHARACTER_MODE_BUILD:
        L_Leg=doc.SearchObject("Left_IK_parent_rot_algn")
        #L_Leg = charop.FindObject("Left_IK_parent_rot_algn")
        if L_Leg:
            conTag=L_Leg.GetFirstTag()
            conTag[c4d.EXPRESSION_ENABLE]=False
        R_Leg=doc.SearchObject("Right_IK_parent_rot_algn")
        #R_Leg = charop.FindObject("Right_IK_parent_rot_algn")
        if R_Leg:
            conTag=R_Leg.GetFirstTag()
            conTag[c4d.EXPRESSION_ENABLE]=False

    if nmode==c4d.ID_CA_CHARACTER_MODE_ANIMATE:
        CharacterAnimateMode(characterObject)
        doc.SetActiveObject(masterControl, c4d.SELECTION_NEW)
    c4d.EventAdd()

Code
"""Scans the document for all nodes that have multi-line string parameters that contain the word "adjustment" 
(case-insensitive) and prints the node name, type, parameter ID, and value to the console.
"""

import c4d
import mxutils

doc: c4d.documents.BaseDocument

def main() -&gt; None:
    """
    """
    node: c4d.BaseList2D
    pid: c4d.DescID

    # Iterate over all objects and tags in the document that live outside of caches and within the 
    # object branch of the document.
    for node in mxutils.RecurseGraph(doc.GetFirstObject(), yieldBranches=True, yieldHierarchy=True, 
                                     branchFilter=[c4d.Obase, c4d.Tbase]):
        # Iterate over the description, i.e., parameters of the node.
        for data, pid, _ in node.GetDescription(c4d.DESCFLAGS_DESC_NONE):
            # The first level of this parameter is not of type string or does not use the
            # multi-line string GUI, so  we skip it.
            if pid[0].dtype != c4d.DTYPE_STRING or data[c4d.DESC_CUSTOMGUI] != c4d.CUSTOMGUI_STRINGMULTI:
                continue
            
            # Try to read the value, the exception block is needed as not all parameter types are 
            # accessible in Python (and this could be some kind of exotic multi level string data type).
            try:
                value: str | None = str(node[pid]).strip()
                if not isinstance(value, str) or not value:
                    continue
            except Exception:
                continue

            # Check if the value contains the word "adjustment" (case-insensitive), if not, skip it.
            if "adjustment" not in value.lower():
                continue
            
            # Print the match.
            print ("\n\n" + ("=" * 100))
            print (f"Node: {node.GetName()} ({node.GetTypeName()}) | Attribute: {pid}\n\nValue:\n\n{value}")

            # For good measure, select the node and break the loop.
            (doc.SetActiveObject(node, c4d.SELECTION_NEW) 
             if isinstance(node, c4d.BaseObject) else 
             doc.SetActiveTag(node, c4d.SELECTION_NEW))
            break
        c4d.EventAdd()


if __name__ == '__main__':
    main()

]]></description><link>http://developers.maxon.net/forum/topic/16429/accessing-the-mixamo-control-rig-auto-adjustment-logic</link><guid isPermaLink="true">http://developers.maxon.net/forum/topic/16429/accessing-the-mixamo-control-rig-auto-adjustment-logic</guid><dc:creator><![CDATA[ferdinand]]></dc:creator><pubDate>Thu, 09 Jul 2026 14:34:00 GMT</pubDate></item><item><title><![CDATA[Programmatically created Redshift Lights in an Object Plugin converts lights into Null-Objekts without an function after making Objekt-Plugin editable]]></title><description><![CDATA[This is not common knowledge, otherwise I would have spotted that earlier.
So, you are not really expected to know this as a third party dev. You can look at the geometry_caches_s26.py example and there at this line, under [2] I mention that the cache of that object is muted. But that is of course only very indirect information and not enough to deduce this. I think we also talked about it a couple of times on the forum.
The flag BIT_CONTROLOBJECT is set on input objects of generators, e.g., the to be extruded spline of an Extrude object, the profile and rail spine of a Loft object, the to be cloned cube and sphere objects of a MoGraph cloner, etc. As shown in the caching example, Cinema  will mute the caches of input objects. To be very formal, they are actually built but then again deleted. BIT_CONTROLOBJECT marks such input objects.
Your light in that scene should not have that flag, as it is not the input for some generator. But it somehow got it anyway, there is probably somewhere a bug. For normal scene evaluation that bug is also not really a problem (because as you saw from your own test, the light still converted correctly when you inserted it on its own outside of caches). The problems start when that irregularly as BIT_CONTROLOBJECT marked object is part of a cache. 'Make Editable' then incorrectly collapses the cache. Not only does it not stop at nested generators which are incorrectly marked like this, it also seems to apply the cache muting logic for input objects (resulting in the empty null object instead of the content of the cache of the collapsed generator).
I already told the relevant devs that I consider this borderline buggy but they do not seem very inclined to do anything about this. What is effectively missing somewhere in cache resolvement is the evaluation if the flag BIT_CONTROLOBJECT 'makes sense on an object'. That is of not so easy to do, as generator - input object relations can be quite complex in Cinema. Which is probably also why the modelling team does not want to touch this.
Cheers,
Ferdinand
]]></description><link>http://developers.maxon.net/forum/topic/16428/programmatically-created-redshift-lights-in-an-object-plugin-converts-lights-into-null-objekts-without-an-function-after-making-objekt-plugin-editable</link><guid isPermaLink="true">http://developers.maxon.net/forum/topic/16428/programmatically-created-redshift-lights-in-an-object-plugin-converts-lights-into-null-objekts-without-an-function-after-making-objekt-plugin-editable</guid><dc:creator><![CDATA[ferdinand]]></dc:creator><pubDate>Fri, 03 Jul 2026 09:05:04 GMT</pubDate></item><item><title><![CDATA[Sub materials links and undo]]></title><description><![CDATA[@DronKozy
Yeah, I got the general direction, that you are probably implementing a material mixer type of material/shader. When you implement a full render engine binding, you should go the event notification route, as I am sure you can then easily handle the (slight) complexity that comes with them.
Regarding the architecture, in principle you can do with whatever you want. As Maxon employee I would of course recommend to use our Nodes API (what you call 'native'). The SDK contains both code examples and documentation about this. But this biggest flaw of the Nodes API is probably that it is not entirely non-trivial. But implementing a full node editor also requires a relatively high level of expertise about our APIs, although in different areas.
Currently Redshift, Vray, Arnold, and CentiLeo use the Nodes API and all other render engines (Corona, Octane, Cycles, etc.) use either a completely custom system or the old Xpresso Nodes API.
Cheers,
Ferdinand
PS: When you are developing a render engine binding, you might want to consider our Maxon Registered Developer Program.
]]></description><link>http://developers.maxon.net/forum/topic/16426/sub-materials-links-and-undo</link><guid isPermaLink="true">http://developers.maxon.net/forum/topic/16426/sub-materials-links-and-undo</guid><dc:creator><![CDATA[ferdinand]]></dc:creator><pubDate>Wed, 01 Jul 2026 12:21:50 GMT</pubDate></item><item><title><![CDATA[How to get selected DescId and BaseList2D at the same time]]></title><description><![CDATA[Hey @ECHekman,
well, when a node displays an embedded description (what you exemplify at the case of a BaseLink), it uses a DescriptionCustomGui too. Other parameter types aside from a BaseLink which can do this are for example field lists.
I am quite frankly a bit surprised that this even bubbles up in the form you report it, that you get a DescID which is selected inside a DescriptionCustomGui that is shown by the node in the DescriptionCustomGui you opened yourself. Attribute Manger selection states (and by extension DescriptionCustomGui) are not a public API feature, as we usually keep our GUIs sealed, i.e., inaccessible to third parties. DescriptionCustomGui is here a bit in a grey area, but I am afraid there is no way to do what you want to do. To do what you want to do, you would need access to internal systems.
And other than in some other cases, where we do not always had the time to expose something (and are open to the idea of changing something), this is for GUIs not the case; they are intentionally sealed as we do not want third parties to change basic UX concepts.
My general advice would also be to not to try to overcome this boundary (that a plugin reacts to which parameter in a node is being highlighted by the user), as you break UX conventions of Cinema 4D with that.
Cheers,
Ferdinand
You can do this, but it is very hacky:

Get the description of the node(s) you are currently displaying in the your DescriptionCustomGui.
Check if desc is a member via CheckDescID (I think GetParameter would also work and return an empty container when the ID does not exist).
When the ID does not exist, start browsing the container for parameters that are of a DTYPE that implies an underlying node, such as DTYPE_BASELINK.
When you find such parameter, get the node node, and check there.
Rinse and repeat recursively.

Issues:

You cannot distinguish the case where an object A has two BaseLink parameters which both hold an object of type T, but not the same instances.
You will run into issues with DescID translations which some nodes might do. This is a really big point, which is probably already overlooked in your current code.
Nested node relations can get REALLY complicated. BaseLink is trivial, but stuff like field lists, volume lists, or some special shader stuff can get really complicated. You would probably only have to worry about base links and field lists in your case, as other renderers are irrelevant for you, but field list alone are already complex enough that I would strongly advise against 'just implementing it'.

]]></description><link>http://developers.maxon.net/forum/topic/16420/how-to-get-selected-descid-and-baselist2d-at-the-same-time</link><guid isPermaLink="true">http://developers.maxon.net/forum/topic/16420/how-to-get-selected-descid-and-baselist2d-at-the-same-time</guid><dc:creator><![CDATA[ferdinand]]></dc:creator><pubDate>Fri, 12 Jun 2026 13:56:06 GMT</pubDate></item><item><title><![CDATA[How to export icons of asset]]></title><description><![CDATA[Hey @ferdinand ,
Sorry I forgot click Submit button 
well, that is the info I need, I use same tech to get icons for CMD, and now waiting for svg part in the future.
Cheers~
DunHou
]]></description><link>http://developers.maxon.net/forum/topic/16418/how-to-export-icons-of-asset</link><guid isPermaLink="true">http://developers.maxon.net/forum/topic/16418/how-to-export-icons-of-asset</guid><dc:creator><![CDATA[Dunhou]]></dc:creator><pubDate>Mon, 08 Jun 2026 10:59:04 GMT</pubDate></item><item><title><![CDATA[Reverse direction of multi-segment splines]]></title><description><![CDATA[Okay,
I think I was a bit overly cautious in my answer here. You gave my a very broad question, or to be precise, you gave me a video and a scene file, and did not really ask a precise question. I understand that asking good questions can be hard, especially with a language barrier. But when I have nothing to go by, I of course assume the worst case possible.
The scene you have there is a trivial case, even the best case possible. You have there spline segments which lie in a perfect plane and which have no self intersections. You can treat them just like polygons (in the CG sense, not in the mathematical sense) and simply compute their normal over the first three vertices of each segment. Due to the fundamental property of a polygon - reversing the order of the vertices reverses the normal - you can then easily determine if two segments have opposite or equal winding.
But all this starts to fall apart, as soon as you cannot make these assumptions. And I cannot help you to write the code for this, as this is then more than just a few lines. Hope this helps, and that I my answer was now less 'overly cautious'.
Cheers,
Ferdinand
Result
[image: 1780950951310-85f415e7-7f45-4c00-9651-b4091ee543e2-image.png]
The code correctly identifies that in this six segment spline are four segments of one winding direction (clockwise in this case) and two of the other winding direction.
winding_direction.c4d
Code
"""Treats spline segments as polygons and compares their plane normals to find 
segments with reversed winding order.
"""

import c4d

op: c4d.SplineObject # The currently active object in the scene.

def main() -&gt; None:
    """Called by Cinema 4D when the script is being executed.
    """
    if not isinstance(op, c4d.SplineObject):
        return c4d.gui.MessageDialog("Please select a spline object.")
    
    # Get all points in the spline and organize them into their segments.
    points: list[c4d.Vector] = op.GetAllPoints()
    segments: list[list[c4d.Vector&rsqb;&rsqb; = []

    j: int = 0
    for i in [op.GetSegment(i)["cnt"] for i in range(op.GetSegmentCount())]:
        segments.append(points[j:j+i])
        j += i

    if len(segments) &lt; 2:
        return c4d.gui.MessageDialog("Please select a spline object with at least 2 segments.") 

    # Now build normal data for the spline segments. This assumes:
    #
    #   - A spline where all points lie in a single plane, a '2D' spline in 3D space.
    #   - No self intersections in the spline.
    #   - A piecewise linear spline, i.e., what Cinema 4D calls a 'Linear' spline. When we have a
    #     'Cubic' or 'Bezier' spline, we would have make it linear with 'Current State to Object' 
    #     first.
    #
    # We build the normal for each segment over its three first vertices. The reason why we are doing 
    # this is because of the fundamental identity of a polygon (in a computer graphics sense), 
    # reversing the order of the vertices of a polygon will reverse the normal. So if we have a 
    # spline with two segments with reversed winding order, they will have antiparallel normals (normals 
    # pointing in opposite directions). 
    segmentNormals: list[c4d.Vector] = []
    for segment in segments:
        if len(segment) &lt; 3:
            print("Segment has less than 3 points, skipping normal calculation.")
            continue

        a, b, c = segment[0], segment[1], segment[2]
        edge1: c4d.Vector = b - a
        edge2: c4d.Vector = c - b
        normal: c4d.Vector = edge1.Cross(edge2).GetNormalized()
        segmentNormals.append(normal)

    # Now we just declare one segment as 'ground truth' and check if the other segments have normals 
    # that are parallel or antiparallel to it. When we found a segment with an antiparallel normal, we 
    # know we found a segment with reversed winding order. To check if two normals are parallel or 
    # antiparallel, we just compute their dot product (i.e., spanned angle). When the dot product is 
    # negative, the normals are antiparallel.
    print(f"Establishing the first segment normal {segmentNormals[0]} as ground truth.")
    baseNormal: c4d.Vector = segmentNormals[0]
    for i, normal in enumerate(segmentNormals[1:], start=1):
        isAntiparallel: bool = baseNormal.Dot(normal) &lt; 0
        print(f"Segment {i} normal: {normal} is {'antiparallel' if isAntiparallel else 'parallel'} "
              f"to the base normal {baseNormal}.")
        
if __name__ == '__main__':
    main()

]]></description><link>http://developers.maxon.net/forum/topic/16417/reverse-direction-of-multi-segment-splines</link><guid isPermaLink="true">http://developers.maxon.net/forum/topic/16417/reverse-direction-of-multi-segment-splines</guid><dc:creator><![CDATA[ferdinand]]></dc:creator><pubDate>Mon, 08 Jun 2026 10:06:50 GMT</pubDate></item><item><title><![CDATA[Copy res folder without shortcut]]></title><description><![CDATA[That is of course also a valid option, just pick the scripting language you are most comfortable with.
]]></description><link>http://developers.maxon.net/forum/topic/16416/copy-res-folder-without-shortcut</link><guid isPermaLink="true">http://developers.maxon.net/forum/topic/16416/copy-res-folder-without-shortcut</guid><dc:creator><![CDATA[ferdinand]]></dc:creator><pubDate>Fri, 05 Jun 2026 14:13:55 GMT</pubDate></item><item><title><![CDATA[How to draw svg to bitmaps?]]></title><description><![CDATA[I started to work last Friday on a BaseBitmap.InitWithVectorImage. It will for sure not make it into the next release of Cinema 4D, as we are too close to that, and I cannot make any promises when or if it will arrive. But I see value in this especially since I realized that you cannot even really use this in C++, as VectorImageInterface requires access to some internal components to be rasterized.
]]></description><link>http://developers.maxon.net/forum/topic/16415/how-to-draw-svg-to-bitmaps</link><guid isPermaLink="true">http://developers.maxon.net/forum/topic/16415/how-to-draw-svg-to-bitmaps</guid><dc:creator><![CDATA[ferdinand]]></dc:creator><pubDate>Thu, 28 May 2026 03:57:17 GMT</pubDate></item><item><title><![CDATA[[Python] Best practice for creating multiple custom tabs in a .res file (ObjectData)]]></title><description><![CDATA[@ferdinand thanks so much, problem solved!
]]></description><link>http://developers.maxon.net/forum/topic/16413/python-best-practice-for-creating-multiple-custom-tabs-in-a-.res-file-objectdata</link><guid isPermaLink="true">http://developers.maxon.net/forum/topic/16413/python-best-practice-for-creating-multiple-custom-tabs-in-a-.res-file-objectdata</guid><dc:creator><![CDATA[rndm_cg]]></dc:creator><pubDate>Wed, 20 May 2026 13:07:17 GMT</pubDate></item><item><title><![CDATA[Can we draw alpha image in viewport]]></title><description><![CDATA[I do not think that DRAW_ALPHA_FROM_IMAGE should not work. I just picked DRAW_ALPHA_NORMAL because it is the "most default one" out of the DRAW_ALPHA flags. I will have a look later. But you can for now probably just use DRAW_ALPHA_NORMAL.
]]></description><link>http://developers.maxon.net/forum/topic/16412/can-we-draw-alpha-image-in-viewport</link><guid isPermaLink="true">http://developers.maxon.net/forum/topic/16412/can-we-draw-alpha-image-in-viewport</guid><dc:creator><![CDATA[ferdinand]]></dc:creator><pubDate>Mon, 18 May 2026 17:00:51 GMT</pubDate></item><item><title><![CDATA[How to add tabs to tool plugins.]]></title><description><![CDATA[You very likely have not unpacked your resources. The resource folder found in an installation only contains a part of the application resources (we are doing this since release 2023 if I remember correctly). A good portion of the resources sits inside resource.zip for performance reasons and is unpacked on demand. You can just unpack the resource.zip into you resource folder without an performance or stability issues.
[image: 1779175199591-b8e8db94-ae20-42b4-9a5d-e92ac928e89d-image.png]
You as an MRD can also look at the Bugslife client, as it is probably the better example, as it uses more features of the quick tab GUI. But my answer above was targeted at a general audience who can see the resources for Bugslife  but not Bugslife itself, and it therefore is not a good example for them. Just grep the resource folder as I did in my screen for files that match the path dialogs/*.res (i.e., are res files for dialogs) and contain the word QUICKTAB.
Cheers,
Ferdinand
]]></description><link>http://developers.maxon.net/forum/topic/16411/how-to-add-tabs-to-tool-plugins.</link><guid isPermaLink="true">http://developers.maxon.net/forum/topic/16411/how-to-add-tabs-to-tool-plugins.</guid><dc:creator><![CDATA[ferdinand]]></dc:creator><pubDate>Sun, 17 May 2026 19:55:10 GMT</pubDate></item><item><title><![CDATA[Shader bitmap rendering with MemoryFileStruct]]></title><description><![CDATA[Hey,
there is no shame in using the 'old' stuff since it is part of our API (and it will stay like that). I just point it out whenever a user is dealing with one of the hollowed out types.
My guess would be that there is a threading issue or threading safety mechanism in the way. I think the memory struct stuff is internally using locks to avoid race conditions/corrupted data. When the renderer backend is doing its threading thing, this could cause issues.
But ramdisk should work because our assets use the concept heavily (most assets are only a 'promise' which will only be truly created once accessed). You would even profit from the ramdisk caching in the temp folder of Cinema 4D.
So, in its simplest form, this should just look like this:

void RenderMyDoc(...)
{
  // --- Error Handling and Resource Management ----------------------------------------------------

  // Declare heap allocated resources that must be freed once the function exists either normally or
  // through an error.
  HeapRescource * myResource = nullptr;
  BaseBitmap * bmp = nullptr;

  // Catch any Maxon API error and turn them into the return type of this function and do optional 
  // logging. We could also free resources here, but then we would have to do it twice, once for
  // normal exit and once for error exit.
  iferr_scope_handler 
  {
    // DiagnosticOutput("Error during RenderMyDoc: @", err);
    return;
  };

  // finally is Maxon API syntax to execute code once a scope (not necessarily a function) is left, 
  // no matter if it is left through normal execution or through an error. This way we can ensure 
  // that resources are freed.
  finally
  {
    if (myResource)
      HeapResource::Free(myResource);
    if (bmp)
      BaseBitmap::Free(bmp);
  };

  // Allocate all the resources we need. When we use AutoAlloc&lt;T&gt;, we would not need the finally block 
  // above, but not all types have an AutoAlloc allocator.
  myResource = HeapResource::Alloc();
  if (!myResource)
    return maxon::OutOfMemoryError(MAXON_SOURCE_LOCATION, "Failed to allocate heap resource.");
  bmp = ...

  // --- Main Logic --------------------------------------------------------------------------------

  // Create our ramdisk mounting point and an URL within it. In practice, we probably would want this
  // ramdisk mounting point to be a class member or even a global singleton.
  RamDiskRef mountingPoint = RamDiskInterface::Create("net.mycompany.ramdisk.foo"_s) iferr_return;
  const Url ramdiskUrl = (mountingPoint.GetRoot() + "my_virtual.psd"_s) iferr_return;

  // Call BaseBitmap::Save with the ramdisk URL converted to a Filename.
  bmp-&gt;Save(cinema::MaxonConvert(ramdiskUrl), ...);
  // ...
}

Cheers,
Ferdinand
]]></description><link>http://developers.maxon.net/forum/topic/16409/shader-bitmap-rendering-with-memoryfilestruct</link><guid isPermaLink="true">http://developers.maxon.net/forum/topic/16409/shader-bitmap-rendering-with-memoryfilestruct</guid><dc:creator><![CDATA[ferdinand]]></dc:creator><pubDate>Thu, 07 May 2026 10:28:47 GMT</pubDate></item><item><title><![CDATA[How to start a new line in plugin help with localization?]]></title><description><![CDATA[Another small tip: you can simply use a single &lt;br&gt; for line breaks — there’s no need to use &lt;b&gt; or &lt;/b&gt;.
Also, just like in the script example, scripts can support localized language strings as well:
"""
Name-US:Example
Name-CN:示例
Description-US:Example line 1.&lt;br&gt;Example line 2.&lt;br&gt;Example line 3.
Description-CN:示例第 1 行。&lt;br&gt;示例第 2 行。&lt;br&gt;示例第 3 行。
"""

]]></description><link>http://developers.maxon.net/forum/topic/16408/how-to-start-a-new-line-in-plugin-help-with-localization</link><guid isPermaLink="true">http://developers.maxon.net/forum/topic/16408/how-to-start-a-new-line-in-plugin-help-with-localization</guid><dc:creator><![CDATA[Anlv]]></dc:creator><pubDate>Sat, 02 May 2026 06:53:40 GMT</pubDate></item><item><title><![CDATA[Executing a Redshift texture bake from Python]]></title><description><![CDATA[Hello @mplec1,
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
The bake tag and by extension cinema::BakeTexture (and its Python equivalent c4d.utils.BakeTexture) unfortunately do not support Redshift, even for the older Xpresso based materials.
Redshift uses the tools found in Redshift/Tools/Texture Baking for baking. And while you can programmatically create a bake set and then programmatically click the 'Bake' button in it (which both would also work in a headless version of Cinema 4D, such as c4dpy), the following dialog which opens to set baking details and actually start the rendering is sealed, i.e., you cannot interact with it from the public API. And opening such dialog would also fail in a headless Cinema 4D instance.
So, I am afraid there is currently no solution for your problem. You can technically export the whole scene to a format such FBX or USD, and use the builtin baking output (which would also work in a headless environment, as long as you do NOT pass SAVEDOCUMENTFLAGS_DIALOGSALLOWED to the save/export operation). But the output of that automated baking is often of poor quality compared to manually baking object(s) via bake sets.
Cheers,
Ferdinand
]]></description><link>http://developers.maxon.net/forum/topic/16407/executing-a-redshift-texture-bake-from-python</link><guid isPermaLink="true">http://developers.maxon.net/forum/topic/16407/executing-a-redshift-texture-bake-from-python</guid><dc:creator><![CDATA[ferdinand]]></dc:creator><pubDate>Tue, 28 Apr 2026 22:28:49 GMT</pubDate></item><item><title><![CDATA[How do I retrieve a command call from it&#x27;s index number ?]]></title><description><![CDATA[The second ID is the sub ID. You can have a command (FOO = 100) which supports the sub-commands (BAR=1, and BAZ=2). So, you can then call CallCommand(100, 1) or CallCommand(100, 2) to invoke the two different things FOO can do. But as you said yourself, this is a rather unusual thing, and it is even more unusual that I manged to randomly click five things in a row that all have sub IDs 
What should also be said is that my little "extrude" log there does not really reproduce the modelling operations I carried out. It just runs the tools in the order they are invoked. And because when you run this right after your created the log, the extrude tool will for example still have set the same extrude depth from the last operation. But when you manually extrude something, and then run the script again, it will use these new current extrude tool settings, i.e., and not the ones from when your 'recorded' the log.
The script log can be useful, but it is not the auto-script-recording feature users often whish it was.
]]></description><link>http://developers.maxon.net/forum/topic/16406/how-do-i-retrieve-a-command-call-from-it-s-index-number</link><guid isPermaLink="true">http://developers.maxon.net/forum/topic/16406/how-do-i-retrieve-a-command-call-from-it-s-index-number</guid><dc:creator><![CDATA[ferdinand]]></dc:creator><pubDate>Fri, 24 Apr 2026 21:43:27 GMT</pubDate></item><item><title><![CDATA[Using the Bridge Tool]]></title><description><![CDATA[Hello @Dimitris_Derm.
No, with islands I do not mean different objects. With islands are polygon (selection) islands meant. And that is just the term that is used for these things.  The "Plane" object below has two polygon islands, the left and right rectangle shown in the viewport, each composed out of 4 * 5 polygons. They are islands because they are topological disjunct from each other - you cannot 'get' from one island to another without jumping over a gap.
[image: 1777019034509-a88ac530-c105-430c-bd11-f922f5688881-image.png]
The same can be applied to selections, as shown below. Now the left polygon island in the mesh has two polygonal selection islands. You cannot get from one selection island to another without jumping over a gap (they are topologically disjunct).
[image: 1777019065284-bf41be6a-3993-4eb1-b603-438df9aa1bb3-image.png]
This is a requirement because as my code example demonstrates, when bridging in island mode, you just specify a polygon and a point (and set the flags), and the tool then 'grows out' the to be bridged patch from the given polygon, based on the active polygon selection. And this growing will stop at topological boundaries. So, when you would bridge from the selection in the lower left corner of the left rectangle (to some unspecified target in another mesh), it would only grow into these four polygons. The selection in the top right corner would never be part of the operation.
Cheers,
Ferdinand
]]></description><link>http://developers.maxon.net/forum/topic/16404/using-the-bridge-tool</link><guid isPermaLink="true">http://developers.maxon.net/forum/topic/16404/using-the-bridge-tool</guid><dc:creator><![CDATA[ferdinand]]></dc:creator><pubDate>Fri, 17 Apr 2026 11:29:19 GMT</pubDate></item></channel></rss>