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

    Still not possible to find out what's visible in the Timeline?

    Cinema 4D SDK
    r21 c++ python
    4
    19
    2.5k
    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.
    • CairynC
      Cairyn
      last edited by

      Hello; I was trying to implement a function on the timeline, sadly it still doesn't seem possible to find out what objects (or tracks) are currently shown in the timeline. Moreover, keys and tracks seem to stay selected even if they become invisible for the respective timeline.

      I have found a relatively recent topic on the matter:
      https://developers.maxon.net/forum/topic/10989/how-to-get-the-selected-keyframes-in-active-timeline-fcurve-manager
      which ended with the statement "There is no way to get a filtered list of objects visible in the timeline. Sorry."

      Must I conclude that this is still not possible? That would mean that there is no way at all to implement a function that works on "visible objects" (as any self-respecting interactive function should) in the timeline. Which is strange with all those timeline-affecting NBITs around.

      Does anybody know a way around that? How do you implement "I do activity XYZ with all visible and selected keys in timeline 1"?

      1 Reply Last reply Reply Quote 0
      • CairynC
        Cairyn
        last edited by

        In addition: I played around with the code in that thread, and I wonder where you would even get all your tracks. As far as I can see, you need to call GetCTracks() on the animated object. That is a function of BaseList2D however, which is a pretty basic class, so... about anything in a scene tree can be animated.

        That's great as a concept, but I don't see how to find all tracks. For starters, I went through the document itself (I didn't find any tracks there but who knows), the objects, their tags, and the materials. That's not enough however: I did not find the proper object that stores the global Dynamics settings (they can be animated, but the track for e.g. Enabled is not in the document). I also can't see a track in the XPresso tag when I animate a node attribute - I suppose I have to parse the whole node tree to find these tracks. I imagine there are many more potential tracks that are possibly hidden in the depths of a scene tree which I currently can't even imagine. (Not to think of plugins that may create their own object structures with some track deep, deep inside.)

        I don't see the following functions in the API:

        • one that would return a list of all tracks in the scene.
        • one that would return a list of BaseList2Ds that possess tracks (I could call GetCTracks on those, obviously).
        • one that would return the tracks currently shown in a timeline.
        • documentation on where those special tracks (global settings, XPresso nodes, likely others) can be found

        (I have read the C++ manuals on CKey, CCurve, and CTrack without finding any global "find all tracks" functionality.)

        Interestingly, using the shortcut Alt-T on a tag does give me the tag's tracks in the timeline (despite "Show Tracks..." missing from the tag's RMenu), including the node animation for an XPresso tag, so there seems to be some functionality behind it that already deep-searches the structure for tracks.

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

          Hi,

          I have problems to pinpoint your actual question(s), because you are both talking about keys and objects in the regards of being hidden and selected, without clearly distinguishing between them (at least that is how it is appearing for me).

          1. Keys cannot be hidden in a Timeline Editor AFAIK. Keys can be muted, which then is reflected in the BaseContainer of the respective CKey instance.
          2. To my knowledge it is true, that we still cannot determine if a key is being selected in a Timeline Editor or not.
          3. But we can determine if an object is selected in a Timeline Editor (to some degree, see 4.) and if it is visible via GeListNode.GetNBit.
          4. However, objects "share" their selection state between the Object Manager and a Timeline Editor. Which can lead to the scenario that an object can be hidden and selected at the same time in a Timeline Editor.

          Here is a quick example which will print out all object nodes which are visible and selected in the Timeline 1 Editor in the currently active document.

          import c4d
          
          def iter_scene_graph(doc):
              """Iterates the ``BaseObject`` nodes of the scene graph of a passed
               document.
              
              Args:
                  doc (``c4d.documents.BaseDocument``): The document to iterate.
              
              Yields:
                  c4d.BaseObject: An object node in the scene graph of the passed 
                  document.
              
              Raises:
                  TypeError: When ``doc`` ist not a ``c4d.documents.BaseDocument``.
              """
              if not isinstance(doc, c4d.documents.BaseDocument):
                  raise TypeError(doc)
          
              node = doc.GetFirstObject()
              visited = []
          
              while(node):
                  if node not in visited:
                      visited.append(node)
                      yield node
          
                  # We will tarverse depth first.
                  down_ = node.GetDown()
                  up_ = node.GetUp()
                  next_ = node.GetNext()
          
                  if down_ and down_ not in visited:
                      node = down_
                  elif next_:
                      node = next_
                  else:
                      node = up_
          
          def main():
              """Entry point.
              """
              for node in iter_scene_graph(doc):
                  selected = node.GetNBit(c4d.NBIT_TL1_SELECT)
                  hidden = node.GetNBit(c4d.NBIT_TL1_HIDE)
                  if selected and not hidden:
                      print "Do stuff with node '{node}'".format(node=node)
              
          if __name__=='__main__':
              main()
          

          Cheers,
          zipit

          MAXON SDK Specialist
          developers.maxon.net

          1 Reply Last reply Reply Quote 0
          • CairynC
            Cairyn
            last edited by

            Well, ultimately I want to get at the keys, but that part is trivial (get the CCurve, get the CKeys).

            My questions are in the bullet points: I am missing these functions, how can I implement them with my own code? (Or potentially better: Where in the API are they? Not that I have much hope about that...)

            Let's look at the first bullet point: where do I get a list of all tracks in a scene (regardless of the timeline for now)? Here's what code I currently have, in absence of an API function:

            """Name-en-US: Test - List all tracks
            Description-en-US: track list"""
            
            import c4d
            from c4d import gui
            
            def GetNextObject(op):
                if op==None: return None
                if op.GetDown(): return op.GetDown()
                while not op.GetNext() and op.GetUp(): op = op.GetUp()
                return op.GetNext()
            
            def IterAllObjects(doc):
                if doc is None: return
                op = doc.GetFirstObject()
                while op is not None:
                    yield op
                    op = GetNextObject(op)
            
            def IterAllShaders(obj):
                if obj is None: return
                op = obj.GetFirstShader()
                while op is not None:
                    yield op
                    op = GetNextObject(op)
            
            def IterAllMaterials(doc):
                if doc is None: return
                op = doc.GetFirstMaterial()
                while op is not None:
                    yield op
                    op = op.GetNext()
            
            def IterAllObjTracks(doc):
                if doc is None: return
                for track in doc.GetCTracks():
                    yield track, "Document"
                for obj in IterAllObjects(doc):
                    for track in obj.GetCTracks():
                        yield track, obj.GetName()
                    for tag in obj.GetTags():
                        for track in tag.GetCTracks():
                            yield track, obj.GetName() + "::" + tag.GetName()
                        for shader in IterAllShaders(tag):
                            for track in shader.GetCTracks():
                                yield track, obj.GetName() + "::" + tag.GetName() + "::" + shader.GetName()
                        if tag.GetType() == c4d.Texpresso:
                            nodemaster = tag.GetNodeMaster()
                            if nodemaster:
                                node = nodemaster.GetRoot()
                                while node is not None:
                                    for track in node.GetCTracks():
                                        yield track, obj.GetName() + "::" + tag.GetName() + "::" + node.GetName()
                                    node = GetNextObject(node)
                    for shader in IterAllShaders(obj):
                        for track in shader.GetCTracks():
                            yield track, obj.GetName() + "::" + shader.GetName()
            
            def IterAllMatTracks(doc):
                if doc is None: return
                for mat in IterAllMaterials(doc):
                    for track in mat.GetCTracks():
                        yield track, mat.GetName()
                    for shader in IterAllShaders(mat):
                        for track in shader.GetCTracks():
                            yield track, mat.GetName() + "::" + shader.GetName()
            
            def GetActiveTracks(doc, timeline_bit=c4d.NBIT_TL1_SELECT):
                # Timeline 1: c4d.NBIT_TL1_SELECT,
                # F-Curve 1: c4d.NBIT_TL1_FCSELECT,
                if doc is None: return
                active_tracks = []
            
                for track, name in IterAllObjTracks(doc):
                    if track.GetNBit(timeline_bit):
                        active_tracks.append(track)
                for track, name in IterAllMatTracks(doc):
                    if track.GetNBit(timeline_bit):
                        active_tracks.append(track)
            
                return active_tracks
            
            def main():
            
                print " "
                print "All Tracks"
                print "============="
                for track, name in IterAllObjTracks(doc):
                    print name, track.GetName()
                for track, name in IterAllMatTracks(doc):
                    print name, track.GetName()
                print " "
                print "Active Tracks"
                print "============="
                activeTracks = GetActiveTracks(doc)
                if activeTracks:
                    for track in activeTracks:
                        print track.GetName()
            
            if __name__=='__main__':
                main()
            

            Note that it is not sufficient to go through all objects, I also need to consider all materials, all tags, all shaders which may not be limited to materials but also exist on objects or tags, and all nodes within an XPresso tag. Any of these may have an animation track.

            The code above works fine, but I can only find what I am looking for... For example, I have added an animation track in a test scene to the project's Dynamics settings. This code does not find that track because I have no idea in what object I need to look. (It's not the document, I already consider that one - if the doc can even hold any tracks at all, which I don't know).

            I also cannot know what I may miss with this code: Can an XPresso node have shaders again? May a shader have sub-trees that I don't know about? Can further tracks hide in proprietary plugins? Are there tag types with other subtrees of BaseList2D that may have tracks? How can I ever be sure that I have collected all the tracks?

            1 Reply Last reply Reply Quote 0
            • CairynC
              Cairyn
              last edited by

              The next question would be, how do I know whether any of these tracks are visible in the Timeline? (Alternatively, which objects - BaseList2D - are visible in the timeline.)

              I have checked the flags with the code above, and neither NBIT_TL1_SELECT nor NBIT_TL1_SELECT2 are doing what I want. They do represent the selection status of the objects, so... no complaints there... but they are not limited to visible elements. Here's an experiment:

              1. I create an animation with a cube and a sphere (each has a position track).
              2. I select them with Alt-T for the dope sheet in timeline 1, and use the timeline's bookmark function to create two views:
              3. One view shows only the cube's tracks, the other view shows only the sphere's tracks.
              4. I play with the selections and the bookmarks/views, and call my script from above to find out what tracks bear the NBIT_TL1_SELECT flag.

              Turns out: The script lists all selected elements even if the cube or the sphere are not visible in the timeline (the HIDE flag is never set, I work only with the bookmarks).

              That is logical; after all, when I change the view, the selected elements stay selected, even after being hidden for a while.

              But: That means the flag doesn't tell me what is currently visible in the timeline.

              Moreover: If I click on a track, I apparently deselect all others. "All others" are only the visible ones, though: running the script, or changing the bookmark, quickly proves that the invisible selected objects are still selected! The internal functionality seems to take that into account, e.g. when I DEL a few tracks, the selected ones on currently hidden objects are not deleted. As a user would expect.

              And that's a functionality I cannot replicate with my own code. I can find all selected tracks for a timeline, but these selected tracks are not necessarily visible in the TL at any given moment. And I can't find out which are. NBIT_TL1_SELECT plus NBIT_TL1_HIDE are not sufficient.

              Scene with simple animations_0002.c4d

              1 Reply Last reply Reply Quote 0
              • CairynC
                Cairyn
                last edited by

                Here's a more complete example that goes down to the curves and keys and counts keys in certain states:

                import c4d
                from c4d import gui
                
                def GetNextObject(op):
                    if op==None: return None
                    if op.GetDown(): return op.GetDown()
                    while not op.GetNext() and op.GetUp(): op = op.GetUp()
                    return op.GetNext()
                
                def IterAllObjects(doc):
                    if doc is None: return
                    op = doc.GetFirstObject()
                    while op is not None:
                        yield op
                        op = GetNextObject(op)
                
                def IterAllShaders(obj):
                    if obj is None: return
                    op = obj.GetFirstShader()
                    while op is not None:
                        yield op
                        op = GetNextObject(op)
                
                def IterAllMaterials(doc):
                    if doc is None: return
                    op = doc.GetFirstMaterial()
                    while op is not None:
                        yield op
                        op = op.GetNext()
                
                def IterAllObjTracks(doc):
                    if doc is None: return
                    for track in doc.GetCTracks():
                        yield track, "Document"
                    for obj in IterAllObjects(doc):
                        for track in obj.GetCTracks():
                            yield track, obj.GetName()
                        for tag in obj.GetTags():
                            for track in tag.GetCTracks():
                                yield track, obj.GetName() + "::" + tag.GetName()
                            for shader in IterAllShaders(tag):
                                for track in shader.GetCTracks():
                                    yield track, obj.GetName() + "::" + tag.GetName() + "::" + shader.GetName()
                            if tag.GetType() == c4d.Texpresso:
                                nodemaster = tag.GetNodeMaster()
                                if nodemaster:
                                    node = nodemaster.GetRoot()
                                    while node is not None:
                                        for track in node.GetCTracks():
                                            yield track, obj.GetName() + "::" + tag.GetName() + "::" + node.GetName()
                                        node = GetNextObject(node)
                        for shader in IterAllShaders(obj):
                            for track in shader.GetCTracks():
                                yield track, obj.GetName() + "::" + shader.GetName()
                
                def IterAllMatTracks(doc):
                    if doc is None: return
                    for mat in IterAllMaterials(doc):
                        for track in mat.GetCTracks():
                            yield track, mat.GetName()
                        for shader in IterAllShaders(mat):
                            for track in shader.GetCTracks():
                                yield track, mat.GetName() + "::" + shader.GetName()
                
                def showTrack(track, name):
                    print name, track.GetName(), 
                    print "SelDope," if track.GetNBit(c4d.NBIT_TL1_SELECT) else "UnselDope,",
                    print "SelFC," if track.GetNBit(c4d.NBIT_TL1_SELECT2) else "UnselFC,",
                    print "SelCurve," if track.GetNBit(c4d.NBIT_TL1_FCSELECT) else "UnselCurve,",
                    if track.GetNBit(c4d.NBIT_TL1_HIDE):
                        print "Hidden!"
                    else:
                        curve = track.GetCurve()
                        if curve == None:
                            print "No curve!"
                        else:
                            keySelected = 0
                            keySelectedFC = 0
                            keyMute = 0
                            # curve does not have a function "GetFirstKey()"
                            for key_id in xrange(curve.GetKeyCount()):
                                key = curve.GetKey(key_id)
                                if key.GetNBit(c4d.NBIT_TL1_SELECT):
                                    keySelected += 1
                                if key.GetNBit(c4d.NBIT_TL1_SELECT2):
                                    keySelectedFC += 1
                                if key.GetNBit(c4d.NBIT_CKEY_MUTE):
                                    keyMute += 1
                            print "Keys:", curve.GetKeyCount(), "SelDope:", keySelected, "SelFC:", keySelectedFC, "Mute:", keyMute
                    
                def main():
                    print " "
                    print "Keys per track"
                    print "=============="
                    for track, name in IterAllObjTracks(doc):
                        showTrack(track, name)
                    for track, name in IterAllMatTracks(doc):
                        showTrack(track, name)
                
                if __name__=='__main__':
                    main()
                

                showTrack and main are the functions that are different from the last example. It is easy to verify now that stuff stays selected independently of what is shown in the timeline.

                1 Reply Last reply Reply Quote 0
                • P
                  PluginStudent @Cairyn
                  last edited by PluginStudent

                  @Cairyn said in Still not possible to find out what's visible in the Timeline?:

                  one that would return a list of all tracks in the scene.

                  Things in Cinema are often stored as internal branches (GetBranchInfo()). So you can read all the branches of all things to get all the things.

                  The "Active Object" examples does that to list the content of the scene, including the tracks.

                  all_the_tracks.jpg

                  1 Reply Last reply Reply Quote 1
                  • CairynC
                    Cairyn
                    last edited by

                    Thank you, @PluginStudent . I have tried to translate the C++ code from the example into a basic Python script that just writes the GetBranchInfo tree to the console; sadly there is a mysterious crash/hang somewhere...

                    import c4d
                    from c4d import gui
                    
                    
                    emCount = 0
                    
                    def showBranchInfo(depth, obj):
                        global emCount
                    
                        # Validity check
                        if obj == None: return
                    
                        # Object output
                        emCount += 1
                        print "  " * depth, "Count=", emCount, "(object)"
                        if isinstance(obj, c4d.documents.BaseDocument):
                            print "  " * depth, "DOC:", obj.GetDocumentName()
                        elif not isinstance(obj, c4d.BaseList2D):
                            print "  " * depth, "Not a BaseList2D: ", obj
                            return
                        else:
                            print "  " * depth, "OBJ:", obj.GetName()
                    
                        bis = obj.GetBranchInfo()
                        if bis == None or len(bis) == 0:
                            return
                        for bi in bis: # list of dict{‘head’, ‘name’, ‘id’, ‘flags’}
                            if len(bi) == 0: return # ### Test
                            listHead = bi['head']
                            nodeName = bi['name']
                    
                            # List head output
                            emCount += 1
                            node = None
                            print "  " * (depth+1), "Count=", emCount, "(list head)", nodeName
                            if listHead == None:
                                print "  " * (depth+1), "No list head"
                            elif not isinstance(listHead, c4d.GeListHead):
                                print "  " * (depth+1), "Not a GeListHead:", listHead
                            else:
                                print "  " * (depth+1), nodeName, listHead
                                node = listHead.GetFirst()
                    
                            # Content of sublist branch
                            locCount = 0
                            if node == None:
                                print "  " * (depth+2), "Empty list"
                            while node != None:
                                emCount += 1
                                print "  " * (depth+2), "El:", locCount, node
                                locCount += 1
                                if isinstance(node, c4d.BaseList2D):
                                    showBranchInfo(depth + 3, node)
                                    # no else case; node was already printed
                                node = node.GetNext()
                        # missing here: recurse into children of "node" or "obj"
                    
                    
                    def main():
                        print " "
                        print "GetBranchInfo"
                        print "============="
                        showBranchInfo(1, doc)
                    
                    if __name__=='__main__':
                        main()
                    

                    If I limit emCount or depth to certain values, this recursion works fine (as mentioned in the code it's not going down into the child lists of a BaseObject but that's a thing I can add later).
                    If I just run the code with no limits, C4D hangs (and in one instance even crashed with an access violation) while doing... something. Even after two days of experimenting, I have not found the exact point where it fails. (Half of the code is already validation...) I guess I will need to set up a complete debugging environment to catch the culprit... before even returning to the original question...

                    1 Reply Last reply Reply Quote 0
                    • P
                      PluginStudent
                      last edited by

                      Not sure if it is related to your crash, but I have never seen someone using isinstance() this way.

                      In the C4D API, there is IsInstanceOf() to check for inheritance or type. See also Type.

                      CairynC 2 Replies Last reply Reply Quote 0
                      • CairynC
                        Cairyn @PluginStudent
                        last edited by Cairyn

                        @PluginStudent isinstance is the plain Python class comparison. IsInstanceOf() does not work here because it compares against type constants, and there is (AFAIK) no such constant for BaseDocument or BaseList2D or GeListHead.

                        EDIT: Oh, I'm stupid. I just saw that such a constant is used in the original example... okay, let's try that...

                        1 Reply Last reply Reply Quote 0
                        • CairynC
                          Cairyn @PluginStudent
                          last edited by

                          @PluginStudent No, didn't work with IsInstanceOf either.

                          For completeness: Tbasedocument and Tbaselist2d exist but are only documented in the C++ manual; missing in Python. Tgelisthead does not exist in either, so I still need to check the class against that with the Pythonic isinstance.

                          1 Reply Last reply Reply Quote 0
                          • M
                            m_adam
                            last edited by m_adam

                            Hi Just to let you know its seems there is currently a bug somewhere (still have to figure out) but to avoid any issue check if the returned GeListHead and then if you iterate over all its children that each c4dAtom returned is alive with C4DAtom.IsAlive before doing anything on it(e.g.s print them).

                            That should fix your issue.

                            Cheers,
                            Maxime.

                            MAXON SDK Specialist

                            Development Blog, MAXON Registered Developer

                            CairynC 1 Reply Last reply Reply Quote 0
                            • CairynC
                              Cairyn @m_adam
                              last edited by

                              @m_adam Thank you, unfortunately that didn't work either... I check the object, the list head, and the list nodes all for being alive, and I do not get any output that they aren't. The crashes continue.

                              I have compiled the SDK, and the "Active Object Properties" dialog works fine even in places where the Python script hangs. So, it might be a Python-only issue.

                              So far, I think that these places are when the script encounters a "Tags" object (type 200001048). The first place I saw this was under ProjektHooks/PLKHUD/PSUNDOHEAD/PKHOP. When I skipped that branch explicitly, the next place of crash was under Modeling Objects Hooks/Modeling Objects Branch/Plane Manipulator and Pivot Manipulator (both branches contain a "Tags" element). Didn't continue after that.

                              However, trying to exclude these node types didn't help either. Apparently, the objects are so volatile that any way of touching them creates the crash already.
                              (Since the scene tags also hide under these nodes, I do not want to exclude them, anyway.)

                              Here's the code I tried last:

                              import c4d
                              from c4d import gui
                              
                              emCount = 0
                              handledList = []
                              
                              def showBranchInfo(depth, obj, f):
                                  global emCount, handledList, maxCount, dialogCount
                              
                                  try:
                                      # Validity check
                                      if obj == None: return
                                      if obj in handledList:
                                          print "Already handled! : ", obj
                                          print >>f, "Already handled! : ", obj
                                          f.flush()
                                          return
                                      else:
                                          handledList.append(obj)
                                      if not obj.IsAlive():
                                          print "Object not alive!"
                                          print >>f, "Object not alive!"
                                          f.flush()
                                          return
                                      if obj.GetType() == 200001048:
                                          print "Object is 200001048!"
                                          print >>f, "Object is 200001048!"
                                          f.flush()
                                          return
                                  except:
                                      print >>f, "ERROR in validity check"
                                      f.flush()
                                      return
                              
                                  try:
                                      # Object output
                                      emCount += 1
                                      print "  " * depth, "Count=", emCount, "(object)"
                                      print >>f, "  " * depth, "Count=", emCount, "(object)"
                                      f.flush()
                                      if isinstance(obj, c4d.documents.BaseDocument):
                                          print "  " * depth, "DOC:", obj.GetDocumentName()
                                          print >>f, "  " * depth, "DOC:", obj.GetDocumentName()
                                          f.flush()
                                          # this is also a BaseList2D so we can continue
                                      elif not isinstance(obj, c4d.BaseList2D):
                                          print "  " * depth, "Not a BaseList2D: ", obj
                                          print >>f, "  " * depth, "Not a BaseList2D:", obj
                                          f.flush()
                                          return
                                      else:
                                          print "  " * depth, "OBJ:", obj.GetName()
                                          print >>f, "  " * depth, "OBJ:", obj.GetName()
                                          f.flush()
                                  except:
                                      print >>f, "ERROR in object output"
                                      f.flush()
                                      return
                              
                                  try:
                                      child = obj.GetDown()
                                      if child != None:
                                          while child:
                                              showBranchInfo(depth + 1, child, f)
                                              child = child.GetNext()
                                  except:
                                      print >>f, "ERROR in subobject recursion"
                                      f.flush()
                                      return
                              
                                  try:
                                      bis = obj.GetBranchInfo()
                                      if bis == None:
                                          print "ERROR: no branch info retrieved"
                                          print >>f, "ERROR: no branch info retrieved"
                                          f.flush()
                                          return
                                      # empty branch info is allowed, so no len() check!
                                  except:
                                      print >>f, "ERROR in GetBranchInfo"
                                      f.flush()
                                      return
                              
                                  try:
                                      for bi in bis: # list of dict{‘head’, ‘name’, ‘id’, ‘flags’}
                              
                                          try:
                                              if len(bi) == 0:
                                                  print "ERROR: no entries in dict list"
                                                  print >>f, "ERROR: no entries in dict list"
                                                  f.flush()
                                                  return
                                              listHead = bi['head']
                                              nodeName = bi['name']
                                              # print bi['id']
                                              # print bi['flags']
                                          except:
                                              print >>f, "ERROR in dict list check"
                                              f.flush()
                                              return
                              
                                          try:
                                              # List head output
                                              emCount += 1
                                              node = None
                                              print "  " * (depth+1), "Count=", emCount, "(list head)", nodeName
                                              print >>f, "  " * (depth+1), "Count=", emCount, "(list head)", nodeName
                                              f.flush()
                                              # Crash irgendwo hier
                                              if listHead == None:
                                                  print "  " * (depth+1), "No list head"
                                                  print >>f, "  " * (depth+1), "No list head"
                                                  f.flush()
                                              elif not listHead.IsAlive():
                                                  print "List head not alive!"
                                                  print >>f, "List head not alive!"
                                                  f.flush()
                                              elif not isinstance(listHead, c4d.GeListHead):
                                                  print "  " * (depth+1), "Not a GeListHead:", listHead
                                                  print >>f, "  " * (depth+1), "Not a GeListHead:", listHead
                                                  f.flush()
                                              else:
                                                  print "  " * (depth+1), nodeName, listHead
                                                  print >>f, "  " * (depth+1), nodeName, listHead
                                                  f.flush()
                                                  node = listHead.GetFirst()
                                          except:
                                              print >>f, "ERROR in list head output"
                                              f.flush()
                                              return
                              
                                          try:
                                              # Content of sublist branch
                                              locCount = 0
                                              if node == None:
                                                  print "  " * (depth+2), "Empty list"
                                                  print >>f, "  " * (depth+2), "Empty list"
                                                  f.flush()
                                              while node != None:
                                                  if node.IsAlive():
                                                      if node.GetType() != 200001048:
                                                          emCount += 1
                                                          print "  " * (depth+2), "El:", locCount, node
                                                          print >>f, "  " * (depth+2), "El:", locCount, node
                                                          f.flush()
                                                          locCount += 1
                                                          if isinstance(node, c4d.BaseList2D):
                                                              showBranchInfo(depth + 3, node, f)
                                                              # no else case; node was already printed
                                                      else:
                                                          print "List node is 200001048!"
                                                          print >>f, "List node is 200001048!"
                                                          f.flush()
                                                          return
                                                  else:
                                                      print "List node not alive!"
                                                      print >>f, "List node not alive!"
                                                      f.flush()
                                                  node = node.GetNext()
                                          except:
                                              print >>f, "ERROR in sublist branch handling"
                                              f.flush()
                                              return
                              
                                      # missing here: recurse into children of "node" or "obj"
                                  except:
                                      print >>f, "ERROR in dictionary loop"
                                      f.flush()
                                      return
                              
                                  try:
                                      print >>f, "  " * depth, "Returning from depth:", depth
                                      f.flush()
                                  except:
                                      print >>f, "ERROR in return"
                                      f.flush()
                                      return
                              
                              
                              def main():
                                  print " "
                                  print "GetBranchInfo"
                                  print "============="
                                  f = open("L:/getbranchinfo.txt", "at")
                                  showBranchInfo(1, doc, f)
                                  f.close()
                              
                              
                              if __name__=='__main__':
                                  main()
                              

                              You can happily ignore the try/except blocks, as they never caught any error.
                              Considering that the code now consists of 80% error checking, this is a very frustrating exercise.

                              Interestingly, my initial example that uses GetCTracks() works fine (for what it does) and does not crash, even when handling (scene) tags. I suspect the issue ultimately lies in the GetBranchInfo() function somewhere.

                              1 Reply Last reply Reply Quote 0
                              • M
                                m_adam
                                last edited by

                                creating a cube and call op.GetBranchInfo is enough to trigger a crash, since its a C++ crash, there is no room for python to catch it.

                                MAXON SDK Specialist

                                Development Blog, MAXON Registered Developer

                                CairynC 1 Reply Last reply Reply Quote 0
                                • CairynC
                                  Cairyn @m_adam
                                  last edited by

                                  @m_adam oops... didn't expect that, since the Active Object Properties sample works fine (solely in C++).

                                  1 Reply Last reply Reply Quote 0
                                  • CairynC
                                    Cairyn
                                    last edited by

                                    Okay, it's definitely the Tags node. I added some code to check for the name "Tags" in the list returned by GetBranchInfo() to skip that dictionary entry before even the list head is touched. That works, the recursion/iteration is running through without crash.

                                    Of course, that makes the scene tags disappear from the list, so I also added in an else case that explicitly calls GetTags() on the original object, and continues the recursion with any returned tags.

                                    For testing, I also added a collector list for the tracks (which were the original goal of this exercise...) and print the tracks at the end of the code. For a few test scenes, this apparently works; I have no idea about the overall soundness and completeness of this workaround though.

                                    Here's the code for anyone who might face the same issue:

                                    import c4d
                                    from c4d import gui
                                    
                                    emCount = 0
                                    handledList = []
                                    tracks = []
                                    
                                    def showBranchInfo(depth, obj, f):
                                        global emCount, handledList, maxCount, dialogCount, tracks
                                    
                                        # Validity check
                                        if obj == None: return
                                        if not obj.IsAlive():
                                            print "Object not alive!"
                                            print >>f, "Object not alive!"
                                            f.flush()
                                            return
                                        if obj.GetType() == 200001048:
                                            print "Object is 200001048!"
                                            print >>f, "Object is 200001048!"
                                            f.flush()
                                            return
                                        if obj in handledList:
                                            print "Already handled! : ", obj
                                            print >>f, "Already handled! : ", obj
                                            f.flush()
                                            return
                                        else:
                                            handledList.append(obj)
                                    
                                        # Object output
                                        emCount += 1
                                        print "  " * depth, "Count=", emCount, "(object)"
                                        print >>f, "  " * depth, "Count=", emCount, "(object)"
                                        f.flush()
                                        if isinstance(obj, c4d.documents.BaseDocument):
                                            print "  " * depth, "DOC:", obj.GetDocumentName()
                                            print >>f, "  " * depth, "DOC:", obj.GetDocumentName()
                                            f.flush()
                                            # this is also a BaseList2D so we can continue
                                        elif not isinstance(obj, c4d.BaseList2D):
                                            print "  " * depth, "Not a BaseList2D: ", obj
                                            print >>f, "  " * depth, "Not a BaseList2D:", obj
                                            f.flush()
                                            return
                                        else:
                                            print "  " * depth, "OBJ:", obj.GetName()
                                            print >>f, "  " * depth, "OBJ:", obj.GetName()
                                            f.flush()
                                    
                                        # assemble the tracks of the object
                                        addTracks = obj.GetCTracks() # returns a list
                                        tracks.extend(addTracks)
                                    
                                        # go through the children of the object if present
                                        child = obj.GetDown()
                                        if child != None:
                                            while child:
                                                showBranchInfo(depth + 1, child, f)
                                                child = child.GetNext()
                                    
                                        # check the branch info and go into all dependent branches
                                        bis = obj.GetBranchInfo()
                                        if bis == None or len(bis) == 0:
                                            print "  " * (depth+1), "No branch info retrieved, or empty"
                                            print >>f, "  " * (depth+1), "No branch info retrieved, or empty"
                                            f.flush()
                                            return
                                    
                                        for bi in bis: # list of dict{‘head’, ‘name’, ‘id’, ‘flags’}
                                    
                                            if len(bi) == 0:
                                                print "ERROR: no entries in dict list"
                                                print >>f, "ERROR: no entries in dict list"
                                                f.flush()
                                                return
                                            nodeName = bi['name']
                                            if nodeName != "Tags":
                                                listHead = bi['head']
                                                # print bi['id']
                                                # print bi['flags']
                                    
                                                # List head output
                                                emCount += 1
                                                node = None
                                                print "  " * (depth+1), "Count=", emCount, "(list head)", nodeName
                                                print >>f, "  " * (depth+1), "Count=", emCount, "(list head)", nodeName
                                                f.flush()
                                                # Crash irgendwo hier
                                                if listHead == None:
                                                    print "  " * (depth+1), "No list head"
                                                    print >>f, "  " * (depth+1), "No list head"
                                                    f.flush()
                                                elif not listHead.IsAlive():
                                                    print "List head not alive!"
                                                    print >>f, "List head not alive!"
                                                    f.flush()
                                                elif not isinstance(listHead, c4d.GeListHead):
                                                    print "  " * (depth+1), "Not a GeListHead:", listHead
                                                    print >>f, "  " * (depth+1), "Not a GeListHead:", listHead
                                                    f.flush()
                                                else:
                                                    print "  " * (depth+1), nodeName, listHead
                                                    print >>f, "  " * (depth+1), nodeName, listHead
                                                    f.flush()
                                                    node = listHead.GetFirst()
                                    
                                                # Content of sublist branch
                                                locCount = 0
                                                if node == None:
                                                    print "  " * (depth+2), "Empty list"
                                                    print >>f, "  " * (depth+2), "Empty list"
                                                    f.flush()
                                                while node != None:
                                                    if node.IsAlive():
                                                        if node.GetType() != 200001048:
                                                            emCount += 1
                                                            print "  " * (depth+2), "El:", locCount, node
                                                            print >>f, "  " * (depth+2), "El:", locCount, node
                                                            f.flush()
                                                            locCount += 1
                                                            if isinstance(node, c4d.BaseList2D):
                                                                showBranchInfo(depth + 3, node, f)
                                                                # no else case; node was already printed
                                                        else:
                                                            print "List node is 200001048!"
                                                            print >>f, "List node is 200001048!"
                                                            f.flush()
                                                            return
                                                    else:
                                                        print "List node not alive!"
                                                        print >>f, "List node not alive!"
                                                        f.flush()
                                                    node = node.GetNext()
                                            else:
                                                print "Tags skipped!"
                                                print >>f, "Tags skipped!"
                                                f.flush()
                                                if isinstance(obj,c4d.BaseObject):
                                                    taglist = obj.GetTags()
                                                    print "  " * (depth+2), "Tag list, length=", len(taglist)
                                                    print >>f, "  " * (depth+2), "Tag list, length=", len(taglist)
                                                    f.flush()
                                                    for tag in taglist:
                                                        showBranchInfo(depth + 3, tag, f)
                                    
                                        # missing here: recurse into children of "node" or "obj"
                                    
                                        print >>f, "  " * depth, "Returning from depth:", depth
                                        f.flush()
                                    
                                    
                                    def main():
                                        global tracks
                                        print " "
                                        print "GetBranchInfo"
                                        print "============="
                                        f = open("L:/getbranchinfo.txt", "at")
                                        showBranchInfo(1, doc, f)
                                        for track in tracks:
                                            print track
                                            print >>f, track
                                        f.close()
                                    
                                    
                                    if __name__=='__main__':
                                        main()
                                    

                                    (also, unsolicited advertising because I really spend too much time with this)
                                    https://www.patreon.com/cairyn
                                    there you go!

                                    1 Reply Last reply Reply Quote 0
                                    • M
                                      m_adam
                                      last edited by

                                      Hi, I found the issue, unfortunately, nothing on your side you can do to fix it.

                                      I marked this topic as solved, and will bump the topic once a fix is included in a public release.
                                      Keep in mind this is a Python only issue.

                                      Cheers,
                                      Maxime.

                                      MAXON SDK Specialist

                                      Development Blog, MAXON Registered Developer

                                      CairynC 1 Reply Last reply Reply Quote 0
                                      • CairynC
                                        Cairyn @m_adam
                                        last edited by

                                        @m_adam okay, good that it's found... I guess I can go with the code above for now that excludes tags explicitly and gathers them in a different recursion.

                                        As per the original issue (there is no function in the API that returns the currently visible tracks in timeline n), I guess I would need to make a requirement/suggestion on Maxon's site? Or is something like this planned already?

                                        1 Reply Last reply Reply Quote 0
                                        • M
                                          m_adam
                                          last edited by

                                          The bug is fixed in R23.

                                          Cheers,
                                          Maxime

                                          MAXON SDK Specialist

                                          Development Blog, MAXON Registered Developer

                                          1 Reply Last reply Reply Quote 1
                                          • First post
                                            Last post