From 25b31e5b7d911d57996042a10f9e3cf645d07b93 Mon Sep 17 00:00:00 2001 From: Campbell Barton <ideasman42@gmail.com> Date: Mon, 12 Mar 2012 16:04:00 +0000 Subject: [PATCH] maintain order from import to export (secondlife expects bone order to be maintained). also use less confusing bone rotation matrix -> euler conversion on export. --- io_anim_bvh/export_bvh.py | 16 ++++----- io_anim_bvh/import_bvh.py | 68 +++++++++++++++++++++++---------------- 2 files changed, 49 insertions(+), 35 deletions(-) diff --git a/io_anim_bvh/export_bvh.py b/io_anim_bvh/export_bvh.py index 977c36f53..fcddb63cf 100644 --- a/io_anim_bvh/export_bvh.py +++ b/io_anim_bvh/export_bvh.py @@ -54,13 +54,11 @@ def write_armature(context, for bone in arm.bones: children[bone.name] = [] + # keep bone order from armature, no sorting, not esspential but means + # we can maintain order from import -> export which secondlife incorrectly expects. for bone in arm.bones: children[getattr(bone.parent, "name", None)].append(bone.name) - # sort the children - for children_list in children.values(): - children_list.sort() - # bone name list in the order that the bones are written serialized_names = [] @@ -157,7 +155,8 @@ def write_armature(context, "skip_position", # is the bone disconnected to the parent bone? "rot_order", "rot_order_str", - ) + "rot_order_str_reverse", # needed for the euler order when converting from a matrix + ) _eul_order_lookup = { 'XYZ': (0, 1, 2), @@ -177,6 +176,7 @@ def write_armature(context, self.rot_order_str = ensure_rot_order(self.pose_bone.rotation_mode) else: self.rot_order_str = rotate_mode + self.rot_order_str_reverse = self.rot_order_str[::-1] self.rot_order = DecoratedBone._eul_order_lookup[self.rot_order_str] @@ -192,7 +192,7 @@ def write_armature(context, self.rest_local_imat = self.rest_local_mat.inverted() self.parent = None - self.prev_euler = Euler((0.0, 0.0, 0.0), self.rot_order_str) + self.prev_euler = Euler((0.0, 0.0, 0.0), self.rot_order_str_reverse) self.skip_position = ((self.rest_bone.use_connect or root_transform_only) and self.rest_bone.parent) def update_posedata(self): @@ -246,12 +246,12 @@ def write_armature(context, loc = mat_final.to_translation() + dbone.rest_bone.head # keep eulers compatible, no jumping on interpolation. - rot = mat_final.to_3x3().inverted().to_euler(dbone.rot_order_str, dbone.prev_euler) + rot = mat_final.to_euler(dbone.rot_order_str_reverse, dbone.prev_euler) if not dbone.skip_position: file.write("%.6f %.6f %.6f " % (loc * global_scale)[:]) - file.write("%.6f %.6f %.6f " % (-degrees(rot[dbone.rot_order[0]]), -degrees(rot[dbone.rot_order[1]]), -degrees(rot[dbone.rot_order[2]]))) + file.write("%.6f %.6f %.6f " % (degrees(rot[dbone.rot_order[0]]), degrees(rot[dbone.rot_order[1]]), degrees(rot[dbone.rot_order[2]]))) dbone.prev_euler = rot diff --git a/io_anim_bvh/import_bvh.py b/io_anim_bvh/import_bvh.py index 0022cc8ca..110ee83e5 100644 --- a/io_anim_bvh/import_bvh.py +++ b/io_anim_bvh/import_bvh.py @@ -28,20 +28,22 @@ from mathutils import Vector, Euler, Matrix class BVH_Node(object): __slots__ = ( - 'name', # bvh joint name - 'parent', # BVH_Node type or None for no parent - 'children', # a list of children of this type. - 'rest_head_world', # worldspace rest location for the head of this node - 'rest_head_local', # localspace rest location for the head of this node - 'rest_tail_world', # worldspace rest location for the tail of this node - 'rest_tail_local', # worldspace rest location for the tail of this node - 'channels', # list of 6 ints, -1 for an unused channel, otherwise an index for the BVH motion data lines, lock triple then rot triple - 'rot_order', # a triple of indices as to the order rotation is applied. [0,1,2] is x/y/z - [None, None, None] if no rotation. - 'rot_order_str', # same as above but a string 'XYZ' format. - 'anim_data', # a list one tuple's one for each frame. (locx, locy, locz, rotx, roty, rotz), euler rotation ALWAYS stored xyz order, even when native used. - 'has_loc', # Conveinience function, bool, same as (channels[0]!=-1 or channels[1]!=-1 channels[2]!=-1) - 'has_rot', # Conveinience function, bool, same as (channels[3]!=-1 or channels[4]!=-1 channels[5]!=-1) - 'temp') # use this for whatever you want + 'name', # bvh joint name + 'parent', # BVH_Node type or None for no parent + 'children', # a list of children of this type. + 'rest_head_world', # worldspace rest location for the head of this node + 'rest_head_local', # localspace rest location for the head of this node + 'rest_tail_world', # worldspace rest location for the tail of this node + 'rest_tail_local', # worldspace rest location for the tail of this node + 'channels', # list of 6 ints, -1 for an unused channel, otherwise an index for the BVH motion data lines, lock triple then rot triple + 'rot_order', # a triple of indices as to the order rotation is applied. [0,1,2] is x/y/z - [None, None, None] if no rotation. + 'rot_order_str', # same as above but a string 'XYZ' format. + 'anim_data', # a list one tuple's one for each frame. (locx, locy, locz, rotx, roty, rotz), euler rotation ALWAYS stored xyz order, even when native used. + 'has_loc', # Conveinience function, bool, same as (channels[0]!=-1 or channels[1]!=-1 channels[2]!=-1) + 'has_rot', # Conveinience function, bool, same as (channels[3]!=-1 or channels[4]!=-1 channels[5]!=-1) + 'index', # index from the file, not strictly needed but nice to maintain order + 'temp', # use this for whatever you want + ) _eul_order_lookup = {(0, 1, 2): 'XYZ', (0, 2, 1): 'XZY', @@ -51,7 +53,7 @@ class BVH_Node(object): (2, 1, 0): 'ZYX', } - def __init__(self, name, rest_head_world, rest_head_local, parent, channels, rot_order): + def __init__(self, name, rest_head_world, rest_head_local, parent, channels, rot_order, index): self.name = name self.rest_head_world = rest_head_world self.rest_head_local = rest_head_local @@ -61,6 +63,7 @@ class BVH_Node(object): self.channels = channels self.rot_order = tuple(rot_order) self.rot_order_str = BVH_Node._eul_order_lookup[self.rot_order] + self.index = index # convenience functions self.has_loc = channels[0] != -1 or channels[1] != -1 or channels[2] != -1 @@ -80,6 +83,12 @@ class BVH_Node(object): self.rest_head_world.x, self.rest_head_world.y, self.rest_head_world.z) +def sorted_nodes(bvh_nodes): + bvh_nodes_list = list(bvh_nodes.values()) + bvh_nodes_list.sort(key=lambda bvh_node: bvh_node.index) + return bvh_nodes_list + + def read_bvh(context, file_path, rotate_mode='XYZ', global_scale=1.0): # File loading stuff # Open the file for importing @@ -167,7 +176,7 @@ def read_bvh(context, file_path, rotate_mode='XYZ', global_scale=1.0): else: rest_head_world = my_parent.rest_head_world + rest_head_local - bvh_node = bvh_nodes[name] = BVH_Node(name, rest_head_world, rest_head_local, my_parent, my_channel, my_rot_order) + bvh_node = bvh_nodes[name] = BVH_Node(name, rest_head_world, rest_head_local, my_parent, my_channel, my_rot_order, len(bvh_nodes) - 1) # If we have another child then we can call ourselves a parent, else bvh_nodes_serial.append(bvh_node) @@ -199,7 +208,9 @@ def read_bvh(context, file_path, rotate_mode='XYZ', global_scale=1.0): # Dont use anymore del bvh_nodes_serial - bvh_nodes_list = bvh_nodes.values() + # importing world with any order but nicer to maintain order + # second life expects it, which isnt to spec. + bvh_nodes_list = sorted_nodes(bvh_nodes) while lineIdx < len(file_lines): line = file_lines[lineIdx] @@ -228,13 +239,13 @@ def read_bvh(context, file_path, rotate_mode='XYZ', global_scale=1.0): lineIdx += 1 # Assign children - for bvh_node in bvh_nodes.values(): + for bvh_node in bvh_nodes_list: bvh_node_parent = bvh_node.parent if bvh_node_parent: bvh_node_parent.children.append(bvh_node) # Now set the tip of each bvh_node - for bvh_node in bvh_nodes.values(): + for bvh_node in bvh_nodes_list: if not bvh_node.rest_tail_world: if len(bvh_node.children) == 0: @@ -359,10 +370,12 @@ def bvh_node_dict2armature(context, bpy.ops.object.mode_set(mode='OBJECT', toggle=False) bpy.ops.object.mode_set(mode='EDIT', toggle=False) + bvh_nodes_list = sorted_nodes(bvh_nodes) + # Get the average bone length for zero length bones, we may not use this. average_bone_length = 0.0 nonzero_count = 0 - for bvh_node in bvh_nodes.values(): + for bvh_node in bvh_nodes_list: l = (bvh_node.rest_head_local - bvh_node.rest_tail_local).length if l: average_bone_length += l @@ -380,9 +393,10 @@ def bvh_node_dict2armature(context, arm_ob.edit_bones.remove(arm_data.edit_bones[-1]) ZERO_AREA_BONES = [] - for name, bvh_node in bvh_nodes.items(): + for bvh_node in bvh_nodes_list: + # New editbone - bone = bvh_node.temp = arm_data.edit_bones.new(name) + bone = bvh_node.temp = arm_data.edit_bones.new(bvh_node.name) bone.head = bvh_node.rest_head_world bone.tail = bvh_node.rest_tail_world @@ -401,7 +415,7 @@ def bvh_node_dict2armature(context, ZERO_AREA_BONES.append(bone.name) - for bvh_node in bvh_nodes.values(): + for bvh_node in bvh_nodes_list: if bvh_node.parent: # bvh_node.temp is the Editbone @@ -417,7 +431,7 @@ def bvh_node_dict2armature(context, # Replace the editbone with the editbone name, # to avoid memory errors accessing the editbone outside editmode - for bvh_node in bvh_nodes.values(): + for bvh_node in bvh_nodes_list: bvh_node.temp = bvh_node.temp.name # Now Apply the animation to the armature @@ -429,7 +443,7 @@ def bvh_node_dict2armature(context, pose_bones = pose.bones if rotate_mode == 'NATIVE': - for bvh_node in bvh_nodes.values(): + for bvh_node in bvh_nodes_list: bone_name = bvh_node.temp # may not be the same name as the bvh_node, could have been shortened. pose_bone = pose_bones[bone_name] pose_bone.rotation_mode = bvh_node.rot_order_str @@ -449,7 +463,7 @@ def bvh_node_dict2armature(context, # Replace the bvh_node.temp (currently an editbone) # With a tuple (pose_bone, armature_bone, bone_rest_matrix, bone_rest_matrix_inv) - for bvh_node in bvh_nodes.values(): + for bvh_node in bvh_nodes_list: bone_name = bvh_node.temp # may not be the same name as the bvh_node, could have been shortened. pose_bone = pose_bones[bone_name] rest_bone = arm_data.bones[bone_name] @@ -479,7 +493,7 @@ def bvh_node_dict2armature(context, scene.frame_set(frame_start + frame_current) # Dont neet to set the current frame - for i, bvh_node in enumerate(bvh_nodes.values()): + for i, bvh_node in enumerate(bvh_nodes_list): pose_bone, bone, bone_rest_matrix, bone_rest_matrix_inv = bvh_node.temp lx, ly, lz, rx, ry, rz = bvh_node.anim_data[frame_current + 1] -- GitLab