Group Details Private

administrators

  • RE: Service Announcement: Regression in GeUserArea Handling in 2025.3.X Releases

    Hey,

    I now know how our API will look like in this regard in 2026.0.0. Its compile/runtime behaviour will be as for 2025.2.1 and before. And we will introduce a new flag NOHANDLEFOCUS which must be set when attaching the area when one explicitly wants to ignore focus events. So, you will not have to change your code when you were happy with your user area behaviour as it is has been.

    Cheers,
    Ferdinand

    posted in News & Information
  • RE: ExecutePasses performance keeps slowing down per-frame and per-run — possible cache issue?

    Hey @Aprecigout,

    helps me avoid saying more “silly things.”

    You cannot make an omelette without breaking eggs, so no worries. There are no silly questions.

    To give you even more context on why we lean so heavily on ExecutePasses: our artists make massive use of MoGraph ...

    Thank you for the details. This gives a bit more insight. But without a concrete scene, it is still very hard to evaluate for me where this comes from (if there is a bug somewhere). As hinted at in my last posting, the Python VM is a rather unlikely suspect for a cause. But the alternative would be that our scene evaluation is massively bugged which is even more unlikely. But it is good to know that C++ is an option for you. In general, I am still not convinced that your findings, that Cinema 4D irregularly slows down on scene execution, are correct. Are you sure that you unload the documents between runs? I.e., something like this shown in [1]? Because if you do not, you will of course occupy more and more memory with each document loaded.

    Right now, ExecutePasses is the only way I know to retrieve per-frame information for every clone.

    This is also a bit ambiguous, but this might be wrong. Executing the passes does not necessarily mean a document will build discrete data for each clone or everything in general. It will just build what it deems necessary. When a MoGraph cloner is in 'Multi-Instance' mode it will actually only build the first clone concretely, the rest of the clones is still being described non-discretely (i.e., sparely) via the MoData tag (so that it realizes the memory saving aspect this mode promises). You can read this thread, it might contain relevant information for you.

    When you want a super flattened document, you could invoke Save for Cineware from the app, or SaveDocument with the SAVEDOCUMENTFLAGS flag SAVECACHES from the API to save a document with exhaustively build caches. But once you load such document into any form of Cinema 4D (e.g., Cinema 4D, Commandline, c4dpy, etc. ) it will throw away all these caches and switch back to an optimized scene model. To faithfully read such c4d export document, you must use the Cineware AP. Which is C++ only and not always trivial in its details. But when you are comfortable with C++, and you need a truly discretely serialized document, this is the way to go. Just to be verbose: With the Cineware API you can ONLY read discrete data. Anything dynamic/computed is not available here. Because you can use the Cineware API without a Cinema 4D installation. So, executing the passes is for example not possible here. And not all aspects of a scene can be exported into this format. But MoGraph systems will be exported.

    Cheers,
    Ferdinand

    [1]

    """A simple example for how to unload documents and execute passes in a thread.
    
    This is pseudo code I wrote blindly and did not run.
    """
    
    
    import c4d
    import time
    
    doc: c4d.documents.BaseDocument # A preloaded document, could be None.
    
    class PassesThread (c4d.threading.C4DThread):
        """Executes the passes on a document in a thread.
    
        Using this inside a plain Script Manager makes no sense, since it is itself blocking. You need
        something asynchronous such as an async dialog for this to be useful.
        """
        def __init__(self, doc: c4d.documents.BaseDocument) -> None:
            self._doc = doc
            self._result: bool = False 
            self.Start()
    
        def Main(self) -> None:
            self._result = self._doc.ExecutePasses(
                self.Get(), True, True, True, c4d.BUILDFLAGS_NONE)
    
    def main() -> None:
        """Called by Cinema 4D to execute the script.
        """
        # The file paths of the documents to load and execute, could also be the same file over and over again.
        for fPath in (...):
            # Kill the current document to free up resources and load the new one.
            if isinstance(doc, c4d.documents.BaseDocument):
                c4d.documents.KillDocument(doc)
    
            doc: c4d.documents.BaseDocument = c4d.documents.LoadDocument(fPath, ...)
            frames: list[c4d.BaseTime] = [...]
            for f in frames:
                doc.SetTime(f)
    
                # Create a thread to execute the passes, which makes no sense here since we will wait for
                # the outcome anyway, but it shows the principle. But you can in any case always only run
                # of these threads at a time.
                thread: PassesThread = PassesThread(doc)
                while thread.IsRunning():
                    time.sleep(1.0) # Avoid blasting the thread with finish checks.
    
            # For the final frame, we should execute the passes again, so that simulations can settle.
            thread: PassesThread = PassesThread(doc)
            while thread.IsRunning():
                time. Sleep(1.0)
    
    posted in Cinema 4D SDK
  • RE: ExecutePasses performance keeps slowing down per-frame and per-run — possible cache issue?

    Hey @Aprecigout,

    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

    Please have a look at Support Procedures: How to Ask Questions, there are too many question here. Which often derails threads, but since you are new, let's try it like this.

    Is there a recommended way to flush or bypass the cache/memory buildup that seems to happen inside ExecutePasses, so execution time stays consistent?

    Caches cannot be flushed, as they are a mandatory part of the scene graph. A scene always has exactly one cache per scene element which requires a cache. Without it is not operational. And while the term is effectively correct, the scene graph cache is not what you usually picture when talking about caches. Caches are the discrete embodiment of parametric objects and deformers. You can think of them as an invisible part of the scene hierarchy. With the 'Active Object' plugin from the C++ SDK we can have a peek at what cache means. Here we unfold the first cube object clone of the cloner in the cache.

    846f7672-bdff-4745-9c6f-2c95a8cc8b80-image.png

    In the next release of Cinema 4D there will also be mxutils.GetSceneGraphString which has a similar purpose as the the plugin from the C++ SDK. For the scene shown above it will print what is shown in [1]. Everything below the [Cache] of Cloner is the cache of that scene element; a rather complex hidden hierarchy which itself contains caches which must be unpacked.

    This also hints at the non-linear nature of caches. When you have a scene with 100 frames, where frame 0 is literally the empty scene and on frame 100 you have thousands of high resolution parametric objects which have to be rebuilt for this frame, and on top of that multiple complex simulations (pyro, particles, liquids), then executing the passes for frame 0 will be very quick as there is literally nothing to do, while executing the passes for frame 100 could cost seconds or even minutes (when none of the simulations are cached).

    If such mechanisms exist, could someone outline the usual workflow or API calls to use—or point me to the relevant documentation?

    Without knowing what you want to do, that is impossible to answer. Your code there could be slightly incorrect, as for 'pre-rolling' you usually want to execute the last pass twice, so that simulations can settle. When you just want to step through a scene, what you are doing is okay. It also should not make a big difference if your execute the passes for a scene state once ot twice, as all scene elements should only rebuild its caches when necessary when asked to do so. So, when you for example excute the passes and it takes 60 seconds, and then do it right again, the second run should only take a fraction of the first execution, as most scene elments should see that they are not dirty anymore, and just return their already existing cache.

    But in general it is a bit odd that you execute the passes on all frames. It is very rare that you have to do that from Python. Maybe you could explain why you are doing that?

    Finally, could C4DThread help in this context, or am I barking up the wrong tree? My experiments based on the thread linked above haven’t produced conclusive results.

    The first argument of ExecutePasses is the thread to which the call shall be bound. You can use this to make the pass execution non-blocking for the main thread.

    This also hints at the broader answer. The pass execution is of course already heavily optimized and runs in as many threads as the machine can muster and things such as ObjectData::GetVirtualObjects which are the backbone of cache building are run massively in parallel. The only thing you can decide is if you want to make your call non-blocking for the main thread or not (where the GUI code runs).

    Not explicitly asked but sort of the elephant in the room: Executing the passes for all frames of a document varies drastically.

    Just like many modern DCCs, Cinema 4D has a pretty sophisticated backed. Cinema 4D has for example on top of the "caching" of the scene graph a memoization core, which records the results of previous computations and reuses them when the same data is requested again. There is naturally some variance in such complex systems, where tiny changes in the input conditions can lead to significant differences in the execution time.

    But what you show us there, that executing all passes of a scene takes twice or three times as long as the first time, is not normal. But I would at first be a bit doubtful that your findings are correct, as this would hint at a massive bug in the pass execution system. There could be an issue with the Python VM. I would recommend to unload the document in between the runs, to ensure that all possible memory is really freed.

    Executing the passes for a single frame is already a not cheap operation, executing the passes for all frames of a document can be extensively expensive, since scene initialization makes up a good chunk of the render time of a document. So, doing this more than once in a row, is not the most clever thing. When you want to do this on multiple documents in a row, you should of course unload documents you are done with, so that you can free the memory.

    Cheers,
    Ferdinand

    [1] Using print(mxutils.GetSceneGraphString(doc)) to visualize the content of a scene. Since we pass the whole document and not just the cloner, really everything gets unpacked here. Everything below the [Cache] child of 'Cloner' (BaseObject: Omgcloner) is the cache of the cloner object.

    '' (BaseDocument: Tbasedocument)
    ├── [Branch] 'Objects' (Obase)
    │   └── 'Cloner' (BaseObject: Omgcloner)
    │       ├── [Cache]
    │       │   └── 'Null' (BaseObject: Onull)
    │       │       ├── 'Cube 0' (BaseObject: Ocube)
    │       │       │   ├── [Cache]
    │       │       │   │   └── 'Cube 0' (PolygonObject: Opolygon)
    │       │       │   │       ├── [Deform Cache]
    │       │       │   │       │   └── 'Cube 0' (PolygonObject: Opolygon)
    │       │       │   │       │       └── [Branch] 'Tags' (Tbase)
    │       │       │   │       │           ├── 'Phong' (BaseTag: Tphong)
    │       │       │   │       │           ├── 'UVW' (UVWTag: Tuvw)
    │       │       │   │       │           ├── '' (PolygonTag: Tpolygon)
    │       │       │   │       │           └── '' (PointTag: Tpoint)
    │       │       │   │       └── [Branch] 'Tags' (Tbase)
    │       │       │   │           ├── 'Phong' (BaseTag: Tphong)
    │       │       │   │           ├── 'UVW' (UVWTag: Tuvw)
    │       │       │   │           ├── '' (PolygonTag: Tpolygon)
    │       │       │   │           └── '' (PointTag: Tpoint)
    │       │       │   ├── [Branch] 'Tags' (Tbase)
    │       │       │   │   ├── 'Motion Graphics Color Tag' (BaseTag: Tmgcolor)
    │       │       │   │   └── 'Phong' (BaseTag: Tphong)
    │       │       │   └── 'Bend' (BaseObject: Obend)
    │       │       ├── 'Sphere 1' (BaseObject: Osphere)
    │       │       │   ├── [Cache]
    │       │       │   │   └── 'Sphere 1' (PolygonObject: Opolygon)
    │       │       │   │       ├── [Deform Cache]
    │       │       │   │       │   └── 'Sphere 1' (PolygonObject: Opolygon)
    │       │       │   │       │       └── [Branch] 'Tags' (Tbase)
    │       │       │   │       │           ├── 'Phong' (BaseTag: Tphong)
    │       │       │   │       │           ├── 'UVW' (UVWTag: Tuvw)
    │       │       │   │       │           ├── '' (PolygonTag: Tpolygon)
    │       │       │   │       │           └── '' (PointTag: Tpoint)
    │       │       │   │       └── [Branch] 'Tags' (Tbase)
    │       │       │   │           ├── 'Phong' (BaseTag: Tphong)
    │       │       │   │           ├── 'UVW' (UVWTag: Tuvw)
    │       │       │   │           ├── '' (PolygonTag: Tpolygon)
    │       │       │   │           └── '' (PointTag: Tpoint)
    │       │       │   ├── [Branch] 'Tags' (Tbase)
    │       │       │   │   ├── 'Motion Graphics Color Tag' (BaseTag: Tmgcolor)
    │       │       │   │   └── 'Phong' (BaseTag: Tphong)
    │       │       │   └── 'Bend' (BaseObject: Obend)
    │       │       ├── 'Cube 2' (BaseObject: Ocube)
    │       │       │   ├── [Cache]
    │       │       │   │   └── 'Cube 2' (PolygonObject: Opolygon)
    │       │       │   │       ├── [Deform Cache]
    │       │       │   │       │   └── 'Cube 2' (PolygonObject: Opolygon)
    │       │       │   │       │       └── [Branch] 'Tags' (Tbase)
    │       │       │   │       │           ├── 'Phong' (BaseTag: Tphong)
    │       │       │   │       │           ├── 'UVW' (UVWTag: Tuvw)
    │       │       │   │       │           ├── '' (PolygonTag: Tpolygon)
    │       │       │   │       │           └── '' (PointTag: Tpoint)
    │       │       │   │       └── [Branch] 'Tags' (Tbase)
    │       │       │   │           ├── 'Phong' (BaseTag: Tphong)
    │       │       │   │           ├── 'UVW' (UVWTag: Tuvw)
    │       │       │   │           ├── '' (PolygonTag: Tpolygon)
    │       │       │   │           └── '' (PointTag: Tpoint)
    │       │       │   ├── [Branch] 'Tags' (Tbase)
    │       │       │   │   ├── 'Motion Graphics Color Tag' (BaseTag: Tmgcolor)
    │       │       │   │   └── 'Phong' (BaseTag: Tphong)
    │       │       │   └── 'Bend' (BaseObject: Obend)
    │       │       ├── 'Sphere 3' (BaseObject: Osphere)
    │       │       │   ├── [Cache]
    │       │       │   │   └── 'Sphere 3' (PolygonObject: Opolygon)
    │       │       │   │       ├── [Deform Cache]
    │       │       │   │       │   └── 'Sphere 3' (PolygonObject: Opolygon)
    │       │       │   │       │       └── [Branch] 'Tags' (Tbase)
    │       │       │   │       │           ├── 'Phong' (BaseTag: Tphong)
    │       │       │   │       │           ├── 'UVW' (UVWTag: Tuvw)
    │       │       │   │       │           ├── '' (PolygonTag: Tpolygon)
    │       │       │   │       │           └── '' (PointTag: Tpoint)
    │       │       │   │       └── [Branch] 'Tags' (Tbase)
    │       │       │   │           ├── 'Phong' (BaseTag: Tphong)
    │       │       │   │           ├── 'UVW' (UVWTag: Tuvw)
    │       │       │   │           ├── '' (PolygonTag: Tpolygon)
    │       │       │   │           └── '' (PointTag: Tpoint)
    │       │       │   ├── [Branch] 'Tags' (Tbase)
    │       │       │   │   ├── 'Motion Graphics Color Tag' (BaseTag: Tmgcolor)
    │       │       │   │   └── 'Phong' (BaseTag: Tphong)
    │       │       │   └── 'Bend' (BaseObject: Obend)
    │       │       └── 'Cube 4' (BaseObject: Ocube)
    │       │           ├── [Cache]
    │       │           │   └── 'Cube 4' (PolygonObject: Opolygon)
    │       │           │       ├── [Deform Cache]
    │       │           │       │   └── 'Cube 4' (PolygonObject: Opolygon)
    │       │           │       │       └── [Branch] 'Tags' (Tbase)
    │       │           │       │           ├── 'Phong' (BaseTag: Tphong)
    │       │           │       │           ├── 'UVW' (UVWTag: Tuvw)
    │       │           │       │           ├── '' (PolygonTag: Tpolygon)
    │       │           │       │           └── '' (PointTag: Tpoint)
    │       │           │       └── [Branch] 'Tags' (Tbase)
    │       │           │           ├── 'Phong' (BaseTag: Tphong)
    │       │           │           ├── 'UVW' (UVWTag: Tuvw)
    │       │           │           ├── '' (PolygonTag: Tpolygon)
    │       │           │           └── '' (PointTag: Tpoint)
    │       │           ├── [Branch] 'Tags' (Tbase)
    │       │           │   ├── 'Motion Graphics Color Tag' (BaseTag: Tmgcolor)
    │       │           │   └── 'Phong' (BaseTag: Tphong)
    │       │           └── 'Bend' (BaseObject: Obend)
    │       ├── [Branch] 'Tags' (Tbase)
    │       │   └── 'Info' (BaseTag: ID_MOTAGDATA)
    │       ├── 'Cube' (BaseObject: Ocube)
    │       │   ├── [Branch] 'Tags' (Tbase)
    │       │   │   └── 'Phong' (BaseTag: Tphong)
    │       │   └── 'Bend' (BaseObject: Obend)
    │       └── 'Sphere' (BaseObject: Osphere)
    │           ├── [Branch] 'Tags' (Tbase)
    │           │   └── 'Phong' (BaseTag: Tphong)
    │           └── 'Bend' (BaseObject: Obend)
    ├── [Branch] 'Render Settings' (Rbase)
    │   └── 'My Render Setting' (RenderData: Rbase)
    │       ├── [Branch] 'Post Effects' (VPbase)
    │       │   ├── 'Magic Bullet Looks' (BaseVideoPost: VPMagicBulletLooks)
    │       │   └── 'Redshift' (BaseVideoPost: VPrsrenderer)
    │       └── [Branch] 'Multi-Pass' (Zmultipass)
    │           └── 'Post Effects' (BaseList2D: Zmultipass)
    ├── [Branch] 'Scene Hooks' (SHplugin)
    │   ├── 'STHOOK' (BaseList2D: 1012061)
    │   ├── 'RSCameraObjectTargetDistancePicker' (BaseList2D: 31028063)
    │   ├── 'Python Embedded Change Monitor' (BaseList2D: 1058422)
    │   ├── 'SceneHook' (BaseList2D: 1028481)
    │   ├── 'CmSceneHook' (BaseList2D: 1026839)
    │   ├── 'CameraMorphDrawSceneHook' (BaseList2D: 1029281)
    │   ├── 'MotionCameraDrawSceneHook' (BaseList2D: 1029338)
    │   ├── 'USD Scene Hook' (BaseList2D: 1055307)
    │   ├── 'Substance Assets' (BaseList2D: 1032107)
    │   ├── 'Alembic Archive Hook' (BaseList2D: 1028458)
    │   ├── 'UpdateMerge Hook' (BaseList2D: 465001602)
    │   ├── 'ArchiExchangeCADHook' (BaseList2D: 200000216)
    │   ├── 'SLA wave scene hook' (BaseList2D: REG_EXP_PARSER)
    │   ├── 'Thinking Particles' (TP_MasterSystem: ID_THINKINGPARTICLES)
    │   ├── '' (BaseList2D: 1035577)
    │   ├── 'Bullet' (BaseList2D: 180000100)
    │   ├── 'XRefs' (BaseList2D: 1025807)
    │   ├── 'CAManagerHook' (BaseList2D: 1019636)
    │   │   └── [Branch] 'Weights Handler Head' (Tbaselist2d)
    │   │       └── 'Weights Handler' (BaseList2D: 1037891)
    │   ├── 'Volume Save Manager Hook' (BaseList2D: 1040459)
    │   ├── 'UV Display 3D SceneHook' (BaseList2D: 1054166)
    │   ├── 'uvhook' (BaseList2D: 1053309)
    │   ├── 'ScatterPlacementHook' (BaseList2D: 1058060)
    │   ├── 'Tool System Hook' (BaseList2D: ID_TOOL_SYSTEM_HOOK)
    │   │   └── [Branch] 'SBM' (431000215)
    │   │       └── 'Symmetry node' (BaseList2D: 431000215)
    │   │           └── [Branch] 'C4DCoreWrapper' (200001044)
    │   │               └── 'Symmetry node - net.maxon.symmetry.context.modeling' (BaseList2D: 300001078)
    │   ├── 'MoGraphSceneHook' (BaseList2D: 1019525)
    │   ├── 'gozScenehook' (BaseList2D: 1059748)
    │   ├── 'Simulation' (BaseList2D: ID_SIMULATIONSCENE_HOOK)
    │   │   └── [Branch] 'Simulation World' (Obase)
    │   │       └── 'Default Simulation Scene' (BaseObject: Osimulationscene)
    │   ├── 'PersistentHook' (BaseList2D: 180420202)
    │   ├── 'Scene Nodes' (BaseList2D: SCENENODES_IDS_SCENEHOOK_ID)
    │   ├── 'NE_SceneHook' (BaseList2D: 465002367)
    │   ├── 'Take Hook' (BaseList2D: 431000055)
    │   │   └── [Branch] 'Take System Branch' (TakeBase)
    │   │       └── 'Main' (BaseTake: TakeBase)
    │   │           └── [Branch] 'Override Folders' (431000073)
    │   │               └── 'Overrides' (BaseList2D: 431000073)
    │   │                   ├── 'Others' (BaseList2D: 431000073)
    │   │                   ├── 'Layers' (BaseList2D: 431000073)
    │   │                   ├── 'Materials' (BaseList2D: 431000073)
    │   │                   ├── 'Shaders' (BaseList2D: 431000073)
    │   │                   ├── 'Tags' (BaseList2D: 431000073)
    │   │                   └── 'Objects' (BaseList2D: 431000073)
    │   ├── 'CombineAc18_AutoCombine_SceneHook' (BaseList2D: 1032178)
    │   ├── 'PLKHUD' (BaseList2D: 1020132)
    │   │   └── [Branch] 'PSUNDOHEAD' (Obase)
    │   │       └── 'PKHOP' (BaseObject: 1020120)
    │   ├── 'RenderManager Hook' (BaseList2D: 465003509)
    │   ├── 'Sound Scrubbing Hook' (BaseList2D: 100004815)
    │   ├── 'To Do' (BaseList2D: 465001536)
    │   ├── 'Animation' (BaseList2D: 465001535)
    │   ├── 'BaseSettings Hook' (BaseList2D: ID_BS_HOOK)
    │   ├── '' (BaseList2D: 1060457)
    │   ├── 'SculptBrushModifierSceneHook' (BaseList2D: 1030499)
    │   ├── 'Sculpt Objects' (BaseList2D: 1024182)
    │   ├── 'HairHighlightHook' (BaseList2D: 1018870)
    │   ├── 'MeshObject Scene Hook' (BaseList2D: 1037041)
    │   ├── 'Lod Hook' (BaseList2D: 431000182)
    │   ├── 'Annotation Tag SceneHook' (BaseList2D: 1030679)
    │   ├── 'Sniper' (BaseList2D: 430000000)
    │   ├── 'Mesh Check Hook' (BaseList2D: 431000027)
    │   ├── 'Modeling Objects Hook' (BaseList2D: 431000032)
    │   │   └── [Branch] 'Modeling Objects Branch' (431000031)
    │   │       ├── 'Pattern Direction Manipulator' (BaseObject: Opatternmanipulator)
    │   │       ├── 'Plane Manipulator' (BaseObject: Oplanemanipulator)
    │   │       ├── 'Pivot Manipulator' (BaseObject: Opivotmanipulator)
    │   │       ├── 'Knife Line Manipulator' (BaseObject: 431000168)
    │   │       ├── 'Subdivision Manipulator' (BaseObject: 431000172)
    │   │       └── 'PolyPenObject' (BaseObject: 431000031)
    │   ├── 'Snap Scenehook' (BaseList2D: 440000111)
    │   │   ├── [Branch] 'WpSH' (440000111)
    │   │   │   └── 'WorkPlane' (BaseObject: Oworkplane)
    │   │   └── [Branch] 'MdSH' (Tbase)
    │   │       └── 'Modeling Settings' (BaseList2D: 440000140)
    │   ├── 'Doodle Hook' (BaseList2D: 1022212)
    │   ├── 'Stereoscopic' (BaseList2D: 450000226)
    │   ├── 'ViewportExtHookHUD' (BaseList2D: ID_VIEW_SCENEHOOKHUD)
    │   ├── 'ViewportExtHookhighlight' (BaseList2D: ID_VIEW_SCENEHOOKHIGHLIGHT)
    │   ├── 'MeasureSceneHook' (BaseList2D: ID_MEASURE_SCENEHOOK)
    │   ├── 'Redshift' (BaseList2D: 1036748)
    │   ├── 'GvHook' (BaseList2D: ID_SCENEHOOK_PLUGIN)
    │   ├── 'Material Scene Hook' (BaseList2D: 300001077)
    │   ├── 'TargetDistancePicker' (BaseList2D: 1028063)
    │   └── 'BodyPaint SceneHook' (BaseList2D: 1036428)
    └── [Branch] '' (Tbasedraw)
        └── '' (BaseList2D: 110306)
    
    posted in Cinema 4D SDK
  • RE: 'Octane Renderer' menu settings not appearing when I switch render data to octane with python.

    Hey @lionlion44,

    Thank you for reaching out to us. We cannot provide support on third party libraries (Octane). But, yes, in general you are on the right track. We have this C++ example, which I loosely translated to Python. The thing to do which you are missing, is to check if such VP already exists, as you otherwise can land in a world of hurt.

    For everything else, you would have to talk with the Octane devs (of which some are here on this forum), if there are any special further steps to be taken for Octane.

    Cheers,
    Ferdinand

    """Provides an example for generically setting a render engine in Cinema 4D.
    
    Note that there is no guarantee that every render engine has a video post node, and when it has one,
    that it uses the same ID as the render engine. But it is highly conventional to implement a render
    engine like this.
    
    Derived from the C++ Example "Set Render Engine to Redshift": 
    
        https://developers.maxon.net/docs/cpp/2023_2/page_manual_redshift_rendrer.html
    
    """
    
    import c4d
    import mxutils
    
    doc: c4d.documents.BaseDocument # The active Cinema 4D document.
    
    def SetRenderEngine(doc: c4d.documents.BaseDocument, newEngineId: int, createsVideoPostNode: bool) -> bool:
        """Sets the render engine of the given document to the specified ID.
        """
        # Make sure we are on the main thread, as we plan to modify the document and ensure that our
        # inputs are what we think they are.
        if not c4d.threading.GeIsMainThread():
            raise RuntimeError("SetRenderEngine must be called from the main thread.")
    
        mxutils.CheckType(doc, c4d.documents.BaseDocument)
        mxutils.CheckType(newEngineId, int)
        mxutils.CheckType(createsVideoPostNode, bool)
    
        # Get the currently active render engine ID and get out if it matches the new one.
        renderData: c4d.documents.RenderData = doc.GetActiveRenderData()
        currentEngineId: int = renderData[c4d.RDATA_RENDERENGINE]
        if currentEngineId == newEngineId:
            print(f"Render engine {newEngineId} is already set, no changes made.")
            return True
        
        # Try to find a video post with the render engine ID. There is no absolute guarantee that every
        # render engine either has a video post node or that is gives it the same ID as the render 
        # engine (but it is strongly conventional).
        if createsVideoPostNode:
            # Try to find an already existing video post node with the render engine ID.
            node: c4d.documents.BaseVideoPost | None = renderData.GetFirstVideoPost()
            while node:
                if node.GetType() == newEngineId:
                    break
                node = node.GetNext()
    
            # There is no video post for the render engine, so we try to a new create one.
            if not node:
                try:
                    node: c4d.documents.BaseVideoPost = c4d.documents.BaseVideoPost(newEngineId)
                    renderData.InsertVideoPost(node)
                except Exception as e:
                    raise RuntimeError(f"Failed to create video post node for render engine {newEngineId} ({e}).")
        
        # Finally, we set the render engine ID in the render data.
        renderData[c4d.RDATA_RENDERENGINE] = newEngineId
        return True
    
    def main() -> None:
        """Called by Cinema 4D to run the script.
        """
        # Setting the standard render engine, here we do not have to create a video post node, since
        # the standard renderer is one of the rare cases that does not have a dedicated video post.
        SetRenderEngine(doc, newEngineId=c4d.RDATA_RENDERENGINE_STANDARD, createsVideoPostNode=False)
    
        # Set Redshift as the render engine, which does have a video post node.
        SetRenderEngine(doc, newEngineId=c4d.VPrsrenderer, createsVideoPostNode=True)
    
        # Push an update event.
        c4d.EventAdd()
    
    if __name__ == "__main__":
        main()
    
    posted in Cinema 4D SDK
  • RE: Cinema4D SDK C++ Implementation of a Plugin Module on Windows Issues

    Hey @shir,

    Thank you for reaching out to us. A Program Database (PDB) is a debug information format from Microsoft. It is comparable to the DWARF debug information format often used under Linux and macOS. However, unlike DWARF under Linux, where debug information is directly compiled into the binary, Microsoft chooses to store debug information in separate files, the pdb files.

    When you attach a debugger to a binary without any debug information, it will by default only see the machine code of the binary. So when you have an issue and the debugger puts out a stack trace, it will only show you the offsets in a library, e.g., something like this:

    #1 0x0000000000767576 in myBinary.dll
    #2 0x0000000000767df4 in otherBinary.dll
    #3 0x0000000000773aca in myBinary.dll
    #4 0x00000000004b893e in myBinary.dll
    

    You can see this happen in the call stack window in your screenshot. VS only provides information in the format someBinary.ext!someAddress(), e.g., c4d_base.xdl64!00007ffb200acfb7(), as it has no further information. With bin!address() VS means a function at that address is being called. In my opinion, VS has one of the most cryptic stack trace formats out there and can be a bit confusing for beginners.

    To see meaningful output, you need the debug information for that binary, which among other things contains the mapping of addresses to source code. If you have the pdb file for the binary, you can load it into your debugger, and it will then show you something like this instead:

    #1 0x0000000000767576 in MyClass::MyMethod() at myClass.cpp:42
    #2 0x0000000000767df4 in OtherClass::OtherMethod() at otherClass.cpp:15
    #3 0x0000000000773aca in MyClass::AnotherMethod() at myClass.cpp:78
    #4 0x00000000004b893e in main() at main.cpp:10
    

    When you compile the Cinema 4D SDK and your source code, it will automatically generate the pdb files for these binaries for you, so that you can debug them in a meaningful manner. But what we see here is Visual Studio asking you for the pdb for c4d_base.xdl64, one of the core library binaries located in the corelibs folder of the Cinema 4D application you are debugging with. You did not compile that binary, so you do not have the pdb file for it. And we do not ship our binaries with debug information, as that would not only be a very large download, but also would expose our source code to the public.

    You are hitting a debug stop there (VS tells you that in the info box by stating this is a __debugbreak). This is the less critical case of a debug event, which is covered by the very tutorial you are following (the other one being a critical stop). You can simply hit continue in your debugger and ignore this. The event seems to be raised from Redshift, judging by the stack trace we can see in the screenshot you provided. There is probably some minor hardware issue or so, and Redshift is trying to handle it gracefully by raising this debug event.

    It is, however, not normal when this happens permanently and usually it hints at a corrupted installation of Cinema 4D or a hardware issue when you are always greeted by debug events on startup (or even when just running and interacting with Cinema 4D). Sometimes debug stops can happen as a one-time thing when you are debugging for the first time against some Cinema 4D instance (and it has not yet built all its prefs, caches, and other things Cinema 4D builds in the background). When this persists and you are annoyed by having to press continue, I would recommend trying to either remove Redshift from your Cinema 4D installation or reinstall Cinema 4D altogether.

    You could also check inside of Cinema 4D if you can see any errors in the 'Redshift Feedback Display' window. For you as a third party, it is however not possible to find out what that issue in c4d_base.xdl64 at the offset 7ffb200acfb7 is.

    Cheers,
    Ferdinand

    PS: There is also g_enableDebugBreak=true|false which you can pass to your Cinema 4D instance as a commandline argument. With that you can permanently mute debug stops. But that is more of an expert feature and you probably do not want to enable that as a beginner.

    posted in Cinema 4D SDK
  • RE: How do I create a Plugin Identifier?

    Hey @shir,

    good to hear that you solved the issue. Maybe NodeBB has an issue with the specific (top level) domain your mail handle was under? I just checked the logs and this is the event for the second registration mail that has been sent out (I edited your mail handle for privacy reasons). I.e., this is the one I manually invoked. There is another event for your actual registration. As far as NodeBB is concerned, it seems to be convinced that it successfully sent these mails.

    {
        "confirm_code": "dbcc0d6c-8646-4191-9975-badc1c7035f2",
        "email": "[email protected]",
        "subject": "Welcome to PluginCafé",
        "template": "welcome",
        "timestamp": 1751883962965
    }
    

    NodeBB can be a bit buggy from time to time but that it fails to send a mail and then creates an event for successfully sending it, would be a bit odd. I will have an eye on this.

    Cheers,
    Ferdinand

    posted in Cinema 4D SDK
  • RE: Getting an effective value of an enum from a GraphNode

    Hey @ECHekman,

    I sense there is some frustration, but I am not sure telling us how bad our API is will get us anywhere. Yes, the Nodes API ist not trivial, but you are only on the using part (which is not that hard to understand) not the implementation part (which is the trickly one). There are multiple render engine vendors who took that hurdle. I already answered your questions, and as always you will not see source code from us, unless you give us executable code in the first place upon which we can build, or we deem a subject new.

    I often bend these rules a bit where it makes sense to meet our customers and third parties halfway. But you cannot just throw a snippet at us and then expect us to invent everything around it and then fix that for you. Executable code makes a difference as lined out in our support procedures. My hunch would be that your getConnectedNode does not work because you do not check if your nodes are valid.

    You can get the value of a port with GetPortValue or GetEffectivePortValue. What you are doing with GetValue and EffectivePortValue is the old way but will still work.

    // Redshift expresses a lot of its enums as strings and not as ints (did not check if that is here the case).
    const String value = myPort.GetEffectivePortValue<String>().GetOrDefault() iferr_return;
    

    And as lined out before, what your function is trying to do, can likely be done via GraphModelHelper too, e.g., with GraphModelHelper::GetDirectPredecessors. An alternative and more manual approach would be using GraphNode.GetInnerNodes and GraphNode.GetConnections.

    And as always, I am not really looking for a discussion about what you or I would consider a good API. I am telling you that you will be in a world of hurt when you terminate your errors everywhere as you did in your code. Your code will then just silently fail without you knowing why. So, I gave you an example on how to use our error handling.

    Cheers,
    Ferdinand

    posted in Cinema 4D SDK
  • RE: Getting an effective value of an enum from a GraphNode

    Hey @Dunhou ,

    That is a good question, although slightly off-topic. What I used there is called trailing return type. It was introduced in C++11, specifically in the context of lambda expressions. The syntax allows you to specify the return type after the function parameters, which can be useful in certain situations, especially when dealing with complex types or when the return type depends on template parameters. It is functionally identical to the leading style. Python is deeply related to C and C++, and many of its conventions, concepts, and features are rooted in C/C++ programming practices.

    I just went here instinctively for this notation, as it is somewhat common to use it when you talk about a function in terms of its signature, rather than its concrete implementation. It is not a requirement, and you can write the function both ways, we in fact - except for lambda expressions - do not use this notation in our codebase.

    
    // This is the same as
    Sum1(const int a, const int b) -> int  { return a + b; }
    
    // as this (except for the name so that they can coexist).
    int Sum2(const int a, const int b) { return a + b; }
    
    // But we need the trailing notation for lambdas, of which modern Maxon API Cinema 4D makes heavy use.
    void Foo() {
        auto add = [](const int a, const int b) -> int { return a + b; };
        cout << add(1, 2) << std::endl;
    }
    

    So, to recap, we recommend to use Result<T> as the return type for everything that comes into contact with Maxon API error handling, so that you can propagate errors. We do not make any strong recommendations regarding return type conventions. We slightly recommend the traditional leading return type notation, but it is not a requirement.

    Cheers
    Ferdinand

    posted in Cinema 4D SDK
  • RE: Getting an effective value of an enum from a GraphNode

    Hey @ECHekman,

    The value of a port should be accessed with GraphNodeFunctions.GetPortValue or GraphNodeFunctions::GetEffectivePortValue. Usually, the former is fine, and only for some specialty cases where the actual port data does not reflect what the user sees, and you want to access the user facing value, you have to use the latter.

    What you are doing there, is the old access via GetValue which is primarily meant for attribute access these days. But it should still work. As always for non-executable code examples, it is hard to judge what is going wrong there for you. But note that checks like these are not doing what you probably think they are doing:

    ifnoerr(auto & typeNode = bumpNode.GetInputs().FindChild(maxon::Id("...")))
    {
      ...
    }
    

    I personally would use auto only sparingly and especially with the added line-break and the unfortunate placement of the reference operator had to look twice what you are doing here. But that is just personal taste, even if we wrote it like this in a more Maxonic way, it would not do what you probably think it does.

    void SomeFunction()
    {
      iferr_scope_handler
      {
        // DiagnosticOutput("@ failed with error: @", MAXON_FUNCTIONNAME, err);
        return;
      };
    
      const maxon::GraphNode& someInput = bumpNode.GetInputs().FindChild(maxon::Id("...")) iferr_return;
      if (!someInput)
        return maxon::UnexpectedError(MAXON_SOURCE_LOCATION, "Input node not found"_s);
    }
    

    The return value of FindChild will always be a maxon::GraphNode, unless an internal error occurs. A non-valid node path will not raise an error but return an empty and with that invalid node.

    if (someInput.IsValid())
    {
      // Do something with someInput
    }
    else
    {
      // Handle the case where the input node is not found
    }
    

    Cheers,
    Ferdinand

    edit: It is good to see that you are moving towards the Maxon API. But when I see things like this:

    maxon::GraphNode bumpNode = getConnectedNode(bumpRes, maxon::NODE_KIND::NODE);
    

    I am not so sure you are on the right track. When you write functions within the Maxon API, you should use our error handling. When you terminate the error handling within your functions, you will probably not have a good time. I.e., the signature should be this

    getConnectedNode(const GraphNode& input, const NODE_KIND kind) -> Result<GraphNode>
    

    and not -> GraphNode& or -> GraphNode. Note that there is also GraphModelHelperInterface which likely already implements what you are implementing there. See the GraphModelInterface Manual for an overview.

    posted in Cinema 4D SDK
  • RE: How do I create a Plugin Identifier?

    I see your edited/deleted posting, did you find the mail? When push comes to shove, I can also just give you a bunch of plugin IDs.

    posted in Cinema 4D SDK