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.
    • 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