_c4d_descid

Demonstrates important functions for the types c4d.DescID and c4d.DescLevel.

_c4d_descid.py

"""Demonstrates important functions for the types c4d.DescID and c4d.DescLevel.

Topics:
    - Creating and using DescID and DescLevel objects
    - Accessing and modifying object parameters using DescID
    - Handling multi-level DescID for complex parameters

Related:
    scene/basics_parameters.py
"""
__author__ = "Ferdinand Hoppe"
__copyright__ = "Copyright (C) 2026 MAXON Computer GmbH"
__license__ = "Apache-2.0 License"
__version__ = "2027.0.0"
__date__ = "07/09/2026"

import c4d
import mxutils


doc: c4d.documents.BaseDocument # The active Cinema 4D document.
op: c4d.BaseObject | None # The currently selected object in the Cinema 4D document, can be None.


def main() -> None:
    """Runs the example.
    """
    # Instantiate a cube object and insert it into the document.
    cube: c4d.BaseObject = mxutils.CheckType(c4d.BaseObject(c4d.Ocube))
    doc.InsertObject(cube) 
    doc.SetActiveObject(cube)

    # A DescID is an identifier for a specific parameter within an scene elements description. A
    # simple DescID can be constructed with just the parameter ID, as here done with the relative
    # position parameter of the object.
    pos: c4d.DescID = c4d.DescID(c4d.ID_BASEOBJECT_REL_POSITION)
    print(f"{pos = }")

    # This descID can be used to access or modify the corresponding parameter of the cube object.
    cube[pos] = c4d.Vector(100, 200, 300) # Example of setting the position using the DescID.
    # Which in this case is the same as:
    cube[c4d.ID_BASEOBJECT_REL_POSITION] = c4d.Vector(100, 200, 300)

    # But what we used above was actually a shorthand for creating a DescID. A DescID is composed
    # of up to three DescLevels and we could also define #pos like this. Practically, this is the
    # same as the shorthand we used earlier, but it explicitly shows the DescLevel structure.
    pos: c4d.DescID = c4d.DescID(c4d.DescLevel(c4d.ID_BASEOBJECT_REL_POSITION))

    # But even this was not the full complexity, as we used a shorthand for the DescLevel 
    # construction. At its fully complexity, the ID_BASEOBJECT_REL_POSITION parameter is being+
    # addressed like this:
    pos: c4d.DescID = c4d.DescID(
        # First desc level of the ID.
        c4d.DescLevel(
            c4d.ID_BASEOBJECT_REL_POSITION, # The ID of the parameter.
            c4d.DTYPE_VECTOR,               # The data type of the parameter.
            c4d.Ocube)                      # The creator of the parameter, passing 0 is also allowed
                                            # (at least in most cases).
        )

    # This full complexity #pos acts the same way as the shorthand versions above. In some very
    # rare cases such as constructing ports for Xpresso nodes, Cinema however forces us to be 
    # explicit about the full DescID structure.

    # Do exactly the same thing.
    print(f"\n{cube[pos] = }")
    print(f"{cube[c4d.ID_BASEOBJECT_REL_POSITION] = }")

    # So why exist there 'up to three DescLevels' in a DescID? The reason is that some parameters in 
    # Cinema 4D can be decomposed into sub-parameters, for example vectors.

    # Print the X component of the position vector.
    posx: c4d.DescID = c4d.DescID(
        # First level, the vector itself.
        c4d.DescLevel(c4d.ID_BASEOBJECT_REL_POSITION, c4d.DTYPE_VECTOR, c4d.Ocube),
        # Second level, the X component of the vector.
        c4d.DescLevel(c4d.VECTOR_X, c4d.DTYPE_REAL, c4d.Ocube)
    )
    print(f"\n{posx = }")
    print(f"{cube[posx] = }")

    # User data is another case of multi level DescIDs, as each user data parameter is nested under 
    # the user data container.

    # Add a new user data vector parameter to the cube.
    vector: c4d.BaseContainer = c4d.GetCustomDataTypeDefault(c4d.DTYPE_VECTOR)
    vector[c4d.DESC_NAME] = "Vector"
    did: c4d.DescID = cube.AddUserData(vector)

    # did is itself an example for a two-level DescID, because all user data parameters are 
    # nested under the user data container (ID_USERDATA).
    print(f"{did = }") # The full descID
    print(f"{did.GetDepth() = }") # it has two levels
    print(f"\n{cube[did] = }") # access the parameter value

    # Specifically access the levels and their data.
    print(f"\n{did[0].id = }")      # ID of the first level: ID_USERDATA
    print(f"{did[0].dtype = }")     # Type of the first level: DTYPE_SUBCONTAINER
    print(f"{did[0].creator = }")   # Creator of the first level: Ocube or 0.
    print(f"{did[1].id = }")        # ID of the second level: 1 (first user data parameter)
    print(f"{did[1].dtype = }")     # Type of the second level: DTYPE_REAL
    print(f"{did[1].creator = }")   # Creator of the second level, always 0. Because sub-levels do 
                                    # not have a creator.

    # These again are equivalent.
    cube[did] = c4d.Vector(1, 2, 3)
    cube[c4d.ID_USERDATA, 1] = c4d.Vector(3, 2, 1)

    c4d.EventAdd()

if __name__ == "__main__":
    c4d.ClearPythonConsole()
    main()