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