Keyframing UserData with Value Tracks
-
Hello,
I'm trying to set keyframes for any type of UserData that can be set with a float value (Value CTracks). I believe those are Real, Long, Vector, Vector 4D, Color, & Matrix. Using this article from Cineversity as my guide, I've hit three issues and could use some help.-
Setting the Min & Max values for Vectors
I want to get the min & max values for a CTrack so if I try to set the value to something out of range, it defaults to either the minimum or maximum value. I am confused how to go about this. -
Keyframing the 'x', 'y', & 'z' of the User Data's Matrix Vectors
Currently I'm just setting keyframes on the Matrix Vectors themselves, not their 'x', 'y', & 'z' values. I can tell because the CTracks do not belong to the object and when I keyframe manually, they do.
This is how it should look (this was done with manual keyframing):
-
Getting 'w' CTrack in Vector 4D Data Type
When keyframed manually, Vector 4D shows a CTrack for 'w' but there doesn't seem to be aVECTOR_W
.
This is with manual keyframing:
My results (from the code below) do not target the Matrix's x, y, z and neither those tracks nor the Vector 4D are not being added to the Circle's User Data group:
Here is my code and a scene file with an object that has many userdata types:
import c4d def GetUDCtracks(op,id): cTracks = [] ud = id[1] dtype = ud.dtype if dtype in (c4d.DTYPE_VECTOR,c4d.DTYPE_COLOR): for v in xrange(c4d.VECTOR_X, c4d.VECTOR_Z+1): descID = c4d.DescID(id[0],id[1],c4d.DescLevel(v,c4d.DTYPE_REAL)) cTracks.append(c4d.CTrack(op,descID)) elif dtype == c4d.DTYPE_VECTOR4D: for v in xrange(c4d.VECTOR_X, c4d.VECTOR_Z+2): # How to get Vector4D.w? # this is not adding under the correct user data group descID = c4d.DescID(id[0],id[1],c4d.DescLevel(v,c4d.DTYPE_REAL)) cTracks.append(c4d.CTrack(op,descID)) elif dtype == c4d.DTYPE_MATRIX: # This is not adding the correct cTrack (x,y, or z) to the cTracks list # nor is it adding under the correct user data group for v in xrange(c4d.MATRIX_OFF, c4d.MATRIX_V3+1): descID = c4d.DescID(id[0],id[1],c4d.DescLevel(v,c4d.DTYPE_LONG)) for i in xrange(c4d.VECTOR_X, c4d.VECTOR_Z+2): descID2 = c4d.DescID(descID[0],descID[1],c4d.DescLevel(i,c4d.DTYPE_REAL)) cTracks.append(c4d.CTrack(op,descID2)) else: cTracks.append(c4d.CTrack(op,id)) return cTracks def main(doc): doc.StartUndo() value = 1.0 keyTime = doc.GetTime() for id, bc in op.GetUserDataContainer(): dtype = id[1].dtype print "dtype: %s"%dtype print "bc[c4d.DESC_NAME]: %s"%bc[c4d.DESC_NAME] if dtype not in (c4d.DTYPE_MATRIX,c4d.DTYPE_REAL,c4d.DTYPE_VECTOR, c4d.DTYPE_VECTOR4D, c4d.DTYPE_LONG, c4d.DTYPE_COLOR): continue print "bc[c4d.DESC_MAX]: %s"%bc[c4d.DESC_MAX] print "bc[c4d.DESC_MIN]: %s"%bc[c4d.DESC_MIN] cTrack = op.FindCTrack(id) if not cTrack: cTracks = GetUDCtracks(op,id) for track in cTracks: print track doc.AddUndo(c4d.UNDOTYPE_CHANGE, op) op.InsertTrackSorted(track) curve = track.GetCurve() keyDict = curve.AddKey(keyTime) myKey = keyDict["key"] track.FillKey(doc,op,myKey) doc.AddUndo(c4d.UNDOTYPE_CHANGE, op) # How can I check the curve's min & max before setting the value # if it's one dimension of a Vector? myKey.SetValue(curve,value) c4d.EventAdd(c4d.EVENT_ANIMATE) doc.EndUndo() if __name__=='__main__': main(doc)
Any help with this would be tremendous! Thank you
-
-
Hi @blastframe,
-
There is no way to define a minimal and a maximal value in a Curve, the only way would be to edit each Ckey and define their values.
However keep in mind this is the parameter that wins at the end, so if I have a float parameter limited to 1.0.
I can define animation going from 0 to 2000.0 nothing prevents me, but when Cinema 4D will evaluate the Animation track, it will then clamp the value according to the parameter max value so 1.0. -
You have to hardcode everything, here its one way:
def CopyDescID(descIDToCopy): buffer = c4d.DescID() for x in xrange(descIDToCopy.GetDepth()): buffer.PushId(descIDToCopy[x]) return buffer def GetAllChildrenDescId(baseDescId): results = [] # Hardcode in a dict, the Type and a list of ID sub entry tracksData = {} tracksData[c4d.DTYPE_VECTOR] = [c4d.DTYPE_REAL, [c4d.VECTOR_X, c4d.VECTOR_Y, c4d.VECTOR_Z]] tracksData[c4d.DTYPE_COLOR] = [c4d.DTYPE_REAL, [c4d.COLOR_R, c4d.COLOR_G, c4d.COLOR_B]] tracksData[c4d.DTYPE_VECTOR4D] = [c4d.DTYPE_REAL, [c4d.VECTOR4D_X, c4d.VECTOR4D_Y, c4d.VECTOR4D_Z, c4d.VECTOR4D_W]] tracksData[c4d.DTYPE_MATRIX] = [c4d.DTYPE_VECTOR, [c4d.MATRIX_OFF, c4d.MATRIX_V1, c4d.MATRIX_V2, c4d.MATRIX_V3]] # Retrieve the last part of the descId and check if it in our tracksData dict, otherwise return dtype = baseDescId[-1].dtype descIdData = tracksData.get(dtype) if descIdData is None: return [baseDescId] # For each entry of the Base for descId in descIdData[1]: # Get a new copy of the baseDescID and add the current leve descLevel = c4d.DescLevel(descId, descIdData[0], 0) newDescId = CopyDescID(baseDescId) newDescId.PushId(descLevel) # Recursivly checks all descId (eg a matrix will generate vector, so we need then to process each vector) resolvedDescIds = GetAllChildrenDescId(newDescId) # If there is some results we had the result to the final list if len(resolvedDescIds) > 1: for resolvedDescId in resolvedDescIds: results.append(resolvedDescId) # Otherwise we the created one else: results.append(newDescId) return results def GetUDCtracks(op, baseDescId): cTracks = [] allDescId = GetAllChildrenDescId(baseDescId) for descId in allDescId: cTracks.append(c4d.CTrack(op, descId)) return cTracks
- It's called
VECTOR4D_W
.
Cheers,
Maxime -
-
@m_adam Hi Maxime, thank you for the reply. Your post was very helpful but I'm still have an issue: the FindKey, AddKey, GetKeyCount methods aren't working properly for me with this code. If I move my playhead in the timeline, I'm able to get the track, but unable to create more than one keyframe. My script cannot see any of the keyframes in the curve. Maybe I am missing something simple. Here's the combined script:
import c4d def CopyDescID(descIDToCopy): buffer = c4d.DescID() for x in xrange(descIDToCopy.GetDepth()): buffer.PushId(descIDToCopy[x]) return buffer def GetAllChildrenDescId(baseDescId): results = [] # Hardcode in a dict, the Type and a list of ID sub entry tracksData = {} tracksData[c4d.DTYPE_VECTOR] = [c4d.DTYPE_REAL, [c4d.VECTOR_X, c4d.VECTOR_Y, c4d.VECTOR_Z]] tracksData[c4d.DTYPE_COLOR] = [c4d.DTYPE_REAL, [c4d.COLOR_R, c4d.COLOR_G, c4d.COLOR_B]] tracksData[c4d.DTYPE_VECTOR4D] = [c4d.DTYPE_REAL, [c4d.VECTOR4D_X, c4d.VECTOR4D_Y, c4d.VECTOR4D_Z, c4d.VECTOR4D_W]] tracksData[c4d.DTYPE_MATRIX] = [c4d.DTYPE_VECTOR, [c4d.MATRIX_OFF, c4d.MATRIX_V1, c4d.MATRIX_V2, c4d.MATRIX_V3]] # Retrieve the last part of the descId and check if it in our tracksData dict, otherwise return dtype = baseDescId[-1].dtype descIdData = tracksData.get(dtype) if descIdData is None: return [baseDescId] # For each entry of the Base for descId in descIdData[1]: # Get a new copy of the baseDescID and add the current level descLevel = c4d.DescLevel(descId, descIdData[0], 0) newDescId = CopyDescID(baseDescId) newDescId.PushId(descLevel) ''' Recursivly checks all descId (eg a matrix will generate vector, so we need then to process each vector) ''' resolvedDescIds = GetAllChildrenDescId(newDescId) # If there is some results we add the result to the final list if len(resolvedDescIds) > 1: for resolvedDescId in resolvedDescIds: results.append(resolvedDescId) # Otherwise we create one else: results.append(newDescId) return results def GetUDCtracks(op, baseDescId): cTracks = [] allDescId = GetAllChildrenDescId(baseDescId) for descId in allDescId: cTracks.append(c4d.CTrack(op, descId)) return cTracks def main(doc): doc.StartUndo() value = 1.0 keyTime = doc.GetTime() fps = doc.GetFps() frame = keyTime.GetFrame(doc.GetFps()) for id, bc in op.GetUserDataContainer(): dtype = id[1].dtype if dtype not in (c4d.DTYPE_MATRIX,c4d.DTYPE_REAL,c4d.DTYPE_VECTOR, c4d.DTYPE_VECTOR4D, c4d.DTYPE_LONG, c4d.DTYPE_COLOR): continue cTrack = op.FindCTrack(id) if not cTrack: cTracks = GetUDCtracks(op,id) for track in cTracks: doc.AddUndo(c4d.UNDOTYPE_CHANGE, op) op.InsertTrackSorted(track) curve = track.GetCurve() keyDict = curve.AddKey(keyTime) key = keyDict["key"] doc.AddUndo(c4d.UNDOTYPE_CHANGE, op) key.SetValue(curve,value) keyCount = curve.GetKeyCount() print "frame: %s, track: %s, keyCount: %s"\ %(frame,track.GetName(),keyCount) else: cTracks = GetUDCtracks(op,id) for track in cTracks: curve = track.GetCurve() findKey = curve.FindKey(keyTime); keyCount = curve.GetKeyCount() if findKey == None: print "frame: %s, track: %s, keyCount: %s, "\ "findKey: %s"%(frame,track.GetName(), keyCount,findKey) keyDict = curve.AddKey(keyTime) key = keyDict["key"] doc.AddUndo(c4d.UNDOTYPE_CHANGE, op) key.SetValue(curve,value) c4d.EventAdd(c4d.EVENT_ANIMATE) doc.EndUndo() if __name__=='__main__': main(doc)
Is there a special method for User Data CTracks?
-
The main issue is that in your GetUDCtracks you always create a new track and search within this track, so obviously there is no key already presents.
Here your code adapted.
import c4d def CopyDescID(descIDToCopy): buffer = c4d.DescID() for x in xrange(descIDToCopy.GetDepth()): buffer.PushId(descIDToCopy[x]) return buffer def GetAllChildrenDescId(baseDescId): results = [] # Hardcode in a dict, the Type and a list of ID sub entry tracksData = {} tracksData[c4d.DTYPE_VECTOR] = [c4d.DTYPE_REAL, [c4d.VECTOR_X, c4d.VECTOR_Y, c4d.VECTOR_Z]] tracksData[c4d.DTYPE_COLOR] = [c4d.DTYPE_REAL, [c4d.COLOR_R, c4d.COLOR_G, c4d.COLOR_B]] tracksData[c4d.DTYPE_VECTOR4D] = [c4d.DTYPE_REAL, [c4d.VECTOR4D_X, c4d.VECTOR4D_Y, c4d.VECTOR4D_Z, c4d.VECTOR4D_W]] tracksData[c4d.DTYPE_MATRIX] = [c4d.DTYPE_VECTOR, [c4d.MATRIX_OFF, c4d.MATRIX_V1, c4d.MATRIX_V2, c4d.MATRIX_V3]] # Retrieve the last part of the descId and check if it in our tracksData dict, otherwise return dtype = baseDescId[-1].dtype descIdData = tracksData.get(dtype) if descIdData is None: return [baseDescId] # For each entry of the Base for descId in descIdData[1]: # Get a new copy of the baseDescID and add the current level descLevel = c4d.DescLevel(descId, descIdData[0], 0) newDescId = CopyDescID(baseDescId) newDescId.PushId(descLevel) ''' Recursivly checks all descId (eg a matrix will generate vector, so we need then to process each vector) ''' resolvedDescIds = GetAllChildrenDescId(newDescId) # If there is some results we add the result to the final list if len(resolvedDescIds) > 1: for resolvedDescId in resolvedDescIds: results.append(resolvedDescId) # Otherwise we create one else: results.append(newDescId) return results def GetUDCtracks(op, baseDescId): cTracks = [] allDescId = GetAllChildrenDescId(baseDescId) for descId in allDescId: track = op.FindCTrack(descId) if track is None: track = c4d.CTrack(op, descId) doc.AddUndo(c4d.UNDOTYPE_NEW, track) op.InsertTrackSorted(track) cTracks.append(track) return cTracks def main(doc): doc.StartUndo() value = 1.0 keyTime = doc.GetTime() fps = doc.GetFps() frame = keyTime.GetFrame(doc.GetFps()) for descID, bc in op.GetUserDataContainer(): dtype = descID[1].dtype if dtype not in (c4d.DTYPE_MATRIX,c4d.DTYPE_REAL,c4d.DTYPE_VECTOR, c4d.DTYPE_VECTOR4D, c4d.DTYPE_LONG, c4d.DTYPE_COLOR): continue # This will retrieve or create the track if it doesnt exist cTracks = GetUDCtracks(op, descID) for cTrack in cTracks: curve = cTrack.GetCurve() keyDict = curve.FindKey(keyTime) # Never checks None by == operator but always use is if keyDict is None: keyDict = curve.AddKey(keyTime) key = keyDict["key"] if keyDict is None: raise RuntimeError('key is None, this should never happens') key = keyDict["key"] doc.AddUndo(c4d.UNDOTYPE_CHANGE, key) key.SetValue(curve,value) c4d.EventAdd(c4d.EVENT_ANIMATE) doc.EndUndo() if __name__=='__main__': main(doc)
Cheers,
Maxime. -
@m_adam You are a genius! Thank you, Maxime