You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

213 lines
7.8 KiB
Python

import bpy.path
import bpy
import math
from mathutils import Vector, Quaternion
try:
from .anim import *
from .bones import *
except:
from anim import *
from bones import *
def export_fix_coord(v):
return Vector((-v[0], v[2], -v[1]))
def export_fix_normal(v):
return Vector(( v[0], -v[2], v[1]))
def export_fix_quaternion(quat):
return Quaternion((-quat[1], quat[3], -quat[2], quat[0]))
def getBoneRotation(bone, bone_tracks, index):
if bone is None:
#rot_p = Quaternion()
return Quaternion()
else:
rot_p = getBoneRotation(bone.parent, bone_tracks, index)
if bone.name in bone_tracks:
trk = bone_tracks[bone.name]
chn = trk["rotation_quaternion"]
if index >= len(chn):
rot_s = chn[-1].copy()
else:
rot_s = chn[index].copy()
else:
rot_s = Quaternion()
rot_p.rotate(rot_s)
return rot_p
#rot_s.rotate(rot_p)
#return rot_s
def convert_animation(context, arm_obj, arm_data, nla_track, anim, save_skel):
bone_tracks = {}
for nla_strip in nla_track.strips:
#todo: what's the proper way to handle multiple strips?
#Presently later strips will overwrite earlier strips.
#>>> [x.data_path for x in bpy.data.objects['fem'].animation_data.nla_tracks['run'].strips[0].action.fcurves]
#>>> [x.array_index for x in bpy.data.objects['fem'].animation_data.nla_tracks['run'].strips[0].action.fcurves]
for crv in nla_strip.action.fcurves:
#>>> [y.co for x in bpy.data.objects['fem'].animation_data.nla_tracks['run'].strips[0].action.fcurves for y in x.sampled_points]
dp = crv.data_path
idx = crv.array_index
print("crv: %s : %s" % (dp, idx))
#Naively convert data_path into bone, and transform type
parts_a = dp.split('["')
parts_b = parts_a[1].split('"].')
bone_name = parts_b[0]
transform = parts_b[1]
if bone_name not in bone_tracks:
bone_tracks[bone_name] = {}
bone_track = bone_tracks[bone_name]
if transform == "location":
data_type = Vector
elif transform == "rotation_quaternion":
data_type = Quaternion
else:
#todo:
raise
data_type = None
if transform not in bone_track:
bone_track[transform] = []
bone_track_channel = bone_track[transform]
for pnt in crv.sampled_points:
k = int(math.floor(pnt.co[0] + 0.5))
if k < 0:
#ignore samples before 0
continue
while len(bone_track_channel) <= k:
bone_track_channel.append(data_type())
bone_track_channel[k][idx] = pnt.co[1]
#print("bone_tracks: %s" % bone_tracks)
print("bone_tracks['Head']: %s" % bone_tracks.get("Head", None))
#todo: convert FCurve data to track positions and rotations
#todo: Get bones required for export.
if save_skel:
#If we need to save the skeleton, ensure we have values for the T-pose loaded into bones that haven't been referenced yet.
for bn in arm_data.bones.keys():
if bn not in bone_tracks:
bone_tracks[bn] = {
"location": [Vector()],
"rotation_quaternion": [Quaternion()],
}
#todo: trim back tracks that have duplicates on their tail.
for bn, bt in bone_tracks.items():
#ensure missing channels have a T-pose value.
if "location" not in bt:
bt["location"] = [Vector()]
if "rotation_quaternion" not in bt:
bt["rotation_quaternion"] = [Quaternion()]
#Trim back duplicates at the end of a track.
for cn in ("location", "rotation_quaternion"):
chn = bt[cn]
while len(chn) >= 2:
if chn[-1] == chn[-2]:
chn.pop()
else:
break
#Convert track positions and rotations into a more convenient form.
for bn, bt in bone_tracks.items():
bone = arm_data.bones[bn]
#Get position of bone, relative to parent (or armature origin for root bones).
if bone.parent is None:
bone_position = bone.head
else:
bone_position = bone.head + (bone.parent.tail - bone.parent.head)
#print("parent[%s]: %s %s" % (bone.parent.name, bone.parent.head, bone.parent.tail))
print("bone_position[%s(%s)]: %s" % (bn, bone.name, bone_position))
bt["net_location"] = [bone_position]
bt["net_rotation_quaternion"] = []
rot_chn = bt["rotation_quaternion"]
for i in range(len(rot_chn)):
bt["net_rotation_quaternion"].append(getBoneRotation(bone, bone_tracks, i))
for i in range(1, len(bt["location"])):
if i >= len(bt["net_rotation_quaternion"]):
rot = bt["net_rotation_quaternion"][-1]
else:
rot = bt["net_rotation_quaternion"][i]
pos = bone_position + bt["location"][i]
print(" pos[%s]: %s" % (i, pos))
pos.rotate(rot)
bt["net_location"].append(pos)
#print("bone_tracks: %s" % bone_tracks)
#Store bone track information in the Anim
for bn, bt in bone_tracks.items():
bat = BoneAnimTrack()
bat.bone_id = BONES_LOOKUP[bn]
bat.rotations = [export_fix_quaternion(x) for x in bt["rotation_quaternion"]]
bat.positions = [export_fix_coord(x) for x in bt["net_location"]]
anim.bone_tracks.append(bat)
#Save the skeleton (if flagged).
if save_skel:
anim.skeleton_hierarchy = SkeletonHierarchy()
for bn in BONES_LIST:
if bn in arm_data.bones:
bone = arm_data.bones[bn]
if bone.parent is None:
parent_id = None
else:
parent_id = BONES_LOOKUP[bone.parent.name]
bone_id = BONES_LOOKUP[bn]
anim.skeleton_hierarchy.addBone(parent_id, bone_id)
anim.dump()
pass
def save(operator, context, scale = 1.0, filepath = "", global_matrix = None, use_mesh_modifiers = True, save_skeleton = False):
#todo: prefer armature name that matches import?
#Choose the first selected armature as the armature to attach to.
armature = None
for ob in context.selected_objects:
if ob.type == "ARMATURE":
armature_obj = ob
armature = ob.data
break
else:
#If none found try again with all objects.
for ob in bpy.data.objects:
if ob.type == "ARMATURE":
armature_obj = ob
armature = ob.data
break
#todo:
track = None
skel_track = None
#todo: error/warning if multiple tracks are selected.
#todo: error/warning if multiple skel_ tracks are found
#todo: error if no skel_tracks are found
for t in armature_obj.animation_data.nla_tracks:
if t.select:
track = t
if t.name.lower().startswith("skel_"):
skel_track = t
if save_skeleton:
skel_track = track
arm_name = armature_obj.name
track_name = bpy.path.display_name_from_filepath(filepath)
skel_track_name = skel_track.name
#Get name and base name
anim_name = "%s/%s" % (arm_name, track_name)
anim_base_name = "%s/%s" % (arm_name, skel_track_name)
#todo: warning if anim_name doesn't match file path
anim = Anim()
anim.header_name = anim_name
anim.header_base_anim_name = anim_base_name
save_skel = save_skeleton or anim_name == anim_base_name
convert_animation(context, armature_obj, armature, track, anim, save_skel)
data = anim.saveToData()
fh = open(filepath, "wb")
fh.write(data)
fh.close()
return {'FINISHED'}