#!BPY """ Name: 'Bake Constraints' Blender: 246 Group: 'Animation' Tooltip: 'Bake a Constrained object/rig to IPOs' Fillename: 'Bake_Constraint.py' """ __author__ = "Roger Wickes (rogerwickes(at)yahoo.com)" __script__ = "Animation Bake Constraints" __version__ = "0.7" __url__ = ["Communicate problems and errors, http://www.blenderartists.com/forum/private.php?do=newpm to PapaSmurf"] __email__= ["Roger Wickes, rogerwickes@yahoo.com", "scripts"] __bpydoc__ = """\ bake_constraints This script bakes the real-world LocRot of an object (the net effect of any constraints - (Copy, Limit, Track, Follow, - that affect Location, Rotation) (usually one constrained to match another's location and/or Tracked to another) and creates a clone with a set of Ipo Curves named Ipo These curves control a non-constrained object and thus make it mimic the constrained object Actions can be then be edited without the need for the drivers/constraining objects Developed for use with MoCap data, where a bone is constrained to point at an empty moving through space and time. This records the actual locrot of the armature so that the motion can be edited, reoriented, scaled, and used as NLA Actions see also wiki Scripts/Manual/ Tutorial/Motion Capture
Usage:
- Select the reference Object(s) you want to bake
- Set the frame range to bake in the Anim Panel
- Set the test code (if you want a self-test) in the RT field in the Anim Panel
-- Set RT:1 to create a test armature
-- Set RT: up to 100 for more debug messages and status updates

- Run the script
- The clone copy of the object is created and it has an IPO curve assigned to it.
- The clone shadows the object by an offset locrot (see usrDelta)
- That Object has Ipo Location and Rotation curves that make the clone mimic the movement
of the selected object, but without using constraints.
- If the object was an Armature, the clone's bones move identically in relation to the
original armature, and an Action is created that drives the bone movements.
Version History: 0.1: bakes Loc Rot for a constrained object 0.2: bakes Loc and Rot for the bones within Armature object 0.3: UI for setting options 0.3.1 add manual to script library 0.4: bake multiple objects 0.5: root bone worldspace rotation 0.6: re-integration with BPyArmature 0.7: bakes parents and leaves clones selected License, Copyright, and Attribution: by Roger WICKES May 2008, released under Blender Artistic Licence to Public Domain feel free to add to any Blender Python Scripts Bundle. Thanks to Jean-Baptiste PERIN, IdeasMan42 (Campbell Barton), Basil_Fawlty/Cage_drei (Andrew Cruse) much lifted/learned from blender.org/documentation/245PytonDoc and wiki some modules based on c3D_Import.py, PoseLib16.py and IPO/Armature code examples e.g. camera jitter Pseudocode: Initialize If at least one object is selected For each selected object, create a cloned object remove any constraints on the clone create or reset an ipo curve named like the object for each frame set the clone's locrot key based on the reference object if it's an armature, create an action (which is an Ipo for each bone) for each frame of the animation for each bone in the armature set the key Else you're a smurf Test Conditions and Regressions: 1. (v0.1) Non-armatures (the cube), with ipo curve and constraints at the object level 2. armatures, with ipo curve and constraints at the object level 3. armatures, with bones that have ipo curves and constraints 4. objects without parents, children with unselected parents, select children first. Naming conventions: arm = a specific objec type armature bone = bones that make up the skeleton of an armature ob = object, an instance of an object type ebone = edit bone, a bone in edit mode pbone = pose bone, a posed bone in an object tst = testing, self-test routines usr = user-entered or designated stuff """ ######################################## import Blender from Blender import * from Blender.Mathutils import * import struct import string import bpy import BPyMessages import BPyArmature # reload(BPyArmature) from BPyArmature import getBakedPoseData Vector= Blender.Mathutils.Vector Euler= Blender.Mathutils.Euler Matrix= Blender.Mathutils.Matrix #invert() function at least RotationMatrix = Blender.Mathutils.RotationMatrix TranslationMatrix= Blender.Mathutils.TranslationMatrix Quaternion = Blender.Mathutils.Quaternion Vector = Blender.Mathutils.Vector POSE_XFORM= [Blender.Object.Pose.LOC, Blender.Object.Pose.ROT] #================= # Global Variables #================= # set senstitivity for displaying debug/console messages. 0=none, 100=max # then call debug(num,string) to conditionally display status/info in console window MODE=Blender.Get('rt') #execution mode: 0=run normal, 1=make test armature DEBUG=Blender.Get('rt') #how much detail on internal processing for user to see. range 0-100 BATCH=False #called from command line? is someone there? Would you like some cake? #there are two coordinate systems, the real, or absolute 3D space, # and the local relative to a parent. COORDINATE_SYSTEMS = ['local','real'] COORD_LOCAL = 0 COORD_REAL = 1 # User Settings - Change these options manually or via GUI (future TODO) usrCoord = COORD_REAL # what the user wants usrParent = False # True=clone keeps original parent, False = clone's parent is the clone of the original parent (if cloned) usrFreeze = 2 #2=yes, 0=no. Freezes shadow object in place at current frame as origin # delta is amount to offset/change from the reference object. future set in a ui, so technically not a constant usrDelta = [10,10,0,0,0,0] #order specific - Loc xyz Rot xyz usrACTION = True # Offset baked Action frames to start at frame 1 CURFRAME = 'curframe' #keyword to use when getting the frame number that the scene is presently on ARMATURE = 'Armature' #en anglais BONE_SPACES = ['ARMATURESPACE','BONESPACE'] # 'ARMATURESPACE' - this matrix of the bone in relation to the armature # 'BONESPACE' - the matrix of the bone in relation to itself #Ipo curves created are prefixed with a name, like Ipo_ or Bake_ followed by the object/bone name #bakedArmName = "b." #used for both the armature class and object instance usrObjectNamePrefix= "" #ipoBoneNamePrefix = "" # for example, if on entry an armature named Man was selected, and the object prefix was "a." # on exit an armature and an IPO curve named a.Man exists for the object as a whole # if that armature had bones (spine, neck, arm) and the bone prefix was "a." # the bones and IPO curves will be (a.spine, a.neck, a.arm) R2D = 18/3.1415 # radian to grad BLENDER_VERSION = Blender.Get('version') # Gets the current scene, there can be many scenes in 1 blend file. scn = Blender.Scene.GetCurrent() #================= # Methods #================= ######################################## def debug(num,msg): #use log4j or just console here. if DEBUG >= num: if BATCH == False: print 'debug: '[:num/10+7]+msg #TODO: else write out to file (runs faster if it doesnt have to display details) return ######################################## def error(str): debug(0,'ERROR: '+str) if BATCH == False: Draw.PupMenu('ERROR%t|'+str) return ######################################## def getRenderInfo(): context=scn.getRenderingContext() staframe = context.startFrame() endframe = context.endFrame() if endframe= 1: for i in range(10): curFrame+=frameinc Blender.Set(CURFRAME,curFrame) # computes the constrained location of the 'real' objects Blender.Redraw() Blender.Set(CURFRAME, staFrame) return ######################################## def bakeBones(ref_ob,arm_ob): #copy pose from ref_ob to arm_ob scrub() staFrame,endFrame,curFrame = getRenderInfo() act = getBakedPoseData(ref_ob, staFrame, endFrame, ACTION_BAKE = True, ACTION_BAKE_FIRST_FRAME = usrACTION) # bake the pose positions of the reference ob to the armature ob arm_ob.action = act scrub() # user comprehension feature - change action name and channel ipo names to match the names of the bone they drive debug (80,'Renaming each action ipo to match the bone they pose') act.name = arm_ob.name arm_channels = act.getAllChannelIpos() pose= arm_ob.getPose() pbones= pose.bones.values() #we want the bones themselves, not the dictionary lookup for pbone in pbones: debug (100,'Channel listing for %s: %s' % (pbone.name,arm_channels[pbone.name] )) ipo=arm_channels[pbone.name] ipo.name = pbone.name # since bone names are unique within an armature, the pose names can be the same since they are within an Action return ######################################## def getOrCreateCurve(ipo, curvename): """ Retrieve or create a Blender Ipo Curve named C{curvename} in the C{ipo} Ipo Either an ipo curve named C{curvename} exists before the call then this curve is returned, Or such a curve doesn't exist before the call .. then it is created into the c{ipo} Ipo and returned """ try: mycurve = ipo.getCurve(curvename) if mycurve != None: pass else: mycurve = ipo.addCurve(curvename) except: mycurve = ipo.addCurve(curvename) return mycurve ######################################## def eraseCurve(ipo,numCurves): debug(90,'Erasing %i curves for %' % (numCurves,ipo.GetName())) for i in range(numCurves): nbBezPoints= ipo.getNBezPoints(i) for j in range(nbBezPoints): ipo.delBezPoint(i) return ######################################## def resetIPO(ipo): debug(60,'Resetting ipo curve named %s' %ipo.name) numCurves = ipo.getNcurves() #like LocX, LocY, etc if numCurves > 0: eraseCurve(ipo, numCurves) #erase data if one exists return ######################################## def resetIPOs(ob): #resets all IPO curvess assocated with an object and its bones debug(30,'Resetting any ipo curves linked to %s' %ob.getName()) ipo = ob.getIpo() #may be None ipoName = ipo.getName() #name of the IPO that guides/controls this object debug(70,'Object IPO is %s' %ipoName) try: ipo = Ipo.Get(ipoName) except: ipo = Ipo.New('Object', ipoName) resetIPO(ipo) if ob.getType() == ARMATURE: arm_data=ob.getData() bones=arm_data.bones.values() for bone in bones: #for each bone: get the name and check for a Pose IPO debug(10,'Processing '+ bone.name) return ######################################## def parse(string,delim): index = string.find(delim) # -1 if not found, else pointer to delim if index+1: return string[:index] return string ######################################## def newIpo(ipoName): #add a new Ipo object to the Blender scene ipo=Blender.Ipo.New('Object',ipoName) ipo.addCurve('LocX') ipo.addCurve('LocY') ipo.addCurve('LocZ') ipo.addCurve('RotX') ipo.addCurve('RotY') ipo.addCurve('RotZ') return ipo ######################################## def makeUpaName(type,name): #i know this exists in Blender somewhere... debug(90,'Making up a new %s name using %s as a basis.' % (type,name)) name = (parse(name,'.')) if type == 'Ipo': ipoName = name # maybe we get lucky today ext = 0 extlen = 3 # 3 digit extensions, like hello.002 success = False while not(success): try: debug(100,'Trying %s' % ipoName) ipo = Ipo.Get(ipoName) #that one exists if we get here. add on extension and keep trying ext +=1 if ext>=10**extlen: extlen +=1 # go to more digits if 999 not found ipoName = '%s.%s' % (name, str(ext).zfill(extlen)) except: # could not find it success = True name=ipoName else: debug (0,'FATAL ERROR: I dont know how to make up a new %s name based on %s' % (type,ob)) return None return name ######################################## def createIpo(ob): #create an Ipo and curves and link them to this object #first, we have to create a unique name #try first with just the name of the object to keep things simple. ipoName = makeUpaName('Ipo',ob.getName()) # make up a name for a new Ipo based on the object name debug(20,'Ipo and LocRot curves called %s' % ipoName) ipo=newIpo(ipoName) ob.setIpo(ipo) #link them return ipo ######################################## def getLocLocal(ob): key = [ ob.LocX, ob.LocY, ob.LocZ, ob.RotX*R2D, #get the curves in this order ob.RotY*R2D, ob.RotZ*R2D ] return key ######################################## def getLocReal(ob): obMatrix = ob.matrixWorld #Thank you IdeasMan42 loc = obMatrix.translationPart() rot = obMatrix.toEuler() key = [ loc.x, loc.y, loc.z, rot.x/10, rot.y/10, rot.z/10 ] return key ######################################## def getLocRot(ob,space): if space in xrange(len(COORDINATE_SYSTEMS)): if space == COORD_LOCAL: key = getLocLocal(ob) return key elif space == COORD_REAL: key = getLocReal(ob) return key else: #hey, programmers make mistakes too. debug(0,'Fatal Error: getLoc called with %i' % space) return ######################################## def getCurves(ipo): ipos = [ ipo[Ipo.OB_LOCX], ipo[Ipo.OB_LOCY], ipo[Ipo.OB_LOCZ], ipo[Ipo.OB_ROTX], #get the curves in this order ipo[Ipo.OB_ROTY], ipo[Ipo.OB_ROTZ] ] return ipos ######################################## def addPoint(time,keyLocRot,ipos): if BLENDER_VERSION < 245: debug(0,'WARNING: addPoint uses BezTriple') for i in range(len(ipos)): point = BezTriple.New() #this was new with Blender 2.45 API point.pt = (time, keyLocRot[i]) point.handleTypes = [1,1] ipos[i].append(point) return ipos ######################################## def bakeFrames(ob,myipo): #bakes an object in a scene, returning the IPO containing the curves myipoName = myipo.getName() debug(20,'Baking frames for scene %s object %s to ipo %s' % (scn.getName(),ob.getName(),myipoName)) ipos = getCurves(myipo) #TODO: Gui setup idea: myOffset # reset action to start at frame 1 or at location myOffset=0 #=1-staframe #loop through frames in the animation. Often, there is rollup and the mocap starts late staframe,endframe,curframe = getRenderInfo() for frame in range(staframe, endframe+1): debug(80,'Baking Frame %i' % frame) #tell Blender to advace to frame Blender.Set(CURFRAME,frame) # computes the constrained location of the 'real' objects if not BATCH: Blender.Redraw() # no secrets, let user see what we are doing #using the constrained Loc Rot of the object, set the location of the unconstrained clone. Yea! Clones are FreeMen key = getLocRot(ob,usrCoord) #a key is a set of specifed exact channel values (LocRotScale) for a certain frame key = [a+b for a,b in zip(key, usrDelta)] #offset to the new location myframe= frame+myOffset Blender.Set(CURFRAME,myframe) time = Blender.Get('curtime') #for BezTriple ipos = addPoint(time,key,ipos) #add this data at this time to the ipos debug(100,'%s %i %.3f %.2f %.2f %.2f %.2f %.2f %.2f' % (myipoName, myframe, time, key[0], key[1], key[2], key[3], key[4], key[5])) # eye-candy - smoothly rewind the animation, showing now how the clone match moves if endframe-staframe <400 and not BATCH: for frame in range (endframe,staframe,-1): #rewind Blender.Set(CURFRAME,frame) # computes the constrained location of the 'real' objects Blender.Redraw() Blender.Set(CURFRAME,staframe) Blender.Redraw() return ipos ######################################## def duplicateLinked(ob): obType = ob.type debug(10,'Duplicating %s Object named %s' % (obType,ob.getName())) scn.objects.selected = [ob] ## rdw: simplified by just duplicating armature. kept code as reference for creating armatures ## disadvantage is that you cant have clone as stick and original as octahedron ## since they share the same Armature. User can click Make Single User button. ## if obType == ARMATURE: #build a copy from scratch ## myob= dupliArmature(ob) ## else: Blender.Object.Duplicate() # Duplicate linked, including pose constraints. myobs = Object.GetSelected() #duplicate is top on the list myob = myobs[0] if usrParent == False: myob.clrParent(usrFreeze) debug(20,'=myob= was created as %s' % myob.getName()) return myob ######################################## def removeConstraints(ob): for const in ob.constraints: debug(90,'removed %s => %s' % (ob.name, const)) ob.constraints.remove(const) return ######################################## def removeConstraintsOb(ob): # from object or armature debug(40,'Removing constraints from '+ob.getName()) if BLENDER_VERSION > 241: #constraints module not available before 242 removeConstraints(ob) if ob.getType() == ARMATURE: pose = ob.getPose() for pbone in pose.bones.values(): #bone = pose.bones[bonename] removeConstraints(pbone) #should also check if it is a deflector? return ######################################## def deLinkOb(type,ob): #remove linkages if type == 'Ipo': success = ob.clearIpo() #true=there was one if success: debug(80,'deLinked Ipo curve to %s' % ob.getName()) return ######################################## def bakeObject(ob): #bakes the core object locrot and assigns the Ipo to a Clone if ob != None: # Clone the object - duplicate it, clean the clone, and create an ipo curve for the clone myob = duplicateLinked(ob) #clone it myob.setName(usrObjectNamePrefix + ob.getName()) removeConstraintsOb(myob) #my object is a free man deLinkOb('Ipo',myob) #kids, it's not nice to share. you've been lied to if ob.getType() != ARMATURE: # baking armatures is based on bones, not object myipo = createIpo(myob) #create own IPO and curves for the clone object ipos = bakeFrames(ob,myipo) #bake the locrot for this obj for the scene frames return myob ######################################## def bake(ob,par): #bakes an object of any type, linking it to parent debug(0,'Baking %s object %s' % (ob.getType(), ob)) clone = bakeObject(ob) #creates and bakes the object motion if par!= None: par.makeParent([clone]) debug(20,"assigned object to parent %s" % par) if ob.getType() == ARMATURE: ## error('Object baked. Continue with bones?') bakeBones(ob,clone) #go into the bones and copy from -> to in frame range #future idea: bakeMesh (net result of Shapekeys, Softbody, Cloth, Fluidsim,...) return clone ######################################## def tstCreateArm(): #create a test armature in scene # rip-off from http://www.blender.org/documentation/245PythonDoc/Pose-module.html - thank you! debug(0,'Making Test Armature') # New Armature arm_data= Armature.New('myArmature') print arm_data arm_ob = scn.objects.new(arm_data) arm_data.makeEditable() # Add 4 bones ebones = [Armature.Editbone(), Armature.Editbone(), Armature.Editbone(), Armature.Editbone()] # Name the editbones ebones[0].name = 'Bone.001' ebones[1].name = 'Bone.002' ebones[2].name = 'Bone.003' ebones[3].name = 'Bone.004' # Assign the editbones to the armature for eb in ebones: arm_data.bones[eb.name]= eb # Set the locations of the bones ebones[0].head= Mathutils.Vector(0,0,0) ebones[0].tail= Mathutils.Vector(0,0,1) #tip ebones[1].head= Mathutils.Vector(0,0,1) ebones[1].tail= Mathutils.Vector(0,0,2) ebones[2].head= Mathutils.Vector(0,0,2) ebones[2].tail= Mathutils.Vector(0,0,3) ebones[3].head= Mathutils.Vector(0,0,3) ebones[3].tail= Mathutils.Vector(0,0,4) ebones[1].parent= ebones[0] ebones[2].parent= ebones[1] ebones[3].parent= ebones[2] arm_data.update() # Done with editing the armature # Assign the pose animation arm_pose = arm_ob.getPose() act = arm_ob.getAction() if not act: # Add a pose action if we dont have one act = Armature.NLA.NewAction() act.setActive(arm_ob) xbones=arm_ob.data.bones.values() pbones = arm_pose.bones.values() frame = 1 for pbone in pbones: # set bones to no rotation pbone.quat[:] = 1.000,0.000,0.000,0.0000 pbone.insertKey(arm_ob, frame, Object.Pose.ROT) # Set a different rotation at frame 25 pbones[0].quat[:] = 1.000,0.1000,0.2000,0.20000 pbones[1].quat[:] = 1.000,0.6000,0.5000,0.40000 pbones[2].quat[:] = 1.000,0.1000,0.3000,0.40000 pbones[3].quat[:] = 1.000,-0.2000,-0.3000,0.30000 frame = 25 for i in xrange(4): pbones[i].insertKey(arm_ob, frame, Object.Pose.ROT) pbones[0].quat[:] = 1.000,0.000,0.000,0.0000 pbones[1].quat[:] = 1.000,0.000,0.000,0.0000 pbones[2].quat[:] = 1.000,0.000,0.000,0.0000 pbones[3].quat[:] = 1.000,0.000,0.000,0.0000 frame = 50 for pbone in pbones: # set bones to no rotation pbone.quat[:] = 1.000,0.000,0.000,0.0000 pbone.insertKey(arm_ob, frame, Object.Pose.ROT) return arm_ob ######################################## def tstMoveOb(ob): # makes a simple LocRot animation of object in the scene anim = [ #Loc Rot/10 # ( 0,0,0, 0, 0, 0), #frame 1 origin ( 1,0,0, 0, 0, 0), #frame 2 ( 1,1,0, 0, 0, 0), ( 1,1,1, 0, 0, 0), ( 1,1,1,4.5, 0, 0), ( 1,1,1,4.5,4.5, 0), ( 1,1,1,4.5,4.5,4.5) ] space = COORD_LOCAL ipo = createIpo(ob) #create an Ipo and curves for this object ipos = getCurves(ipo) # span this motion over the currently set anim range # to set points, i need time but do not know how it is computed, so will have to advance the animation staframe,endframe,curframe = getRenderInfo() frame = staframe #x position of new ipo datapoint. set to staframe if you want a match frameDelta=(endframe-staframe)/(len(anim)) #accomplish the animation in frame range for key in anim: #effectively does a getLocRot() #tell Blender to advace to frame Blender.Set('curframe',frame) # computes the constrained location of the 'real' objects time = Blender.Get('curtime') ipos = addPoint(time,key,ipos) #add this data at this time to the ipos debug(100,'%s %i %.3f %.2f %.2f %.2f %.2f %.2f %.2f' % (ipo.name, frame, time, key[0], key[1], key[2], key[3], key[4], key[5])) frame += frameDelta Blender.Set(CURFRAME,curframe) # reset back to where we started return #================= # Program Template #================= ######################################## def main(): # return code set via rt button in Blender Buttons Scene Context Anim panel if MODE == 1: #create test armature #1 ob = tstCreateArm() # make test arm and select it tstMoveOb(ob) scn.objects.selected = [ob] obs= Blender.Object.GetSelected() #scn.objects.selected obs= sortObjects(obs) debug(0,'Baking %i objects' % len(obs)) if len(obs) >= 1: # user might have multiple objects selected i= 0 clones=[] # my clone army for ob in obs: par= ob.getParent() if not usrParent: if par in obs: par= clones[obs.index(par)] clones.append(bake(ob,par)) scn.objects.selected = clones else: error('Please select at least one object') return ######################################## def benchmark(): # This lets you benchmark (time) the script's running duration Window.WaitCursor(1) t = sys.time() debug(60,'%s began at %.0f' %(__script__,sys.time())) # Run the function on the active scene in_editmode = Window.EditMode() if in_editmode: Window.EditMode(0) main() if in_editmode: Window.EditMode(1) # Timing the script is a good way to be aware on any speed hits when scripting debug(0,'%s Script finished in %.2f seconds' % (__script__,sys.time()-t) ) Window.WaitCursor(0) return ######################################## # This lets you can import the script without running it if __name__ == '__main__': debug(0, "------------------------------------") debug(0, "%s %s Script begins with mode=%i debug=%i batch=%s" % (__script__,__version__,MODE,DEBUG,BATCH)) benchmark()