Compare commits
10 Commits
3c469c0276
...
8603399f19
Author | SHA1 | Date |
---|---|---|
TigerKat | 8603399f19 | 4 years ago |
TigerKat | 03f9bf8a14 | 4 years ago |
TigerKat | cffe622ef2 | 4 years ago |
TigerKat | 749bc7577b | 4 years ago |
TigerKat | 43bf1a79f8 | 4 years ago |
TigerKat | f0388ac65d | 4 years ago |
TigerKat | 4f69ebdf1d | 4 years ago |
TigerKat | 4f8e162b91 | 4 years ago |
TigerKat | ada937ce82 | 4 years ago |
TigerKat | 545f6f203d | 4 years ago |
@ -0,0 +1,107 @@
|
|||||||
|
import math
|
||||||
|
import struct
|
||||||
|
|
||||||
|
MAX_5_BYTE_QUATERNION = 1 / (2 ** 0.5)
|
||||||
|
SCALE_8_BYTE_QUATERNION_COMPRESS = 10000
|
||||||
|
SCALE_8_BYTE_QUATERNION_DECOMPRESS = 1.0 / SCALE_8_BYTE_QUATERNION_COMPRESS
|
||||||
|
SCALE_6_BYTE_VECTOR3_COMPRESS = 32000
|
||||||
|
SCALE_6_BYTE_VECTOR3_DECOMPRESS = 1.0 / SCALE_6_BYTE_VECTOR3_COMPRESS
|
||||||
|
|
||||||
|
def findBiggest(quat):
|
||||||
|
biggest_i = -1
|
||||||
|
biggest_v = 0
|
||||||
|
for i, v in enumerate(quat):
|
||||||
|
if abs(v) > biggest_v:
|
||||||
|
biggest_v = abs(v)
|
||||||
|
biggest_i = i
|
||||||
|
if quat[biggest_i] < 0:
|
||||||
|
return (biggest_i, (-quat[0], -quat[1], -quat[2], -quat[3]))
|
||||||
|
else:
|
||||||
|
return (biggest_i, ( quat[0], quat[1], quat[2], quat[3]))
|
||||||
|
|
||||||
|
def compressQuaternion_5Byte(quat):
|
||||||
|
#Find biggset value and negate the quaternion to make it positive.
|
||||||
|
missing, q = findBiggest(quat)
|
||||||
|
d = []
|
||||||
|
for i in range(4):
|
||||||
|
if i == missing:
|
||||||
|
continue
|
||||||
|
v = int(math.floor(0.5 + q[i] / MAX_5_BYTE_QUATERNION * 2048))
|
||||||
|
if v < -2048:
|
||||||
|
v = -2048
|
||||||
|
elif v > 2047:
|
||||||
|
v = 2047
|
||||||
|
d.append(v + 2048)
|
||||||
|
v = (
|
||||||
|
(missing << 36) |
|
||||||
|
(d[0] << 24) |
|
||||||
|
(d[1] << 12) |
|
||||||
|
d[2]
|
||||||
|
)
|
||||||
|
s = struct.pack("<Q", v)
|
||||||
|
#Return with the top most byte first, but remaining bytes in little endian order.
|
||||||
|
return s[4:5] + s[0:4]
|
||||||
|
|
||||||
|
def decompressQuaternion_5Byte(data):
|
||||||
|
#Source data has the top most byte first, but remaining bytes are little endian.
|
||||||
|
#print("decompressQuaternion_5Byte(data): %s" % ([data],))
|
||||||
|
#Rearrange byte data into a little endian uint64.
|
||||||
|
s = data[1:5] + data[0:1] + b"\x00\x00\x00"
|
||||||
|
(v, ) = struct.unpack("<Q", s)
|
||||||
|
|
||||||
|
#Parse out the data.
|
||||||
|
missing = (v >> 36) & 0x3
|
||||||
|
d = [
|
||||||
|
(v >> 24) & 0xfff,
|
||||||
|
(v >> 12) & 0xfff,
|
||||||
|
v & 0xfff,
|
||||||
|
]
|
||||||
|
|
||||||
|
#Rescale values and summing the squares into x.
|
||||||
|
x = 0
|
||||||
|
#print("%s" % (d, ))
|
||||||
|
for i in range(3):
|
||||||
|
d[i] = (d[i] - 2048) * MAX_5_BYTE_QUATERNION / 2048.0
|
||||||
|
x += d[i] ** 2.0
|
||||||
|
#print("%s -> %s" % (d, x))
|
||||||
|
#Use pythagoras to compute the missing field into x.
|
||||||
|
x = (1 - x) ** 0.5
|
||||||
|
#Rebuild the quaternion.
|
||||||
|
d_i = 0;
|
||||||
|
q = []
|
||||||
|
for i in range(4):
|
||||||
|
if i == missing:
|
||||||
|
q.append(x)
|
||||||
|
else:
|
||||||
|
q.append(d[d_i])
|
||||||
|
d_i += 1
|
||||||
|
return q
|
||||||
|
|
||||||
|
def compressQuaternion_8Byte(quat):
|
||||||
|
return struct.pack("<hhhh",
|
||||||
|
int(math.floor(0.5 + quat[0] * SCALE_8_BYTE_QUATERNION_COMPRESS)),
|
||||||
|
int(math.floor(0.5 + quat[1] * SCALE_8_BYTE_QUATERNION_COMPRESS)),
|
||||||
|
int(math.floor(0.5 + quat[2] * SCALE_8_BYTE_QUATERNION_COMPRESS)),
|
||||||
|
int(math.floor(0.5 + quat[3] * SCALE_8_BYTE_QUATERNION_COMPRESS))
|
||||||
|
)
|
||||||
|
|
||||||
|
def decompressQuaternion_8Byte(data):
|
||||||
|
d = struct.unpack("<hhhh", data[0:8])
|
||||||
|
return [
|
||||||
|
d[0] * SCALE_8_BYTE_QUATERNION_DECOMPRESS,
|
||||||
|
d[1] * SCALE_8_BYTE_QUATERNION_DECOMPRESS,
|
||||||
|
d[2] * SCALE_8_BYTE_QUATERNION_DECOMPRESS,
|
||||||
|
d[3] * SCALE_8_BYTE_QUATERNION_DECOMPRESS
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def compressVector3_6Byte(vec):
|
||||||
|
return struct.pack("<hhh",
|
||||||
|
int(math.floor(0.5 + vec[0] * SCALE_6_BYTE_VECTOR3_COMPRESS)),
|
||||||
|
int(math.floor(0.5 + vec[1] * SCALE_6_BYTE_VECTOR3_COMPRESS)),
|
||||||
|
int(math.floor(0.5 + vec[2] * SCALE_6_BYTE_VECTOR3_COMPRESS))
|
||||||
|
)
|
||||||
|
|
||||||
|
def decompressVector3_6Byte(data):
|
||||||
|
d = struct.unpack("<hhh", data[0:6])
|
||||||
|
return [(x * SCALE_6_BYTE_VECTOR3_DECOMPRESS) for x in d]
|
@ -0,0 +1,212 @@
|
|||||||
|
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'}
|
@ -0,0 +1,12 @@
|
|||||||
|
import bpy.path
|
||||||
|
import bpy
|
||||||
|
|
||||||
|
try:
|
||||||
|
from .export_anim import *
|
||||||
|
except:
|
||||||
|
from export_anim import *
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def save_skel(operator, context, scale = 1.0, filepath = "", global_matrix = None, use_mesh_modifiers = True):
|
||||||
|
return save(operator, context, scale = scale, filepath = filepath, global_matrix = global_matrix, use_mesh_modifiers = use_mesh_modifiers, save_skeleton = True)
|
@ -0,0 +1,191 @@
|
|||||||
|
import bpy.path
|
||||||
|
import bpy
|
||||||
|
from mathutils import Vector, Quaternion
|
||||||
|
try:
|
||||||
|
from .anim import *
|
||||||
|
from .bones import *
|
||||||
|
except:
|
||||||
|
from anim import *
|
||||||
|
from bones import *
|
||||||
|
|
||||||
|
def import_fix_coord(v):
|
||||||
|
return Vector((-v[0], -v[2], v[1]))
|
||||||
|
def import_fix_normal(v):
|
||||||
|
return Vector(( v[0], v[2], -v[1]))
|
||||||
|
def import_fix_quaternion(quat):
|
||||||
|
return Quaternion((quat[3], -quat[0], -quat[2], quat[1]))
|
||||||
|
|
||||||
|
|
||||||
|
def getBoneLength(arm_data, bone_name):
|
||||||
|
bl = arm_data.bones[bone_name]
|
||||||
|
if bl.parent is None:
|
||||||
|
return bl.head
|
||||||
|
|
||||||
|
def getBoneRotation(bone, bone_trk_lookup, trk_rot_list, index):
|
||||||
|
if bone is None:
|
||||||
|
#rot_p = Quaternion()
|
||||||
|
return Quaternion()
|
||||||
|
else:
|
||||||
|
rot_p = getBoneRotation(bone.parent, bone_trk_lookup, trk_rot_list, index)
|
||||||
|
if bone.name in bone_trk_lookup:
|
||||||
|
trk_index = bone_trk_lookup[bone.name]
|
||||||
|
rot_list = trk_rot_list[trk_index]
|
||||||
|
if index >= len(rot_list):
|
||||||
|
rot_s = rot_list[-1].copy()
|
||||||
|
else:
|
||||||
|
rot_s = rot_list[index].copy()
|
||||||
|
else:
|
||||||
|
rot_s = Quaternion()
|
||||||
|
rot_p.rotate(rot_s)
|
||||||
|
return rot_p
|
||||||
|
#rot_s.rotate(rot_p)
|
||||||
|
#return rot_s
|
||||||
|
def convertAnimation(context, arm_obj, arm_data, anim, rescale = True):
|
||||||
|
full_name = anim.header_name.decode("utf-8")
|
||||||
|
anim_name = full_name.split("/")[1]#.lstrip("skel_")
|
||||||
|
#get all bones used in animation, and maximum fram count
|
||||||
|
max_frames = 0
|
||||||
|
bone_ids = []
|
||||||
|
bone_names = []
|
||||||
|
bone_trk_lengths = []
|
||||||
|
bone_arm_lengths = []
|
||||||
|
bone_scales = []
|
||||||
|
bone_trk_lookup = {}
|
||||||
|
for i, bt in enumerate(anim.bone_tracks):
|
||||||
|
#get maximum frame count
|
||||||
|
max_frames = max(max_frames, len(bt.positions), len(bt.rotations))
|
||||||
|
#get IDs and names
|
||||||
|
bone_id = bt.bone_id
|
||||||
|
bone_name = BONES_LIST[bone_id]
|
||||||
|
bone_trk_lookup[bone_name] = i
|
||||||
|
bone_ids.append(bone_id)
|
||||||
|
bone_names.append(bone_name)
|
||||||
|
#get animation's T-pose length
|
||||||
|
bone_trk_len = Vector(bt.positions[0]).length
|
||||||
|
bone_trk_lengths.append(bone_trk_len)
|
||||||
|
#get armature's T-pose length
|
||||||
|
bone_arm_len = getBoneLength(arm_data, bone_name)
|
||||||
|
bone_arm_lengths.append(bone_arm_len)
|
||||||
|
#determine scale
|
||||||
|
bone_scale = rescale and (bone_arm_len / bone_trk_len) or (1.0)
|
||||||
|
bone_scales.append(bone_scale)
|
||||||
|
|
||||||
|
#create animation
|
||||||
|
if arm_obj.animation_data is None:
|
||||||
|
arm_obj.animation_data_create()
|
||||||
|
if anim_name in arm_obj.animation_data.nla_tracks:
|
||||||
|
#todo: properly handle cases where the name already exists
|
||||||
|
nla_track = arm_obj.animation_data.nla_tracks[anim_name]
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
nla_track = arm_obj.animation_data.nla_tracks.new()
|
||||||
|
nla_track.name = anim_name
|
||||||
|
action = bpy.data.actions.new(anim_name)
|
||||||
|
action.use_fake_user = True
|
||||||
|
nla_strip = nla_track.strips.new(anim_name, 0, action)
|
||||||
|
nla_strip.action_frame_start = 0
|
||||||
|
nla_strip.action_frame_end = max_frames
|
||||||
|
|
||||||
|
#Extract all position and rotation track data in blender coordinates.
|
||||||
|
trk_pos_list = []
|
||||||
|
trk_rot_list = []
|
||||||
|
for i, bt in enumerate(anim.bone_tracks):
|
||||||
|
#pos_start = (len(bt.positions) > 1) and 1 or 0
|
||||||
|
pos_start = 0
|
||||||
|
pos_stop = len(bt.positions)
|
||||||
|
#rot_start = (len(bt.rotations) > 1) and 1 or 0
|
||||||
|
rot_start = 0
|
||||||
|
rot_stop = len(bt.rotations)
|
||||||
|
pos_list = []
|
||||||
|
rot_list = []
|
||||||
|
for j in range(pos_start, pos_stop):
|
||||||
|
v = import_fix_coord(bt.positions[j])
|
||||||
|
pos_list.append(v)
|
||||||
|
pass
|
||||||
|
for j in range(rot_start, rot_stop):
|
||||||
|
v = import_fix_quaternion(bt.rotations[j])
|
||||||
|
rot_list.append(v)
|
||||||
|
pass
|
||||||
|
trk_pos_list.append(pos_list)
|
||||||
|
trk_rot_list.append(rot_list)
|
||||||
|
|
||||||
|
|
||||||
|
#Iterate over bone tracks and generate FCurves for each of them.
|
||||||
|
for i, bt in enumerate(anim.bone_tracks):
|
||||||
|
bone_name = bone_names[i]
|
||||||
|
bone = arm_data.bones[bone_name]
|
||||||
|
pose_bone = arm_obj.pose.bones[bone_name]
|
||||||
|
#pos_start = (len(bt.positions) > 1) and 1 or 0
|
||||||
|
pos_start = 0
|
||||||
|
pos_stop = len(bt.positions)
|
||||||
|
#rot_start = (len(bt.rotations) > 1) and 1 or 0
|
||||||
|
rot_start = 0
|
||||||
|
rot_stop = len(bt.rotations)
|
||||||
|
pos_list = trk_pos_list[i]
|
||||||
|
rot_list = trk_rot_list[i]
|
||||||
|
props = [(pose_bone.path_from_id("location"), 3, bone_name), #"Location"),
|
||||||
|
(pose_bone.path_from_id("rotation_quaternion"), 4, bone_name), #"Quaternion Rotation"),
|
||||||
|
#(pose_bone.path_from_id("rotation_axis_angle"), 4, "Axis Angle Rotation"),
|
||||||
|
#(pose_bone,path_from_id("rotatin_euler"), 3, "Euler Rotation"),
|
||||||
|
#(pose_bone.path_from_id("scale"), 3, "Scale"),
|
||||||
|
]
|
||||||
|
curves = [action.fcurves.new(prop, index = cidx, action_group = agrp)
|
||||||
|
for prop, channel_count, agrp in props
|
||||||
|
for cidx in range(channel_count)]
|
||||||
|
pos_curves = curves[0:3]
|
||||||
|
rot_curves = curves[3:7]
|
||||||
|
for j, pos in enumerate(pos_list):
|
||||||
|
#Remove the bone component from the position.
|
||||||
|
if bone.parent is None:
|
||||||
|
#Only compute it for root bones.
|
||||||
|
rot = getBoneRotation(bone.parent, bone_trk_lookup, trk_rot_list, j)
|
||||||
|
#rot.invert()
|
||||||
|
pos0 = pos_list[0].copy()
|
||||||
|
pos0.rotate(rot)
|
||||||
|
l = (pos - pos0).length
|
||||||
|
if l >= 0.001:# and (bone.name in ["Hips", "Waist","Chest"]):
|
||||||
|
print("%s, %s: %s: pos: %s : %s ::: %s : %s" % (bone.name, j, l, pos, pos0, pos_list[0], rot))
|
||||||
|
pos = pos - pos0
|
||||||
|
if l < 0.001:
|
||||||
|
#Distance is close to zero, force position adjustment to zero.
|
||||||
|
pos = Vector()
|
||||||
|
else:
|
||||||
|
#assume 0,0,0 correction in other nodes.
|
||||||
|
pos = Vector()
|
||||||
|
for k, crv in enumerate(pos_curves):
|
||||||
|
crv.keyframe_points.insert(j, pos[k], options={'NEEDED', 'FAST'}).interpolation = 'LINEAR'
|
||||||
|
for j, rot in enumerate(rot_list):
|
||||||
|
for k, crv in enumerate(rot_curves):
|
||||||
|
crv.keyframe_points.insert(j, rot[k], options={'NEEDED', 'FAST'}).interpolation = 'LINEAR'
|
||||||
|
for crv in curves:
|
||||||
|
crv.update()
|
||||||
|
|
||||||
|
|
||||||
|
#todo: delete mid points for simple motions?
|
||||||
|
pass
|
||||||
|
|
||||||
|
def load(operator, context, scale = 1.0, filepath = "", global_matrix = None, use_mesh_modifiers = True, ignore_lod = True):
|
||||||
|
#Load .anim file
|
||||||
|
fh_in = open(filepath, "rb")
|
||||||
|
anim = Anim()
|
||||||
|
anim.loadFromFile(fh_in)
|
||||||
|
fh_in.close()
|
||||||
|
|
||||||
|
#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
|
||||||
|
convertAnimation(context, armature_obj, armature, anim, False)
|
||||||
|
|
||||||
|
return {'FINISHED'}
|
@ -0,0 +1,110 @@
|
|||||||
|
import bpy.path
|
||||||
|
import bpy
|
||||||
|
from mathutils import Vector, Quaternion
|
||||||
|
try:
|
||||||
|
from .anim import *
|
||||||
|
from .bones import *
|
||||||
|
from .import_anim import convertAnimation
|
||||||
|
except:
|
||||||
|
from anim import *
|
||||||
|
from bones import *
|
||||||
|
from import_anim import convertAnimation
|
||||||
|
|
||||||
|
def import_fix_coord(v):
|
||||||
|
return Vector((-v[0], -v[2], v[1]))
|
||||||
|
def import_fix_normal(v):
|
||||||
|
return Vector(( v[0], v[2], -v[1]))
|
||||||
|
def import_fix_quaternion(quat):
|
||||||
|
return Quaternion((quat[3], -quat[0], -quat[2], quat[1]))
|
||||||
|
|
||||||
|
TAIL_LENGTH = 0.05 #0.204377
|
||||||
|
TAIL_VECTOR = Vector((0, 1, 0))
|
||||||
|
|
||||||
|
def getTposeOffset(anim, bone_id):
|
||||||
|
offset = Vector((0, 0, 0))
|
||||||
|
for bt in anim.bone_tracks:
|
||||||
|
if bt.bone_id == bone_id:
|
||||||
|
return import_fix_coord(bt.positions[0])
|
||||||
|
return offset
|
||||||
|
|
||||||
|
def convertBone(anim, arm_obj, arm_data, bone_id, parent, parent_position, tail_nub):
|
||||||
|
bone_link = anim.skeleton_hierarchy.bones[bone_id]
|
||||||
|
#if bone_link.boneid != bone_id:
|
||||||
|
# raise Exception("")
|
||||||
|
bone_name = BONES_LIST[bone_id]
|
||||||
|
|
||||||
|
# Create the (edit)bone.
|
||||||
|
new_bone = arm_data.edit_bones.new(name=bone_name)
|
||||||
|
new_bone.select = True
|
||||||
|
new_bone.name = bone_name
|
||||||
|
|
||||||
|
bone_offset = getTposeOffset(anim, bone_id)
|
||||||
|
bone_position = parent_position + bone_offset
|
||||||
|
if parent is None:
|
||||||
|
if tail_nub:
|
||||||
|
#new_bone.head = bone_offset + TAIL_VECTOR * TAIL_LENGTH
|
||||||
|
#new_bone.tail = bone_offset
|
||||||
|
new_bone.head = bone_position
|
||||||
|
new_bone.tail = bone_position + TAIL_VECTOR * TAIL_LENGTH
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
new_bone.parent = parent
|
||||||
|
if tail_nub:
|
||||||
|
#new_bone.head = bone_offset - TAIL_VECTOR * TAIL_LENGTH
|
||||||
|
#new_bone.tail = bone_offset
|
||||||
|
new_bone.head = bone_position
|
||||||
|
new_bone.tail = bone_position + TAIL_VECTOR * TAIL_LENGTH
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
|
#visit children
|
||||||
|
if bone_link.next != -1:
|
||||||
|
convertBone(anim, arm_obj, arm_data, bone_link.next, parent, parent_position, tail_nub)
|
||||||
|
if bone_link.child != -1:
|
||||||
|
convertBone(anim, arm_obj, arm_data, bone_link.child, new_bone, bone_position, tail_nub)
|
||||||
|
|
||||||
|
|
||||||
|
def convertSkeleton(context, anim):
|
||||||
|
full_name = anim.header_name.decode("utf-8")
|
||||||
|
skeleton_name = full_name.split("/")[0]
|
||||||
|
|
||||||
|
#create armature
|
||||||
|
arm_data = bpy.data.armatures.new(name=skeleton_name)
|
||||||
|
arm_obj = bpy.data.objects.new(name=skeleton_name, object_data=arm_data)
|
||||||
|
|
||||||
|
# instance in scene
|
||||||
|
context.view_layer.active_layer_collection.collection.objects.link(arm_obj)
|
||||||
|
arm_obj.select_set(True)
|
||||||
|
|
||||||
|
# Switch to Edit mode.
|
||||||
|
context.view_layer.objects.active = arm_obj
|
||||||
|
is_hidden = arm_obj.hide_viewport
|
||||||
|
arm_obj.hide_viewport = False # Can't switch to Edit mode hidden objects...
|
||||||
|
bpy.ops.object.mode_set(mode='EDIT')
|
||||||
|
|
||||||
|
#Traverse tree, creating bones, and position heads and tails.
|
||||||
|
convertBone(anim, arm_obj, arm_data, anim.skeleton_hierarchy.root, None, Vector(), True)
|
||||||
|
|
||||||
|
#Switch to Object mode.
|
||||||
|
bpy.ops.object.mode_set(mode='OBJECT')
|
||||||
|
arm_obj.hide_viewport = is_hidden
|
||||||
|
|
||||||
|
convertAnimation(context, arm_obj, arm_data, anim, rescale = False)
|
||||||
|
|
||||||
|
def load(operator, context, scale = 1.0, filepath = "", global_matrix = None, use_mesh_modifiers = True, ignore_lod = True):
|
||||||
|
#Load .anim file
|
||||||
|
fh_in = open(filepath, "rb")
|
||||||
|
anim = Anim()
|
||||||
|
anim.loadFromFile(fh_in)
|
||||||
|
fh_in.close()
|
||||||
|
|
||||||
|
|
||||||
|
#Check for a skeleton hierarchy
|
||||||
|
if not anim.checkSkeletonHierarchy():
|
||||||
|
raise Exception("Animation does not have a skeleton. (Or it has errors.)")
|
||||||
|
#todo: check for a T-pose
|
||||||
|
convertSkeleton(context, anim)
|
||||||
|
|
||||||
|
return {'FINISHED'}
|
Loading…
Reference in New Issue