Maxon Developers Maxon Developers
    • Documentation
      • Cinema 4D Python API
      • Cinema 4D C++ API
      • Cineware API
      • ZBrush GoZ API
      • Code Examples on Github
    • Forum
    • Downloads
    • Support
      • Support Procedures
      • Registered Developer Program
      • Plugin IDs
      • Contact Us
    • Categories
      • Overview
      • News & Information
      • Cinema 4D SDK Support
      • Cineware SDK Support
      • ZBrush 4D SDK Support
      • Bugs
      • General Talk
    • Unread
    • Recent
    • Tags
    • Users
    • Login

    Can I get active things order with python?

    Cinema 4D SDK
    windows python s26
    2
    8
    2.1k
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • DunhouD
      Dunhou
      last edited by Manuel

      Hello :

      Question :

      I want to get a order of what I select and activate last . Can I do this wth python ?

      More Descriptions :

      For example , I select some thing and they can exist active at the sametime. e.g.

      • object A
      • tag A on object B
      • Mat A in material manager
      • layer B in layer manager .
      • even a attribute in Atrribute Manager

      If I want a quick function for example reneme . I don't want to GetActiveObjects scripts for Object Manager and another GetActiveMaterials scripts for Material Manager , that means to many scripts with a same task .

      Instead , I want only one script that can judge which is my last select thing or which context my mouse in , so that I can run a corresponding function . ( even more like execute on tags and both on materials )

      Can or how can I do this with python ?

      Here is a picture for better explain what I mean active at same time .
      ab2eca97-20c4-474c-96e3-3dfde4c35a59-image.png

      https://boghma.com
      https://github.com/DunHouGo

      ferdinandF 1 Reply Last reply Reply Quote 0
      • ferdinandF
        ferdinand @Dunhou
        last edited by

        Hello @dunhou,

        Thank you for reaching out to us. The answer to your question is "sort of', especially since it implies a follow up question 😉

        In the front end there are several convenience methods with which you can retrieve selected things in a document, you already know them, e.g.:

        • c4d.BaseDocument.GetActiveObjects(flags)
        • c4d.BaseDocument.GetActiveMaterials()
        • c4d.BaseDocument.GetActiveTags()
        • ...

        With .GetActiveObjects(c4d.GETACTIVEOBJECTFLAGS_SELECTIONORDER) you can also preserve the selection order of objects in the list returned by that method.

        • There are however no equivalents for the selection order for materials, tags, etc.
        • There also do not exist methods for all scene element types which are selectable, and you sometimes you must iterate yourself over elements to find out if they are selected or not.
        • The selection state of a node is expressed in the flag c4d.BIT_ACTIVE, i.e., someNode.GetBit(c4d.BIT_ACTIVE) will return true when that node is selected.
        • The selection state of parameters in the Attribute Manger is not accessible from the public API.

        Cinema 4D does manage the content of a document as any 3D DCC app in some notion of a scene graph. In the Cinema 4D classic API this is primarily realized with the type c4d.GeListNode and the concept of branches as embodied by the type c4d.GeListHead and the method c4d.GeListNode.GetBranchInfo.

        With the help of c4d.GeListNode.GetBranchInfo you can fashion yourself a function with which you can iterate abstractly over a document, i.e., have something like this:

        for node in IterateGraph(doc, [c4d.Obase, c4d.Tbase, c4d.Mbase, c4d.Olayer]):
        

        where then the function will yield all objects, tags, materials, and layers in doc. This touches on the non-trivial topic of scene traversal. We are aware that we should provide convenience functions for that, but it has not been done yet. Find below a Python script which highlights a simple implementation of such IterateGraph function.

        Cheers,
        Ferdinand

        The example scene:
        example_scene.c4d
        500588b8-4923-4520-9ada-78c391359407-image.png

        The output for the example scene, with some comments:

        
        # We get both layers from doc, the second one is selected.
        IterateGraph(doc, [c4d.Olayer])
            LayerObject 'Layer' at 0x000001519703F5C0 (isActive = False)
            LayerObject 'Layer 1' at 0x0000015197040880 (isActive = True)
        
        # We get all cube objects and all tags in the scene.
        IterateGraph(doc, [c4d.Ocube, c4d.Tbase])
            # Tags of the sphere object, the object itself is not included because it is not Ocube
            BaseTag 'Phong' at 0x00000151970300C0 (isActive = False)
            TextureTag 'Material' at 0x000001519702B580 (isActive = False)
            # The cube objects and their tags.
            BaseObject 'Cube' at 0x00000151970345C0 (isActive = True)
            TextureTag 'Material' at 0x0000015197009680 (isActive = False)
            BaseObject 'Cube.1' at 0x0000015197041B80 (isActive = False)
            BaseTag 'Phong' at 0x0000015197013E80 (isActive = False)
            BaseTag 'Look at Camera' at 0x000001519703D4C0 (isActive = False)
            TextureTag 'Material' at 0x000001519702CFC0 (isActive = False)
        
        # The generator object "Cube" has no phong tag, but a PolygonObject cache which we unpack here.
        IterateGraph(cube, [c4d.Obase, c4d.Tphong], inspectCaches=True)
            BaseObject 'Cube' at 0x0000015197006F00 (isActive = True)
            PolygonObject 'Cube' at 0x0000015114952980 (isActive = False)
        
        # This yields nothing although there is a override in the scene, because we never go in the
        # object branches here (where the override is located in this case)
        IterateGraph(doc, [c4d.OverrideBase])
        
        # This works :)
        IterateGraph(doc, [c4d.Obase, c4d.OverrideBase])
            BaseObject 'Sphere' at 0x000001519701D2C0 (isActive = False)
            BaseObject 'Cube' at 0x0000015197037AC0 (isActive = True)
            BaseObject 'Cube.1' at 0x0000015197021DC0 (isActive = False)
            BaseOverride 'Override' at 0x0000015197012B80 (isActive = False)
            BaseOverride 'Override' at 0x0000015197026F80 (isActive = True)
        
        # Your case plus some description inspection for parameters.
        IterateGraph(doc, [c4d.Obase, c4d.Tbase, c4d.Mbase, c4d.Olayer])
            BaseObject 'Sphere' at 0x000001519702B840 (isActive = False)
                Parameter 'Basic Properties'(descId = (110050, 1, 110050)): None
                Parameter 'Icon'(descId = (1041666, 1, 110050)): None
                Parameter 'Icon File / ID'(descId = (1041668, 7, 110050)):
                Parameter 'Icon Color'(descId = (1041670, 15, 110050)): 0
                ...
            BaseTag 'Phong' at 0x000001519703C1C0 (isActive = False)
                Parameter 'Basic Properties'(descId = (110050, 1, 110050)): None
                Parameter 'Icon'(descId = (1041666, 1, 110050)): None
                Parameter 'Icon File / ID'(descId = (1041668, 7, 110050)):
                Parameter 'Icon Color'(descId = (1041670, 400006001, 110050)): 0
                ...
            TextureTag 'Material' at 0x000001519703F3C0 (isActive = False)
                Parameter 'Basic Properties'(descId = (110050, 1, 110050)): None
                Parameter 'Icon'(descId = (1041666, 1, 110050)): None
                Parameter 'Icon File / ID'(descId = (1041668, 7, 110050)):
                Parameter 'Icon Color'(descId = (1041670, 400006001, 110050)): 0
                ...
            BaseObject 'Cube' at 0x0000015197031780 (isActive = True)
                Parameter 'Basic Properties'(descId = (110050, 1, 110050)): None
                Parameter 'Icon'(descId = (1041666, 1, 110050)): None
                Parameter 'Icon File / ID'(descId = (1041668, 7, 110050)):
                Parameter 'Icon Color'(descId = (1041670, 15, 110050)): 0
                ...
            TextureTag 'Material' at 0x000001519702FA80 (isActive = False)
                Parameter 'Basic Properties'(descId = (110050, 1, 110050)): None
                Parameter 'Icon'(descId = (1041666, 1, 110050)): None
                Parameter 'Icon File / ID'(descId = (1041668, 7, 110050)):
                Parameter 'Icon Color'(descId = (1041670, 400006001, 110050)): 0
                ...
            BaseObject 'Cube.1' at 0x0000015197023B80 (isActive = False)
                Parameter 'Basic Properties'(descId = (110050, 1, 110050)): None
                Parameter 'Icon'(descId = (1041666, 1, 110050)): None
                Parameter 'Icon File / ID'(descId = (1041668, 7, 110050)):
                Parameter 'Icon Color'(descId = (1041670, 15, 110050)): 0
                ...
            BaseTag 'Phong' at 0x0000015197010BC0 (isActive = False)
                Parameter 'Basic Properties'(descId = (110050, 1, 110050)): None
                Parameter 'Icon'(descId = (1041666, 1, 110050)): None
                Parameter 'Icon File / ID'(descId = (1041668, 7, 110050)):
                Parameter 'Icon Color'(descId = (1041670, 400006001, 110050)): 0
                ...
            BaseTag 'Look at Camera' at 0x000001519701FDC0 (isActive = False)
                Parameter 'Basic Properties'(descId = (110050, 1, 110050)): None
                Parameter 'Icon'(descId = (1041666, 1, 110050)): None
                Parameter 'Icon File / ID'(descId = (1041668, 7, 110050)):
                Parameter 'Icon Color'(descId = (1041670, 400006001, 110050)): 0
                ...
            TextureTag 'Material' at 0x000001519702FE00 (isActive = False)
                Parameter 'Basic Properties'(descId = (110050, 1, 110050)): None
                Parameter 'Icon'(descId = (1041666, 1, 110050)): None
                Parameter 'Icon File / ID'(descId = (1041668, 7, 110050)):
                Parameter 'Icon Color'(descId = (1041670, 400006001, 110050)): 0
                ...
            Material 'Red' at 0x000001519701B1C0 (isActive = False)
                Parameter ''(descId = (831, 1, 1001065)): None
                Parameter ''(descId = (536871064, 1, 1001065)): None
                Parameter ''(descId = (520000000, 1011153, 1001065)): Not accessible
                Parameter ''(descId = (832, 12, 1001065)):
                ...
            Material 'Green' at 0x0000015197010440 (isActive = True)
                Parameter ''(descId = (831, 1, 1001065)): None
                Parameter ''(descId = (536871064, 1, 1001065)): None
                Parameter ''(descId = (520000000, 1011153, 1001065)): Not accessible
                Parameter ''(descId = (832, 12, 1001065)):
                ...
            Material 'Blue' at 0x000001519703DD80 (isActive = False)
                Parameter ''(descId = (831, 1, 1001065)): None
                Parameter ''(descId = (536871064, 1, 1001065)): None
                Parameter ''(descId = (520000000, 1011153, 1001065)): Not accessible
                Parameter ''(descId = (832, 12, 1001065)):
                ...
            LayerObject 'Layer' at 0x00000151970098C0 (isActive = False)
                Parameter 'Basic Properties'(descId = (110050, 1, 110050)): None
                Parameter 'Icon'(descId = (1041666, 1, 110050)): None
                Parameter 'Icon File / ID'(descId = (1041668, 7, 110050)):
                Parameter 'Icon Color'(descId = (1041670, 400006001, 110050)): 0
                ...
            LayerObject 'Layer 1' at 0x0000015197029A40 (isActive = True)
                Parameter 'Basic Properties'(descId = (110050, 1, 110050)): None
                Parameter 'Icon'(descId = (1041666, 1, 110050)): None
                Parameter 'Icon File / ID'(descId = (1041668, 7, 110050)):
                Parameter 'Icon Color'(descId = (1041670, 400006001, 110050)): 0
                ...
        
        # We can also just look at everything in a document when passing no type list.
        # As you can see, there are a lot of nodes in a scene, even when it seems
        # almost empty, most of them are scene hooks.
        IterateGraph(doc)
            BaseDocument '' at 0x0000015114951C80 (isActive = True)
            BaseObject 'Sphere' at 0x000001519700B3C0 (isActive = False)
            BaseTag 'Phong' at 0x000001519701B4C0 (isActive = False)
            TextureTag 'Material' at 0x0000015197023A00 (isActive = False)
            BaseObject 'Cube' at 0x0000015197024080 (isActive = True)
            TextureTag 'Material' at 0x00000151970417C0 (isActive = False)
            BaseObject 'Cube.1' at 0x0000015197024540 (isActive = False)
            BaseOverride 'Override' at 0x00000151970246C0 (isActive = False)
            BaseOverride 'Override' at 0x000001519702BA00 (isActive = True)
            BaseTag 'Phong' at 0x0000015197020B00 (isActive = False)
            BaseTag 'Look at Camera' at 0x000001519700CEC0 (isActive = False)
            TextureTag 'Material' at 0x0000015197011540 (isActive = False)
            Material 'Red' at 0x000001519701A740 (isActive = False)
            Material 'Green' at 0x0000015197016EC0 (isActive = True)
            Material 'Blue' at 0x00000151970394C0 (isActive = False)
            RenderData 'My Render Setting' at 0x00000151970148C0 (isActive = False)
            BaseVideoPost 'Magic Bullet Looks' at 0x0000015197031540 (isActive = False)
            LayerObject 'Layer' at 0x000001519701A2C0 (isActive = False)
            LayerObject 'Layer 1' at 0x0000015197029780 (isActive = True)
            BaseList2D 'USD Scene Hook' at 0x0000015197025780 (isActive = False)
            BaseList2D 'Substance Assets' at 0x00000151970223C0 (isActive = False)
            BaseList2D 'STHOOK' at 0x000001519703BCC0 (isActive = False)
            BaseList2D 'SceneHook' at 0x0000015197014140 (isActive = False)
            BaseList2D 'CmSceneHook' at 0x0000015197004600 (isActive = False)
            BaseList2D 'CameraMorphDrawSceneHook' at 0x0000015197012F00 (isActive = False)
            BaseList2D 'MotionCameraDrawSceneHook' at 0x00000151970291C0 (isActive = False)
            BaseList2D 'UpdateMerge Hook' at 0x0000015197008000 (isActive = False)
            BaseList2D 'ArchiExchangeCADHook' at 0x000001519701B680 (isActive = False)
            BaseList2D 'Alembic Archive Hook' at 0x00000151970146C0 (isActive = False)
            BaseList2D 'SLA wave scene hook' at 0x0000015197002B80 (isActive = False)
            TP_MasterSystem 'Thinking Particles' at 0x000001519703D600 (isActive = False)
            BaseList2D 'Bullet' at 0x0000015197002F00 (isActive = False)
            BaseList2D 'XRefs' at 0x000001519702A700 (isActive = False)
            BaseList2D 'CAManagerHook' at 0x0000015197030540 (isActive = False)
            BaseList2D 'Weights Handler' at 0x0000015197007500 (isActive = False)
            BaseList2D 'Volume Save Manager Hook' at 0x00000151970404C0 (isActive = False)
            BaseList2D 'UV Display 3D SceneHook' at 0x000001519702DE80 (isActive = False)
            BaseList2D 'uvhook' at 0x000001519700EDC0 (isActive = False)
            BaseList2D 'ScatterPlacementHook' at 0x0000015197033CC0 (isActive = False)
            BaseList2D 'Tool System Hook' at 0x0000015197034AC0 (isActive = False)
            BaseList2D 'Simulation' at 0x000001519702C5C0 (isActive = False)
            BaseObject 'Simulation Scene' at 0x0000015197021980 (isActive = False)
            BaseList2D 'NE_SceneHook' at 0x000001519702CA80 (isActive = False)
            BaseList2D 'Take Hook' at 0x0000015197020400 (isActive = False)
            BaseTake 'Main' at 0x000001519703FD80 (isActive = False)
            BaseList2D 'Overrides' at 0x000001519703C200 (isActive = False)
            BaseList2D 'Others' at 0x0000015197035E40 (isActive = False)
            BaseList2D 'Layers' at 0x000001519701D2C0 (isActive = False)
            BaseList2D 'Materials' at 0x0000015197014BC0 (isActive = False)
            BaseList2D 'Shaders' at 0x000001519701B040 (isActive = False)
            BaseList2D 'Tags' at 0x000001519700A9C0 (isActive = False)
            BaseList2D 'Objects' at 0x000001519700D200 (isActive = False)
            BaseTake 'Take - Cube.Size.X' at 0x0000015197006B80 (isActive = True)
            BaseList2D 'Overrides' at 0x000001519703DC80 (isActive = False)
            BaseList2D 'Others' at 0x000001519701BD40 (isActive = False)
            BaseList2D 'Layers' at 0x000001519701AC40 (isActive = False)
            BaseList2D 'Materials' at 0x000001519701E700 (isActive = False)
            BaseList2D 'Shaders' at 0x00000151970088C0 (isActive = False)
            BaseList2D 'Tags' at 0x0000015197003780 (isActive = False)
            BaseList2D 'Objects' at 0x000001519701EA00 (isActive = False)
            BaseList2D 'CombineAc18_AutoCombine_SceneHook' at 0x0000015197014E40 (isActive = False)
            BaseList2D 'PLKHUD' at 0x000001519702B7C0 (isActive = False)
            BaseObject 'PKHOP' at 0x00000151970270C0 (isActive = False)
            BaseList2D 'RenderManager Hook' at 0x00000151970108C0 (isActive = False)
            BaseList2D 'Sound Scrubbing Hook' at 0x000001519702CFC0 (isActive = False)
            BaseList2D 'To Do' at 0x000001519701B5C0 (isActive = False)
            BaseList2D 'Animation' at 0x000001519700C3C0 (isActive = False)
            BaseList2D 'BaseSettings Hook' at 0x0000015197003D00 (isActive = False)
            BaseList2D 'PersistentHook' at 0x000001519703D4C0 (isActive = False)
            BaseList2D 'Scene Nodes' at 0x000001519702A680 (isActive = False)
            BaseList2D 'MoGraphSceneHook' at 0x000001519702C240 (isActive = False)
            BaseList2D '' at 0x000001519703E800 (isActive = False)
            BaseList2D 'StrNotFound' at 0x0000015197009800 (isActive = False)
            BaseList2D 'Sculpt Objects' at 0x0000015197006840 (isActive = False)
            BaseList2D 'HairHighlightHook' at 0x000001519700D940 (isActive = False)
            BaseList2D 'Mesh Check Hook' at 0x000001519702F6C0 (isActive = False)
            BaseList2D 'Modeling Objects Hook' at 0x0000015197041F40 (isActive = False)
            BaseList2D 'Snap Scenehook' at 0x0000015197032000 (isActive = False)
            BaseObject 'WorkPlane' at 0x000001519703F240 (isActive = False)
            BaseList2D 'Modeling Settings' at 0x00000151970189C0 (isActive = False)
            BaseList2D 'Doodle Hook' at 0x000001519701C380 (isActive = False)
            BaseList2D 'Stereoscopic' at 0x000001519703FD40 (isActive = False)
            BaseList2D 'ViewportExtHookHUD' at 0x000001519700B580 (isActive = False)
            BaseList2D 'ViewportExtHookhighlight' at 0x0000015197010400 (isActive = False)
            BaseList2D 'MeasureSceneHook' at 0x000001519701E740 (isActive = False)
            BaseList2D 'MeshObject Scene Hook' at 0x0000015197015E80 (isActive = False)
            BaseList2D 'Lod Hook' at 0x00000151970212C0 (isActive = False)
            BaseList2D 'Annotation Tag SceneHook' at 0x0000015197013900 (isActive = False)
            BaseList2D 'Sniper' at 0x0000015197025340 (isActive = False)
            BaseList2D 'Redshift SceneHook' at 0x000001519700BD00 (isActive = False)
            BaseList2D 'GvHook' at 0x000001519700F540 (isActive = False)
            BaseList2D 'Material Scene Hook' at 0x000001519703CDC0 (isActive = False)
            BaseList2D 'TargetDistancePicker' at 0x0000015197034240 (isActive = False)
            BaseList2D 'BodyPaint SceneHook' at 0x0000015197021000 (isActive = False)
            BaseList2D '' at 0x0000015197017E40 (isActive = False)
        >>> 
        

        And finally, the code:

        """Demonstrates traversing a classic API scene graph via branches. 
        """
        
        import c4d
        import typing
        
        doc: c4d.documents.BaseDocument # The active document
        
        # Toggle this for IterateGraph to print out some lines showing what it does.
        IS_DEBUG: bool = False
        # A table of (int, BaseList2D) tuples used by HasSymbolBase() below. Doing this is necessary,
        # because classic API scene element branches express their content in terms of base types. So, we 
        # might come along a branch with the ID Obase when iterating over a document. When we then want to
        # find all Ocube instances in a scene, we must be able to infer that Ocube is an instance of Obase, 
        # so that we know that we must branch into the Obase object branch to find Ocube instances. There
        # is currently no other way to get this information except with BaseList2D.IsInstanceOf, i.e., we
        # need an instance of a type symbol to test that. This table achieves that in a performant manner by
        # only creating these dummy instances once.
        G_TYPE_TESTERS_TABLE: dict[int: c4d.BaseList2D] = {}
        
        def HasSymbolBase(t: int, other: int) -> bool:
            """Returns if the Cinema 4D type symbol #t is in an inheritance relation with the type symbol 
            #other.
        
            E.g., Ocube is an instance of Obase, Mmaterial is an instance of Mbase, but Tphong for example
            is not an instance of CTbase.
            """
            if not isinstance(t, int) or not isinstance(other, int):
                raise TypeError(f"Illegal argument types: {t, other}")
            
            # Try to get a previously allocated dummy node for the type symbol #t.
            dummyNode: typing.Union[c4d.BaseList2D, int, None] = G_TYPE_TESTERS_TABLE.get(t, None)
            # There is no node yet.
            if dummyNode is None:
                # Try to allocate one, this can fail, as the user could have passed a base symbol type.
                # E.g., one cannot allocate c4d.BaseList2D(c4d.Obase)
        
                # The symbol was a concrete type symbol, insert the dummy node under #t in the table.
                try:
                    dummyNode = c4d.BaseList2D(t)
                    G_TYPE_TESTERS_TABLE[t] = dummyNode
                # The type symbol was a base type, insert NOTOK under #t in the table.
                except BaseException:
                    dummyNode = c4d.NOTOK
                    G_TYPE_TESTERS_TABLE[t] = c4d.NOTOK
        
            # There can be no node instances for #t, #t must be a base type, compare #t with #other directly.
            if dummyNode == c4d.NOTOK:
                return t == other
        
            # Test if #t is an instance of #other
            return dummyNode.IsInstanceOf(other)
        
        
        def IterateGraph(node: c4d.BaseList2D, 
                         types: typing.Optional[list[int]] = None,
                         inspectCaches: bool = False, 
                         root: typing.Optional[c4d.BaseList2D] = None) -> typing.Iterator[c4d.BaseList2D]:
            """_summary_
        
            Args:
                node: The starting node to inspect the contents for. Can be anything that is a BaseList2D,
                    e.g., a document, an object, a material, a tag, a layer, etc.
                types (optional): The type of nodes which are in a relation with #node which should be 
                    yielded. This will also respect inheritance, e.g., [c4d.Obase] will yield all objects,
                    and [c4d.Ocube] will only yield cube objects. Will yield all node types when None. 
                    Defaults to None.
                inspectCaches (optional): If the iteration should also branch into caches. Defaults to False.
                root: Private, do not override.
        
            Yields:
                Nodes which are in a relation with with #node and of a type or base type in #types.
            """
            # Stop the iterator when #node is None.
            if not isinstance(node, c4d.BaseList2D):
                return
            if IS_DEBUG:
                print (f"IterateGraph({node, types, root})")
            
            # When this is an first user call of this function and not a recursion we set #root to #node, 
            # so that we do not accidentally leak into other nodes. E.g, when we have this:
            #
            #  Node.0
            #  Node.1
            #   Node.1.0
            #   Node.1.1
            #  Node.2
            #  ...
            #
            # Then we usually do want for IterateGraph(Node.1, ...) to only look at Node.1, Node.1.0, and
            # Node.1.1, but not at anything after that, e.g., Node.2.
            if root is None:
                root = node
            
            # Start iterating over the nodes ...
            while isinstance(node, c4d.BaseList2D):
                # Yield the node itself if it does match the type criteria.
                if types is None or any(node.IsInstanceOf(t) for t in types):
                    yield node
        
                # Yield nodes which are placed in branches below #node.
                #
                # Branches are basically just contextualized hierarchical relations, e.g, a document has
                # an object branch, where all objects are placed. We are iterating here over all these 
                # relations of a node. The content of a branch is not attached directly below a branch,
                # but under a GeListHead, a specialized variant of the hierarchy interface GeListNode of
                # the Cinema 4D classic API.
                if IS_DEBUG:
                    print (f"\tBranches({node})")
                for branchDict in node.GetBranchInfo(c4d.GETBRANCHINFO_NONE):
                    # The thing to which all nodes are attached which are in the #branchName relation with
                    # #node. For a BaseDocument and its object branch, #geListHead.GetDown() would return 
                    # the first object in that document.
                    geListHead: c4d.GeListHead = branchDict.get("head", None)
                    # A human readable description of the branch purpose, e.g., "Objects" for the object
                    # branch of a document.
                    branchName: str = branchDict.get("name", None)
                    # The branch ID, this describes the base type of the nodes to find in this branch, for
                    # the object branch of a document this would be c4d.Obase (5155).
                    branchId: int = branchDict.get("id", None)
                    # There are also flags in branch data, but I am ignoring them here.
          
                    # This is malformed branching data, should not happen :)
                    if None in (geListHead, branchId):
                        continue
                    if IS_DEBUG:
                        print (f"\t\t{branchName = }, {branchId = }")
                    
                    # Get the first actual node in the branch, this can be None as Cinema often creates
                    # empty branches in nodes for future access.
                    firstNodeInBranch: typing.Optional[c4d.BaseList2D] = geListHead.GetDown()
                    # Determine based on the branch type if we want to branch into this branch, i.e., the
                    # user has passed Ocube and we must decide if we want to branch into Obase (yes, we 
                    # do want to :)).
                    shouldBranch: bool = (True 
                                          if types is None else 
                                          any(HasSymbolBase(t, branchId) for t in types))
        
                    # Step over empty branches or branches which do not contain relevant nodes.
                    if firstNodeInBranch is None or not shouldBranch:
                        continue
        
                    # Iterate over each node in the branch which is its own hierarchy with the root
                    # #geListHead.
                    for branchNode in IterateGraph(firstNodeInBranch, types, inspectCaches, root):
                        yield branchNode
                
                # --- End of branching iteration.
        
                # Yield the content of BaseObject caches.
                if inspectCaches and isinstance(node, c4d.BaseObject):
                    for cache in (node.GetCache(), node.GetDeformCache()):
                        for cacheNode in IterateGraph(cache, types, inspectCaches, root):
                            yield cacheNode
        
                # Yield nodes which are in a direct hierarchical down relation with #node, e.g., the 
                # children of objects, layers, render data, etc.
                for descendant in IterateGraph(node.GetDown(), types, inspectCaches, root):
                    yield descendant
        
                # We update node for the outer loop to the next sibling of #node. This a bit weird form
                # of handling direct hierarchies both with the outer loop, the loop above, and this
                # GetNext() call is necessary to implement this semi-iteratively, and thereby avoid full
                # recursion which could easily lead to stack overflows/Python's deep recursion mechanism
                # kicking in.
                #
                # We also only go to the 'next' thing when #node was not what the user considered to be
                # the root.
                node = node.GetNext() if node != root else None
        
        def main() -> None:
            """
            """
            def printNode(node: c4d.BaseList2D) -> None:
                """Prints a node in a fashion convenient for this example.
                """
                # Print the class name of the node, e.g., BaseObject, its name, e.g., "Cube", the Python
                # __str__ style memory location of the node, and if the node is active, i.e., selected, or 
                # not.         
                memLoc: str = f"0x{hex(id(node)).upper()[2:].zfill(16)}"
                isActive: bool = node.GetBit(c4d.BIT_ACTIVE)
                if not IS_DEBUG:
                    print (f"\t{node.__class__.__name__} '{node.GetName()}' at {memLoc} ({isActive = })")
        
            
            # A very simple call to the function, we just request all layers in the passed document. 
            print ("\nIterateGraph(doc, [c4d.Olayer])")
            for node in IterateGraph(doc, [c4d.Olayer]):
                printNode(node)
        
            # Iterate over all cube objects and any type of tag in the passed document.
            print ("\nIterateGraph(doc, [c4d.Ocube, c4d.Tbase])")
            for node in IterateGraph(doc, [c4d.Ocube, c4d.Tbase]):
                printNode(node)
        
            # We can also look at the partial graph of something, here we are looking for all phong tags and
            # descendant of the object named "Cube", in this case we are also traversing the caches of 
            # objects.
            print ("\nIterateGraph(cube, [c4d.Obase, c4d.Tphong], inspectCaches=True)")
            cube: typing.Optional[c4d.BaseObject] = doc.SearchObject("Cube")
            if isinstance(cube, c4d.BaseObject):
                for node in IterateGraph(cube, [c4d.Obase, c4d.Tphong], inspectCaches=True):
                    printNode(node)
        
            # Where things get a bit tricky is when we want to look at branches which are inside branches.
            # One could implement #IterateGraph differently, so that it always iterate over everything, but
            # in the form I have implemented it here, the call below which asks for OverrideBase, i.e., 
            # take nodes will return nothing, because the takes are branches within branches, and we do not
            # go into any branches other than the take overrides. The same applies for the [c4d.Ocube, 
            # c4d.Tbase] example above, we only discover the tags because Ocube implies going into object
            # branches.
            print ("\nIterateGraph(doc, [c4d.OverrideBase])")
            for node in IterateGraph(doc, [c4d.OverrideBase]):
                printNode(node)
        
            # This will yield the overrides.
            print ("\nIterateGraph(doc, [c4d.Obase, c4d.OverrideBase])")
            for node in IterateGraph(doc, [c4d.Obase, c4d.OverrideBase]):
                printNode(node)
        
            # This would be the call for what you wanted, it will yield all objects, tags, materials, and
            # layers in #doc.
            print ("\nIterateGraph(doc, [c4d.Obase, c4d.Tbase, c4d.Mbase, c4d.Olayer])")
            for node in IterateGraph(doc, [c4d.Obase, c4d.Tbase, c4d.Mbase, c4d.Olayer]):
                printNode(node)
                # Parameters are not part of this data model, and once cannot retrieve the selection state
                # of a parameter in the Attribute Manager. But we can iterate over the description of a 
                # node.
                data: c4d.BaseContainer # The description container of the parameter
                descId: c4d.DescID # The id of the parameter.
                for i, (data, descId, _) in enumerate(node.GetDescription(c4d.DESCFLAGS_DESC_NONE)):
                    # There are many things in a description container, I am only retrieving the name of the
                    # parameter here, for details see the documentation of c4d.Description.
                    name: str = data[c4d.DESC_NAME] or data[c4d.DESC_SHORT_NAME]
                    # The value the parameter with the name #name currently has, one has however to be 
                    # careful with such broad parameter access, not all data types are wrapped in Python.
                    try:
                        value: typing.Any = node[descId]
                    except:
                        value = "Not accessible"
                    print (f"\t\tParameter '{name}'({descId = }): {value}")
                    # Break out after the first three parameters, so that we do not saturate the console
                    # too much :)
                    if i >= 3:
                        print ("\t\t...")
                        break
        
            # We can also just iterate over everything in a node, here a document, by passing no type IDs.
            print ("\nIterateGraph(doc)")
            for node in IterateGraph(doc):
                printNode(node)
        
        if __name__ == "__main__":
            main()
        

        MAXON SDK Specialist
        developers.maxon.net

        DunhouD 1 Reply Last reply Reply Quote 2
        • DunhouD
          Dunhou @ferdinand
          last edited by

          @ferdinand

          Thanks for extreme detailed explains , but I think maybe had a bad description , so you go deeper .
          In my views , your point at how to get all sence active nodes . ( It is a great explain and very useful to learn )

          But my goal is get last select node ,for more specific minimal example , when a texture tag and a cube both selected , assign material maybe a problem expecially when scripts support them at same time , so the goal more like when cube is lastest select ,apply mat to cube , and vis versa , when texture tag is the lastest selected , then ignore the cube selection.

          With your great codes , it more like returns a state more than a order .

          And you said "There are however no equivalents for the selection order for materials, tags, etc." , Is that means I cannot approach it .

          Like I have to set a more hard condition to decide what is consider first type ( It's a easy way , but not a user friendlly one , expecially to the C4D beginner , they don't want to remember so much priority conditions😂 , such as tag > object > layer or somethings)

          https://boghma.com
          https://github.com/DunHouGo

          1 Reply Last reply Reply Quote 0
          • DunhouD
            Dunhou
            last edited by

            @ferdinand Hello😊

            Another question about this active things topic , with moving on , I check videopost in RenderData , but I find SetBit(c4d.BIT_ACTIVE) can't set a videopost selected .

            For example :
            I selected a Magic Looks , and I want to set another videopost like Redshift active , but it doesn't works , I test a BIT_VPDISABLED , it does worked . Do I use a wrong mask ?

            And SetBit has some private pram like BIT_ACTIVE2 , how it works ?

            Thanks 😁

            https://boghma.com
            https://github.com/DunHouGo

            ferdinandF 1 Reply Last reply Reply Quote 0
            • ferdinandF
              ferdinand @Dunhou
              last edited by ferdinand

              Hello @dunhou,

              Thank you for reaching out to us. I am struggling a bit with understanding what you want to do. When we look and the following Render Settings dialog, we can see that there are two kinds of things we could consider 'active'.

              c3ee6bc7-d332-42a8-a41f-62bbaa56dfaa-image.png

              The Magic Bullet Looks video post is active in the sense that it has been enabled in the render settings to contribute to a rendering. This is indicated by the little check box next to it and can be set programmatically with BIT_VPDISABLED.

              Then there is the Denoiser videopost which is active in the sense as it is selected in the window. This state is not exposed in the API and you can neither read nor write it.

              Cheers,
              Ferdinand

              MAXON SDK Specialist
              developers.maxon.net

              DunhouD 1 Reply Last reply Reply Quote 0
              • DunhouD
                Dunhou @ferdinand
                last edited by

                @ferdinand

                Unfortunally , my purpore is the 2rd : set the state of Denoise . I think I should forgot this idea😂

                eg : find a certain tab in redshift like the color spcae and show it first always when open rendersetting
                d36b6865-dac1-4ab9-a1be-3f42be3407b9-image.png

                Thanks ~

                https://boghma.com
                https://github.com/DunHouGo

                ferdinandF 1 Reply Last reply Reply Quote 0
                • ferdinandF
                  ferdinand @Dunhou
                  last edited by ferdinand

                  Hey @dunhou,

                  you cannot do that. Both things, setting the active video post item in the GeListView on the left and setting the active tab in the DescriptionCustomGui on the right, would require access to the dialog implementation. The former type is also only available in C++.

                  In general, the Cinema 4D APIs provide no or only extremely limited access to the manger implementations of Cinema 4D. This is very much intentional, as we do not want third parties to write plugins which for example forcibly set the selected video post node in the Render Settings and then also open a specific tab within that node. As this could easily confuse users or lead them to the conclusion that something is broken with the Render Settings dialog, not knowing the behavior is caused by a plugin they have installed.

                  When you want to just display/modify data of a specific video post node, you should write a GeDialog with a DescriptionCustomGui in it to display the node. I once showed here how this would work for the user preferences, find also a modernized example for your use case at the end. The caveat is that in Python DescriptionCustomGui::SetActiveTabPage has not been wrapped, which will make it impossible [1] for you to set the active tab. You would have to use C++ for that.

                  Cheers,
                  Ferdinand

                  [1] Technically, there might be ways to circumvent these limitations, when getting creative with descriptions. But this would be very much a hack and therefore out of scope of support. I cannot help you with that. Also, and this might be highly subjective, just using C++ will be much easier than trying to hack the description of the redshift node in Python.

                  The result:
                  632bc4b9-43c6-4e62-977a-c8b469d43e89-image.png
                  The code:

                  """Provides a simple example for displaying a video post node with a CUSTOMGUI_DESCRIPTION in a dialog.
                  """
                  
                  import c4d
                  import typing
                  
                  doc: c4d.documents.BaseDocument # The active document.
                  dialog: c4d.gui.GeDialog # Global RedshiftDialog instance used by #main().
                  
                  
                  class RedshiftDialog(c4d.gui.GeDialog):
                      """Implements a dialog which displays a node in a CUSTOMGUI_DESCRIPTION.
                  
                      This specific implementation also retrieves the Redshift video post node of a passed document
                      and sets it as the to be displayed node. When there is no such node in the passed document, the
                      type will raise an error.
                      """
                      ID_DESCRIPTION_GUI: int = 1000
                  
                      def __init__(self, doc: c4d.documents.BaseDocument, 
                                   onCloseCallback: typing.Optional[callable] = None) -> None:
                          """Inits the dialog instance.
                          """
                          self._onCloseCallback: typing.Optional[callable[object]] = onCloseCallback
                  
                          self._doc: c4d.documents.BaseDocument = doc
                          self._descriptionGui: typing.Optional[c4d.gui.DescriptionCustomGui] = None
                          self._redshiftVideoPostNode: c4d.documents.BaseVideoPost = self._getRedshiftVideoPost()
                  
                      def _getRedshiftVideoPost(self) -> c4d.documents.BaseVideoPost:
                          """Attempts to retrieve the Redshift video post node from #self._doc.
                          """
                          renderData: c4d.documents.RenderData = self._doc.GetActiveRenderData()
                          videoPost: c4d.documents.BaseVideoPost = renderData.GetFirstVideoPost()
                  
                          while videoPost:
                              # This is 2023.0 code, in prior releases you must replace the symbol c4d.VPrsrenderer 
                              # with the integer 1036219.
                              if videoPost.GetType() == c4d.VPrsrenderer:
                                  break
                              videoPost = videoPost.GetNext()
                  
                          # When #videoPost is #None at this point, it means there is no Redshift video post in the
                          # active render settings of #doc. You could of course also add it yourself here, but I did
                          # not do that.
                          if not isinstance(videoPost, c4d.documents.BaseVideoPost):
                              raise RuntimeError("Could not find Redshift renderer in document.")
                          
                          return videoPost
                  
                      def CreateLayout(self) -> None:
                          """Adds the description custom GUI to the dialog which is used to display the Redshift video
                          post node.
                          """
                          self.GroupBorderSpace(10, 10, 10, 10)
                  
                          bc: c4d.BaseContainer = c4d.BaseContainer()
                          bc[c4d.DESCRIPTION_ALLOWFOLDING] = True
                          bc[c4d.DESCRIPTION_SHOWTITLE] = False
                          bc[c4d.DESCRIPTION_NOUNDO] = False
                          bc[c4d.DESCRIPTION_OBJECTSNOTINDOC] = True
                          bc[c4d.DESCRIPTION_FORCEGETOBJECT] = True
                          bc[c4d.DESCRIPTION_MODALMODE] = 1
                          bc[c4d.DESCRIPTION_LEFTMATEDITREGION] = False
                          bc[c4d.DESCRIPTION_SCALE_ALL_ELEMENTS] = False
                          bc[c4d.DESCRIPTION_NO_SHOW_SUBCHANNELS] = False
                          bc[c4d.DESCRIPTION_OPEN_ALL_GROUPS] = False
                          bc[c4d.DESCRIPTION_NO_TAKE_OVERRIDES] = False
                          bc[c4d.DESCRIPTION_SINGLEDESC_MODE] = False
                          bc[c4d.DESCRIPTION_MANAGER_TYPE] = True
                          bc[c4d.DESCRIPTION_HIDE_EMPTY_GROUPS] = True
                  
                          self._descriptionGui = self.AddCustomGui(
                              RedshiftDialog.ID_DESCRIPTION_GUI, c4d.CUSTOMGUI_DESCRIPTION, "",
                              c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, 600, 300, bc)
                          if not isinstance(self._descriptionGui, c4d.gui.DescriptionCustomGui):
                              return MemoryError("Could not allocate CUSTOMGUI_DESCRIPTION.")
                  
                          self._descriptionGui.SetObject(self._redshiftVideoPostNode)
                          return True
                  
                      def AskClose(self) -> bool:
                          """Call our callback when the dialog is about to be closed.
                  
                          Not really needed in this case and only a fragment of the old example.
                          """
                          if self._onCloseCallback:
                              self._onCloseCallback(self)
                          return False
                  
                  def on_close(host: object) -> None:
                      """Called by the dialog instance #MY_DIALOG when it is closing.
                      """
                      print (f"Whee, I got called by a '{host.__class__.__name__}' instance.")
                  
                  
                  def main() -> None:
                      """
                      """
                      # This is the global dialog instance hack to open and maintain an async dialog in a Script 
                      # Manager script. It will result in a dangling dialog reference when the script has closed,
                      # please do not use this hack in a production environment as it can lead to crashes and 
                      # therefore data loss.
                      global MY_DIALOG
                      MY_DIALOG = RedshiftDialog(doc, on_close)
                      MY_DIALOG.Open(c4d.DLG_TYPE_ASYNC)
                  
                  
                  if __name__ == "__main__":
                      main()
                  

                  MAXON SDK Specialist
                  developers.maxon.net

                  DunhouD 1 Reply Last reply Reply Quote 0
                  • DunhouD
                    Dunhou @ferdinand
                    last edited by

                    @ferdinand

                    Thanks for the detailed explain😳

                    I think DescriptionCustomGui is the best way to solve this for now .

                    With learning furthur , maybe I will try a C++ version , but for me it's not time .

                    Anyway , It is helpful for this techniclly explain and the example🤝

                    https://boghma.com
                    https://github.com/DunHouGo

                    1 Reply Last reply Reply Quote 0
                    • ferdinandF ferdinand referenced this topic on
                    • ferdinandF ferdinand referenced this topic on
                    • ferdinandF ferdinand referenced this topic on
                    • ferdinandF ferdinand referenced this topic on
                    • M m_adam referenced this topic on
                    • First post
                      Last post