Global Moderators

Forum wide moderators

Private

Posts

  • RE: How to access animation tracks for a GraphNode in a capsule

    Hello @kng_ito,

    thank you for reaching out to us. The answer to your question is BaseList2D and NimbusBaseInterface, as they both in tandem realize the relationship between Cinema and Maxon API scene elements. What node materials offer is just a convenience interface which is also offered in a more generic way via the mentioned classes.

    A BaseList2D is the base type for a generic Cinema API scene element that holds a data container. It also offers methods to retrieve one or many NimbusBaseInterface references for that element, each associating this element of a Cinema API scene with a Maxon API nodes graph. A node material will for example have one NimbusBaseInterface reference for each material node space (i.e., render engine and with that node graph) it supports.

    I actually thought there were already forum posts or even a code example about this subject, but I could not find any. So, I created a small code example addressing exactly this topic, as this is probably something people want to do commonly. It will be part of an upcoming Python SDK, but you can find a draft version below. The code is not that complicated, but the subject/theory around it is not entirely trivial. So, it is one of these code examples which is more words than code. I hope this helps you to understand the relationship between Cinema API scene elements and Maxon API nodes graphs better.

    Cheers,
    Ferdinand

    Result

    aecfa33c-654a-4a1d-884a-b353ace9e45c-image.png

    '' (BaseDocument: Tbasedocument)
    ├── [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)
    │   ├── '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)
    │   ├── 'Paint Brush' (BaseList2D: 1031368)
    │   ├── '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)
    │   ├── 'Octane X' (BaseList2D: 1030798)
    │   ├── 'Simulation' (BaseList2D: ID_SIMULATIONSCENE_HOOK)
    │   │   └── [Branch] 'Simulation World' (Obase)
    │   │       └── 'Default Simulation Scene' (BaseObject: Osimulationscene)
    │   ├── 'PersistentHook' (BaseList2D: 180420202)
    │   ├── 'Scene Nodes' (BaseList2D: SCENENODES_IDS_SCENEHOOK_ID)
    │   │   └── [Branch] 'Nodes' (300001078)
    │   │       ├── 'Group' (BaseList2D: 300001078)
    │   │       ├── 'Group' (BaseList2D: 300001078)
    │   │       ├── 'Group' (BaseList2D: 300001078)
    │   │       ├── 'Geometry Op' (BaseList2D: 300001078)
    │   │       ├── 'Scene Root' (BaseList2D: 300001078)
    │   │       └── 'Cube' (BaseList2D: 300001078)
    │   │           └── [Branch] 'Tracks' (CTbase)
    │   │               ├── 'Size . X' (CTrack: CTbase)
    │   │               │   └── [Branch] 'Sequences' (CSbase)
    │   │               │       └── '' (CCurve: CSbase)
    │   │               ├── 'Size . Y' (CTrack: CTbase)
    │   │               │   └── [Branch] 'Sequences' (CSbase)
    │   │               │       └── '' (CCurve: CSbase)
    │   │               └── 'Size . Z' (CTrack: CTbase)
    │   │                   └── [Branch] 'Sequences' (CSbase)
    │   │                       └── '' (CCurve: CSbase)
    │   ├── '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)
    
    
    --- Maxon API Scene Nodes Data ---
    
    Maxon API node: net.maxon.neutron.scene.root -> Cinema API surrogate node: <c4d.BaseList2D object called Scene Root with ID 300001078 at 22186209472>
    	Input port: net.maxon.neutron.scene.root/_0<in -> DescID: ((1768816640, 5, 0), (1, 133, 0))
    	Input port: net.maxon.neutron.scene.root/_0<in/flags -> DescID: ((1768828774, 5, 0), (1818322803, 12, 0))
    	Input port: net.maxon.neutron.scene.root/_0<filter -> DescID: ((1718185076, 5, 0), (1701969920, 5, 0), (1, 400006001, 0))
    	Input port: net.maxon.neutron.scene.root/_0/rot<in -> DescID: ((1768816640, 5, 0), (1, 133, 0))
    	Input port: net.maxon.neutron.scene.root/_0/rot<datatype -> DescID: ((1684108385, 5, 0), (1954115685, 5, 0), (1, 15, 0))
    	Input port: net.maxon.neutron.scene.root/net.maxon.neutron.corenode.multransform_0<datatype -> DescID: ((1684108385, 5, 0), (1954115685, 5, 0), (1, 15, 0))
    	Input port: net.maxon.neutron.scene.root/net.maxon.neutron.corenode.multransform_0<in2 -> DescID: ((1768829440, 5, 0), (1, 133, 0))
    	Input port: net.maxon.neutron.scene.root/net.maxon.neutron.corenode.multransform_0<in1 -> DescID: ((1768829184, 5, 0), (1, 133, 0))
    	Input port: net.maxon.neutron.scene.root<net.maxon.neutron.op.objectbase.children -> DescID: ((1852142638, 5, 0), (1835104367, 5, 0), (1848536677, 5, 0), (1970565743, 5, 0), (1848536944, 5, 0), (779051626, 5, 0), (1701016674, 5, 0), (1634952494, 5, 0), (1667787116, 5, 0), (1685218670, 5, 0), (593719149, 5, 0), (1835101796, 5, 0), (593585252, 5, 0), (1986097769, 5, 0), (1633970531, 5, 0), (1886351988, 8, 0))
    	Input port: net.maxon.neutron.scene.root<net.maxon.neutron.op.objectbase.children/_0 -> DescID: ((1852142638, 5, 0), (1835104367, 5, 0), (1848536677, 5, 0), (1970565743, 5, 0), (1848536944, 5, 0), (779051626, 5, 0), (1701016674, 5, 0), (1634952494, 5, 0), (1667787116, 5, 0), (1685218670, 5, 0), (774975086, 5, 0), (1702112877, 5, 0), (1635282798, 5, 0), (778986869, 5, 0), (1953656686, 5, 0), (779055150, 5, 0), (1868720741, 5, 0), (1668571745, 5, 0), (1936010851, 5, 0), (1751739492, 5, 0), (1919249920, 5, 0), (1, 133, 0))
    Maxon API node: net.maxon.neutron.scene.root/_0 -> Cinema API surrogate node: <c4d.BaseList2D object called Group with ID 300001078 at 22186202432>
    	Input port: net.maxon.neutron.scene.root/_0<in -> DescID: ((1768816640, 5, 0), (1, 133, 0))
    	Input port: net.maxon.neutron.scene.root/_0<in/flags -> DescID: ((1768828774, 5, 0), (1818322803, 12, 0))
    	Input port: net.maxon.neutron.scene.root/_0<filter -> DescID: ((1718185076, 5, 0), (1701969920, 5, 0), (1, 400006001, 0))
    	Input port: net.maxon.neutron.scene.root/_0/rot<in -> DescID: ((1768816640, 5, 0), (1, 133, 0))
    	Input port: net.maxon.neutron.scene.root/_0/rot<datatype -> DescID: ((1684108385, 5, 0), (1954115685, 5, 0), (1, 15, 0))
    Maxon API node: net.maxon.neutron.scene.root/_0/rot -> Cinema API surrogate node: <c4d.BaseList2D object called Group with ID 300001078 at 22186170048>
    	Input port: net.maxon.neutron.scene.root/_0/rot<in -> DescID: ((1768816640, 5, 0), (1, 133, 0))
    	Input port: net.maxon.neutron.scene.root/_0/rot<datatype -> DescID: ((1684108385, 5, 0), (1954115685, 5, 0), (1, 15, 0))
    Maxon API node: net.maxon.neutron.scene.root/concat -> Cinema API surrogate node: None
    Maxon API node: net.maxon.neutron.scene.root/net.maxon.neutron.corenode.multransform_0 -> Cinema API surrogate node: <c4d.BaseList2D object called Group with ID 300001078 at 22186170048>
    	Input port: net.maxon.neutron.scene.root/net.maxon.neutron.corenode.multransform_0<datatype -> DescID: ((1684108385, 5, 0), (1954115685, 5, 0), (1, 15, 0))
    	Input port: net.maxon.neutron.scene.root/net.maxon.neutron.corenode.multransform_0<in2 -> DescID: ((1768829440, 5, 0), (1, 133, 0))
    	Input port: net.maxon.neutron.scene.root/net.maxon.neutron.corenode.multransform_0<in1 -> DescID: ((1768829184, 5, 0), (1, 133, 0))
    Maxon API node: geometry@WCuZ0OX$L9brZcIW8Vdmn6 -> Cinema API surrogate node: <c4d.BaseList2D object called Geometry Op with ID 300001078 at 22186209472>
    	Input port: geometry@WCuZ0OX$L9brZcIW8Vdmn6<geometry -> DescID: ((1734700909, 5, 0), (1702130297, 5, 0), (1, 133, 0))
    	Input port: geometry@WCuZ0OX$L9brZcIW8Vdmn6<net.maxon.node.bypassable.bypass -> DescID: ((1852142638, 5, 0), (1835104367, 5, 0), (1848536687, 5, 0), (1684352610, 5, 0), (2037408115, 5, 0), (1935762028, 5, 0), (1697538681, 5, 0), (1885434739, 5, 0), (1, 400006001, 0))
    Maxon API node: cube@MJo3tkcbJ1Zgd2oqJzGis5 -> Cinema API surrogate node: <c4d.BaseList2D object called Cube with ID 300001078 at 22186170048>
    	Input port: cube@MJo3tkcbJ1Zgd2oqJzGis5<net.maxon.command.modeling.primitive.cube.subz -> DescID: ((1852142638, 5, 0), (1835104367, 5, 0), (1848533871, 5, 0), (1835884910, 5, 0), (1680764271, 5, 0), (1684368489, 5, 0), (1852255856, 5, 0), (1919511913, 5, 0), (1953068645, 5, 0), (778270050, 5, 0), (1697543029, 5, 0), (1652162560, 5, 0), (1, 15, 0))
    	Input port: cube@MJo3tkcbJ1Zgd2oqJzGis5<net.maxon.command.modeling.primitive.cube.suby -> DescID: ((1852142638, 5, 0), (1835104367, 5, 0), (1848533871, 5, 0), (1835884910, 5, 0), (1680764271, 5, 0), (1684368489, 5, 0), (1852255856, 5, 0), (1919511913, 5, 0), (1953068645, 5, 0), (778270050, 5, 0), (1697543029, 5, 0), (1652097024, 5, 0), (1, 15, 0))
    	Input port: cube@MJo3tkcbJ1Zgd2oqJzGis5<net.maxon.command.modeling.primitive.cube.subx -> DescID: ((1852142638, 5, 0), (1835104367, 5, 0), (1848533871, 5, 0), (1835884910, 5, 0), (1680764271, 5, 0), (1684368489, 5, 0), (1852255856, 5, 0), (1919511913, 5, 0), (1953068645, 5, 0), (778270050, 5, 0), (1697543029, 5, 0), (1652031488, 5, 0), (1, 15, 0))
    	Input port: cube@MJo3tkcbJ1Zgd2oqJzGis5<net.maxon.command.modeling.primitive.fillets.segment -> DescID: ((1852142638, 5, 0), (1835104367, 5, 0), (1848533871, 5, 0), (1835884910, 5, 0), (1680764271, 5, 0), (1684368489, 5, 0), (1852255856, 5, 0), (1919511913, 5, 0), (1953068645, 5, 0), (778463596, 5, 0), (1818588275, 5, 0), (779314535, 5, 0), (1835363956, 5, 0), (1, 15, 0))
    	Input port: cube@MJo3tkcbJ1Zgd2oqJzGis5<net.maxon.command.modeling.primitive.fillets.radius -> DescID: ((1852142638, 5, 0), (1835104367, 5, 0), (1848533871, 5, 0), (1835884910, 5, 0), (1680764271, 5, 0), (1684368489, 5, 0), (1852255856, 5, 0), (1919511913, 5, 0), (1953068645, 5, 0), (778463596, 5, 0), (1818588275, 5, 0), (779247972, 5, 0), (1769304832, 5, 0), (1, 19, 0))
    	Input port: cube@MJo3tkcbJ1Zgd2oqJzGis5<net.maxon.command.modeling.primitive.fillets.enable -> DescID: ((1852142638, 5, 0), (1835104367, 5, 0), (1848533871, 5, 0), (1835884910, 5, 0), (1680764271, 5, 0), (1684368489, 5, 0), (1852255856, 5, 0), (1919511913, 5, 0), (1953068645, 5, 0), (778463596, 5, 0), (1818588275, 5, 0), (778399329, 5, 0), (1651270912, 5, 0), (1, 400006001, 0))
    	Input port: cube@MJo3tkcbJ1Zgd2oqJzGis5<net.maxon.command.modeling.primitive.cube.separate -> DescID: ((1852142638, 5, 0), (1835104367, 5, 0), (1848533871, 5, 0), (1835884910, 5, 0), (1680764271, 5, 0), (1684368489, 5, 0), (1852255856, 5, 0), (1919511913, 5, 0), (1953068645, 5, 0), (778270050, 5, 0), (1697543013, 5, 0), (1885434465, 5, 0), (1952776192, 5, 0), (1, 400006001, 0))
    	Input port: cube@MJo3tkcbJ1Zgd2oqJzGis5<net.maxon.command.modeling.primitive.size -> DescID: ((1852142638, 5, 0), (1835104367, 5, 0), (1848533871, 5, 0), (1835884910, 5, 0), (1680764271, 5, 0), (1684368489, 5, 0), (1852255856, 5, 0), (1919511913, 5, 0), (1953068645, 5, 0), (779315578, 5, 0), (1694498816, 5, 0), (1, 23, 0))
    	Input port: cube@MJo3tkcbJ1Zgd2oqJzGis5<net.maxon.command.modeling.primitive.size/*access*zin -> DescID: ((1852142638, 5, 0), (1835104367, 5, 0), (1848533871, 5, 0), (1835884910, 5, 0), (1680764271, 5, 0), (1684368489, 5, 0), (1852255856, 5, 0), (1919511913, 5, 0), (1953068645, 5, 0), (779315578, 5, 0), (1697589857, 5, 0), (1667458419, 5, 0), (1932163689, 5, 0), (1845493760, 5, 0), (1, 19, 0))
    	Input port: cube@MJo3tkcbJ1Zgd2oqJzGis5<net.maxon.command.modeling.primitive.size/*access*xin -> DescID: ((1852142638, 5, 0), (1835104367, 5, 0), (1848533871, 5, 0), (1835884910, 5, 0), (1680764271, 5, 0), (1684368489, 5, 0), (1852255856, 5, 0), (1919511913, 5, 0), (1953068645, 5, 0), (779315578, 5, 0), (1697589857, 5, 0), (1667458419, 5, 0), (1932163177, 5, 0), (1845493760, 5, 0), (1, 19, 0))
    	Input port: cube@MJo3tkcbJ1Zgd2oqJzGis5<net.maxon.command.modeling.primitive.size/*access*yin -> DescID: ((1852142638, 5, 0), (1835104367, 5, 0), (1848533871, 5, 0), (1835884910, 5, 0), (1680764271, 5, 0), (1684368489, 5, 0), (1852255856, 5, 0), (1919511913, 5, 0), (1953068645, 5, 0), (779315578, 5, 0), (1697589857, 5, 0), (1667458419, 5, 0), (1932163433, 5, 0), (1845493760, 5, 0), (1, 19, 0))
    Maxon API node: cube@MJo3tkcbJ1Zgd2oqJzGis5/parambuilder -> Cinema API surrogate node: None
    Maxon API node: cube@MJo3tkcbJ1Zgd2oqJzGis5/defaultselections -> Cinema API surrogate node: None
    Maxon API node: cube@MJo3tkcbJ1Zgd2oqJzGis5/generategeometry -> Cinema API surrogate node: None
    Maxon API node: context_externaltimeinput -> Cinema API surrogate node: <c4d.BaseList2D object called Group with ID 300001078 at 22186170048>
    	Input port: context_externaltimeinput<searchpaths -> DescID: ((1936023922, 5, 0), (1667788897, 5, 0), (1953002240, 12, 0))
    	Input port: context_externaltimeinput<fps -> DescID: ((1718645504, 5, 0), (1, 19, 0))
    	Input port: context_externaltimeinput<time -> DescID: ((1953066341, 5, 0), (1, 19, 0))
    	Input port: context_externaltimeinput<nimbus -> DescID: ((1852403042, 5, 0), (1970470912, 12, 0))
    	Input port: context_externaltimeinput<ocioconfig -> DescID: ((1868786031, 5, 0), (1668247142, 5, 0), (1768357888, 12, 0))
    	Input port: context_externaltimeinput<renderspace -> DescID: ((1919250020, 5, 0), (1701999472, 5, 0), (1633903872, 12, 0))
    Maxon API node: context_notime -> Cinema API surrogate node: <c4d.BaseList2D object called Group with ID 300001078 at 16252169408>
    	Input port: context_notime<searchpaths -> DescID: ((1936023922, 5, 0), (1667788897, 5, 0), (1953002240, 12, 0))
    	Input port: context_notime<fps -> DescID: ((1718645504, 5, 0), (1, 19, 0))
    	Input port: context_notime<time -> DescID: ((1953066341, 5, 0), (1, 19, 0))
    	Input port: context_notime<nimbus -> DescID: ((1852403042, 5, 0), (1970470912, 12, 0))
    	Input port: context_notime<ocioconfig -> DescID: ((1868786031, 5, 0), (1668247142, 5, 0), (1768357888, 12, 0))
    	Input port: context_notime<renderspace -> DescID: ((1919250020, 5, 0), (1701999472, 5, 0), (1633903872, 12, 0))
    Maxon API node: builder -> Cinema API surrogate node: <c4d.BaseList2D object called Group with ID 300001078 at 16252169792>
    	Input port: builder<in -> DescID: ((1768816640, 5, 0), (1, 133, 0))
    	Input port: builder<in/flags -> DescID: ((1768828774, 5, 0), (1818322803, 12, 0))
    	Input port: builder<filter -> DescID: ((1718185076, 5, 0), (1701969920, 5, 0), (1, 133, 0))
    	Input port: builder/rot<in -> DescID: ((1768816640, 5, 0), (1, 133, 0))
    	Input port: builder/rot<datatype -> DescID: ((1684108385, 5, 0), (1954115685, 5, 0), (1, 15, 0))
    Maxon API node: builder/rot -> Cinema API surrogate node: <c4d.BaseList2D object called Group with ID 300001078 at 22170201152>
    	Input port: builder/rot<in -> DescID: ((1768816640, 5, 0), (1, 133, 0))
    	Input port: builder/rot<datatype -> DescID: ((1684108385, 5, 0), (1954115685, 5, 0), (1, 15, 0))
    

    Code

    """Demonstrates how to associate nodes in a Maxon API graph with their corresponding Cinema 4D API 
    surrogate elements.
    
    Cinema 4D is split into two major APIs:
    
    - *Cinema API*: The 'classic' API that represents and describes most tangible entities in Cinema 4D,
       such as materials, objects, tags, and so on. This API is mostly based on the concept of
       `BaseList2D` scene elements that hold data containers and are organized in hierarchical
       and generic node relationships (via `GeListNode`, one of the base classes of `BaseList2D`). I.e.,
       this data forms the graph that makes up a Cinema 4D scene, even when we usually think of it as a 
       tree structure (but GeListNode relationships are more than pure tree relationships).
    - *Maxon API*: The Maxon API is a more modern API that also includes the Nodes API, which offers a
       a new (but not drop-in replacement) way to represent nodal scene data. It is used for Scene and
       Material Nodes at the moment (and subject of this part of the documentation).
    
    But the majority of the UI of Cinema 4D is still based on the Cinema API, and the Attribute Manager
    for example can only display `BaseList2D` scene elements and not `GraphNode` entities of the Maxon 
    API. So, for user interaction purposes, Maxon API nodes must be associated with Cinema API surrogate
    scene elements that represent those nodes in the Cinema API world.
    
    A `NimusBaseInterface` is the glue between a Cinema API scene element and a Maxon API node graph. It 
    not only provides access to the actual Nodes API graph associated with that element, but also 
    manages the association between Nodes API nodes/ports and their Cinema API surrogate elements.
    
    This example focuses on the Scene Nodes system, but the same principles apply to other Maxon API 
    node graphs.
    
    Compatibility Note: 
    
        Remove all code in #main up to `print("\n\n--- Maxon API Scene Nodes Data ---\n") and the 
        #mxutils import statement to make this example run in older versions of Cinema 4D (should work 
        at least in 2025.x and with minor modifications even in 2024.x).
        
    """
    __author__ = "Ferdinand Hoppe"
    __copyright__ = "Copyright (C) 2026 MAXON Computer GmbH"
    __date__ = "05/01/2026"
    __license__ = "Apache-2.0 License"
    __version__ = "2026.0.0"
    
    import c4d
    import maxon
    import mxutils
    
    doc: c4d.documents.BaseDocument  # The currently active document.
    op: c4d.BaseObject | None  # The primary selected object in `doc`. Can be `None`.
    
    def main() -> None:
        """Called by Cinema 4D when the script is being executed.
        """
        # An good way to visualize what we are doing is mxutils.GetSceneGraphString, as it will give
        # us a visual representation of a Cinema API scene graph. We can find there the nodes we
        # will retrieve and associate with Maxon API nodes further below. You have to look for a 
        # Scene Nodes scene hook in the output. It will also hold all surrogate nodes for the Maxon
        # API Scene Nodes graph of the document.
        print("--- Cinema API Scene Graph ---\n")
        c4d.ClearPythonConsole()
        print(mxutils.GetSceneGraphString(doc))
    
        print("\n\n--- Maxon API Scene Nodes Data ---\n")
    
        # Now we attempt to get the Cinema API scene element to which the Scene Nodes system of a 
        # document is tied (and which also physically holds all Cinema API surrogate nodes for the
        # Maxon API Scene Nodes graph of the document). For scene nodes, this is a scene hook, for
        # material graphs it would be a BaseMaterial.
        # 
        # Scene hooks are a Cinema API node type for which always exactly one instance exists per 
        # document. It is only a coincidence that they also use the term "scene" in their name, there 
        # is no semantic relation between scene hooks and Scene Nodes. For material graphs, we would 
        # for example call `FindNimbusRef` on the `BaseMaterial` instance instead of retrieving 
        # a scene hook.
        hook: c4d.BaseList2D = doc.FindSceneHook(c4d.SCENENODES_IDS_SCENEHOOK_ID)
        if not hook:
            raise RuntimeError("Could not retrieve Scene Nodes scene hook.")
    
        # A `BaseList2D` offers various methods to retrieve `NimusBaseInterface` references for itself.
        # And a NimusBaseInterface is the glue between a Cinema API scene element and a Maxon API node 
        # graph. Here is the connection being made between a Cinema API scene element in form of a
        # scene hook and the the Scene Nodes Maxon API graph that is associated with that scene hook. 
        # Scene nodes come with the special condition that the their graph might not yet exist for 
        # performance reasons, so we must always send #MSG_CREATE_IF_REQUIRED before attempting to
        # access a scene nodes graph. For a material node graph, this would not be necessary.
        hook.Message(maxon.neutron.MSG_CREATE_IF_REQUIRED)
        handler: maxon.NimbusBaseRef | None = hook.GetNimbusRef(maxon.NodeSpaceIdentifiers.SceneNodes)
        if not handler:
            raise RuntimeError("Could not retrieve Scene Nodes handler.")
    
        # Now we get the nodes graph that is associated with this nimbus handler and iterate over all 
        # true nodes in it. The Nodes API follows the a bit odd notion that it represents graphs as 
        # trees of entities, where each entity is a  GraphNode. Some of those entities are 'true' nodes,
        # i.e., nodes that also an end user would see in the Node Editor, while other entities represent
        # things like input and output ports (but are also GraphNodes). So, in short, a GraphNode does
        # not necessarily represent a 'true' node.
        graph: maxon.NodesGraphModelRef = handler.GetGraph()
        root: maxon.GraphNode = graph.GetViewRoot()
        
        # For each entity in the graph...
        for entity in root.GetInnerNodes(maxon.NODE_KIND.ALL_MASK, False, None):
            # .. step over all non 'true node' entities ...
            if entity.GetKind() != maxon.NODE_KIND.NODE:
                continue
    
            # .. and find (or create) the BaseList2D Cinema API surrogate entity that represents the 
            # current #entity in this Maxon API graph. This is the surrogate that is shown in an 
            # Attribute Manager when the user selects the node in the Node Editor. Reading and writing
            # its parameters will be reflected in the Maxon API node and vice versa.
            surrogate: c4d.BaseList2D = handler.FindOrCreateCorrespondingBaseList(entity.GetPath())
            print(f"Maxon API node: {entity.GetPath()} -> Cinema API surrogate node: {surrogate}")
    
            # Now we are going to iterate over all input ports of #entity and translate them into
            # parameter IDs for our #surrogate.
            for port in entity.GetInnerNodes(maxon.NODE_KIND.ALL_MASK, False, None):
                if port.GetKind() != maxon.NODE_KIND.INPORT:
                    continue
    
                # The reasons why we use here a try/except block is because nodes tend to hold input
                # ports which only fulfill internal purposes and do not have a corresponding parameter
                # in the Cinema API surrogate node (ports without an UI). Getting the DescID for such 
                # ports will fail with a ValueError because ports without an UI are not translated (and 
                # we are also probably not interested in them here).
                try:
                    did: c4d.DescID = handler.GetDescID(port.GetPath()) # This fails
                    print(f"\tInput port: {port.GetPath()} -> DescID: {did}")
                except:
                    pass
    
    if __name__ == '__main__':
        main()
    
  • Maxon SDK 2025 Winter Holidays

    Dear developer community,

    The year 2025 comes to its end and between 15/12/2025 and 5/1/2026, the Maxon SDK Team will be short staffed. Please understand that we cannot deliver forum support in that period. For absolutely urgent matters - in the sense of business critical - please reach out via our contact form.

    Happy Christmas and a happy new year to all developers,
    the Maxon SDK Team

  • RE: How to change the Node spaces

    Hello @gelobui,

    Welcome to the Maxon developers forum and its community, it is great to have you with us!

    Getting Started

    Before creating your next postings, we would recommend making yourself accustomed with our forum and support procedures. You did not do anything wrong, we point all new users to these rules.

    • Forum Overview: Provides a broad overview of the fundamental structure and rules of this forum, such as the purpose of the different sub-forums or the fact that we will ban users who engage in hate speech or harassment.
    • Support Procedures: Provides a more in detail overview of how we provide technical support for APIs here. This topic will tell you how to ask good questions and limits of our technical support.
    • Forum Features: Provides an overview of the technical features of this forum, such as Markdown markup or file uploads.

    It is strongly recommended to read the first two topics carefully, especially the section Support Procedures: How to Ask Questions.

    About your First Question

    It depends a bit on how you mean your question. There is GetActiveNodeSpaceId which allows you to get the ID of the current node space. But there is no setting equivalent of that function. So, you cannot set a node space by its ID.

    What you can do, is call the command which switches node spaces. These are however dynamically assigned and can have a different meaning, depending on how many render engines are installed. You can just check the script log after changing the space.

    On this installation I have for example no extra render engines or node spaces installed, therefore Redshift is there 72000, 4.
    a0da1ed7-7add-456e-a8cc-63d8bd1ced2a-image.png

    But on this machine I have the C++ SDK installed and therefore the Example nodes space, so Redshift is now 72000, 5:

    49b0838b-49d5-4f14-b638-811d8d26ada4-image.png

    When you really want to do this in a fail safe manner, you would have to parse the menu of Cinema 4D to know with which sub-id to call CallCommand.

    Cheers,
    Ferdinand

  • RE: ColorField/ColorDialog and GeUserArea – Color Space Questions

    Hey @lasselauch,

    Thank you for reaching out to us. The issue is likely that you do not respect OCIO color management in your document. You have marked this posting as S26, but my hunch would be that you are using a newer version of Cinema 4D and an OCIO enabled document. The first implementation of OCIO showed up with S26 in Cinema 4D, although it was just some internal systems then such as the flag DOCUMENT_COLOR_MANAGEMENT_OCIO and user facing systems arrived with 2024 the earliest I think. In Python, you can only truly deal with this in 2025.0.0 and higher, as that is when we added the OCIO API to Python.

    When you indeed are testing this on an S26 or lower instance of Cinema 4D, the major question would be if DOCUMENT_LINEARWORKFLOW is enabled or not. Because you cannot just blindly convert colors.

    Is this the intended behavior? Should we always convert LINEAR→sRGB when drawing colors from ColorField/ColorDialog in a GeUserArea?

    The question would be what you consider here this ;). But if the question is if it is intended behavior for a scene with DOCUMENT_COLOR_MANAGEMENT_BASIC and DOCUMENT_LINEARWORKFLOW enabled, to have all its scene element and parameter colors expressed as sRGB 1.0, then yes, that is the major gist of the old linear workflow. It is also intended that drawing happens in sRGB 2.2 up this day.

    Issue 2: Eyedropper Roundtrip Doesn't Preserve Saturated Colors

    Generally, multi question topics tend to become a mess (which is why we do not allow them). But in short: While roundtrips in the form of sRGB 1.0 -> sRGB 2.2 -> sRGB 1.0 are not absolutely lossless (you are always subject to floating point precision errors), there is no giant loss by default. I am not sure what math TransformColor is using explicitly. A simple pow(color, 2.2) and pow(color, 1/2.2) are the naive way to do this and the loss would be rather small. TransformColor might be respecting tristimulus weights which is a bit more lossy but still in a small range.

    OCIO roundtrips on the other hand are generally quite lossy, because ACEScg is a very wide gamut color space and converting from ACEScg to sRGB 2.2 and back can lead to significant losses in saturated colors. Some conversion paths in OCIO are even irreversible in a certain sense (depending on what color spaces you have assigned to which transform). OCIO is rather complicated to put it mildly.

    Is there a known issue with the ColorDialog eyedropper and color space conversion for saturated colors?

    Not that I am aware of. But you likely just ignored OCIO. And while the default OCIO Render Space of Cinema 4D (ACEScg) is in a certain sense similar to sRGB 1.0 for low saturated colors, it diverges significantly for highly saturated colors. So, your loss of saturation is likely stemming from treating an OCIO document with an ACEScg render space as sRGB 1.0.

    See also:

    Last but not least, I attached an example of what you are trying to achieve, get a color from a color gadget in a dialog and draw with it faithfully in your own user area.

    Cheers,
    Ferdinand

    """Demonstrates how to correctly draw with OCIO colors in a dialog.
    
    This examples assumes that you are using Cinema 4D 2025+ with an OCIO enabled document. It will also
    work in other versions and color management modes, but the point of this example is to demonstrate
    OCIO color conversion for drawing in dialogs (more or less the same what is already shown in other
    OCIO examples in the SDK).
    """
    import c4d
    from c4d import gui
    
    class ColorArea(gui.GeUserArea):
        """Draws a color square in a custom UI element for a dialog.
        """
        def __init__(self):
            self._color: c4d.Vector = c4d.Vector(1, 0, 0) # The color to draw, this is in sRGB 2.2
    
        def GetMinSize(self):
            return 75, 20
    
        def DrawMsg(self, x1: int, y1: int, x2: int, y2: int, msg: c4d.BaseContainer) -> None:
            """Draw the color of the area.
            """
            self.OffScreenOn()
    
            # Draw the color.
            self.DrawSetPen(self._color)
            self.DrawRectangle(x1, y1, x2, y2)
    
    class ColorDialog(gui.GeDialog):
        """Implements a dialog that hosts a color field and chooser as well as our custom color area.
        
        The colors in the color field and chooser are in render space, so we have to convert them to sRGB
        for correct display in our user area. All three color widgets are kept in sync, i.e., changing one
        updates the others.
        """
        ID_DUMMY_ELEMENT: int = 1000
        ID_COLOR_CHOOSER: int = 1001
        ID_COLOR_FIELD: int = 1002
        ID_COLOR_AREA: int = 1003
    
        SPACING_BORDER: int = (5, 5, 5, 5)
        SPACING_ELEMENTS: int = (5, 5)
        DEFAULT_FLAGS: int = c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT
    
        def __init__(self) -> None:
            """Initializes the dialog.
            """
            # Reinitializing the color area each time CreateLayout is called, could cause loosing its
            # state when this is an async dialog docked in the UI and part of a layout, as CreateLayout 
            # can be called more than once when a dialog must be reinitialized on layout changes. So, 
            # doing it in __init__ or guarding it with a check if it is already created in CreateLayout 
            # is better.
            self._color_area: ColorArea = ColorArea()
    
        def CreateLayout(self) -> None:
            """Called by Cinema 4D to populate the dialog with elements.
            """
            self.SetTitle("Dialog Color OCIO Demo")
            # Using the same ID for dummy elements multiple times is fine, using IDs < 1000 is often
            # not a good idea, as Cinema 4D usually operates in that range, and therefore an ID such 
            # as 0 can lead to issues (0 is AFIAK not actually used but better safe than sorry).
            if self.GroupBegin(self.ID_DUMMY_ELEMENT, self.DEFAULT_FLAGS, cols=1):
                self.GroupBorderSpace(*self.SPACING_BORDER)
                self.GroupSpace(*self.SPACING_ELEMENTS)
    
                # Add a color chooser and a color field.
                self.AddColorChooser(self.ID_COLOR_CHOOSER, c4d.BFH_LEFT)
                self.AddColorField(self.ID_COLOR_FIELD, c4d.BFH_LEFT)
    
                # Add our user area to display the color.
                self.AddUserArea(self.ID_COLOR_AREA, c4d.BFH_LEFT)
                self.AttachUserArea(self._color_area, self.ID_COLOR_AREA)
                self.GroupEnd()
            return True
    
        def InitValues(self) -> bool:
            """Called by Cinema 4D to initialize the dialog values.
            """
            self.SetColors(c4d.Vector(1, 0, 0))
            return True
    
        def SetColors(self, color: c4d.Vector, doc: c4d.documents.BaseDocument | None = None) -> None:
            """Sets the colors of all color widgets to the given render space #color.
            """
            # Just set the two color widgets first, as they expect render space colors.
            self.SetColorField(self.ID_COLOR_CHOOSER, color, 1.0, 1.0, c4d.DR_COLORFIELD_NO_BRIGHTNESS)
            self.SetColorField(self.ID_COLOR_FIELD, color, 1.0, 1.0, c4d.DR_COLORFIELD_NO_BRIGHTNESS)
    
            # When the call did not provide a document, use the active document.
            if not isinstance(doc, c4d.documents.BaseDocument):
                doc = c4d.documents.GetActiveDocument()
    
            # Check in which color mode the document is. Explicit OCIO color management exists in this 
            # form since S26 but it really only took off with 2025.
            isOCIO: bool = False
            if (c4d.GetC4DVersion() >= 2025000 and
                doc[c4d.DOCUMENT_COLOR_MANAGEMENT] == c4d.DOCUMENT_COLOR_MANAGEMENT_OCIO):
                # All colors in a document are render space colors (including the color fields in 
                # dialogs). GUI drawing however still happens in sRGB space, so we need to convert
                # the render space color to sRGB for correct display. For that we need a document
                # because it contains the OCIO config and the converted which is derived from it.
                converter: c4d.modules.render.OcioConverter = doc.GetColorConverter()
    
                # Transform a render space color to sRGB space (there are other conversion paths
                # too, check the docs/examples on OCIO).
                color: c4d.Vector = converter.TransformColor(
                    color, c4d.COLORSPACETRANSFORMATION_OCIO_RENDERING_TO_SRGB)
                
                isOCIO = True
            elif not isOCIO and doc[c4d.DOCUMENT_LINEARWORKFLOW]:
                # For non-OCIO documents (older than S26 or DOCUMENT_COLOR_MANAGEMENT_BASIC), the scene
                # element color space ('render space' in OCIO terms) can either be sRGB 2.2 or sRGB 1.0
                # (linear sRGB), depending on whether DOCUMENT_LINEARWORKFLOW is set or not. In that 
                # case, we would have to convert from gamma 1.0 to 2.2. In a modern OCIO document, we
                # could also use #converter for this, but for legacy reasons I am using here the old
                # c4d.utils function. It might be better to use the converter when this is a 2025+
                # instance of Cinema 4D. #DOCUMENT_LINEARWORKFLOW is really old, it exists at least 
                # since #R21 (I did not check earlier versions), so I am not doing another version check.
                color = c4d.utils.TransformColor(color, c4d.COLORSPACETRANSFORMATION_LINEAR_TO_SRGB)
    
            # Last but not least, in practice you would probably encapsulate this logic in your user
            # area, similarly to how native color elements operate just in Render Space but draw in 
            # sRGB space. For dialogs (compared to description parameters), this is a bit complicated
            # by the fact that one cannot unambiguously associate a dialog with a document from which
            # to take the color management settings. A custom GUI of a description parameter can
            # always get the node it is hosted by and its document. For dialog GUIs that is not possible.
            # So, we have to do the active document approach I showed here.
    
            # In a super production scenario, you would overwrite CoreMessage() of the user area or
            # dialog, to catch the active document changing, to then update the color conversion, as
            # with the document change, also the OCIO config could changed and with that its render
            # space transform.
            #
            # All in all probably a bit overkill, and I would ignore this under the banner of "who
            # cares, just reopen the dialog and you are fine". Because users will also rarely change
            # the default render space transform of ACEScg to something else.
    
            self._color_area._color = color
            self._color_area.Redraw()
            
        def Command(self, id: int, msg: c4d.BaseContainer) -> bool:
            """Called by Cinema 4D when the user interacts with a dialog element.
            """
            if id == self.ID_COLOR_CHOOSER:
                color: c4d.Vector = self.GetColorField(self.ID_COLOR_CHOOSER)["color"]
                self.SetColors(color)
            elif id == self.ID_COLOR_FIELD:
                color: c4d.Vector = self.GetColorField(self.ID_COLOR_FIELD)["color"]
                self.SetColors(color)
            return True
    
    # Please do not do this hack in production code. ASYNC dialogs should never be opened in a Script
    # Manager script like this, because this will entail a dangling dialog instance. Use modal dialogs
    # in Script Manager scripts or implement a plugin such as a command to use async dialogs.
    dlg: ColorDialog = ColorDialog()
    if __name__ == '__main__':
        dlg.Open(c4d.DLG_TYPE_ASYNC, defaultw=480, defaulth=400)
    
  • RE: ObjectData handles - Unexpected position jitter

    Good to hear!

  • RE: ObjectData handles - Unexpected position jitter

    I now also see that my example is buggy in the perspective view (and other views I have not implemented). For these cases you would have to do exactly what I did in my ohandlenull example, project the point into a plane placed on the origin of the object with a normal that is the inverse of the camera normal.

    Given that this also affects internal code, it is quite likely that we will fix this. If I were you, I would just keep my old code and ignore this very niche edge case. When you really want this to work you would have to implement MoveHandle and handle the different view projections of Cinema 4D. This can probably be done in 50-100 lines of code or so, but it would be something I would try to avoid doing, as viewport projections can be tricky to handle.

  • RE: ObjectData handles - Unexpected position jitter

    Hey @PixelsInProgress,

    so, I had a look. First of all, I came up with reproduction steps which you were lacking (see our Support Procedures). It is important to provide these, because when you just provide a 'sometimes it works, sometimes it doesn't' or a 'it is hard to reproduce' the risk is high, that I or Maxime will just declare something non-reproducible and move on (which almost happened here). We understand this is extra work and that you already put work into boiling down the issue, but it is in your own interest to be more precise with reproduction steps.

    I am not 100% sure if we will consider this a bug, because I do not yet fully understand why this happens, but I have moved this into bugs for now. I also provide a workaround based on MoveHandle as already hinted at before.

    Edit: Since this bug also affects objects provided by Cinema 4D, such as a cloner in linear mode or the field force object, this is now a Cinema 4D and not SDK bug anymore.

    Issue

    ObjectData.SetHandle code can lead to jitter issues when using HANDLECONSTRAINTTYPE_FREE.

    Reproduction

    1. Add the example object plugin to a scene.
    2. Switch to a two panel viewport layout, make the right panel a "Right" projection, the left panel a "Top" projection.
    3. Give the object a non-default transform.
    4. Interact with the handle in one of the panels.
    5. Now move the object in that panel.
    6. Interact with the handle in the other panel.

    Result

    • The handle jitters between the world plane perpendicular to the other view and the 'correct' position.

    Code

    import c4d
    
    PLUGIN_ID = 1067013
    PIP_HANDLE_EXAMPLE_POSITION = 1000
    PIP_HANDLE_EXAMPLE_GROUP_STORAGE_GROUP = 2000
    PIP_HANDLE_EXAMPLE_CONTAINER_INTERNAL_CONTAINER = 3000
    
    class PIP_HandleExample(c4d.plugins.ObjectData):
    
        def Init(self,op,isCloneInit):
            self.InitAttr(op, c4d.Vector, c4d.PIP_HANDLE_EXAMPLE_POSITION)
            if not isCloneInit:
                op[c4d.PIP_HANDLE_EXAMPLE_POSITION] = c4d.Vector(0,0,0)
    
            return True
    
        def GetHandleCount(self, op):
            return 1
    
        def GetHandle(self, op, i, info):
            info.position  = op[c4d.PIP_HANDLE_EXAMPLE_POSITION] 
            info.type      = c4d.HANDLECONSTRAINTTYPE_FREE
    
        def SetHandle(self, op, i, p, info):
            op[c4d.PIP_HANDLE_EXAMPLE_POSITION] = p
    
        def Draw(self, op, drawpass, bd, bh):
            if drawpass != c4d.DRAWPASS_HANDLES:
                return c4d.DRAWRESULT_SKIP
    
            bd.SetMatrix_Matrix(op, op.GetMg())
            bd.SetPen(c4d.GetViewColor(c4d.VIEWCOLOR_HANDLES))
    
            info = c4d.HandleInfo()
            self.GetHandle(op, 0, info)
            bd.DrawHandle(info.position, c4d.DRAWHANDLE_BIG, 0)
            return c4d.DRAWRESULT_OK
    
    if __name__ == "__main__":
        if not c4d.plugins.RegisterObjectPlugin(
                    id=PLUGIN_ID,
                    str="PIP - Handle example",
                    g=PIP_HandleExample,
                    description="PIP_HandleExample",
                    icon=None,
                    info=c4d.OBJECT_GENERATOR):
            raise RuntimeError("Failed to register PIP_HandleExample plugin.")
    

    Workaround

    As already hinted at, you can override MoveHandle instead of SetHandle to implement your own handle movement logic. This way you have full control over how the mouse position is interpreted and can work around the jitter issue. See below for an example implementation.

    Files: PIP_HandleExample.zip

        # def SetHandle(self, op, i, p, info):
        #     """ Not required as we override MoveHandle.
        #     """
        #     op[c4d.PIP_HANDLE_EXAMPLE_POSITION] = p
    
        def MoveHandle(self, op: c4d.BaseObject, undo: c4d.BaseObject, mouse_pos: c4d.Vector, 
                       hit_id: int, qualifier: int, bd: c4d.BaseDraw) -> bool:
            """Called by Cinema 4D when the user interacts with a handle.
            """
            # Get the mouse position in world space and then convert it to object space. The issue
            # of this solution is that it will project the point down to one of the world planes (
            # the plane to which the the #SetHandle code jitters). So, the axis which is perpendicular
            # to the view plane will be zeroed out.
            worldMousePos: c4d.Vector = bd.SW(mouse_pos)
            localMousePos: c4d.Vector = ~op.GetMg() * worldMousePos
    
            # To fix that, we must project the point into a plane we consider correct. You could do this
            # brutishly by for example checking the projection of #bd and then just align the component
            # of #worldMousePos that is perpendicular to that plane. You could also add some 'carry on' 
            # logic here which respects previous data, but I didn't do that.
            projection: int = bd[c4d.BASEDRAW_DATA_PROJECTION]
            if projection in (c4d.BASEDRAW_PROJECTION_TOP, c4d.BASEDRAW_PROJECTION_BOTTOM):
                worldMousePos.y = op.GetMg().off.y
            elif projection in (c4d.BASEDRAW_PROJECTION_FRONT, c4d.BASEDRAW_PROJECTION_BACK):
                worldMousePos.z = op.GetMg().off.z
            elif projection in (c4d.BASEDRAW_PROJECTION_LEFT, c4d.BASEDRAW_PROJECTION_RIGHT):
                worldMousePos.x = op.GetMg().off.x
    
            op[c4d.PIP_HANDLE_EXAMPLE_POSITION] = ~op.GetMg() * worldMousePos
    
            return True
    

    Cheers,
    Ferdinand

  • RE: ObjectData handles - Unexpected position jitter

    Well, what I meant with feedback loop, is that you never constraint your data in any shape or form. All code examples and internal code work like this:

    def GetHandle(self, op, i, info):
        info.position  = self.handle_position # or op[c4d.ID_INTERNAL_HANDLE_STORAGE]
        info.type = c4d.HANDLECONSTRAINTTYPE_SOMETYPE
        info.direction = Vector(...)
    
    def SetHandle(self, op, i, p, info):
        self.handle_position = Clamp(p)
    

    I.e., here Clamp is called on p before it is fed back into handle_position. In my bezier handle example I used MoveHandle to project the current mouse pick point into the plane where I wanted to have it. You could also do the same in SetHandle. The viewport picking algorithm has no idea where you consider to be the 'correct' working plane. It does its best to guess, but you must still check and or help.

    I haven't had a look at your code yet, will do next week (hopefully on Monday). It could be that there is a bug in the Python API, but that unbound nature of your code does strike me as incorrect.

    Cheers,
    Ferdinand

  • RE: ObjectData handles - Unexpected position jitter

    Hey @PixelsInProgress,

    thank you for reaching out to us. That is not possible to answer like this, please provide an executable code example of what you are doing.

    I guess the the reason for your problems are that you use the a bit niche constraint type HANDLECONSTRAINTTYPE_FREE and create a transform feedback loop when you feed your own user data into your handle without constraining that data. Keep in mind that handle code is called view-based. You might have to implement MoveHandle to correctly transform your point. But I have no idea what you are trying to do, so I am mostly guessing.

    I have written a while ago this C++ code example which implements a camera dependent handle, which might be what you are trying to do here.

    Cheers,
    Ferdinand

  • RE: Educational Licenses

    No one might see this here, as notifications are currently broken, but there is an all new licensing manual and example in the SDK which should answer the questions asked here. Feel free to follow up, in case something remains unclear.

    Licensing Manual