mxutils¶
Contains helper functions and types that are written in Python and exclusive to the Python API.
Overview¶
Classes¶
|
Generates |
|
Provides the ability to import packages and modules from directories that are not exposed to the global module search paths. |
|
Generates pseudo-random floating point, integer, vector, color, and matrix values in a deterministic manner. |
|
Generates random scene data such as objects, materials, tags, layers, render data and tracks. |
Functions¶
|
Checks |
|
Checks |
Rewinds the passed node to its first sibling. |
|
|
Rewinds the passed node to its root. |
|
Returns a string representation of a tree structure. |
|
Parses elements of all resource files contained in the given |
|
Yields nodes that are hierarchically related to node. |
|
Yields nodes that are in some relationship with the passed node. |
|
Yields nodes that are hierarchically related to node. |
Attributes¶
Generates |
|
Generates pseudo-random floating point, integer, vector, color, and matrix values in a deterministic manner. |
|
Generates random scene data such as objects, materials, tags, layers, render data and tracks. |
Decorators¶
|
Decorates a function call with a timing or profiling report printed to the console. |
|
Decorates a function call with output to the Cinema 4D status bar. |
Function Listing¶
-
mxutils.
CheckIterable
(item: Any, tElement: Optional[Union[Type, tuple]] = None, tIterable: Optional[Union[Type, tuple]] = None, label: str | None = None, minCount: int | None = None, maxCount: int | None = None) → Any¶ Checks
item
for being an iterable of a specific type, being composed of specific elements, or being of specific length.When any of the checks fail, an error is being raised. If not,
item
is being passed through. The function can automatically discover the symbol ofitem
when it is defined in the local or global scope of the function call. While all types of iterables are supported, it is onlydict
andc4d.BaseContainer
which are supported in their specific item, i.e., pairs, decomposition. All other iterables are treated as iterables of singular elements as for example lists or tuples.When possible, it is advantageous to replace iterable type checks with type conversions due to the computationally expensive nature of checking every item in an iterable. The expensive part of this call is passing not
None
fortElement
, i.e., checking the type of every element initem
(whenitem
is very large), all other checks are negligible in cost.- Parameters
item (typing.Any) – The item to be type checked.
tElement (typing.Type | tuple[typing.Type] | None (, optional)) – The type or types of elements the iterable
item
is allowed to have. PassingNone
will will accept any kind of element type. Defaults toNone
.tIterable (typing.Type | tuple[typing.Type] | None (, optional)) – The type or types the iterable
item
is allowed to be. PassingNone
will accept any kind of iterable. Defaults toNone
.label (str | None (, optional)) – The label with which to refer to
item
in exceptions. PassingNone
will result in an attempt to infer the symbol. Symbol discovery only works for values in the local or global scope. Defaults toNone
.minCount (int | None (, optional)) – The minimum length for the iterable
item
. PassingNone
will accept any iterable that is smaller than or equal tomaxCount
. Value must be smaller than or equal tomaxCount
Defaults toNone
.maxCount (int | None (, optional)) – The maximum length for the iterable
item
. PassingNone
will accept any iterable that is greater than or equal tominCount
. Value must be larger or equal tominCount
Defaults toNone
.
- Raises
TypeError – When
item
is failing the type tests.ValueError – When
minCount
is larger thanmaxCount
.RuntimeError – When any of the length checks fail.
RuntimeError – When accessing a stack trace or reading an executed file failed on a symbol discovery attempt.
- Returns
The value of
item
when the checks succeeded.- Return type
typing.Any
- Example
import c4d from mxutils import CheckIterable # Checks if `data` is composed of strings only; this will fail as there is an `int` in it. data: list[str|int] = ["1", "2", "3", 4, "5"] data = CheckIterable(data, str) # But the prior case is also an example for where element type assertions are unnecessary. # As we can enforce the type of items in `data` with `str()`. item: str = str(data[3]) # The common use case is checking type composition assumptions of function arguments passed # by unsafe sources, in order to avoid carrying out expensive computations which fail in a # very late stage due to encountering a malformed item in the input data. class Foo: def __init__(self, nodes: list[c4d.BaseObject | c4d.BaseTag]) -> None: # Assure that _nodes will be a list of tags and objects. self._nodes = CheckIterable(nodes, (c4d.BaseObject, c4d.BaseTag), list) # Tested can also be the length of the iterable. # Assures that `data` has at least the length of one. No specific type checks are performed # in this case, but `data` is still being tested for being an iterable. This is a very low # cost call, no matter how large data is, as the first argument `tElement` has is `None` and # `data` is therefore never traversed as elements of any type are accepted. data = CheckIterable(data, minCount=1) # Assures that `data` is not longer than five elements. data = CheckIterable(data, maxCount=5) # Assures that the length of `data` lies in the interval [1, 5]. data = CheckIterable(data, minCount=1, maxCount=5) # The function will always test if ``item`` is an iterable, even when both type tests are # set to None. So, this will raise an error as `int` does not implement `__iter__` CheckIterable(1) # but this will not, as `c4d.BaseContainer` does implement `__iter__`: data: c4d.BaseContainer = c4d.BaseContainer() CheckIterable(data) # All forms of iterable are supported, but only `c4d.BaseContainer` and `dict` are handled # manually to type test values in key, value pairs instead of testing the type of the pair. data[0] = "Hello world" data[1] = True # Will test data for being of type BaseContainer, holding only values of type str or bool. CheckIterable(data, (str, bool), c4d.BaseContainer) # The same thing for dict, key type testing is not supported. data = {1: "Hello World", "my_key": True} CheckIterable(data, (str, bool), dict) # Just as for CheckType(), automatic symbol discovery is only possible for values in the # local or global scope. So, when testing `sys.path`, one should pass a label, as errors # will otherwise refer to an 'unknown symbol'. import sys # Assures that sys.path is a list of strings with at least one element. paths = CheckIterable(sys.path, str, list, "sys.path", 1)
-
mxutils.
CheckType
(item: Any, t: Optional[Union[Type, tuple]] = None, label: str | None = None) → Any¶ Checks
item
for being of typet
.When the check fails, an error is being raised. If not,
item
is being passed through. The function can automatically discover the symbol ofitem
when it is defined in the local or global scope of the function call.- Parameters
item (typing.Any) – The item to be type checked.
t (typing.Type | tuple[typing.Type] | None (, optional)) – The type or types
item
is allowed to be. PassingNone
will only assert thatitem
is notNone
. Defaults toNone
.label (str | None (, optional)) – The label with which to refer to
item
in exceptions. PassingNone
will result in an attempt to infer the symbol. Symbol discovery only works for values in the local or global scope. Defaults toNone
.
- Raises
TypeError – When
item
is failing the type test.RuntimeError – When accessing a stack trace or reading an executed file failed on a symbol discovery attempt.
- Returns
The value of
item
when the check succeeded.- Return type
typing.Any
- Example
import c4d import math from mxutils import CheckType MY_INT: int = 42 class Foo: SOME_INT: int = 42 def __init__(self, x: int) -> None: # Asserts that `x` is not `None`. CheckType(x) # Asserts that `x` is not `None` and assigns its values to `self._x`. self._x: object = CheckType(x) # Asserts that `MY_INT` is of type `int` or `float` and assigns its int value to `self._y`. self._y: int = int(CheckType(MY_INT, (int, float))) # Symbols which are neither in the global nor local scope are not automatically # discoverable and error messages for such values will refer to the value with # 'unknown symbol'. # `Foo.SOME_INT` and `math.pi` are not part of the local or global scope. When error # messages should not refer to a generic 'Unknown Symbol', one must pass in a label. # CheckType(Foo.SOME_INT, bool) # TypeError: Expected value of type <class 'bool'> for 'Unknown Symbol'. Received: <class 'int'> CheckType(math.pi, int, 'math.pi') # TypeError: Expected value of type <class 'int'> for 'math.pi'. Received: <class 'float'> # `x`, `y`, and `MY_INT` are in the global/local scope and automatically discovered. y: float = 1.0 CheckType(x, float) # TypeError: Expected value of type <class 'float'> for 'x'. Received: <class 'int'> CheckType(y, int) # TypeError: Expected value of type <class 'int'> for 'y'. Received: <class 'float'> CheckType(MY_INT, bool) # TypeError: Expected value of type <class 'bool'> for 'MY_INT'. Received: <class 'int'>
-
mxutils.
GetFirstSiblingNode
(node: c4d.GeListNode) → c4d.GeListNode¶ Rewinds the passed node to its first sibling.
- Parameters
node (c4d.GeListNode) – The node to rewind.
- Returns
The first sibling node.
- Example
import c4d from mxutils import CheckType, GetFirstSiblingNode # For the object tree: # A # |___B # |___C # |___D # Find the object named "D" in the scene. d: c4d.BaseObject = CheckType(doc.FindObject("D")) # Rewind to the first sibling node. firstSibling: c4d.GeListNode = GetFirstSiblingNode(d) print (firstSibling.GetName()) # Output: C # Find the first sibling of the object named "B" (it is B itself). b: c4d.BaseObject = CheckType(doc.FindObject("B")) # Output: B
-
mxutils.
GetRootNode
(node: c4d.GeListNode) → c4d.GeListNode¶ Rewinds the passed node to its root.
- Parameters
node (c4d.GeListNode) – The node to rewind.
- Returns
The root node.
- Example
import c4d from mxutils import CheckType, GetRootNode # For the object tree: # A # |___B # | |___C # | |___D # E # |___F # Find the object named "D" in the scene. d: c4d.BaseObject = CheckType(doc.FindObject("D")) # Rewind to the root node. root: c4d.GeListNode = GetRootNode(d) print (root.GetName()) # Output: A # Find the root of the object named "F". f: c4d.BaseObject = CheckType(doc.FindObject("F")) e: c4d.BaseObject = GetRootNode(f) # Output: E
-
mxutils.
GetTreeString
(node: Any, childrenAttr: str | None = None, getDownFunc: str | None = 'GetDown', getNextFunc: str | None = 'GetNext', indentWidth: int = 3, callback: Optional[Callable, None] = None) → str¶ Returns a string representation of a tree structure.
The tree of node is traversed using either a list of child nodes attached to each node under the attribute childrenAttr, or by calling the functions getDownFunc and getNextFunc on each node to get the first child and the next sibling, respectively.
- Parameters
node (typing.Any) – The root node of the tree.
childrenAttr (str, optional) – The attribute name to access the children of a node, defaults to None.
getDownFunc (str, optional) – The function name to get the first child of a node, defaults to “GetDown”.
getNextFunc (str, optional) – The function name to get the next sibling of a node, defaults to “GetNext”.
indentWidth (int, optional) – The width of the tree indentation for one level, defaults to 3.
callback (typing.Callable, optional) – A callback function to customize the string representation of each node, defaults to None.
- Returns
The string representation of the tree structure.
- Return type
str
- Raises
ValueError – If the callback is provided but not callable.
ValueError – If the childrenAttr is not found on the node.
ValueError – If the getDownFunc is not found on the node.
ValueError – If the getNextFunc is not found on the node.
ValueError – If neither childrenAttr nor getDownFunc and getNextFunc are provided.
- Example
import c4d from mxutils import GetTreeString # A node type to print trees for, this is a node type which stores its children as a list. class Node: def __init__(self, value: int) -> None: self.value = value self.children = [] def __str__(self) -> str: return str(self.value) def AddChild(self, child: 'Node') -> None: self.children.append(child) # Create a tree structure. tree: Node = Node(1) tree.AddChild(Node(2)) tree.AddChild(Node(3)) tree.children[1].AddChild(Node(4)) # Print the tree structure by telling the function to use the #children attribute. print(GetTreeString(tree, "children"))
1 ├─ 2 └─ 3 └─ 4
# Example for printing a node tree for a node type which can be navigated using a function which # yields the first child and a function which yields the next sibling, here at the example of a # Cinema 4D BaseObject tree. # Get the first object in the active document. doc: c4d.documents.BaseDocument = c4d.documents.GetActiveDocument() op: c4d.BaseObject = doc.GetFirstObject() if not op: raise ValueError("No object found in the document.") # Print the object tree of #op using its navigation functions 'GetDown' and 'GetNext'. We could # just invoke #GetTreeString(op) here since 'GetDown' and 'GetNext' are the default values. print(GetTreeString(op, getDownFunc="GetDown", getNextFunc="GetNext")) print("") def PrintObject(node: c4d.BaseObject) -> str: '''Returns the name of an object and the names of its tags as a string. ''' return f"{node.GetName()} {' '.join([f"[{n.GetName()}]" for n in node.GetTags()])}" # We can also customize the string representation of each node by passing a callback function. print(GetTreeString(op, callback=PrintObject))
<c4d.BaseObject object called Cone/Cone with ID 5162 at 140511902113152> ├── <c4d.BaseObject object called Cube/Cube with ID 5159 at 140511902142592> └── <c4d.BaseObject object called Sphere/Sphere with ID 5160 at 140511902160768> └── <c4d.BaseObject object called Torus/Torus with ID 5163 at 140511902065024> Cone [Display] [Phong] [Material] ├── Cube [Phong] └── Sphere [Phong] [Material] └── Torus [Phong]
-
mxutils.
ImportSymbols
(path: str, output: Type = None) → dict[str, float | int | str] | None¶ Parses elements of all resource files contained in the given
path
into the global scope of the caller or into a dictionary.See also
- Parameters
path (str) – The path within which header files should be discovered and parsed.
output (typing.Type) – If the parsed symbols should be placed in the global scope or be returned as a dictionary. Pass
None
to let them be placed in the global scope anddict
to have the function return them as a dictionary. Defaults toNone
.
- Returns
The parsed symbol value pairs as or nothing when the
output
wasNone
- Return type
dict[str, float | int | str] | None
- Example
The following header file
c:\data\c4d_symbols.h
,enum { ID_FOO = 1000, ID_BAR = 1001, _DUMMY_ELEMENT_ };
can be imported as follows:
import os import mxutils # Recursively parse header files in "c:\data" into this scope. mxutils.ImportSymbols("c:\data") # This would also work when only a singular file should be parsed. mxutils.ImportSymbols("c:\data\c4d_symbols.h") # The parsed symbols are now exposed in the global scope. print(ID_FOO) # Will print 1000 print(ID_BAR) # Will print 1001 # Alternatively, symbols can also be parsed into a dictionary. data: dict[str, int] = mxutils.ImportSymbols("c:\data\c4d_symbols.h", output=dict) print(data["ID_FOO"]) # Will print 1000 print(data["ID_BAR"]) # Will print 1001
-
mxutils.
IterateTree
(node: c4d.GeListNode | None, yieldSiblings: bool = False, rewindToFirstSibling: bool = False, rewindToRoot: bool = False) → Iterator[c4d.GeListNode]¶ Yields nodes that are hierarchically related to node.
Note
This function walks a node tree in a purely iterative manner, which makes it fast and robust for medium to large trees. For small trees (<= 100 nodes) RecurseTree is slightly faster. This function is usually the faster and safer choice compared to RecurseTree as the performance gain for large trees is significant while the loss for small trees is negligible.
Note
To also yield non-hierarchical relationships, use RecurseGraph instead.
- Parameters
node (c4d.GeListNode or None) – The root node to start iterating from.
yieldSiblings (bool, optional) – Whether to yield the next siblings of the current node, defaults to False.
rewindToFirstSibling (bool, optional) – Whether to rewind the node to its first sibling before the iteration is carried out, defaults to False.
rewindToRoot (bool, optional) – Whether to rewind the node to its root before the iteration is carried out, defaults to False.
- Returns
An iterator that yields the descendants of the passed node.
- Return type
typing.Iterator[c4d.GeListNode]
- Raises
RuntimeError – If the UUID of a node could not be retrieved.
- Example
import c4d from mxutils import CheckType, IterateTree # For the object tree: # # A # |___B # | |___C # | |___D # | | |___E # | |___F # G # Find the object named "D" in the scene. d: c4d.BaseObject = CheckType(doc.FindObject("D")) # Will yield D and E. for node in IterateTree(d): print (node) # Will yield D, E, and F. But not C as we start out at D and #yieldSiblings only looks # downwards, at next siblings and not previous ones. for node in IterateTree(d, yieldSiblings=True): print (node) # Will yield C, D, E, and F. Because we rewind to the first sibling of D - which is C. for node in IterateTree(d, yieldSiblings=True, rewindToFirstSibling=True): print (node) # Will yield A, B, C, D, E, and F. But not G, as we do not yield siblings. for node in IterNodeTree(d, rewindToRoot=True): print (node) # Will always yield the whole tree, no matter where we start. for node in IterateTree(d, True, True, True): print (node)
-
mxutils.
RecurseGraph
(node: c4d.GeListNode, yieldCaches: bool = False, yieldBranches: bool = False, yieldHierarchy: bool = False, nodeFilter: list[int] | None = None, branchFilter: list[int] | None = None, maxBranchingDepth: int | None = None, stopBranchingEarly: bool = False, complexYield: bool = False, **kwargs: Any) → Iterator[c4d.GeListNode | tuple[c4d.GeListNode, c4d.GeListNode, int, bool, bool]]¶ Yields nodes that are in some relationship with the passed node.
The classic API of Cinema 4D has a special view on the concept of a scene graph where node relations are split into three categories: hierarchies, caches, and branches; each with its own set of functions to traverse such relationships. This function combines these three categories into one interface that can be used to traverse the scene graph in a more user-friendly way.
Note
This function is about the classic API concept of a node as represented by c4d.GeListNode. It is not about nodes in the sense of the Nodes API, i.e. maxon.GraphNode. You cannot traverse node graphs of the Nodes API with this function.
Warning
This function is implemented in a semi-recursive manner. For very deep scene graphs, the function might hit the recursion limit of Python.
Warning
This function is relatively computationally expensive in large scenes. It should not be used in performance critical scenarios, e.g., a MessageData plugin that scans the whole scene every 50ms. In these cases, write a custom traversal function that is tailored to your specific needs. For everything else, e.g., when the user clicks a button, when a tool is executed, or when the cache of an object is being built, using this function is perfectly fine. Using stopBranchingEarly makes also a huge difference for large scenes, as it can mean the difference between traversing 100 or 100 000 nodes in total for the same scene and input.
- Parameters
node (c4d.GeListNode) – The node to start the iteration from.
yieldCaches (bool, optional) – Whether to yield the caches of the node, defaults to False.
yieldBranches (bool, optional) – Whether to yield the branches of the node, defaults to False.
yieldHierarchy (bool, optional) – Whether to yield the hierarchy of the node, defaults to False.
nodeFilter (list[int] or None, optional) –
A list of node types that should be yielded or None for all types, defaults to None. Examples for node type symbols are:
c4d.Tbaselist2d matches all nodes (except for branch heads which are not yielded by this function anyway).
c4d.Obase matches all objects.
c4d.Ocube matches a cube object.
c4d.Mbase matches all materials.
c4d.Mmaterial matches a standard material.
c4d.Tbase matches all tags.
c4d.Ttexture matches a texture tag.
c4d.Xbase matches all shaders.
c4d.Xnoise matches a noise shader.
c4d.GVbase matches all xpresso nodes.
…
See also
Types and Symbols List for a list of node type symbols.
branchFilter (list[int] or None, optional) –
A list of branch types to filter the branches by or None for all types, defaults to None. Common branch types are:
c4d.Obase for objects.
c4d.Mbase for materials.
c4d.Olayer for layers.
c4d.Xbase for shaders.
c4d.Tbase for tags.
c4d.Rbase for render data.
c4d.GVbase for xpresso graphs.
c4d.CTbase for tracks.
c4d.CSbase for curves.
c4d.SHplugin for scene hooks.
maxBranchingDepth (int or None, optional) – The maximum depth to which to traverse branches to or None to apply no limit, defaults to None.
stopBranchingEarly (bool, optional) – Whether to stop branching early when a branch does not meet the branch filter criteria, defaults to False.
complexYield (bool, optional) – Whether to yield complex values or just the current node, defaults to False.
kwargs – Additional private keyword arguments. Do not pass any arguments to this function that are not documented here as you might accidentally override internal arguments.
- Returns
An iterator that yields the nodes that are in some relationship with the passed node based on the search criteria. When complexYield is set to True, the function yields the tuple [node, owner, depth, isBranch, isCache] where:
node is the node that is yielded.
owner is the node that holds the cache or branch.
- depth is the traversal depth of the current node. The depth is 0 for the passed node,
each hierarchy level increases the depth by 1, and each branch and cache increases the depth by 2.
isBranch is a flag that indicates if the node is part of a branch relationship.
isCache is a flag that indicates if the node is part of a cache relationship.
- Return type
typing.Iterator[c4d.GeListNode] or typing.Iterator[tuple[c4d.GeListNode, c4d.GeListNode, int, bool, bool]]
- Example
import c4d import mxutils doc: c4d.documents.BaseDocument # The currently active document. op: c4d.BaseObject | None # The primary selected object in `doc`. Can be `None`. # This is the scene we will be working with. # doc # ├── Objects # │ └── Cube (selected) [Motion Tag][Phong Tag][Material Tag] # │ └── Sphere [Phong Tag][Material Tag] # │ └── Position.Y Track # ├── Materials # │ └── Mat.1 # │ └── Layer Shader # │ ├── Gradient # │ └── Noise # ├── Layers # │ ├── Layer.1 # │ └── Layer.2 # └── Render Settings # ├── Motion Source Render Data # └── My Render Settings.1 # With this function we can iterate over all nodes (GeListNode) in a scene, such as objects, tags, # materials, shaders, and many things more. All these things are in a branching relation to each # other expressed by GeListNode.GetBranchInfo() and the hierarchical relations expressed by the # other GeListNode methods. # When we pass only the active object #op to RecurseGraph() nothing will happen as all traversal # options are disabled by default. To use the function, we always have to configure the output. print("RecurseGraph(op):") for node in mxutils.RecurseGraph(op): print(node) # > RecurseGraph(op): # Here we we enable hierarchical traversal by setting yieldHierarchy=True. This will yield the # nodes that are in a hierarchical descendant relation to the active object. print("RecurseGraph(op, yieldHierarchy=True):") for node in mxutils.RecurseGraph(op, yieldHierarchy=True): print(node) # > RecurseGraph(op, yieldHierarchy=True): # > <c4d.BaseObject object called Cube/Cube with ID 5159 at 2544283200960> # The active object #op. # > <c4d.BaseObject object called Sphere/Sphere with ID 5160 at 2544283291584> # The child of #op. # -------------------------------------------------------------------------------------------------------------- # But there are more traversal options. We can also yield caches for example. We must leave the # hierarchical traversal enabled when we not only want to see the direct cache of #op but also # hierarchal relations within that cache and the cache of objects parented to #op. print("RecurseGraph(op, yieldHierarchy=True, yieldCaches=True):") for node in mxutils.RecurseGraph(op, yieldHierarchy=True, yieldCaches=True): print(node) # > RecurseGraph(op, yieldHierarchy=True, yieldCaches=True): # > <c4d.BaseObject object called Cube/Cube with ID 5159 at 2544283200960> # #op # > <c4d.PolygonObject object called Cube/Polygon with ID 5100 at 2547767808256> # Its cache. # > <c4d.BaseObject object called Sphere/Sphere with ID 5160 at 2544283291584> # The child of #op. # > <c4d.PolygonObject object called Sphere/Polygon with ID 5100 at 2547767808256> # Its cache. # -------------------------------------------------------------------------------------------------------------- # The most important traversal dimension is branching, it is what ties a classic API scene graph # together. We enable it by setting #yieldBranches to #True. We also enable complex yielding here # which will change the yield type of the function to also provide traversal metadata. We turn off # cache traversal as this example would get otherwise too long as caches also hold branches. print("RecurseGraph(op, yieldBranches=True, yieldHierarchy=True, complexYield=True):") for node, owner, depth, isBranch, isCache in mxutils.RecurseGraph( op, yieldBranches=True, yieldHierarchy=True, complexYield=True): prefix: str = "[]" if not isBranch else "[BRANCH]" space: str = " " * depth nodeStr: str = f"{node.GetName()}({node.__class__.__name__})" ownerStr: str = f"[{owner.GetName()}]" if owner else "" print (f"{space}{prefix} {nodeStr}{ownerStr}") # > RecurseGraph(op, yieldBranches=True, yieldHierarchy=True, complexYield=True): # > [] Cube.1(BaseObject) # Active object, #op. # > [BRANCH] Position.X(CTrack)[Cube.1] # Pos.X track of, in the track branch owned by #op. # > [BRANCH] (CCurve)[Position.X] # Curve of it, in the curve branch owned by the track. # > [BRANCH] Motion System(BaseTag)[Cube.1] # Motion System tag of #op, tag branch of #op. # > [BRANCH] Layer(BaseList2D)[Motion System] # A layer in the layer branch of the motion system. # > [BRANCH] Cube(BaseObject)[Layer] # An object in the object branch of the layer. # > [BRANCH] Phong(BaseTag)[Cube.1] # Phong tag in the tag branch of #op. # > [BRANCH] Material(TextureTag)[Cube.1] # Material tag in the tag branch of #op. # > [] Sphere(BaseObject) # The sphere object child of #op. # > [BRANCH] Position . Y(CTrack)[Sphere] # Pos.Y track of the sphere. # > [BRANCH] (CCurve)[Position . Y] # Its curve. # > [BRANCH] Phong(BaseTag)[Sphere] # Phong tag of the sphere. # We can see that there are many hidden things in the branches of a scene element. The motion system # holds attached to #op an internal copy of #op, and there is also a track on #op which is not # visible in the timeline. It is the position track the motion system layer has been created # from (and is therefor now hidden from the user). # -------------------------------------------------------------------------------------------------------------- # Even a seemingly simple scene can be quite complex under the hood. Since a document (BaseDocument) # is a node itself, we can also start the traversal from the document. When we now enable everything # which can be enabled regarding traversal complexity, we will traverse every node in the scene. print("RecurseGraph(doc, yieldBranches=True, yieldHierarchy=True, complexYield=True):") allNodes: list[c4d.GeListNode] = list(mxutils.RecurseGraph(doc, True, True, True)) print(f"Total number of nodes: {len(allNodes)}")flush# # > RecurseGraph(doc, yieldBranches=True, yieldHierarchy=True, complexYield=True): # > Total number of nodes: 118 # Our scene contains 118 nodes in total but there are only 18 tangible nodes when we count every # object, tag, track, curve, material, shader, layer, and render setting. The remaining nodes are # in hidden branches like the motion system or in caches. # -------------------------------------------------------------------------------------------------------------- # To deal with this overwhelming amount of information, RecurseGraph() offers tools to filter the # output of traversal. An important filter is the #branchFilter. It limits the yielded nodes to # the provided branch types. This does not mean by default that the function will not traverse # other branches, but it will not yield from them, # Traverse #doc in full complexity, but only yield nodes that are part of an Object or Curve branch. print("RecurseGraph(doc, ... branchFilter=[c4d.Obase, c4d.CSbase]):") for node in mxutils.RecurseGraph(doc, True, True, True, branchFilter=[c4d.Obase, c4d.CSbase]): print (f"`{node.GetName()}`({node.__class__.__name__})") # > RecurseGraph(doc, ... branchFilter=[c4d.Obase, c4d.CSbase]): # > ``(BaseDocument)` # > `Cube`(BaseObject) # > `Cube`(PolygonObject) # > ``(CCurve)` # > `Sphere`(BaseObject) # > `Sphere`(PolygonObject) # > ``(CCurve)` # > `Default Simulation Scene`(BaseObject) # > `PKHOP`(BaseObject) # We get CCurve and BaseObjects yielded, but also a singular BaseDocument. This is because the input # node does not have a branch in the logic of this method (it might very well have), it simply does # not look at it. Here this is of course true, there is nothing above a document. # The last two yielded nodes might also be a bit surprising, they are objects being held by scene hooks, # a special kind of hidden scene element type that acts like a spider in the net to control things in # the scene; yet another example for hidden scene elements in a scene. # -------------------------------------------------------------------------------------------------------------- # With #stopBranchingEarly we can make sure that the traversal only goes into the branches we # specify. This is different from the default, as which will only yield from the specified # branch types but still traverse the whole scene. print("RecurseGraph(doc, ..., branchFilter=[c4d.Obase, c4d.CTbase], stopBranchingEarly=True):") for node in mxutils.RecurseGraph(doc, True, True, True, branchFilter=[c4d.Obase, c4d.CTbase], stopBranchingEarly=True): print (f"`{node.GetName()}`({node.__class__.__name__})") # > RecurseGraph(doc, ..., branchFilter=[c4d.Obase, c4d.CTbase], stopBranchingEarly=True): # > ``(BaseDocument)` # > `Cube`(BaseObject) # > `Cube`(PolygonObject) # > `Position.X`(CTrack) # > `Sphere`(BaseObject) # > `Sphere`(PolygonObject) # > `Position.Y`(CTrack) # The last two objects have now disappeared from the output. Because they are parts of the # Obase branches of two scene hooks which themselves are part of the SHplugin branch of #doc. # And since we do not include SHplugin in the branchFilter, the traversal stops at the scene # hooks branch SHplugin. Analogously, if our scene would have animations anywhere else than on # objects, we could not see them either, as we never step into tag, material, shader, etc. # branches. # -------------------------------------------------------------------------------------------------------------- # Another filter option is the branching #maxDepth. When we pass here for example 1, and start at # document level, we will never see tags or shaders, as we will only go only one level deep, i.e., # direct branches of the document. But tags are branches of objects and shaders are branches of # materials, shaders, objects, tags, etc., but never documents. So, would need at least a depth of # 2 to see tags and shaders. for node in mxutils.RecurseGraph(doc, True, True, True, maxDepth=1): print (node) # -------------------------------------------------------------------------------------------------------------- # The last and for non-power-users probably the most important filter is the #nodeFilter. It # simply allows us to do type checks before yielding a node. The same could also easily be done # with a line like `if node.Chekcktype(c4d.Obase): doStuff()` inside a RecurseGraph() loop. # Grab all Xpresso nodes from the document, e.g., an Xpresso "Contant" node in an Xpresso tag. for xpressoNode in mxutils.RecurseGraph(doc, True, True, True, nodeFilter=[c4d.GVbase]): print (xpressoNode) # Get all Cube objects and Phong tags in the scene, regardless of where they hide. for node in mxutils.RecurseGraph(doc, True, True, True, nodeFilter=[c4d.Ocube, c4d.Tphong]): print (node) # The danger of these two examples is that we get yielded nodes we assume to be something # different then what they are, there is for example still an Ocube held by the motion system. A # way out of this is using complex yielding and checking the type of the owner of each node, we # then can for example discard every cube that is not owned directly by a document. for node, owner, *_ in mxutils.RecurseGraph(doc, True, True, True, nodeFilter=[c4d.Ocube], complexYield=True): if node.CheckType(c4d.Ocube) and not isinstance(owner, c4d.documents.BaseDocument): continue
-
mxutils.
RecurseTree
(node: c4d.GeListNode) → Iterator[c4d.GeListNode]¶ Yields nodes that are hierarchically related to node.
Note
This function is slower than IterateTree for medium to large trees. For small trees (<= 100 nodes) this function is negligibly faster than the iterative version.
Warning
This function uses recursion to traverse the parent-child relations of the node tree. This bears the risk of hitting the recursion limit of Python; or worse, a stack overflow in the C++ backend when you increase the limit too much. If you want to be safe, use IterateTree instead.
- Parameters
node (c4d.GeListNode) – The root node to start iterating from.
- Returns
An iterator that yields the descendants of the passed node.
- Return type
typing.Iterator[c4d.GeListNode]
- Example
import c4d import mxutils from mxutils import RecurseTree # Iterate over all objects that are descendants of the active object. This will not yield # the siblings before and after op, use `IterateTree` for that. for obj in mxutils.RecurseTree(op): print(obj)
Attribute Listing¶
-
mxutils.
g_id_provider
= <mxutils.mxu_provider.IdProvider object>¶ Generates
int
ormaxon.Id
identifiers.A provider can yield identifiers in multiple scopes, where generation is independent and continuous for each scope.
Note
mxutils.g_idProvider
is a global instance of this class that can be used directly. It yieldsint
IDs.- Example
# An IdProvider can be used in any situation where either integer or maxon.Id identifiers # are required but one does not care too much about the exact makeup of each ID. # Providers can be initialized for one of the ID types, the default is `int`. intProvider: IdProvider = IdProvider(int) maxonProvider: IdProvider = IdProvider(maxon.Id) # A provider can use scopes/namespaces for generating IDs to provide multiple sets of IDs. # A scope can either be an integer or a string. scopeA: int = 0 scopeB: str = 'net.my-company.com.product.ids' # The property IdType will return the type of IDs yielded by an instance. print(f'{intProvider.IdType = }') # Will print <class 'int'> print(f'{maxonProvider.IdType = }') # Will print <class 'maxon.data.Id'> # With Get() a new ID is being yielded for the given scope. Providing no scope will # place the ID in the default scope. When a new scope is opened by passing a scope # identifier for the first time, the first ID will be 1000 unless a different startValue # is being provided. print(f'{intProvider.Get() = }') # 1000 print(f'{intProvider.Get(scopeA) = }') # 1000 print(f'{intProvider.Get(scopeB, 2000) = }') # 2000 print(f'{maxonProvider.Get() = }') # maxon.Id('net.maxon.namespace.user.default#1000') print(f'{maxonProvider.Get(scopeA) = }') # maxon.Id('1000#1000') print(f'{maxonProvider.Get(scopeB, 2000) = }') # maxon.Id('net.my-company.com.product.ids#2000') # Subsequent calls to Get() will yield the next ID in the sequence for the given scope. print(f'{intProvider.Get() = }') # 1001 print(f'{intProvider.Get() = }') # 1002 print(f'{intProvider.Get(scopeA) = }') # 1001 print(f'{intProvider.Get(scopeB) = }') # 2001 print(f'{maxonProvider.Get() = }') # maxon.Id('net.maxon.namespace.user.default#1001') print(f'{maxonProvider.Get() = }') # maxon.Id('net.maxon.namespace.user.default#1002') print(f'{maxonProvider.Get(scopeA) = }') # maxon.Id('1000#1001') print(f'{maxonProvider.Get(scopeB) = }') # maxon.Id('net.my-company.com.product.ids#2001') # GetLast() returns the last ID which has been yielded for a scope and GetAll() returns # all of them. print(f'{intProvider.GetLast() = }') # 1002 print(f'{intProvider.GetLast(scopeA) = }') # 1001 print(f'{maxonProvider.GetLast() = }') # maxon.Id('net.maxon.namespace.user.default#1002') print(f'{maxonProvider.GetLast(scopeA) = }') # maxon.Id('1000#1001') print(f'{intProvider.GetAll() = }') # (1000, 1001, 1002) print(f'{intProvider.GetAll(scopeA) = }') # (1000, 1001) print(f'{maxonProvider.GetAll() = }') # (maxon.Id('net.maxon.namespace.user.default#1000'), # maxon.Id('net.maxon.namespace.user.default#1001'), # maxon.Id('net.maxon.namespace.user.default#1002')) print(f'{maxonProvider.GetAll(scopeA) = }') # (maxon.Id('1000#1000'), # maxon.Id('1000#1001'))
-
mxutils.
g_random
= <mxutils.mxu_math.Random object>¶ Generates pseudo-random floating point, integer, vector, color, and matrix values in a deterministic manner.
Offered are two groups of methods: Methods that are prefixed with Random generate random values using the random module from the Python standard library. The characteristic of this approach is that one does not need any inputs except for an initial seed, the values are picked from a sequence of outputs. The other option are methods that are prefixed with Hash, they generate pseudo-random values using a hash function which requires an input value for each call, the to be hashed value.
The advantage of hash functions is that they generate pseudo-random values that are consistent across multiple calls for the same entity, no matter of the order they are made in. Imagine having a list of points and one wants to associate each point with a random float value. Using the random module, one would have to reseed the generator with the same seed every time one wants to generate the random values for the same point list. Calling random.Random.seed is not only such an expensive call that hashing usually outperforms random based random value generation by an order of magnitude, but also cannot be done in a thread safe manner and cannot deal with reordering, increasing, or decreasing of the inputs. Hash functions do not share any of these problems.
Note
mxutils.g_random
is a global instance of this class that can be used directly.Note
Most functions in this class deliberately do not perform type checks on their arguments to ensure a better performance, as they are expected to be called very often. It is the responsibility of the caller to ensure that the passed arguments are of the correct type.
Note
The hash function used in this class is not suitable for cryptographic purposes. It is a simple hash function that exploits the periodicity of the sine function to compute a pseudo-random hash. See the Hash method for more information.
- Example
import c4d import mxutils # Import the global random number generator instance. from mxutils import g_random obj: c4d.PointObject # Some point object we are given in code. # Associate each point with a random float value using RandomFloat. The tuples will be different each time the code # is run unless we reset the seed before each call (which is a very expensive operation). This is based on the # random module from the Python standard library. values: list[tuple[c4d.Vector, float]] = [(p, g_random.RandomFloat()) for p in obj.GetAllPoints()] # Generate point, random float pairs using a hash function, here hashing the point index. The tuples will be # the same each time the code is run unless the point order/indices change. values: list[tuple[c4d.Vector, float]] = [(p, g_random.HashNumberToNumber(i)) for i, p enumerate(in obj.GetAllPoints())] # And finally generate the tuples using the point values itself. A given point will always be associated with the # same random float value, no matter the count and order of points in #obj. values: list[tuple[c4d.Vector, float]] = [(p, g_random.HashVectorToNumber(p)) for p in obj.GetAllPoints()]
-
mxutils.
g_scene_faker
= <mxutils.mxu_provider.SceneFaker object>¶ Generates random scene data such as objects, materials, tags, layers, render data and tracks.
Meant to be used in development, testing, and debugging scenarios. Is not meant to be used in production code.
Note
mxutils.g_sceneFaker
is a global instance of this class that can be used directly.- Example
import c4d import mxutils # Get the active document and the global scene faker. doc: c4d.documents.BaseDocument = c4d.documents.GetActiveDocument() faker: mxutils.SceneFaker = mxutils.g_sceneFaker # Add 3 to 10 random generator objects to the document. generators: list[c4d.BaseObject] = faker.AddRandomGeneratorObjects(doc, 3, 10) # Add 3 to 5 random materials to the document and apply them to the generator objects. materials: list[c4d.BaseMaterial] = faker.AddRandomMaterials(doc, 3, 5, generators) # Add 1 to 2 random tags to the generator objects. tags: list[c4d.BaseTag] = faker.AddRandomTags(generators, 1, 2) # Flush the whole document and create a new random scene in it. faker.RandomizeScene(doc, flushScene=True)
Decorator Listing¶
-
mxutils.
TIMEIT
(enabled: bool, showDigits: int = 5, showArgs: bool = False, showResult: bool = False, useProfiler: bool = False, inCallProtection: bool = False) → Callable[[T], T]¶ Decorates a function call with a timing or profiling report printed to the console.
Meant for debugging and profiling purposes only. The decorator can be enabled or disabled by passing a boolean flag to the decorator. When enabled, the decorator will print a report to the console after the function call has finished.
Warning
This decorator will have an impact on the performance of the decorated function (timing = low impact, profiling = high impact). It is not recommended to use this decorator in production code.
- Parameters
enabled (bool) – Whether the decorator should be enabled. Setting this to False will result in the decorated function being passed through without any modifications, disabling all measurements and almost all overhead of the decorator. You should pass here some debug flag you defined, e.g., IS_DEBUG.
showDigits (int, optional) – The number of digits to round timing measurements to. Only applies when useProfiler is False. Defaults to 5.
showArgs (bool, optional) – Whether the arguments of the function call should be printed. Defaults to False.
showResult (bool, optional) – Whether the result of the function call should be printed. Defaults to False.
useProfiler (bool, optional) – Whether the function should be profiled instead of timed. Defaults to False.
inCallProtection (bool, optional) – If to mute inner calls of a recursive call chain of the decorated function. This feature is not thread-safe as it decorates the function or class instance object with a dictionary to keep track of the call state. Defaults to False.
- Example
import math import mxutils IS_DEBUG: bool = True # We define a recursive function and decorate it with the default timeit decorator. @mxutils.TIMEIT(IS_DEBUG) def factorial(n: int) -> int: return 1 if n == 0 else n * factorial(n - 1) # We call the function and see the time measurements. Because the function is recursive, we # see the a report for each call in the recursion chain. factorial(3) # -------------------------------------------------------------------------------- # TIMEIT: Ran 'factorial()' in 0.0 sec # -------------------------------------------------------------------------------- # TIMEIT: Ran 'factorial()' in 6e-05 sec # -------------------------------------------------------------------------------- # TIMEIT: Ran 'factorial()' in 0.00011 sec # -------------------------------------------------------------------------------- # TIMEIT: Ran 'factorial()' in 0.00013 sec # We redefine the function but this time enable in-call protection. We also enable seeing # the arguments and result of a function call. @mxutils.TIMEIT(IS_DEBUG, showArgs=True, showResult=True, inCallProtection=True) def factorial(n: int) -> int: return 1 if n == 0 else n * factorial(n - 1) # Now we call the function again. This time we only see the the timing for the outmost # call of the recursion chain. factorial(10) # -------------------------------------------------------------------------------- # TIMEIT: Ran 'factorial((10,), {}) -> 3628800' in 1e-05 sec # And finally a more complex example where we decorate class methods and use full profiling. class Processor: # The arguments we pass to the timeit decorator in this class. TIMEIT_KWARGS: dict[str, typing.Any] = { "enabled": IS_DEBUG, "showArgs": True, "showResult": True, "inCallProtection": True, "useProfiler": True } def __init__(self, n: int) -> None: self.recursive(n) self.iterative(n) @mxutils.TIMEIT(**TIMEIT_KWARGS) def recursive(self, n: int) -> int: return 1 if n == 0 else n * self.recursive(n - 1) @mxutils.TIMEIT(**TIMEIT_KWARGS) def iterative(self, n: int) -> int: x: int = n for i in range(int(n * 1000)): x += math.sqrt(x) x *= math.sin(i) return x p: Processor = Processor(50) # The output will look something like this. For the recursive call we mostly see the overhead # of the decorator. Profiling recursive functions in this manner that are not very expensive # (execute in the microsecond range) is not very useful. # -------------------------------------------------------------------------------- # TIMEIT: Ran 'recursive((<__main__.Processor object at 0x7fd18c0696a0>, 50), {}) -> # 30414093201713378043612608166064768844377641568960512000000000000' with 302 function # calls (203 primitive calls) in 0.000 seconds # # Ordered by: cumulative time # # ncalls tottime percall cumtime percall filename:lineno(function) # 51/1 0.000 0.000 0.000 0.000 scriptmanager:476(recursive) # 50/1 0.000 0.000 0.000 0.000 scriptmanager:401(wrapper) # 50 0.000 0.000 0.000 0.000 /Applications/Maxon Cinema 4D 2024.4.0/... # 50 0.000 0.000 0.000 0.000 {built-in method builtins.isinstance} # 50 0.000 0.000 0.000 0.000 {built-in method builtins.getattr} # 50 0.000 0.000 0.000 0.000 {method 'get' of 'dict' objects} # 1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects} # -------------------------------------------------------------------------------- # TIMEIT: Ran 'iterative((<__main__.Processor object at 0x7fd18c0696a0>, 50), {}) -> -0.0' with # 100002 function calls in 0.037 seconds # # Ordered by: cumulative time # # ncalls tottime percall cumtime percall filename:lineno(function) # 1 0.025 0.025 0.037 0.037 scriptmanager:480(iterative) # 50000 0.007 0.000 0.007 0.000 {built-in method math.sin} # 50000 0.005 0.000 0.005 0.000 {built-in method math.sqrt} # 1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
-
mxutils.
SET_STATUS
(message: str | None, doSpin: bool = False, clearOnReturn: bool = True) → Callable[[T], T]¶ Decorates a function call with output to the Cinema 4D status bar.
This decorator is meant to be used in functions or methods which take a long time to execute and should be indicated in the Cinema 4D status bar. The decorator will display a message in the status bar while the function or method is being executed.
Note
Use this decorator sparingly. Its overhead is low but status bar spam can confuse or annoy users.
- Parameters
message (str | None, optional) – The message to be displayed in the status bar. Will not be displayed when None. Defaults to None.
doSpin (bool, optional) – Whether the status bar should display the spin bar to signal an ongoing computation. Defaults to False.
clearOnReturn (bool, optional) – Whether the status bar should be cleared when the function or method returns. Defaults to True.
- Example
import c4d import mxutils # We define a function and decorate it with the SET_STATUS decorator. @mxutils.SET_STATUS("Processing data ...", doSpin=True) def process_data(data: list[int]) -> list[int]: return [x ** .5 for x in data] # We call the function and see the status bar message in Cinema 4D. process_data(list(range(10000)))