CTrack in a Stage Object



  • On 01/04/2014 at 12:38, xxxxxxxx wrote:

    Hello All.

    First time posting here, but have been creepily lurking in the shadows reading your posts for a couple years. Thanks for all the help ;)

    I am trying to make a tool to break out an R&D scene file into multiple scene files.

    Our directors usually start by creating an animatic in C4D to accompany the pitch boards. Theyll then work inside that scene for a few days/weeks until everything is approved and they have worked out their timing. In many cases, this will all be done in a single scene file that uses a Stage object to direct what camera is active and so on. We also animate layer visibility to match.

    This essentially creates multiple shots into one massive file. IMO, not the best way to work, but thats not my call.

    I would like to now build a tool that reads the keyframes of the stage object and saves out files that only consist of the camera and objects being used within that frame range.

    My plan was to grab the stage object, get all of its keyframes, then check the visibility of the objects in the scene for each of the ranges given by the keyframe list, delete everything invisible, save a file, and so on.

    I thought this would be trivial as listing keyframes and values in CG packages is usually very simple. Not so much as I cannot seem to get a list of the keyed values anywhere. I can start at 0 and scrub my way through keyframes on things like the position and rotation, and even though that seems like a very time consuming and roundabout way to collect them, it does work.

    When I try to do the same with a stage object, the CTrack returned gives very large, seemingly random values in the x with 0 in the y and I am unable to decipher what is happening.

    Am i missing something here? This is my first time really working with the stage object, and I have been unable to find any real indepth documentation on it, so Im somewhat flying blind at the moment.

    Any help would be greatly appreciated! Even if it is simply that I am going about this all wrong ;)

    TIA!



  • On 02/04/2014 at 08:25, xxxxxxxx wrote:

    Hi Glenn,

    The stage object is just a very simple camera activator. All it does is active the camera that's in it's Link.
    It allows you to switch cameras over time using a keyframe, that you can then move around if you want to make changes to the camera editing.
    It doesn't do anything more fancy than that.

    Here's how I would approach breaking it up into separate scene files with a script:

    -Get the frame where the camera switches to the desired camera. And move the time scrubber there
    -Select all of the objects in the scene and execute the Record keys button so they all have a key on that frame
    -Delete all of the keys in the timeline prior to where the scrubber is
    -Select all the remaining keys and move them down to start at frame zero
    -Delete the other cameras leaving just the one being used
    -Save the new scene

    Most or all of the code for doing this can probably be found by searching this forum.

    NOTE:
    If it was something I was doing for myself.
    I would probably skip the moving the remaining keys to zero step. Because that way I could easily rebuild the whole shot again if needed. By simply by using Merge file option.

    Hope that gives you some ideas,
    -ScottA



  • On 02/04/2014 at 14:45, xxxxxxxx wrote:

    Thanks for the reply Scott.

    The real issue I was having was cycling through the keys. Im not sure if I just needed a C4D reboot, or what the issue was, but yesterday the values I was getting when querying the key at a given frame were in the hundreds of millions and patternless. I restarted this morning, ran the same code and everything "magically" worked... I love when that happens :(

    Was just coming back to post a small diagnostic trace that I put together from searching through the info available here to find the # of keyframes, the value of the key, and the frame of that keyframe. Nothing special, but I figure it might help someone like myself in the future. (When I was searching, I was unable to find anything in the boards about parsing the keyframes of stage object using python.)

    If there's a better way to check if an object is a Stage Object and I missed, please let me know.

    This will collect all objects, step through them until it finds a stage object that has a camera or base object in the link field, find the animation track, then print the number of keyframes and each keyframe with its value. Again, nothing special.

    Thanks again for all the help gents!

    import c4d
      
    doc = c4d.documents.GetActiveDocument()
    objects = doc.GetObjects()
      
    for i in objects:
        if i != None:
            if i[c4d.STAGEOBJECT_CLINK] and type(i[c4d.STAGEOBJECT_CLINK]) in [c4d.CameraObject, c4d.BaseObject]:
                
                track = i.GetFirstCTrack()
                fps = float(doc.GetFps())
      
                while track:
                    curve = track.GetCurve()
                    frames = curve.GetKeyCount()
                    print 'Total Keyframes: '+str(frames)
                    
                    for f in xrange(frames) :
                       key = curve.GetKey(f)
                       print str(f+1)+') '+str(key.GetGeData().GetName())+': '+str(key.GetTime().Get()*int(doc.GetFps()))
                    
                    track = track.GetNext()
    


  • On 02/04/2014 at 16:13, xxxxxxxx wrote:

    This way is a little bit shorter: if type(i[c4d.STAGEOBJECT_CLINK]) == c4d.CameraObject:

    -ScottA



  • On 02/04/2014 at 16:42, xxxxxxxx wrote:

    Thanks again, Scott. Will it always be a Camera Object?



  • On 02/04/2014 at 18:37, xxxxxxxx wrote:

    It should work the same way as your code works. Just tiny bit shorter.
    It will return true and go to the next line of code if there is a camera type object in the link field.
    If there's another kind of object in the link field. It will return false and skip any code under the If statement.
    The code you posted was perfectly fine. But since you asked if there was another way to write it I just posted a different way to write it.

    If you wanted to check the type of object that's in the stage's link field.
    You could write something like this:

        for i in objects:  
          if i.GetType() == c4d.Ostage and i[c4d.STAGEOBJECT_CLINK] is not None:  
              if i[c4d.STAGEOBJECT_CLINK].GetType() == c4d.Ocamera: print "camera"  
              else: print i[c4d.STAGEOBJECT_CLINK].GetType()
    

    Lots of way to skin a cat. 🙂

    -ScottA



  • On 03/04/2014 at 09:56, xxxxxxxx wrote:

    Awesome! Much appreciated



  • On 03/04/2014 at 12:58, xxxxxxxx wrote:

    Hey Scott,

    One more question if you dont mind.

    How do I delete cameras with Python? I have my list of cameras that need to be deleted, but the cam.Remove() method I have been attempting isnt returning any results. So far I have been unable to find anything on it on the interwebs.

    TIA



  • On 03/04/2014 at 14:05, xxxxxxxx wrote:

    It sounds like you're doing correctly. But you're just missing c4d.EventAdd()
    When using the ScriptManger. If you don't use that code. Then the task you did with your code is sitting in memory and often doesn't take effect until C4D gets updated. Like mouse clicking somewhere in it.
    Sometimes you'll get lucky and it will update on it's own. But many times you have to send a message to C4D to tell it to update before you see your changes.

    import c4d  
    def main() :  
        
      obj = doc.SearchObject("Camera")  
      obj.Remove()  
        
      c4d.EventAdd() #<---Very important when using the script manager!!     
      
    if __name__=='__main__':  
      main()
    

    -ScottA



  • On 03/04/2014 at 17:44, xxxxxxxx wrote:

    Not working for me consistently... Unsure of why.

    I have had multiple different results in output files after the save. The most common is that the cameras have been untouched. Second is that all cameras aside for a camera that is nested in a Null are deleted. And third is that only one or two cameras have been deleted.

    I have tried rebooting, reinstalling, and moving to other machines, but nothing changes my outcome.

    Am I missing somehting in here?

    Thanks again

    import c4d
    import time
    import os
      
    def buildRanges(doc, objs, fps) :
      
    	for i in objs:
    		if i != None:
    			if type(i[c4d.STAGEOBJECT_CLINK]) == c4d.CameraObject:
      
    				track = i.GetFirstCTrack()
    				if track:
      
    					tbl = {}
    					curve = track.GetCurve()
    					frames = curve.GetKeyCount()
      
    					for f in xrange(frames) :
    						key = curve.GetKey(f)
    						tbl[f] = {}
    						tbl[f]['cam'] = key.GetGeData()
    						tbl[f]['strt'] = key.GetTime().Get()*int(fps)
    						if f != 0:
    							tbl[f-1]['end'] = int(tbl[f]['strt'])-1
    							if f == frames-1:
    								tbl[f]['end'] = doc.GetMaxTime().GetFrame(int(fps))
      
    					return i, tbl
    	
    	return False
      
    #There has to be a better way than this
    def getCams(obj) :
    	if obj is None: 
    		return
      
    	if obj.GetTypeName() == 'Camera':
    		cams[obj.GetName()] = obj
      
    	if (obj.GetDown()) :
    		getCams(obj.GetDown())
    	if (obj.GetNext()) :
    		getCams(obj.GetNext())
      
    doc = c4d.documents.GetActiveDocument()
    cur_name = doc.GetDocumentName()
    obj = doc.GetFirstObject()
    objs = doc.GetObjects()
    fps = float(doc.GetFps())
      
    stage, f_tbl = buildRanges(doc, objs, fps)
      
    #build the camera list. Best method?
    global cams
    cams = {}
    getCams(obj)
      
    if f_tbl != False:
    	for f in f_tbl:
      
    		doc.StartUndo()
      
    		for i in cams:
    			if i != f_tbl[f]['cam'].GetName() :
    				doc.AddUndo(c4d.UNDOTYPE_DELETE, cams[i])
    				cams[i].Remove()
    				c4d.EventAdd()
      
    		shot = 'sh'+str(f+1).zfill(3)+'0'
    		name = cur_name.replace('testSplit', shot)
    		new_scn_path = os.path.join(os.environ['HOME'], 'Desktop', name )
      
    		new_doc = c4d.documents.SaveDocument(doc, new_scn_path, c4d.SAVEDOCUMENTFLAGS_DONTADDTORECENTLIST, c4d.FORMAT_C4DEXPORT)
      
    		doc.EndUndo()
    		doc.DoUndo(multiple = True)
    


  • On 03/04/2014 at 18:28, xxxxxxxx wrote:

    Here's how I would go about getting all of the cameras. And put them into a list.

    import c4d  
      
    #A list where we will store all of the cameras  
    cams = []  
      
    def GetCameras(op) :  
      while(op) :        
        GetCameras(op.GetDown())  
        if op.GetType() == c4d.Ocamera:   
            cams.append(op)  
        op = op.GetNext()  
      
    def main() :  
        
      firstObj = doc.GetFirstObject()  
      GetCameras(firstObj)  
           
      print cams  
        
      #Now lets delete all of the cameras that are in the cams list  
      for i in cams:  
          i.Remove()  
        
      c4d.EventAdd()  
      
    if __name__=='__main__':  
      main()
    

    -ScottA



  • On 03/04/2014 at 18:47, xxxxxxxx wrote:

    Much better! I was pulling that function from another post and felt it was a bit convoluted.

    So I only need a single EvenAdd() for all of the removals? I just left work, but Im starting up my VPN to check if this works in my setup.



  • On 04/04/2014 at 00:26, xxxxxxxx wrote:

    I FOUND IT!!! (Im a bit excited)

    When c4d gets to my for loop to cycle through the different keys on the stage object, the id for the cameras has changed.

    When I set a fixed value for f and ran the script without the loop, everything worked fine. So I printed the object stored in cams _in the code above and compared it to a SearchObject on the name of the object stored in cams _and they were different. Changed the cams _to SearchObject and everything is working perfectly.

    Thanks for all your help Scott!

    This script will read the first available stage object, then separate the scene into as many shots as there are camera changes in the stage object. Inside the new shots will be only the camera in those keyframes and any objects that are visible at the beginning, middle, or end of the range for that particular stage range.

    I just started writing this yesterday, so its still very much in test phase. As such, its dumping to my desktop right now, but anyone could easily change the path and filename subs to make it work for your needs.

    Ill repost once I have it setting each shot and its animations back to 0 and closing the frame range, too.

    import c4d
    import os
      
    def buildRanges(doc, objs, fps) :
      
    	for i in objs:
    		if i != None:
    			if type(i[c4d.STAGEOBJECT_CLINK]) == c4d.CameraObject:
      
    				track = i.GetFirstCTrack()
    				if track:
      
    					tbl = {}
    					curve = track.GetCurve()
    					frames = curve.GetKeyCount()
      
    					for f in xrange(frames) :
    						key = curve.GetKey(f)
    						tbl[f] = {}
    						tbl[f]['cam'] = key.GetGeData()
    						tbl[f]['strt'] = key.GetTime().Get()*int(fps)
    						if f != 0:
    							tbl[f-1]['end'] = int(tbl[f]['strt'])-1
    							if f == frames-1:
    								tbl[f]['end'] = doc.GetMaxTime().GetFrame(int(fps))
      
    					return i.GetName(), tbl
    	
    	return False, False
      
    def getActiveObjects(doc) :
    	
    	on = {}
    	off = {}
    	
    	for i in doc.GetObjects() :
    		if not i.GetName() in on:
    			tag = i.GetTag(c4d.Tdisplay)
    			if tag != None:
    				if tag[c4d.DISPLAYTAG_VISIBILITY] > 0:
    					on[i.GetName()] = i
    				else:
    					off[i.GetName()] = i
      
    	return on, off
      
    def getCams(op) : #<--Thanks ScottA!!
    	while(op) :
    		getCams(op.GetDown())
    		if op.GetType() == c4d.Ocamera:
    			cams[op.GetName()] = op
    		op = op.GetNext()
      
    doc = c4d.documents.GetActiveDocument()
    cur_name = doc.GetDocumentName()
    objs = doc.GetObjects()
    fps = float(doc.GetFps())
      
    stage, f_tbl = buildRanges(doc, objs, fps)
      
    cams = {}
    obj = doc.GetFirstObject()
      
    getCams(obj)
      
    if f_tbl != False:
    	for f in f_tbl:
      
    		doc.StartUndo()
      
    		#calculate the middle frame of the frame section and build a list of the first, middle, and last
    		mid_frame = (f_tbl[f]['end']-f_tbl[f]['strt'])+f_tbl[f]['strt']
    		frames = [f_tbl[f]['strt'], f_tbl[f]['end'], mid_frame]
      
    		on_objs = {}
    		off_objs = {}
      
    		#for each of the first, middle, and last of the frame section
    		for i in frames:
    			#move to the frame
    			doc.SetTime(c4d.BaseTime(i/int(fps)))
    			#update C4D (this took a while to figure out and is a severe pain in the ass :D)
    			c4d.DrawViews(c4d.DRAWFLAGS_ONLY_ACTIVE_VIEW|c4d.DRAWFLAGS_NO_THREAD|c4d.DRAWFLAGS_NO_REDUCTION|c4d.DRAWFLAGS_STATICBREAK)
    			
    			on, off = getActiveObjects(doc)
    			on_objs.update(on)
    			off_objs.update(off)
      
    		#Delete unused objects
    		for i in off_objs:
    			doc.AddUndo(c4d.UNDOTYPE_DELETE, off_objs[i])
    			off_objs[i].Remove()	
    		
    		#Delete unused cameras
    		for i in sorted(cams) :
    			if i != f_tbl[f]['cam'].GetName() :
    				doc.AddUndo(c4d.UNDOTYPE_DELETE, doc.SearchObject(i))
    				doc.SearchObject(i).Remove()
    		
    		#Delete stage object
    		doc.AddUndo(c4d.UNDOTYPE_DELETE, doc.SearchObject(stage))
    		doc.SearchObject(stage).Remove()
    		
    		#update
    		c4d.EventAdd()
      
    		#Build a shot name and path
    		shot = 'sh'+str(f+1).zfill(3)+'0'
    		name = cur_name.replace('ryanSplit', shot)
    		new_scn_path = os.path.join(os.environ['HOME'], 'Desktop', name)
      
    		#Save the file
    		new_doc = c4d.documents.SaveDocument(doc, new_scn_path, c4d.SAVEDOCUMENTFLAGS_DONTADDTORECENTLIST, c4d.FORMAT_C4DEXPORT)
      
    		#Undo and start over 
    		doc.EndUndo()
    		doc.DoUndo(multiple = True)
    


  • On 04/04/2014 at 14:38, xxxxxxxx wrote:

    Copied over a mistake I made early last night.

    mid_frame = (f_tbl[f]['end']-f_tbl[f]['strt'])+f_tbl[f]['strt']
    

    should be:

    mid_frame = ((f_tbl[f]['end']-f_tbl[f]['strt'])/2)+f_tbl[f]['strt']
    

Log in to reply