diff --git a/release/scripts/bpymodules/BPyArmature.py b/release/scripts/bpymodules/BPyArmature.py new file mode 100644 index 00000000000..d0b41dc35c5 --- /dev/null +++ b/release/scripts/bpymodules/BPyArmature.py @@ -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 + + + diff --git a/release/scripts/bpymodules/BPyObject.py b/release/scripts/bpymodules/BPyObject.py index 278ce7fe270..7de2c4113f9 100644 --- a/release/scripts/bpymodules/BPyObject.py +++ b/release/scripts/bpymodules/BPyObject.py @@ -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 diff --git a/release/scripts/export_cal3d.py b/release/scripts/export_cal3d.py index 113b20b4bc3..0ac395b82fe 100644 --- a/release/scripts/export_cal3d.py +++ b/release/scripts/export_cal3d.py @@ -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('\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('\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\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\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%.6f %.6f\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%.6f\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\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%.6f %.6f %.6f\n' % \ (self.loc[0], self.loc[1], self.loc[2])) file.write('\t\t%.6f %.6f %.6f %.6f\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%.6f %.6f %.6f\n' % \ (self.lloc[0], self.lloc[1], self.lloc[2])) file.write('\t\t%.6f %.6f %.6f %.6f\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%i\n' % self.cal3d_parent.id) else: @@ -765,28 +751,25 @@ class Cal3DAnimation: file.write('\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\n' % \ + file.write('\t\n' % (self.bone.id, len(self.keyframes))) - for item in self.keyframes: item.writeCal3D(file) - file.write('\t\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\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%.6f %.6f %.6f %.6f\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\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')