Still not possible to find out what's visible in the Timeline?
-
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
norNBIT_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:- I create an animation with a cube and a sphere (each has a position track).
- I select them with
Alt-T
for the dope sheet in timeline 1, and use the timeline's bookmark function to create two views: - One view shows only the cube's tracks, the other view shows only the sphere's tracks.
- 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
plusNBIT_TL1_HIDE
are not sufficient. -
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.
-
@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.
-
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... -
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.
-
@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 forBaseDocument
orBaseList2D
orGeListHead
.EDIT: Oh, I'm stupid. I just saw that such a constant is used in the original example... okay, let's try that...
-
@PluginStudent No, didn't work with
IsInstanceOf
either.For completeness:
Tbasedocument
andTbaselist2d
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 Pythonicisinstance
. -
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. -
@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 underModeling Objects Hooks/Modeling Objects Branch/Plane Manipulator
andPivot 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 theGetBranchInfo()
function somewhere. -
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.
-
@m_adam oops... didn't expect that, since the Active Object Properties sample works fine (solely in C++).
-
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 callsGetTags()
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! -
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. -
@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?
-
The bug is fixed in R23.
Cheers,
Maxime