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