BPyArmature - has a function that gets final pose locations/rotations. the data it returns can be swaped with IPO curve/locations, so exporters can use this to export bones with IK's/constraints.
export_cal3d.py - option to export with baked animation from posebones, added popup UI with some options, fixed object scaling, get the meshes armature if its not selected.
This commit is contained in:
137
release/scripts/bpymodules/BPyArmature.py
Normal file
137
release/scripts/bpymodules/BPyArmature.py
Normal file
@@ -0,0 +1,137 @@
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
import Blender
|
||||
import bpy
|
||||
def getBakedPoseData(ob_arm, start_frame, end_frame):
|
||||
'''
|
||||
If you are currently getting IPO's this function can be used to
|
||||
return a list of frame aligned bone dictionary's
|
||||
|
||||
The data in these can be swaped in for the IPO loc and quat
|
||||
|
||||
If you want to bake an action, this is not as hard and the ipo hack can be removed.
|
||||
'''
|
||||
|
||||
# --------------------------------- Dummy Action! Only for this functon
|
||||
backup_action = ob_arm.action
|
||||
backup_frame = Blender.Get('curframe')
|
||||
|
||||
DUMMY_ACTION_NAME = '~DONT_USE~'
|
||||
# Get the dummy action if it has no users
|
||||
try:
|
||||
new_action = bpy.data.actions[DUMMY_ACTION_NAME]
|
||||
if new_action.users:
|
||||
new_action = None
|
||||
except:
|
||||
new_action = None
|
||||
|
||||
if not new_action:
|
||||
new_action = bpy.data.actions.new(DUMMY_ACTION_NAME)
|
||||
new_action.fakeUser = False
|
||||
# ---------------------------------- Done
|
||||
|
||||
Matrix = Blender.Mathutils.Matrix
|
||||
Quaternion = Blender.Mathutils.Quaternion
|
||||
Vector = Blender.Mathutils.Vector
|
||||
POSE_XFORM= [Blender.Object.Pose.LOC, Blender.Object.Pose.ROT]
|
||||
|
||||
# Each dict a frame
|
||||
bake_data = [{} for i in xrange(1+end_frame-start_frame)]
|
||||
|
||||
pose= ob_arm.getPose()
|
||||
armature_data= ob_arm.getData();
|
||||
pose_bones= pose.bones
|
||||
|
||||
# --------------------------------- Build a list of arma data for reuse
|
||||
armature_bone_data = []
|
||||
bones_index = {}
|
||||
for bone_name, rest_bone in armature_data.bones.items():
|
||||
pose_bone = pose_bones[bone_name]
|
||||
rest_matrix = rest_bone.matrix['ARMATURESPACE']
|
||||
rest_matrix_inv = rest_matrix.copy().invert()
|
||||
armature_bone_data.append( [len(bones_index), -1, bone_name, rest_bone, rest_matrix, rest_matrix_inv, pose_bone, None ])
|
||||
bones_index[bone_name] = len(bones_index)
|
||||
|
||||
# Set the parent ID's
|
||||
for bone_name, pose_bone in pose_bones.items():
|
||||
parent = pose_bone.parent
|
||||
if parent:
|
||||
bone_index= bones_index[bone_name]
|
||||
parent_index= bones_index[parent.name]
|
||||
armature_bone_data[ bone_index ][1]= parent_index
|
||||
# ---------------------------------- Done
|
||||
|
||||
|
||||
|
||||
# --------------------------------- Main loop to collect IPO data
|
||||
frame_index = 0
|
||||
for current_frame in xrange(start_frame, end_frame+1):
|
||||
ob_arm.action = backup_action
|
||||
#pose.update() # not needed
|
||||
Blender.Set('curframe', current_frame)
|
||||
#Blender.Window.RedrawAll()
|
||||
#frame_data = bake_data[frame_index]
|
||||
ob_arm.action = new_action
|
||||
###for i,pose_bone in enumerate(pose_bones):
|
||||
|
||||
for index, parent_index, bone_name, rest_bone, rest_matrix, rest_matrix_inv, pose_bone, ipo in armature_bone_data:
|
||||
matrix= pose_bone.poseMatrix
|
||||
|
||||
parent_bone= rest_bone.parent
|
||||
|
||||
if parent_index != -1:
|
||||
parent_pose_matrix = armature_bone_data[parent_index][6].poseMatrix
|
||||
parent_bone_matrix_inv = armature_bone_data[parent_index][5]
|
||||
matrix= matrix * parent_pose_matrix.copy().invert()
|
||||
rest_matrix= rest_matrix * parent_bone_matrix_inv
|
||||
|
||||
matrix=matrix * rest_matrix.copy().invert()
|
||||
|
||||
pose_bone.quat= matrix.toQuat()
|
||||
pose_bone.loc= matrix.translationPart()
|
||||
pose_bone.insertKey(ob_arm, 1, POSE_XFORM) # always frame 1
|
||||
|
||||
# THIS IS A BAD HACK! IT SUCKS BIGTIME BUT THE RESULT ARE NICE
|
||||
# - use a temp action and bake into that, always at the same frame
|
||||
# so as not to make big IPO's, then collect the result from the IPOs
|
||||
|
||||
# Now get the data from the IPOs
|
||||
if not ipo: ipo = armature_bone_data[index][7] = new_action.getChannelIpo(bone_name)
|
||||
|
||||
loc = Vector()
|
||||
quat = Quaternion()
|
||||
|
||||
for curve in ipo:
|
||||
val = curve.evaluate(1)
|
||||
curve_name= curve.name
|
||||
if curve_name == 'LocX': loc[0] = val
|
||||
elif curve_name == 'LocY': loc[1] = val
|
||||
elif curve_name == 'LocZ': loc[2] = val
|
||||
elif curve_name == 'QuatW': quat[3] = val
|
||||
elif curve_name == 'QuatX': quat[0] = val
|
||||
elif curve_name == 'QuatY': quat[1] = val
|
||||
elif curve_name == 'QuatZ': quat[2] = val
|
||||
|
||||
bake_data[frame_index][bone_name] = loc, quat
|
||||
|
||||
|
||||
frame_index+=1
|
||||
|
||||
ob_arm.action = backup_action
|
||||
Blender.Set('curframe', backup_frame)
|
||||
return bake_data
|
||||
|
||||
|
||||
|
||||
@@ -5,6 +5,9 @@ def getObjectArmature(ob):
|
||||
This returns the first armature the mesh uses.
|
||||
remember there can be more then 1 armature but most people dont do that.
|
||||
'''
|
||||
if ob.type != 'Mesh':
|
||||
return None
|
||||
|
||||
arm = ob.parent
|
||||
if arm and arm.type == 'Armature' and ob.parentType == Blender.Object.ParentTypes.ARMATURE:
|
||||
arm
|
||||
|
||||
@@ -103,7 +103,8 @@ import sys, os, os.path, struct, math, string
|
||||
import Blender
|
||||
import BPyMesh
|
||||
import BPySys
|
||||
|
||||
import BPyArmature
|
||||
import BPyObject
|
||||
|
||||
|
||||
def best_armature_root(armature):
|
||||
@@ -184,7 +185,7 @@ def matrix2quaternion(m):
|
||||
0.5 * s,
|
||||
])
|
||||
q.normalize()
|
||||
print q
|
||||
#print q
|
||||
return q
|
||||
|
||||
def vector_by_matrix_3x3(p, m):
|
||||
@@ -260,7 +261,8 @@ BASE_MATRIX = None
|
||||
CAL3D_VERSION = 910
|
||||
MATERIALS = {}
|
||||
|
||||
class Cal3DMaterial:
|
||||
class Cal3DMaterial(object):
|
||||
__slots__ = 'amb', 'diff', 'spec', 'shininess', 'maps_filenames', 'id'
|
||||
def __init__(self, map_filename = None):
|
||||
self.amb = (255,255,255,255)
|
||||
self.diff = (255,255,255,255)
|
||||
@@ -292,12 +294,14 @@ class Cal3DMaterial:
|
||||
file.write('</MATERIAL>\n')
|
||||
|
||||
|
||||
class Cal3DMesh:
|
||||
class Cal3DMesh(object):
|
||||
__slots__ = 'name', 'submeshes'
|
||||
def __init__(self, ob, blend_mesh):
|
||||
self.name = ob.name
|
||||
self.submeshes = []
|
||||
|
||||
matrix = ob.matrixWorld
|
||||
matrix_no = matrix.copy().rotationPart()
|
||||
#if BASE_MATRIX:
|
||||
# matrix = matrix_multiply(BASE_MATRIX, matrix)
|
||||
|
||||
@@ -317,7 +321,7 @@ class Cal3DMesh:
|
||||
faces.remove(face)
|
||||
|
||||
if not face.smooth:
|
||||
normal = face.no * matrix
|
||||
normal = face.no * matrix_no
|
||||
normal.normalize()
|
||||
|
||||
face_vertices = []
|
||||
@@ -325,12 +329,11 @@ class Cal3DMesh:
|
||||
for i, blend_vert in enumerate(face_v):
|
||||
vertex = vertices.get(blend_vert.index)
|
||||
if not vertex:
|
||||
#coord = blend_vert.co * matrix
|
||||
coord = blend_vert.co
|
||||
coord = blend_vert.co * matrix
|
||||
|
||||
if face.smooth:
|
||||
#normal = blend_vert.no * matrix
|
||||
normal = blend_vert.no
|
||||
#normal.normalize()
|
||||
normal = blend_vert.no * matrix_no
|
||||
normal.normalize()
|
||||
|
||||
vertex = vertices[blend_vert.index] = Cal3DVertex(coord, normal, len(submesh.vertices))
|
||||
submesh.vertices.append(vertex)
|
||||
@@ -354,7 +357,7 @@ class Cal3DMesh:
|
||||
print 'Couldnt find bone "%s" which influences object "%s"' % (bone_name, ob.name)
|
||||
continue
|
||||
if weight:
|
||||
vertex.influences.append(Influence(BONES[bone_name], weight / sum))
|
||||
vertex.influences.append(Cal3DInfluence(BONES[bone_name], weight / sum))
|
||||
|
||||
elif not face.smooth:
|
||||
# We cannot share vertex for non-smooth faces, since Cal3D does not
|
||||
@@ -372,7 +375,7 @@ class Cal3DMesh:
|
||||
if blend_mesh.faceUV:
|
||||
uv = [face.uv[i][0], 1.0 - face.uv[i][1]]
|
||||
if not vertex.maps:
|
||||
if outputuv: vertex.maps.append(Map(*uv))
|
||||
if outputuv: vertex.maps.append(Cal3DMap(*uv))
|
||||
elif (vertex.maps[0].u != uv[0]) or (vertex.maps[0].v != uv[1]):
|
||||
# This vertex can be shared for Blender, but not for Cal3D !!!
|
||||
# Cal3D does not support vertex sharing for 2 vertices with
|
||||
@@ -390,14 +393,14 @@ class Cal3DMesh:
|
||||
|
||||
vertex.cloned_from = old_vertex
|
||||
vertex.influences = old_vertex.influences
|
||||
if outputuv: vertex.maps.append(Map(*uv))
|
||||
if outputuv: vertex.maps.append(Cal3DMap(*uv))
|
||||
old_vertex.clones.append(vertex)
|
||||
|
||||
face_vertices.append(vertex)
|
||||
|
||||
# Split faces with more than 3 vertices
|
||||
for i in xrange(1, len(face.v) - 1):
|
||||
submesh.faces.append(Face(face_vertices[0], face_vertices[i], face_vertices[i + 1]))
|
||||
submesh.faces.append(Cal3DFace(face_vertices[0], face_vertices[i], face_vertices[i + 1]))
|
||||
|
||||
# Computes LODs info
|
||||
if LODS:
|
||||
@@ -411,7 +414,8 @@ class Cal3DMesh:
|
||||
submesh.writeCal3D(file)
|
||||
file.write('</MESH>\n')
|
||||
|
||||
class Cal3DSubMesh:
|
||||
class Cal3DSubMesh(object):
|
||||
__slots__ = 'material', 'vertices', 'faces', 'nb_lodsteps', 'springs', 'id'
|
||||
def __init__(self, mesh, material, id):
|
||||
self.material = material
|
||||
self.vertices = []
|
||||
@@ -419,7 +423,7 @@ class Cal3DSubMesh:
|
||||
self.nb_lodsteps = 0
|
||||
self.springs = []
|
||||
self.id = id
|
||||
|
||||
|
||||
def compute_lods(self):
|
||||
"""Computes LODs info for Cal3D (there's no Blender related stuff here)."""
|
||||
|
||||
@@ -486,7 +490,7 @@ class Cal3DSubMesh:
|
||||
|
||||
clone.face_collapse_count = 0
|
||||
new_vertices.append(clone)
|
||||
|
||||
|
||||
# HACK -- all faces get collapsed with v1 (and no faces are collapsed with v1's
|
||||
# clones). This is why we add v1 in new_vertices after v1's clones.
|
||||
# This hack has no other incidence that consuming a little few memory for the
|
||||
@@ -534,20 +538,8 @@ class Cal3DSubMesh:
|
||||
|
||||
file.write('\t</SUBMESH>\n')
|
||||
|
||||
class Cal3DVertex:
|
||||
"""
|
||||
__slots__ =\
|
||||
'loc',# vertex location, worldspace
|
||||
'normal',# vertex normal, worldspace
|
||||
'collapse_to',# ?
|
||||
'face_collapse_count',# ?
|
||||
'maps',# uv coords, must support Multi UV's eventually
|
||||
'influences',# Bone influences
|
||||
'weight',# ?
|
||||
'cloned_from',# ?
|
||||
'clones',# ?
|
||||
'id'# index
|
||||
"""
|
||||
class Cal3DVertex(object):
|
||||
__slots__ = 'loc','normal','collapse_to','face_collapse_count','maps','influences','weight','cloned_from','clones','id'
|
||||
def __init__(self, loc, normal, id):
|
||||
self.loc = loc
|
||||
self.normal = normal
|
||||
@@ -588,7 +580,7 @@ class Cal3DVertex:
|
||||
file.write('\t\t</VERTEX>\n')
|
||||
|
||||
|
||||
class Map(object):
|
||||
class Cal3DMap(object):
|
||||
__slots__ = 'u', 'v'
|
||||
def __init__(self, u, v):
|
||||
self.u = u
|
||||
@@ -597,7 +589,7 @@ class Map(object):
|
||||
def writeCal3D(self, file):
|
||||
file.write('\t\t\t<TEXCOORD>%.6f %.6f</TEXCOORD>\n' % (self.u, self.v))
|
||||
|
||||
class Influence(object):
|
||||
class Cal3DInfluence(object):
|
||||
__slots__ = 'bone', 'weight'
|
||||
def __init__(self, bone, weight):
|
||||
self.bone = bone
|
||||
@@ -607,7 +599,7 @@ class Influence(object):
|
||||
file.write('\t\t\t<INFLUENCE ID="%i">%.6f</INFLUENCE>\n' % \
|
||||
(self.bone.id, self.weight))
|
||||
|
||||
class Spring(object):
|
||||
class Cal3DSpring(object):
|
||||
__slots__ = 'vertex1', 'vertex2', 'spring_coefficient', 'idlelength'
|
||||
def __init__(self, vertex1, vertex2):
|
||||
self.vertex1 = vertex1
|
||||
@@ -619,7 +611,7 @@ class Spring(object):
|
||||
file.write('\t\t<SPRING VERTEXID="%i %i" COEF="%.6f" LENGTH="%.6f"/>\n' % \
|
||||
(self.vertex1.id, self.vertex2.id, self.spring_coefficient, self.idlelength))
|
||||
|
||||
class Face(object):
|
||||
class Cal3DFace(object):
|
||||
__slots__ = 'vertex1', 'vertex2', 'vertex3', 'can_collapse',
|
||||
def __init__(self, vertex1, vertex2, vertex3):
|
||||
self.vertex1 = vertex1
|
||||
@@ -648,13 +640,13 @@ class Cal3DSkeleton(object):
|
||||
BONES = {}
|
||||
POSEBONES= {}
|
||||
class Cal3DBone(object):
|
||||
__slots__ = 'head', 'tail', 'name', 'cal3d_parent', 'loc', 'rot', 'children', 'matrix', 'lloc', 'lrot', 'id'
|
||||
__slots__ = 'head', 'tail', 'name', 'cal3d_parent', 'loc', 'quat', 'children', 'matrix', 'lloc', 'lquat', 'id'
|
||||
def __init__(self, skeleton, blend_bone, arm_matrix, cal3d_parent=None):
|
||||
|
||||
# def treat_bone(b, parent = None):
|
||||
head = blend_bone.head['BONESPACE']
|
||||
tail = blend_bone.tail['BONESPACE']
|
||||
#print parent.rot
|
||||
#print parent.quat
|
||||
# Turns the Blender's head-tail-roll notation into a quaternion
|
||||
#quat = matrix2quaternion(blender_bone2matrix(head, tail, blend_bone.roll['BONESPACE']))
|
||||
quat = matrix2quaternion(blend_bone.matrix['BONESPACE'].copy().resize4x4())
|
||||
@@ -669,7 +661,7 @@ class Cal3DBone(object):
|
||||
# but parent_tail and parent_head must be converted from the parent's parent
|
||||
# system coordinate into the parent system coordinate.
|
||||
|
||||
parent_invert_transform = matrix_invert(quaternion2matrix(cal3d_parent.rot))
|
||||
parent_invert_transform = matrix_invert(quaternion2matrix(cal3d_parent.quat))
|
||||
parent_head = vector_by_matrix_3x3(cal3d_parent.head, parent_invert_transform)
|
||||
parent_tail = vector_by_matrix_3x3(cal3d_parent.tail, parent_invert_transform)
|
||||
ploc = vector_add(ploc, blend_bone.head['BONESPACE'])
|
||||
@@ -682,21 +674,15 @@ class Cal3DBone(object):
|
||||
parentheadtotail = vector_sub(parent_tail, parent_head)
|
||||
# hmm this should be handled by the IPos, but isn't for non-animated
|
||||
# bones which are transformed in the pose mode...
|
||||
#loc = vector_add(ploc, parentheadtotail)
|
||||
#rot = quaternion_multiply(blender2cal3dquat(blend_bone.getQuat()), quat)
|
||||
loc = parentheadtotail
|
||||
rot = quat
|
||||
|
||||
else:
|
||||
# Apply the armature's matrix to the root bones
|
||||
head = point_by_matrix(head, arm_matrix)
|
||||
tail = point_by_matrix(tail, arm_matrix)
|
||||
quat = matrix2quaternion(matrix_multiply(arm_matrix, quaternion2matrix(quat))) # Probably not optimal
|
||||
|
||||
# loc = vector_add(head, blend_bone.getLoc())
|
||||
# rot = quaternion_multiply(blender2cal3dquat(blend_bone.getQuat()), quat)
|
||||
loc = head
|
||||
rot = quat
|
||||
quat = matrix2quaternion(matrix_multiply(arm_matrix, quaternion2matrix(quat))) # Probably not optimal
|
||||
|
||||
self.head = head
|
||||
self.tail = tail
|
||||
@@ -704,18 +690,18 @@ class Cal3DBone(object):
|
||||
self.cal3d_parent = cal3d_parent
|
||||
self.name = blend_bone.name
|
||||
self.loc = loc
|
||||
self.rot = rot
|
||||
self.quat = quat
|
||||
self.children = []
|
||||
|
||||
self.matrix = matrix_translate(quaternion2matrix(rot), loc)
|
||||
self.matrix = matrix_translate(quaternion2matrix(quat), loc)
|
||||
if cal3d_parent:
|
||||
self.matrix = matrix_multiply(cal3d_parent.matrix, self.matrix)
|
||||
|
||||
# lloc and lrot are the bone => model space transformation (translation and rotation).
|
||||
# lloc and lquat are the bone => model space transformation (translation and rotation).
|
||||
# They are probably specific to Cal3D.
|
||||
m = matrix_invert(self.matrix)
|
||||
self.lloc = m[3][0], m[3][1], m[3][2]
|
||||
self.lrot = matrix2quaternion(m)
|
||||
self.lquat = matrix2quaternion(m)
|
||||
|
||||
self.id = len(skeleton.bones)
|
||||
skeleton.bones.append(self)
|
||||
@@ -733,11 +719,11 @@ class Cal3DBone(object):
|
||||
file.write('\t\t<TRANSLATION>%.6f %.6f %.6f</TRANSLATION>\n' % \
|
||||
(self.loc[0], self.loc[1], self.loc[2]))
|
||||
file.write('\t\t<ROTATION>%.6f %.6f %.6f %.6f</ROTATION>\n' % \
|
||||
(self.rot[0], self.rot[1], self.rot[2], -self.rot[3]))
|
||||
(self.quat[0], self.quat[1], self.quat[2], -self.quat[3]))
|
||||
file.write('\t\t<LOCALTRANSLATION>%.6f %.6f %.6f</LOCALTRANSLATION>\n' % \
|
||||
(self.lloc[0], self.lloc[1], self.lloc[2]))
|
||||
file.write('\t\t<LOCALROTATION>%.6f %.6f %.6f %.6f</LOCALROTATION>\n' % \
|
||||
(self.lrot[0], self.lrot[1], self.lrot[2], -self.lrot[3]))
|
||||
(self.lquat[0], self.lquat[1], self.lquat[2], -self.lquat[3]))
|
||||
if self.cal3d_parent:
|
||||
file.write('\t\t<PARENTID>%i</PARENTID>\n' % self.cal3d_parent.id)
|
||||
else:
|
||||
@@ -765,28 +751,25 @@ class Cal3DAnimation:
|
||||
|
||||
file.write('</ANIMATION>\n')
|
||||
|
||||
class Cal3DTrack:
|
||||
class Cal3DTrack(object):
|
||||
__slots__ = 'bone', 'keyframes'
|
||||
def __init__(self, bone):
|
||||
self.bone = bone
|
||||
self.keyframes = []
|
||||
|
||||
def writeCal3D(self, file):
|
||||
file.write('\t<TRACK BONEID="%i" NUMKEYFRAMES="%i">\n' % \
|
||||
file.write('\t<TRACK BONEID="%i" NUMKEYFRAMES="%i">\n' %
|
||||
(self.bone.id, len(self.keyframes)))
|
||||
|
||||
for item in self.keyframes:
|
||||
item.writeCal3D(file)
|
||||
|
||||
file.write('\t</TRACK>\n')
|
||||
|
||||
class Cal3DKeyFrame:
|
||||
def __init__(self, track, time, loc, rot):
|
||||
class Cal3DKeyFrame(object):
|
||||
__slots__ = 'time', 'loc', 'quat'
|
||||
def __init__(self, time, loc, quat):
|
||||
self.time = time
|
||||
self.loc = loc
|
||||
self.rot = rot
|
||||
|
||||
self.track = track
|
||||
track.keyframes.append(self)
|
||||
self.quat = quat
|
||||
|
||||
def writeCal3D(self, file):
|
||||
file.write('\t\t<KEYFRAME TIME="%.6f">\n' % self.time)
|
||||
@@ -794,10 +777,10 @@ class Cal3DKeyFrame:
|
||||
(self.loc[0], self.loc[1], self.loc[2]))
|
||||
# We need to negate quaternion W value, but why ?
|
||||
file.write('\t\t\t<ROTATION>%.6f %.6f %.6f %.6f</ROTATION>\n' % \
|
||||
(self.rot[0], self.rot[1], self.rot[2], -self.rot[3]))
|
||||
(self.quat[0], self.quat[1], self.quat[2], -self.quat[3]))
|
||||
file.write('\t\t</KEYFRAME>\n')
|
||||
|
||||
def export_cal3d(filename):
|
||||
def export_cal3d(filename, PREF_SCALE=0.1, PREF_BAKE_MOTION = True, PREF_ACT_ACTION_ONLY=True):
|
||||
if not filename.endswith('.cfg'):
|
||||
filename += '.cfg'
|
||||
|
||||
@@ -811,19 +794,26 @@ def export_cal3d(filename):
|
||||
#if EXPORT_FOR_SOYA:
|
||||
# global BASE_MATRIX
|
||||
# BASE_MATRIX = matrix_rotate_x(-math.pi / 2.0)
|
||||
|
||||
# Get the scene
|
||||
|
||||
scene = Blender.Scene.GetCurrent()
|
||||
|
||||
# ---- Export skeleton (=armature) ----------------------------------------
|
||||
|
||||
# ---- Export skeleton (armature) ----------------------------------------
|
||||
|
||||
skeleton = Cal3DSkeleton()
|
||||
blender_armature = [ob for ob in scene.objects.context if ob.type == 'Armature']
|
||||
if len(blender_armature) > 1: print "Found multiple armatures! using ",armatures[0].name
|
||||
if blender_armature: blender_armature = blender_armature[0]
|
||||
else:
|
||||
Blender.Draw.PupMenu('Aborting%t|No Armature in selection')
|
||||
return
|
||||
# Try find a meshes armature
|
||||
for ob in scene.objects.context:
|
||||
blender_armature = BPyObject.getObjectArmature(ob)
|
||||
if blender_armature:
|
||||
break
|
||||
|
||||
if not blender_armature:
|
||||
Blender.Draw.PupMenu('Aborting%t|No Armature in selection')
|
||||
return
|
||||
|
||||
# we need pose bone locations
|
||||
for pbone in blender_armature.getPose().bones.values():
|
||||
@@ -833,7 +823,6 @@ def export_cal3d(filename):
|
||||
|
||||
# ---- Export Mesh data ---------------------------------------------------
|
||||
meshes = []
|
||||
|
||||
for ob in scene.objects.context:
|
||||
if ob.type != 'Mesh': continue
|
||||
blend_mesh = ob.getData(mesh=1)
|
||||
@@ -841,44 +830,47 @@ def export_cal3d(filename):
|
||||
|
||||
if not blend_mesh.faces: continue
|
||||
meshes.append( Cal3DMesh(ob, blend_mesh) )
|
||||
|
||||
|
||||
# ---- Export animations --------------------------------------------------
|
||||
ANIMATIONS = {}
|
||||
backup_action = blender_armature.action
|
||||
|
||||
ANIMATIONS = []
|
||||
SUPPORTED_IPOS = "QuatW", "QuatX", "QuatY", "QuatZ", "LocX", "LocY", "LocZ"
|
||||
for animation_name, blend_action in Blender.Armature.NLA.GetActions().iteritems():
|
||||
#for blend_action in [blender_armature.action]:
|
||||
#animation_name = a[0]
|
||||
#animation_name = blend_action.name
|
||||
|
||||
if PREF_ACT_ACTION_ONLY: action_items = [(blender_armature.action.name, blender_armature.action)]
|
||||
else: action_items = Blender.Armature.NLA.GetActions().iteritems()
|
||||
|
||||
for animation_name, blend_action in action_items:
|
||||
|
||||
# get frame range
|
||||
_frames = blend_action.getFrameNumbers()
|
||||
action_start= min(_frames);
|
||||
action_end= max(_frames);
|
||||
del _frames
|
||||
|
||||
if PREF_BAKE_MOTION:
|
||||
# We need to set the action active if we are getting baked data
|
||||
blend_action.setActive(blender_armature)
|
||||
pose_data = BPyArmature.getBakedPoseData(blender_armature, action_start, action_end)
|
||||
|
||||
# Fake, all we need is bone names
|
||||
blend_action_ipos_items = [(pbone, True) for pbone in POSEBONES.iterkeys()]
|
||||
else:
|
||||
# real (bone_name, ipo) pairs
|
||||
blend_action_ipos_items = blend_action.getAllChannelIpos().items()
|
||||
|
||||
# Now we mau have some bones with no channels, easiest to add their names and an empty list here
|
||||
# this way they are exported with dummy keyfraames at teh first used frame
|
||||
action_bone_names = [name for name, ipo in blend_action_ipos_items]
|
||||
for bone_name in BONES: # iterkeys
|
||||
if bone_name not in action_bone_names:
|
||||
blend_action_ipos_items.append( (bone_name, []) )
|
||||
|
||||
animation = Cal3DAnimation(animation_name)
|
||||
animation.duration = 0.0
|
||||
|
||||
|
||||
# All tracks need to have at least 1 keyframe.
|
||||
# bones without any keys crash the viewer so we need to find the location for a dummy keyframe.
|
||||
blend_action_ipos = blend_action.getAllChannelIpos()
|
||||
start_frame = 300000 # largest frame
|
||||
for bone_name, ipo in blend_action_ipos.iteritems():
|
||||
if ipo:
|
||||
for curve in ipo:
|
||||
if curve.name in SUPPORTED_IPOS:
|
||||
for p in curve.bezierPoints:
|
||||
start_frame = min(start_frame, p.pt[0])
|
||||
|
||||
# Write all dummy keyframes, find bones with no actions
|
||||
if start_frame == 300000:
|
||||
pass # BAD STUFF NO IPOS
|
||||
|
||||
|
||||
# Now we mau have some bones with no channels, easiest to add their names and an empty list here
|
||||
# this way they are exported with dummy keyfraames at teh first used frame
|
||||
blend_action_ipos_items = blend_action_ipos.items()
|
||||
action_bone_names = [name for name, ipo in blend_action_ipos_items]
|
||||
for bone_name in BONES: # iterkeys
|
||||
if bone_name not in action_bone_names:
|
||||
blend_action_ipos_items.append( (bone_name, []) )
|
||||
|
||||
|
||||
for bone_name, ipo in blend_action_ipos_items:
|
||||
# Baked bones may have no IPO's width motion still
|
||||
if bone_name not in BONES:
|
||||
print "\tNo Bone '" + bone_name + "' in (from Animation '" + animation_name + "') ?!?"
|
||||
continue
|
||||
@@ -889,61 +881,93 @@ def export_cal3d(filename):
|
||||
bone = BONES[bone_name]
|
||||
track = animation.tracks[bone_name] = Cal3DTrack(bone)
|
||||
|
||||
#run 1: we need to find all time values where we need to produce keyframes
|
||||
times = set()
|
||||
for curve in ipo:
|
||||
curve_name = curve.name
|
||||
if curve_name in SUPPORTED_IPOS:
|
||||
for p in curve.bezierPoints:
|
||||
times.add( p.pt[0] )
|
||||
if PREF_BAKE_MOTION:
|
||||
for i in xrange(action_end - action_start):
|
||||
cal3dtime = i / 25.0 # assume 25FPS by default
|
||||
|
||||
if cal3dtime > animation.duration:
|
||||
animation.duration = cal3dtime
|
||||
|
||||
#print pose_data[i][bone_name], i
|
||||
loc, quat = pose_data[i][bone_name]
|
||||
if bone_name == 'top':
|
||||
print 'myquat', quat
|
||||
#print 'rot', quat
|
||||
|
||||
|
||||
loc = vector_by_matrix_3x3(loc, bone.matrix)
|
||||
loc = vector_add(bone.loc, loc)
|
||||
quat = quaternion_multiply(quat, bone.quat)
|
||||
quat = Quaternion(quat)
|
||||
|
||||
quat.normalize()
|
||||
quat = tuple(quat)
|
||||
|
||||
track.keyframes.append( Cal3DKeyFrame(cal3dtime, loc, quat) )
|
||||
|
||||
times = list(times)
|
||||
times.sort()
|
||||
|
||||
# Incase we have no keys here or ipo==None
|
||||
if not times:
|
||||
times.append(start_frame)
|
||||
|
||||
# run2: now create keyframes
|
||||
for time in times:
|
||||
cal3dtime = (time-1) / 25.0 # assume 25FPS by default
|
||||
if cal3dtime > animation.duration:
|
||||
animation.duration = cal3dtime
|
||||
trans = Vector(0,0,0)
|
||||
quat = Quaternion()
|
||||
|
||||
else:
|
||||
#run 1: we need to find all time values where we need to produce keyframes
|
||||
times = set()
|
||||
for curve in ipo:
|
||||
val = curve.evaluate(time)
|
||||
# val = 0.0
|
||||
curve_name= curve.name
|
||||
if curve_name == "LocX": trans[0] = val
|
||||
elif curve_name == "LocY": trans[1] = val
|
||||
elif curve_name == "LocZ": trans[2] = val
|
||||
elif curve_name == "QuatW": quat[3] = val
|
||||
elif curve_name == "QuatX": quat[0] = val
|
||||
elif curve_name == "QuatY": quat[1] = val
|
||||
elif curve_name == "QuatZ": quat[2] = val
|
||||
curve_name = curve.name
|
||||
if curve_name in SUPPORTED_IPOS:
|
||||
for p in curve.bezierPoints:
|
||||
times.add( p.pt[0] )
|
||||
|
||||
transt = vector_by_matrix_3x3(trans, bone.matrix)
|
||||
loc = vector_add(bone.loc, transt)
|
||||
rot = quaternion_multiply(quat, bone.rot)
|
||||
rot = Quaternion(rot)
|
||||
rot.normalize()
|
||||
rot = tuple(rot)
|
||||
Cal3DKeyFrame(track, cal3dtime, loc, rot)
|
||||
times = list(times)
|
||||
times.sort()
|
||||
|
||||
# Incase we have no keys here or ipo==None
|
||||
if not times: times.append(action_start)
|
||||
|
||||
# run2: now create keyframes
|
||||
for time in times:
|
||||
cal3dtime = (time-1) / 25.0 # assume 25FPS by default
|
||||
if cal3dtime > animation.duration:
|
||||
animation.duration = cal3dtime
|
||||
|
||||
trans = Vector()
|
||||
quat = Quaternion()
|
||||
|
||||
for curve in ipo:
|
||||
val = curve.evaluate(time)
|
||||
# val = 0.0
|
||||
curve_name= curve.name
|
||||
if curve_name == "LocX": trans[0] = val
|
||||
elif curve_name == "LocY": trans[1] = val
|
||||
elif curve_name == "LocZ": trans[2] = val
|
||||
elif curve_name == "QuatW": quat[3] = val
|
||||
elif curve_name == "QuatX": quat[0] = val
|
||||
elif curve_name == "QuatY": quat[1] = val
|
||||
elif curve_name == "QuatZ": quat[2] = val
|
||||
|
||||
transt = vector_by_matrix_3x3(trans, bone.matrix)
|
||||
loc = vector_add(bone.loc, transt)
|
||||
quat = quaternion_multiply(quat, bone.quat)
|
||||
quat = Quaternion(quat)
|
||||
|
||||
quat.normalize()
|
||||
quat = tuple(quat)
|
||||
|
||||
track.keyframes.append( Cal3DKeyFrame(cal3dtime, loc, quat) )
|
||||
|
||||
Cal3DKeyFrame(track, cal3dtime, loc, rot)
|
||||
|
||||
if animation.duration <= 0:
|
||||
print "Ignoring Animation '" + animation_name + "': duration is 0.\n"
|
||||
continue
|
||||
ANIMATIONS[animation_name] = animation
|
||||
|
||||
# Restore the original armature
|
||||
backup_action.setActive(blender_armature)
|
||||
|
||||
|
||||
# ----------------------------
|
||||
ANIMATIONS.append(animation)
|
||||
|
||||
|
||||
cfg = open((filename), "wb")
|
||||
cfg.write('# Cal3D model exported from Blender with export_cal3d.py\n')
|
||||
|
||||
if SCALE != 1.0: cfg.write('scale=%.6f\n' % SCALE)
|
||||
if SCALE != 1.0: cfg.write('scale=%.6f\n' % PREF_SCALE)
|
||||
|
||||
fname = file_only_noext + '.xsf'
|
||||
file = open( base_only + fname, "wb")
|
||||
@@ -952,7 +976,7 @@ def export_cal3d(filename):
|
||||
|
||||
cfg.write('skeleton=%s\n' % fname)
|
||||
|
||||
for animation in ANIMATIONS.itervalues():
|
||||
for animation in ANIMATIONS:
|
||||
if not animation.name.startswith('_'):
|
||||
if animation.duration > 0.1: # Cal3D does not support animation with only one state
|
||||
fname = new_name(animation.name, '.xaf')
|
||||
@@ -990,8 +1014,28 @@ def export_cal3d(filename):
|
||||
if len(animation.tracks) < 2:
|
||||
Blender.Draw.PupMenu('Warning, the armature has less then 2 tracks, file may not load in Cal3d')
|
||||
|
||||
|
||||
|
||||
def export_cal3d_ui(filename):
|
||||
|
||||
PREF_SCALE= Blender.Draw.Create(1.0)
|
||||
PREF_BAKE_MOTION = Blender.Draw.Create(1)
|
||||
PREF_ACT_ACTION_ONLY= Blender.Draw.Create(1)
|
||||
|
||||
block = [\
|
||||
('Scale: ', PREF_SCALE, 0.01, 100, "The scale to set in the Cal3d .cfg file"),\
|
||||
('Baked Motion', PREF_BAKE_MOTION, 'use final pose position instead of ipo keyframes (IK and constraint support)'),\
|
||||
('Active Action', PREF_ACT_ACTION_ONLY, 'Only export the active action applied to this armature, otherwise export all'),\
|
||||
]
|
||||
|
||||
if not Blender.Draw.PupBlock("Cal3D Options", block):
|
||||
return
|
||||
|
||||
export_cal3d(filename, 1.0/PREF_SCALE.val, PREF_BAKE_MOTION.val, PREF_ACT_ACTION_ONLY.val)
|
||||
|
||||
|
||||
#import os
|
||||
if __name__ == '__main__':
|
||||
Blender.Window.FileSelector(export_cal3d, "Cal3D Export", Blender.Get('filename').replace('.blend', '.cfg'))
|
||||
Blender.Window.FileSelector(export_cal3d_ui, "Cal3D Export", Blender.Get('filename').replace('.blend', '.cfg'))
|
||||
#export_cal3d('/test' + '.cfg')
|
||||
#os.system('cd /; wine /cal3d_miniviewer.exe /test.cfg')
|
||||
|
||||
Reference in New Issue
Block a user