Welcome to mirror list, hosted at ThFree Co, Russian Federation.

animation_bake_constraints.py « scripts « release - git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 58e9e2b1d02b62c8947134627233da14959ab2f8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
#!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<objname>
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 <br>

Usage: <br>
 - Select the reference Object(s) you want to bake <br>
 - Set the frame range to bake in the Anim Panel <br>
 - Set the test code (if you want a self-test) in the RT field in the Anim Panel <br>
	-- Set RT:1 to create a test armature <br>
	-- Set RT: up to 100 for more debug messages and status updates <br>
<br>
 - Run the script <br>
 - The clone copy of the object is created and it has an IPO curve assigned to it. <br>
 - The clone shadows the object by an offset locrot (see usrDelta) <br>
 - That Object has Ipo Location and Rotation curves that make the clone mimic the movement <br>
	of the selected object, but without using constraints. <br>
 - If the object was an Armature, the clone's bones move identically in relation to the <br>
	original armature, and an Action is created that drives the bone movements. <br>

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<staframe: endframe=staframe
	curframe = Blender.Get(CURFRAME)
	debug(90,'Scene is on frame %i and frame range is %i to %i' % (curframe,staframe,endframe))
	return (staframe,endframe,curframe)

########################################
def sortObjects(obs): #returns a list of objects sorted based on parent dependency
	obClones= [] 
	while len(obClones) < len(obs):
		for ob in obs:
			if not ob in obClones:
				par= ob.getParent()
				#if no parent, or the parent is not scheduled to be cloned
				if par==None:
					obClones.append(ob) # add the independent
				elif par not in obs: # parent will not be cloned
					obClones.append(ob) # add the child
				elif par in obClones: # is it on the list?
					obClones.append(ob) # add the child
				# parent may be a child, so it will be caught next time thru
	debug(100,'clone object order: \n%s' % obClones)
	return obClones # ordered list of (ob, par) tuples

########################################
def sortBones(xbones): #returns a sorted list of bones that should be added,sorted based on parent dependency
#  while there are bones to add,
#    look thru the list of bones we need to add
#      if we have not already added this bone
#         if it does not have a parent
#           add it
#          else, it has a parent
#           if we already added it's parent
#               add it now.
#           else #we need to keep cycling and catch its parent
#         else it is a root bone
#           add it
#       else skip it, it's already in there
#     endfor
#  endwhile
	xboneNames=[]
	for xbone in xbones: xboneNames.append(xbone.name)
	debug (80,'reference bone order: \n%s' % xboneNames)
	eboneNames=[]
	while len(eboneNames) < len(xboneNames):
		for xbone in xbones:
			if not xbone.name in eboneNames:
				if not xbone.parent:
					eboneNames.append(xbone.name)
				else:
					if xbone.parent.name in eboneNames:
						eboneNames.append(xbone.name)
					#else skip it
				#endif
			#else prego
		#endfor
	#endwhile
	debug (80,'clone bone order: \n%s' % eboneNames)
	return eboneNames

########################################
def dupliArmature(ob): #makes a copy in current scn of the armature used by ob and its bones
	ob_mat = ob.matrixWorld
	ob_data = ob.getData()
	debug(49,'Reference object uses %s' % ob_data)
	arm_ob = Armature.Get(ob_data.name) #the armature used by the passed object
	
	arm = Blender.Armature.New()
	debug(20,'Cloning Armature %s to create %s' % (arm_ob.name, arm.name))
	arm.drawType = Armature.STICK #set the draw type

	arm.makeEditable() #enter editmode

	# for each bone in the object's armature,
	xbones=ob.data.bones.values()
	usrSpace = 0 #0=armature, 1=local
	space=[BONE_SPACES[usrSpace]][0] 

	#we have to make a list of bones, then figure out our parents, then add to the arm
	#when creating a child, we cannot link to a parent if it does not yet exist in our armature 
	ebones = [] #list of the bones I want to create for my arm

	eboneNames = sortBones(xbones)

	i=0
	# error('bones sorted. continue?')
	for abone in eboneNames: #set all editable attributes to fully define the bone.
		for bone in xbones: 
			if bone.name == abone: break  # get the reference bone
		ebone = Armature.Editbone() #throw me a bone, bone-man!
		ebones.append(ebone) #you're on my list, buddy

		ebone.name = bone.name
		ebone.headRadius = bone.headRadius
		ebone.tailRadius = bone.tailRadius
		ebone.weight = bone.weight
		ebone.options = bone.options
		
		ebone.head = bone.head[space] #dictionary lookups
		ebone.tail = bone.tail[space]
		ebone.matrix = bone.matrix[space]
		ebone.roll = bone.roll[space]

		debug(30,'Generating new %s as child of %s' % (bone,bone.parent))
		if bone.hasParent(): 
#      parent=bone.parent.name
#      debug(100,'looking for %s' % parent)
#      for parbone in xbones: if parbone.name == parent: break # get the parent bone
#      ebone.parent = arm.bones[ebones[j].name]
			ebone.parent = arm.bones[bone.parent.name]
#    else:
#       ebone.parent = None
		debug(30,'Generating new editbone %s as child of %s' % (ebone,ebone.parent))
		arm.bones[ebone.name] = ebone # i would have expected an append or add function, but this works

	debug (100,'arm.bones: \n%s' % arm.bones)
	debug (20,'Cloned %i bones now in armature %s' %(len(arm.bones),arm.name)) 

	myob = scn.objects.new(arm) #interestingly, object must be created before 
	arm.update()                #armature can be saved
	debug(40,'dupArm finished %s instanced as object %s' % (arm.name,myob.getName()))
	print ob.matrix
	print myob.matrix
	
	return myob
########################################
def scrub(): # scrubs to startframe
	staFrame,endFrame,curFrame = getRenderInfo()

	# eye-candy, go from current to start, fwd or back
	if not BATCH:
		debug(100, "Positioning to start...")
		frameinc=(staFrame-curFrame)/10
		if abs(frameinc) >= 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()