mxutils_decorators¶
Demonstrates mxutils helpers for diagnosing Cinema 4D scene data.
mxutils_decorators.py¶
"""Demonstrates mxutils helpers for diagnosing Cinema 4D scene data.
Run this example in the Cinema 4D Script Manager. It prints a container, parameter,
and scene graph tree for the active document and its selected object.
Topics:
* mxutils.TIMEIT
* mxutils.SET_STATUS
* mxutils.REPORT
"""
__author__ = "Ferdinand Hoppe"
__copyright__ = "Copyright (C) 2026 MAXON Computer GmbH"
__license__ = "Apache-2.0 License"
__version__ = "2027.0"
import math
import time
import c4d
import mxutils
doc: c4d.documents.BaseDocument # The active Cinema 4D document.
def main() -> None:
"""Runs the example for the active document.
"""
@mxutils.REPORT()
def foo() -> None:
print("foo called")
# mxutils.REPORT() will simply report if a function was called. When it is attached to a method,
# it will also report the object instance on which the method was called. This can help with
# debugging call chains without setting breakpoints to see call stacks (which often also contain
# a lot of unrelated information).
foo()
@mxutils.SET_STATUS("Computing ...")
def bar() -> None:
time.sleep(2)
print("bar done")
# SET_STATUS helps with providing visual feedback to the user while your code is running. It will
# set the status bar text to the provided message while the decorated function is running.
bar()
# And last but not least, there is the TIMEIT decorator, which can be used to measure the
# execution time of a function.
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, 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}
import importlib
if __name__ == "__main__":
c4d.ClearPythonConsole()
main()