merge with 2.5 at r18679
[blender.git] / release / scripts / animation_bake_constraints.py
1 #!BPY
2
3 """
4 Name: 'Bake Constraints'
5 Blender: 246
6 Group: 'Animation'
7 Tooltip: 'Bake a Constrained object/rig to IPOs'
8 Fillename: 'Bake_Constraint.py'
9 """
10
11 __author__ = "Roger Wickes (rogerwickes(at)yahoo.com)"
12 __script__ = "Animation Bake Constraints"
13 __version__ = "0.7"
14 __url__ = ["Communicate problems and errors, http://www.blenderartists.com/forum/private.php?do=newpm to PapaSmurf"]
15 __email__= ["Roger Wickes, rogerwickes@yahoo.com", "scripts"]
16 __bpydoc__ = """\
17
18 bake_constraints
19
20 This script bakes the real-world LocRot of an object (the net effect of any constraints - 
21 (Copy, Limit, Track, Follow, - that affect Location, Rotation)
22 (usually one constrained to match another's location and/or Tracked to another)
23 and creates a clone with a set of Ipo Curves named Ipo<objname>
24 These curves control a non-constrained object and thus make it mimic the constrained object
25 Actions can be then be edited without the need for the drivers/constraining objects
26
27 Developed for use with MoCap data, where a bone is constrained to point at an empty
28 moving through space and time. This records the actual locrot of the armature
29 so that the motion can be edited, reoriented, scaled, and used as NLA Actions
30
31 see also wiki Scripts/Manual/ Tutorial/Motion Capture <br>
32
33 Usage: <br>
34  - Select the reference Object(s) you want to bake <br>
35  - Set the frame range to bake in the Anim Panel <br>
36  - Set the test code (if you want a self-test) in the RT field in the Anim Panel <br>
37         -- Set RT:1 to create a test armature <br>
38         -- Set RT: up to 100 for more debug messages and status updates <br>
39 <br>
40  - Run the script <br>
41  - The clone copy of the object is created and it has an IPO curve assigned to it. <br>
42  - The clone shadows the object by an offset locrot (see usrDelta) <br>
43  - That Object has Ipo Location and Rotation curves that make the clone mimic the movement <br>
44         of the selected object, but without using constraints. <br>
45  - If the object was an Armature, the clone's bones move identically in relation to the <br>
46         original armature, and an Action is created that drives the bone movements. <br>
47
48 Version History:
49         0.1: bakes Loc Rot for a constrained object
50         0.2: bakes Loc and Rot for the bones within Armature object
51         0.3: UI for setting options
52         0.3.1 add manual to script library
53         0.4: bake multiple objects
54         0.5: root bone worldspace rotation
55         0.6: re-integration with BPyArmature
56         0.7: bakes parents and leaves clones selected
57     
58 License, Copyright, and Attribution:
59   by Roger WICKES  May 2008, released under Blender Artistic Licence to Public Domain
60     feel free to add to any Blender Python Scripts Bundle.
61  Thanks to Jean-Baptiste PERIN, IdeasMan42 (Campbell Barton), Basil_Fawlty/Cage_drei (Andrew Cruse)
62  much lifted/learned from blender.org/documentation/245PytonDoc and wiki
63  some modules based on c3D_Import.py, PoseLib16.py and IPO/Armature code examples e.g. camera jitter
64
65 Pseudocode:
66         Initialize
67         If at least one object is selected
68                 For each selected object,
69                         create a cloned object
70                         remove any constraints on the clone
71                         create or reset an ipo curve named like the object
72                         for each frame
73                                  set the clone's locrot key based on the reference object
74                         if it's an armature,
75                                 create an action (which is an Ipo for each bone)
76                                 for each frame of the animation
77                                         for each bone in the armature
78                                                 set the key
79         Else you're a smurf
80
81 Test Conditions and Regressions:
82         1. (v0.1) Non-armatures (the cube), with ipo curve and constraints at the object level
83         2. armatures, with ipo curve and constraints at the object level
84         3. armatures, with bones that have ipo curves and constraints
85         4. objects without parents, children with unselected parents, select children first.
86   
87 Naming conventions:
88         arm = a specific objec type armature
89         bone = bones that make up the skeleton of an armature
90
91         ob = object, an instance of an object type
92         ebone = edit bone, a bone in edit mode
93         pbone = pose bone, a posed bone in an object
94         tst = testing, self-test routines
95         usr = user-entered or designated stuff  
96 """
97 ########################################
98
99 import Blender
100 from   Blender import *
101 from   Blender.Mathutils import *
102 import struct
103 import string
104 import bpy
105 import BPyMessages
106 import BPyArmature
107 # reload(BPyArmature)
108 from   BPyArmature import getBakedPoseData
109
110 Vector= Blender.Mathutils.Vector
111 Euler= Blender.Mathutils.Euler
112 Matrix= Blender.Mathutils.Matrix #invert() function at least
113 RotationMatrix = Blender.Mathutils.RotationMatrix
114 TranslationMatrix= Blender.Mathutils.TranslationMatrix
115 Quaternion = Blender.Mathutils.Quaternion
116 Vector = Blender.Mathutils.Vector
117 POSE_XFORM= [Blender.Object.Pose.LOC, Blender.Object.Pose.ROT]
118
119 #=================
120 # Global Variables
121 #=================
122
123 # set senstitivity for displaying debug/console messages. 0=none, 100=max
124 # then call debug(num,string) to conditionally display status/info in console window
125 MODE=Blender.Get('rt')  #execution mode: 0=run normal, 1=make test armature
126 DEBUG=Blender.Get('rt')   #how much detail on internal processing for user to see. range 0-100
127 BATCH=False #called from command line? is someone there? Would you like some cake?
128
129 #there are two coordinate systems, the real, or absolute 3D space,
130 # and the local relative to a parent.
131 COORDINATE_SYSTEMS = ['local','real']
132 COORD_LOCAL = 0
133 COORD_REAL = 1
134
135 # User Settings - Change these options manually or via GUI (future TODO)
136 usrCoord = COORD_REAL # what the user wants
137 usrParent = False # True=clone keeps original parent, False = clone's parent is the clone of the original parent (if cloned)
138 usrFreeze = 2 #2=yes, 0=no. Freezes shadow object in place at current frame as origin
139 # delta is amount to offset/change from the reference object. future set in a ui, so technically not a constant
140 usrDelta = [10,10,0,0,0,0] #order specific - Loc xyz Rot xyz
141 usrACTION = True # Offset baked Action frames to start at frame 1
142
143 CURFRAME = 'curframe' #keyword to use when getting the frame number that the scene is presently on
144 ARMATURE = 'Armature' #en anglais
145 BONE_SPACES = ['ARMATURESPACE','BONESPACE']
146                 # 'ARMATURESPACE' - this matrix of the bone in relation to the armature
147                 # 'BONESPACE' - the matrix of the bone in relation to itself
148
149 #Ipo curves created are prefixed with a name, like Ipo_ or Bake_ followed by the object/bone name
150 #bakedArmName = "b." #used for both the armature class and object instance
151 usrObjectNamePrefix= ""
152 #ipoBoneNamePrefix  = ""
153 # for example, if on entry an armature named Man was selected, and the object prefix was "a."
154 #  on exit an armature and an IPO curve named a.Man exists for the object as a whole
155 # if that armature had bones (spine, neck, arm) and the bone prefix was "a."
156 #  the bones and IPO curves will be (a.spine, a.neck, a.arm)
157
158 R2D = 18/3.141592653589793  # radian to grad
159 BLENDER_VERSION = Blender.Get('version')
160
161 # Gets the current scene, there can be many scenes in 1 blend file. 
162 scn = Blender.Scene.GetCurrent()
163
164 #=================
165 # Methods
166 #=================
167 ########################################
168 def debug(num,msg): #use log4j or just console here.
169         if DEBUG >= num:
170                 if BATCH == False:
171                         print 'debug:             '[:num/10+7]+msg
172                 #TODO: else write out to file (runs faster if it doesnt have to display details)
173         return
174
175 ########################################
176 def error(str):
177         debug(0,'ERROR: '+str)
178         if BATCH == False:
179                 Draw.PupMenu('ERROR%t|'+str)
180         return
181
182 ########################################
183 def getRenderInfo():
184         context=scn.getRenderingContext() 
185         staframe = context.startFrame()
186         endframe = context.endFrame()
187         if endframe<staframe: endframe=staframe
188         curframe = Blender.Get(CURFRAME)
189         debug(90,'Scene is on frame %i and frame range is %i to %i' % (curframe,staframe,endframe))
190         return (staframe,endframe,curframe)
191
192 ########################################
193 def sortObjects(obs): #returns a list of objects sorted based on parent dependency
194         obClones= [] 
195         while len(obClones) < len(obs):
196                 for ob in obs:
197                         if not ob in obClones:
198                                 par= ob.getParent()
199                                 #if no parent, or the parent is not scheduled to be cloned
200                                 if par==None:
201                                         obClones.append(ob) # add the independent
202                                 elif par not in obs: # parent will not be cloned
203                                         obClones.append(ob) # add the child
204                                 elif par in obClones: # is it on the list?
205                                         obClones.append(ob) # add the child
206                                 # parent may be a child, so it will be caught next time thru
207         debug(100,'clone object order: \n%s' % obClones)
208         return obClones # ordered list of (ob, par) tuples
209
210 ########################################
211 def sortBones(xbones): #returns a sorted list of bones that should be added,sorted based on parent dependency
212 #  while there are bones to add,
213 #    look thru the list of bones we need to add
214 #      if we have not already added this bone
215 #         if it does not have a parent
216 #           add it
217 #          else, it has a parent
218 #           if we already added it's parent
219 #               add it now.
220 #           else #we need to keep cycling and catch its parent
221 #         else it is a root bone
222 #           add it
223 #       else skip it, it's already in there
224 #     endfor
225 #  endwhile
226         xboneNames=[]
227         for xbone in xbones: xboneNames.append(xbone.name)
228         debug (80,'reference bone order: \n%s' % xboneNames)
229         eboneNames=[]
230         while len(eboneNames) < len(xboneNames):
231                 for xbone in xbones:
232                         if not xbone.name in eboneNames:
233                                 if not xbone.parent:
234                                         eboneNames.append(xbone.name)
235                                 else:
236                                         if xbone.parent.name in eboneNames:
237                                                 eboneNames.append(xbone.name)
238                                         #else skip it
239                                 #endif
240                         #else prego
241                 #endfor
242         #endwhile
243         debug (80,'clone bone order: \n%s' % eboneNames)
244         return eboneNames
245
246 ########################################
247 def dupliArmature(ob): #makes a copy in current scn of the armature used by ob and its bones
248         ob_mat = ob.matrixWorld
249         ob_data = ob.getData()
250         debug(49,'Reference object uses %s' % ob_data)
251         arm_ob = Armature.Get(ob_data.name) #the armature used by the passed object
252         
253         arm = Blender.Armature.New()
254         debug(20,'Cloning Armature %s to create %s' % (arm_ob.name, arm.name))
255         arm.drawType = Armature.STICK #set the draw type
256
257         arm.makeEditable() #enter editmode
258
259         # for each bone in the object's armature,
260         xbones=ob.data.bones.values()
261         usrSpace = 0 #0=armature, 1=local
262         space=[BONE_SPACES[usrSpace]][0] 
263
264         #we have to make a list of bones, then figure out our parents, then add to the arm
265         #when creating a child, we cannot link to a parent if it does not yet exist in our armature 
266         ebones = [] #list of the bones I want to create for my arm
267
268         eboneNames = sortBones(xbones)
269
270         i=0
271         # error('bones sorted. continue?')
272         for abone in eboneNames: #set all editable attributes to fully define the bone.
273                 for bone in xbones: 
274                         if bone.name == abone: break  # get the reference bone
275                 ebone = Armature.Editbone() #throw me a bone, bone-man!
276                 ebones.append(ebone) #you're on my list, buddy
277
278                 ebone.name = bone.name
279                 ebone.headRadius = bone.headRadius
280                 ebone.tailRadius = bone.tailRadius
281                 ebone.weight = bone.weight
282                 ebone.options = bone.options
283                 
284                 ebone.head = bone.head[space] #dictionary lookups
285                 ebone.tail = bone.tail[space]
286                 ebone.matrix = bone.matrix[space]
287                 ebone.roll = bone.roll[space]
288
289                 debug(30,'Generating new %s as child of %s' % (bone,bone.parent))
290                 if bone.hasParent(): 
291 #      parent=bone.parent.name
292 #      debug(100,'looking for %s' % parent)
293 #      for parbone in xbones: if parbone.name == parent: break # get the parent bone
294 #      ebone.parent = arm.bones[ebones[j].name]
295                         ebone.parent = arm.bones[bone.parent.name]
296 #    else:
297 #       ebone.parent = None
298                 debug(30,'Generating new editbone %s as child of %s' % (ebone,ebone.parent))
299                 arm.bones[ebone.name] = ebone # i would have expected an append or add function, but this works
300
301         debug (100,'arm.bones: \n%s' % arm.bones)
302         debug (20,'Cloned %i bones now in armature %s' %(len(arm.bones),arm.name)) 
303
304         myob = scn.objects.new(arm) #interestingly, object must be created before 
305         arm.update()                #armature can be saved
306         debug(40,'dupArm finished %s instanced as object %s' % (arm.name,myob.getName()))
307         print ob.matrix
308         print myob.matrix
309         
310         return myob
311 ########################################
312 def scrub(): # scrubs to startframe
313         staFrame,endFrame,curFrame = getRenderInfo()
314
315         # eye-candy, go from current to start, fwd or back
316         if not BATCH:
317                 debug(100, "Positioning to start...")
318                 frameinc=(staFrame-curFrame)/10
319                 if abs(frameinc) >= 1:
320                         for i in range(10):
321                                 curFrame+=frameinc
322                                 Blender.Set(CURFRAME,curFrame) # computes the constrained location of the 'real' objects
323                                 Blender.Redraw()
324         Blender.Set(CURFRAME, staFrame)
325         return
326
327 ########################################
328 def bakeBones(ref_ob,arm_ob): #copy pose from ref_ob to arm_ob
329         scrub()
330         staFrame,endFrame,curFrame = getRenderInfo()
331         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
332         arm_ob.action = act
333         scrub()  
334         
335         # user comprehension feature - change action name and channel ipo names to match the names of the bone they drive
336         debug (80,'Renaming each action ipo to match the bone they pose')
337         act.name = arm_ob.name
338         arm_channels = act.getAllChannelIpos()
339         pose= arm_ob.getPose()
340         pbones= pose.bones.values() #we want the bones themselves, not the dictionary lookup
341         for pbone in pbones:
342                 debug (100,'Channel listing for %s: %s' % (pbone.name,arm_channels[pbone.name] ))
343                 ipo=arm_channels[pbone.name]
344                 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
345
346         return
347
348 ########################################
349 def getOrCreateCurve(ipo, curvename):
350         """
351         Retrieve or create a Blender Ipo Curve named C{curvename} in the C{ipo} Ipo
352         Either an ipo curve named C{curvename} exists before the call then this curve is returned,
353         Or such a curve doesn't exist before the call .. then it is created into the c{ipo} Ipo and returned 
354         """
355         try:
356                 mycurve = ipo.getCurve(curvename)
357                 if mycurve != None:
358                         pass
359                 else:
360                         mycurve = ipo.addCurve(curvename)
361         except:
362                 mycurve = ipo.addCurve(curvename)
363         return mycurve
364
365 ########################################
366 def eraseCurve(ipo,numCurves):
367         debug(90,'Erasing %i curves for %' % (numCurves,ipo.GetName()))
368         for i in range(numCurves):
369                 nbBezPoints= ipo.getNBezPoints(i)
370                 for j in range(nbBezPoints):
371                         ipo.delBezPoint(i)
372         return
373
374 ########################################
375 def resetIPO(ipo):
376         debug(60,'Resetting ipo curve named %s' %ipo.name)
377         numCurves = ipo.getNcurves() #like LocX, LocY, etc
378         if numCurves > 0:
379                 eraseCurve(ipo, numCurves) #erase data if one exists
380         return
381
382 ########################################
383 def resetIPOs(ob): #resets all IPO curvess assocated with an object and its bones
384         debug(30,'Resetting any ipo curves linked to %s' %ob.getName())
385         ipo = ob.getIpo() #may be None
386         ipoName = ipo.getName() #name of the IPO that guides/controls this object
387         debug(70,'Object IPO is %s' %ipoName)
388         try:
389                 ipo = Ipo.Get(ipoName)
390         except:
391                 ipo = Ipo.New('Object', ipoName)
392         resetIPO(ipo)
393         if ob.getType() == ARMATURE:
394                 arm_data=ob.getData()
395                 bones=arm_data.bones.values()
396                 for bone in bones:
397                         #for each bone: get the name and check for a Pose IPO
398                         debug(10,'Processing '+ bone.name)
399         return
400
401 ########################################
402 def parse(string,delim):
403         index = string.find(delim) # -1 if not found, else pointer to delim
404         if index+1:  return string[:index]
405         return string
406
407 ########################################
408 def newIpo(ipoName): #add a new Ipo object to the Blender scene
409         ipo=Blender.Ipo.New('Object',ipoName)
410
411         ipo.addCurve('LocX')
412         ipo.addCurve('LocY')
413         ipo.addCurve('LocZ')
414         ipo.addCurve('RotX')
415         ipo.addCurve('RotY')
416         ipo.addCurve('RotZ')
417         return ipo
418
419 ########################################
420 def makeUpaName(type,name): #i know this exists in Blender somewhere...
421         debug(90,'Making up a new %s name using %s as a basis.' % (type,name))
422         name = (parse(name,'.'))
423         if type == 'Ipo':
424                 ipoName = name # maybe we get lucky today
425                 ext = 0
426                 extlen = 3 # 3 digit extensions, like hello.002
427                 success = False
428                 while not(success):
429                         try:
430                                 debug(100,'Trying %s' % ipoName)
431                                 ipo = Ipo.Get(ipoName)
432                                 #that one exists if we get here. add on extension and keep trying
433                                 ext +=1
434                                 if ext>=10**extlen: extlen +=1 # go to more digits if 999 not found
435                                 ipoName = '%s.%s' % (name, str(ext).zfill(extlen))
436                         except: # could not find it
437                                 success = True
438                 name=ipoName 
439         else:
440                 debug (0,'FATAL ERROR: I dont know how to make up a new %s name based on %s' % (type,ob))
441                 return None
442         return name
443
444 ########################################
445 def createIpo(ob): #create an Ipo and curves and link them to this object
446         #first, we have to create a unique name
447         #try first with just the name of the object to keep things simple.
448         ipoName = makeUpaName('Ipo',ob.getName()) # make up a name for a new Ipo based on the object name
449         debug(20,'Ipo and LocRot curves called %s' % ipoName)
450         ipo=newIpo(ipoName)
451         ob.setIpo(ipo) #link them
452         return ipo
453
454 ########################################
455 def getLocLocal(ob):
456         key = [
457                         ob.LocX, 
458                         ob.LocY, 
459                         ob.LocZ,
460                         ob.RotX*R2D, #get the curves in this order
461                         ob.RotY*R2D, 
462                         ob.RotZ*R2D
463                         ]
464         return key
465
466 ########################################
467 def getLocReal(ob):
468         obMatrix = ob.matrixWorld #Thank you IdeasMan42
469         loc = obMatrix.translationPart()
470         rot = obMatrix.toEuler()
471         key = [
472                         loc.x,
473                         loc.y,
474                         loc.z,
475                         rot.x/10,
476                         rot.y/10,
477                         rot.z/10
478                         ]
479         return key
480
481 ########################################
482 def getLocRot(ob,space):
483         if space in xrange(len(COORDINATE_SYSTEMS)):
484                 if space == COORD_LOCAL:
485                         key = getLocLocal(ob)
486                         return key
487                 elif space == COORD_REAL:
488                         key = getLocReal(ob)
489                         return key
490                 else: #hey, programmers make mistakes too.
491                         debug(0,'Fatal Error: getLoc called with %i' % space)
492         return
493
494 ########################################
495 def getCurves(ipo):
496         ipos = [
497                         ipo[Ipo.OB_LOCX],
498                         ipo[Ipo.OB_LOCY],
499                         ipo[Ipo.OB_LOCZ],
500                         ipo[Ipo.OB_ROTX], #get the curves in this order
501                         ipo[Ipo.OB_ROTY],
502                         ipo[Ipo.OB_ROTZ]
503                         ]
504         return ipos
505
506 ########################################
507 def addPoint(time,keyLocRot,ipos):
508         if BLENDER_VERSION < 245:
509                 debug(0,'WARNING: addPoint uses BezTriple')
510         for i in range(len(ipos)):
511                 point = BezTriple.New() #this was new with Blender 2.45 API
512                 point.pt = (time, keyLocRot[i])
513                 point.handleTypes = [1,1]
514
515                 ipos[i].append(point)
516         return ipos
517
518 ########################################
519 def bakeFrames(ob,myipo): #bakes an object in a scene, returning the IPO containing the curves
520         myipoName = myipo.getName()
521         debug(20,'Baking frames for scene %s object %s to ipo %s' % (scn.getName(),ob.getName(),myipoName))
522         ipos = getCurves(myipo)
523         #TODO: Gui setup idea: myOffset
524         # reset action to start at frame 1 or at location
525         myOffset=0 #=1-staframe
526         #loop through frames in the animation. Often, there is rollup and the mocap starts late
527         staframe,endframe,curframe = getRenderInfo()
528         for frame in range(staframe, endframe+1):
529                 debug(80,'Baking Frame %i' % frame)
530                 #tell Blender to advace to frame
531                 Blender.Set(CURFRAME,frame) # computes the constrained location of the 'real' objects
532                 if not BATCH: Blender.Redraw() # no secrets, let user see what we are doing
533                         
534                 #using the constrained Loc Rot of the object, set the location of the unconstrained clone. Yea! Clones are FreeMen
535                 key = getLocRot(ob,usrCoord) #a key is a set of specifed exact channel values (LocRotScale) for a certain frame
536                 key = [a+b for a,b in zip(key, usrDelta)] #offset to the new location
537
538                 myframe= frame+myOffset
539                 Blender.Set(CURFRAME,myframe)
540                 
541                 time = Blender.Get('curtime') #for BezTriple
542                 ipos = addPoint(time,key,ipos) #add this data at this time to the ipos
543                 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]))
544         # eye-candy - smoothly rewind the animation, showing now how the clone match moves
545         if endframe-staframe <400 and not BATCH:
546                 for frame in range (endframe,staframe,-1): #rewind
547                         Blender.Set(CURFRAME,frame) # computes the constrained location of the 'real' objects
548                         Blender.Redraw()
549         Blender.Set(CURFRAME,staframe)
550         Blender.Redraw()
551
552         return ipos
553
554 ########################################
555 def duplicateLinked(ob):
556         obType = ob.type
557         debug(10,'Duplicating %s Object named %s' % (obType,ob.getName()))
558         scn.objects.selected = [ob]
559 ##      rdw: simplified by just duplicating armature. kept code as reference for creating armatures
560 ##        disadvantage is that you cant have clone as stick and original as octahedron
561 ##        since they share the same Armature. User can click Make Single User button.      
562 ##      if obType == ARMATURE: #build a copy from scratch
563 ##        myob= dupliArmature(ob)
564 ##      else:
565         Blender.Object.Duplicate() # Duplicate linked, including pose constraints.
566         myobs = Object.GetSelected() #duplicate is top on the list
567         myob = myobs[0]
568         if usrParent == False:
569                 myob.clrParent(usrFreeze)     
570         debug(20,'=myob= was created as %s' % myob.getName())
571         return myob
572
573 ########################################
574 def removeConstraints(ob):
575         for const in ob.constraints:
576                 debug(90,'removed %s => %s' % (ob.name, const))
577                 ob.constraints.remove(const)
578         return
579     
580 ########################################
581 def removeConstraintsOb(ob): # from object or armature
582         debug(40,'Removing constraints from '+ob.getName())
583         if BLENDER_VERSION > 241: #constraints module not available before 242
584                 removeConstraints(ob)
585                 if ob.getType() == ARMATURE:
586                         pose = ob.getPose()
587                         for pbone in pose.bones.values():
588                                 #bone = pose.bones[bonename]
589                                 removeConstraints(pbone)
590                 #should also check if it is a deflector?
591         return
592
593 ########################################
594 def deLinkOb(type,ob): #remove linkages
595         if type == 'Ipo':
596                 success = ob.clearIpo() #true=there was one
597                 if success: debug(80,'deLinked Ipo curve to %s' % ob.getName())
598         return
599
600 ########################################
601 def bakeObject(ob): #bakes the core object locrot and assigns the Ipo to a Clone
602         if ob != None:  
603                 # Clone the object - duplicate it, clean the clone, and create an ipo curve for the clone
604                 myob = duplicateLinked(ob)  #clone it
605                 myob.setName(usrObjectNamePrefix + ob.getName())
606                 removeConstraintsOb(myob)   #my object is a free man
607                 deLinkOb('Ipo',myob)        #kids, it's not nice to share. you've been lied to
608                 if ob.getType() != ARMATURE: # baking armatures is based on bones, not object
609                         myipo = createIpo(myob)     #create own IPO and curves for the clone object
610                         ipos = bakeFrames(ob,myipo) #bake the locrot for this obj for the scene frames
611         return myob
612     
613 ########################################
614 def bake(ob,par): #bakes an object of any type, linking it to parent
615         debug(0,'Baking %s object %s' % (ob.getType(), ob))
616         clone = bakeObject(ob) #creates and bakes the object motion
617         if par!= None:
618                 par.makeParent([clone])  
619                 debug(20,"assigned object to parent %s" % par)
620         if ob.getType() == ARMATURE:
621 ##              error('Object baked. Continue with bones?')
622                 bakeBones(ob,clone) #go into the bones and copy from -> to in frame range
623         #future idea: bakeMesh (net result of Shapekeys, Softbody, Cloth, Fluidsim,...)
624         return clone
625     
626 ########################################
627 def tstCreateArm(): #create a test armature in scene
628         # rip-off from http://www.blender.org/documentation/245PythonDoc/Pose-module.html - thank you!
629
630         debug(0,'Making Test Armature')
631         # New Armature
632         arm_data= Armature.New('myArmature')
633         print arm_data
634         arm_ob = scn.objects.new(arm_data)
635         arm_data.makeEditable()
636
637         # Add 4 bones
638         ebones = [Armature.Editbone(), Armature.Editbone(), Armature.Editbone(), Armature.Editbone()]
639
640         # Name the editbones
641         ebones[0].name = 'Bone.001'
642         ebones[1].name = 'Bone.002'
643         ebones[2].name = 'Bone.003'
644         ebones[3].name = 'Bone.004'
645
646         # Assign the editbones to the armature
647         for eb in ebones:
648                 arm_data.bones[eb.name]= eb
649
650         # Set the locations of the bones
651         ebones[0].head= Mathutils.Vector(0,0,0)
652         ebones[0].tail= Mathutils.Vector(0,0,1) #tip
653         ebones[1].head= Mathutils.Vector(0,0,1)
654         ebones[1].tail= Mathutils.Vector(0,0,2)
655         ebones[2].head= Mathutils.Vector(0,0,2)
656         ebones[2].tail= Mathutils.Vector(0,0,3)
657         ebones[3].head= Mathutils.Vector(0,0,3)
658         ebones[3].tail= Mathutils.Vector(0,0,4)
659
660         ebones[1].parent= ebones[0]
661         ebones[2].parent= ebones[1]
662         ebones[3].parent= ebones[2]
663
664         arm_data.update()
665         # Done with editing the armature
666
667         # Assign the pose animation
668         arm_pose = arm_ob.getPose()
669
670         act = arm_ob.getAction()
671         if not act: # Add a pose action if we dont have one
672                 act = Armature.NLA.NewAction()
673                 act.setActive(arm_ob)
674
675         xbones=arm_ob.data.bones.values()
676         pbones = arm_pose.bones.values()
677
678         frame = 1
679         for pbone in pbones: # set bones to no rotation
680                 pbone.quat[:] = 1.000,0.000,0.000,0.0000
681                 pbone.insertKey(arm_ob, frame, Object.Pose.ROT)
682
683         # Set a different rotation at frame 25
684         pbones[0].quat[:] = 1.000,0.1000,0.2000,0.20000
685         pbones[1].quat[:] = 1.000,0.6000,0.5000,0.40000
686         pbones[2].quat[:] = 1.000,0.1000,0.3000,0.40000
687         pbones[3].quat[:] = 1.000,-0.2000,-0.3000,0.30000
688
689         frame = 25
690         for i in xrange(4):
691                 pbones[i].insertKey(arm_ob, frame, Object.Pose.ROT)
692
693         pbones[0].quat[:] = 1.000,0.000,0.000,0.0000
694         pbones[1].quat[:] = 1.000,0.000,0.000,0.0000
695         pbones[2].quat[:] = 1.000,0.000,0.000,0.0000
696         pbones[3].quat[:] = 1.000,0.000,0.000,0.0000
697
698         frame = 50      
699         for pbone in pbones: # set bones to no rotation
700                 pbone.quat[:] = 1.000,0.000,0.000,0.0000
701                 pbone.insertKey(arm_ob, frame, Object.Pose.ROT)
702
703         return arm_ob
704
705 ########################################
706 def tstMoveOb(ob): # makes a simple LocRot animation of object in the scene
707         anim = [
708                 #Loc      Rot/10
709                 #
710                 ( 0,0,0, 0, 0, 0), #frame 1 origin
711                 ( 1,0,0, 0, 0, 0), #frame 2
712                 ( 1,1,0, 0, 0, 0),
713                 ( 1,1,1, 0, 0, 0),
714                 ( 1,1,1,4.5,  0,  0),
715                 ( 1,1,1,4.5,4.5,  0),
716                 ( 1,1,1,4.5,4.5,4.5)
717                 ]
718         space = COORD_LOCAL
719         ipo = createIpo(ob) #create an Ipo and curves for this object
720         ipos = getCurves(ipo)
721         
722         # span this motion over the currently set anim range
723         # to set points, i need time but do not know how it is computed, so will have to advance the animation
724         staframe,endframe,curframe = getRenderInfo()
725
726         frame = staframe #x position of new ipo datapoint. set to staframe if you want a match
727         frameDelta=(endframe-staframe)/(len(anim)) #accomplish the animation in frame range
728         for key in anim: #effectively does a getLocRot()
729                 #tell Blender to advace to frame
730                 Blender.Set('curframe',frame) # computes the constrained location of the 'real' objects
731                 time = Blender.Get('curtime')
732
733                 ipos = addPoint(time,key,ipos) #add this data at this time to the ipos
734
735                 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]))
736                 frame += frameDelta
737         Blender.Set(CURFRAME,curframe) # reset back to where we started
738         return
739 #=================
740 # Program Template
741 #=================
742 ########################################
743 def main():
744         # return code set via rt button in Blender Buttons Scene Context Anim panel
745         if MODE == 1: #create test armature #1
746                 ob = tstCreateArm()      # make test arm and select it
747                 tstMoveOb(ob)
748                 scn.objects.selected = [ob]
749
750         obs= Blender.Object.GetSelected() #scn.objects.selected
751         obs= sortObjects(obs)
752         debug(0,'Baking %i objects' % len(obs))
753
754         if len(obs) >= 1:   # user might have multiple objects selected
755                 i= 0
756                 clones=[] # my clone army
757                 for ob in obs:
758                         par= ob.getParent()
759                         if not usrParent:
760                                 if par in obs:
761                                         par= clones[obs.index(par)]
762                         clones.append(bake(ob,par))
763                 scn.objects.selected = clones
764         else:
765                 error('Please select at least one object')
766         return
767
768 ########################################
769 def benchmark(): # This lets you benchmark (time) the script's running duration 
770         Window.WaitCursor(1) 
771         t = sys.time() 
772         debug(60,'%s began at %.0f' %(__script__,sys.time()))
773
774         # Run the function on the active scene
775         in_editmode = Window.EditMode()
776         if in_editmode: Window.EditMode(0)
777
778         main() 
779
780         if in_editmode: Window.EditMode(1)
781         
782         # Timing the script is a good way to be aware on any speed hits when scripting 
783         debug(0,'%s Script finished in %.2f seconds' % (__script__,sys.time()-t) )
784         Window.WaitCursor(0) 
785         return
786
787 ########################################
788 # This lets you can import the script without running it 
789 if __name__ == '__main__': 
790         debug(0, "------------------------------------")
791         debug(0, "%s %s Script begins with mode=%i debug=%i batch=%s" % (__script__,__version__,MODE,DEBUG,BATCH))
792         benchmark()