UNSOLVED Can I get active things order with python?

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

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()

@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)