forked from bartvdbraak/blender
00ed5a2cc9
Fix in Bake Constraints script, better naming for new object.
793 lines
28 KiB
Python
793 lines
28 KiB
Python
#!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()
|