Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'io_anim_bvh')
-rw-r--r--io_anim_bvh/__init__.py140
-rw-r--r--io_anim_bvh/export_bvh.py245
-rw-r--r--io_anim_bvh/import_bvh.py552
3 files changed, 937 insertions, 0 deletions
diff --git a/io_anim_bvh/__init__.py b/io_anim_bvh/__init__.py
new file mode 100644
index 00000000..e8b51a77
--- /dev/null
+++ b/io_anim_bvh/__init__.py
@@ -0,0 +1,140 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+# <pep8 compliant>
+
+bl_info = {
+ "name": "BioVision Motion Capture (BVH) format",
+ "author": "Campbell Barton",
+ "blender": (2, 5, 7),
+ "api": 35622,
+ "location": "File > Import-Export",
+ "description": "Import-Export BVH from armature objects",
+ "warning": "",
+ "wiki_url": "http://wiki.blender.org/index.php/Extensions:2.5/Py/"\
+ "Scripts/Import-Export/MotionCapture_BVH",
+ "tracker_url": "",
+ "support": 'OFFICIAL',
+ "category": "Import-Export"}
+
+# To support reload properly, try to access a package var, if it's there, reload everything
+if "bpy" in locals():
+ import imp
+ if "import_bvh" in locals():
+ imp.reload(import_bvh)
+
+
+import bpy
+from bpy.props import StringProperty, FloatProperty, IntProperty, BoolProperty, EnumProperty
+from io_utils import ImportHelper, ExportHelper
+
+
+class ImportBVH(bpy.types.Operator, ImportHelper):
+ '''Load a BVH motion capture file'''
+ bl_idname = "import_anim.bvh"
+ bl_label = "Import BVH"
+ bl_options = {'REGISTER', 'UNDO'}
+
+ filename_ext = ".bvh"
+ filter_glob = StringProperty(default="*.bvh", options={'HIDDEN'})
+
+ target = EnumProperty(items=(
+ ('ARMATURE', "Armature", ""),
+ ('OBJECT', "Object", ""),
+ ),
+ name="Target",
+ description="Import target type.",
+ default='ARMATURE')
+
+ global_scale = FloatProperty(name="Scale", description="Scale the BVH by this value", min=0.0001, max=1000000.0, soft_min=0.001, soft_max=100.0, default=1.0)
+ frame_start = IntProperty(name="Start Frame", description="Starting frame for the animation", default=1)
+ use_cyclic = BoolProperty(name="Loop", description="Loop the animation playback", default=False)
+ rotate_mode = EnumProperty(items=(
+ ('QUATERNION', "Quaternion", "Convert rotations to quaternions"),
+ ('NATIVE', "Euler (Native)", "Use the rotation order defined in the BVH file"),
+ ('XYZ', "Euler (XYZ)", "Convert rotations to euler XYZ"),
+ ('XZY', "Euler (XZY)", "Convert rotations to euler XZY"),
+ ('YXZ', "Euler (YXZ)", "Convert rotations to euler YXZ"),
+ ('YZX', "Euler (YZX)", "Convert rotations to euler YZX"),
+ ('ZXY', "Euler (ZXY)", "Convert rotations to euler ZXY"),
+ ('ZYX', "Euler (ZYX)", "Convert rotations to euler ZYX"),
+ ),
+ name="Rotation",
+ description="Rotation conversion.",
+ default='NATIVE')
+
+ def execute(self, context):
+ from . import import_bvh
+ return import_bvh.load(self, context, **self.as_keywords(ignore=("filter_glob",)))
+
+
+class ExportBVH(bpy.types.Operator, ExportHelper):
+ '''Save a BVH motion capture file from an armature'''
+ bl_idname = "export_anim.bvh"
+ bl_label = "Export BVH"
+
+ filename_ext = ".bvh"
+ filter_glob = StringProperty(default="*.bvh", options={'HIDDEN'})
+
+ global_scale = FloatProperty(name="Scale", description="Scale the BVH by this value", min=0.0001, max=1000000.0, soft_min=0.001, soft_max=100.0, default=1.0)
+ frame_start = IntProperty(name="Start Frame", description="Starting frame to export", default=0)
+ frame_end = IntProperty(name="End Frame", description="End frame to export", default=0)
+
+ @classmethod
+ def poll(cls, context):
+ obj = context.object
+ return obj and obj.type == 'ARMATURE'
+
+ def invoke(self, context, event):
+ self.frame_start = context.scene.frame_start
+ self.frame_end = context.scene.frame_end
+
+ return super().invoke(context, event)
+
+ def execute(self, context):
+ if self.frame_start == 0 and self.frame_end == 0:
+ self.frame_start = context.scene.frame_start
+ self.frame_end = context.scene.frame_end
+
+ from . import export_bvh
+ return export_bvh.save(self, context, **self.as_keywords(ignore=("check_existing", "filter_glob")))
+
+
+def menu_func_import(self, context):
+ self.layout.operator(ImportBVH.bl_idname, text="Motion Capture (.bvh)")
+
+
+def menu_func_export(self, context):
+ self.layout.operator(ExportBVH.bl_idname, text="Motion Capture (.bvh)")
+
+
+def register():
+ bpy.utils.register_module(__name__)
+
+ bpy.types.INFO_MT_file_import.append(menu_func_import)
+ bpy.types.INFO_MT_file_export.append(menu_func_export)
+
+
+def unregister():
+ bpy.utils.unregister_module(__name__)
+
+ bpy.types.INFO_MT_file_import.remove(menu_func_import)
+ bpy.types.INFO_MT_file_export.remove(menu_func_export)
+
+if __name__ == "__main__":
+ register()
diff --git a/io_anim_bvh/export_bvh.py b/io_anim_bvh/export_bvh.py
new file mode 100644
index 00000000..2a49f083
--- /dev/null
+++ b/io_anim_bvh/export_bvh.py
@@ -0,0 +1,245 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+# <pep8 compliant>
+
+# Script copyright (C) Campbell Barton
+# fixes from Andrea Rugliancich
+
+import bpy
+
+
+def write_armature(context, filepath, frame_start, frame_end, global_scale=1.0):
+
+ from mathutils import Matrix, Vector, Euler
+ from math import degrees
+
+ file = open(filepath, "w", encoding="utf8", newline="\n")
+
+ obj = context.object
+ arm = obj.data
+
+ # Build a dictionary of children.
+ # None for parentless
+ children = {None: []}
+
+ # initialize with blank lists
+ for bone in arm.bones:
+ children[bone.name] = []
+
+ 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 = []
+
+ node_locations = {}
+
+ file.write("HIERARCHY\n")
+
+ def write_recursive_nodes(bone_name, indent):
+ my_children = children[bone_name]
+
+ indent_str = "\t" * indent
+
+ bone = arm.bones[bone_name]
+ loc = bone.head_local
+ node_locations[bone_name] = loc
+
+ # make relative if we can
+ if bone.parent:
+ loc = loc - node_locations[bone.parent.name]
+
+ if indent:
+ file.write("%sJOINT %s\n" % (indent_str, bone_name))
+ else:
+ file.write("%sROOT %s\n" % (indent_str, bone_name))
+
+ file.write("%s{\n" % indent_str)
+ file.write("%s\tOFFSET %.6f %.6f %.6f\n" % (indent_str, loc.x * global_scale, loc.y * global_scale, loc.z * global_scale))
+ if bone.use_connect and bone.parent:
+ file.write("%s\tCHANNELS 3 Xrotation Yrotation Zrotation\n" % indent_str)
+ else:
+ file.write("%s\tCHANNELS 6 Xposition Yposition Zposition Xrotation Yrotation Zrotation\n" % indent_str)
+
+ if my_children:
+ # store the location for the children
+ # to het their relative offset
+
+ # Write children
+ for child_bone in my_children:
+ serialized_names.append(child_bone)
+ write_recursive_nodes(child_bone, indent + 1)
+
+ else:
+ # Write the bone end.
+ file.write("%s\tEnd Site\n" % indent_str)
+ file.write("%s\t{\n" % indent_str)
+ loc = bone.tail_local - node_locations[bone_name]
+ file.write("%s\t\tOFFSET %.6f %.6f %.6f\n" % (indent_str, loc.x * global_scale, loc.y * global_scale, loc.z * global_scale))
+ file.write("%s\t}\n" % indent_str)
+
+ file.write("%s}\n" % indent_str)
+
+ if len(children[None]) == 1:
+ key = children[None][0]
+ serialized_names.append(key)
+ indent = 0
+
+ write_recursive_nodes(key, indent)
+
+ else:
+ # Write a dummy parent node
+ file.write("ROOT %s\n" % key)
+ file.write("{\n")
+ file.write("\tOFFSET 0.0 0.0 0.0\n")
+ file.write("\tCHANNELS 0\n") # Xposition Yposition Zposition Xrotation Yrotation Zrotation
+ key = None
+ indent = 1
+
+ write_recursive_nodes(key, indent)
+
+ file.write("}\n")
+
+ # redefine bones as sorted by serialized_names
+ # so we can write motion
+
+ class decorated_bone(object):
+ __slots__ = (\
+ "name", # bone name, used as key in many places
+ "parent", # decorated bone parent, set in a later loop
+ "rest_bone", # blender armature bone
+ "pose_bone", # blender pose bone
+ "pose_mat", # blender pose matrix
+ "rest_arm_mat", # blender rest matrix (armature space)
+ "rest_local_mat", # blender rest batrix (local space)
+ "pose_imat", # pose_mat inverted
+ "rest_arm_imat", # rest_arm_mat inverted
+ "rest_local_imat", # rest_local_mat inverted
+ "prev_euler", # last used euler to preserve euler compability in between keyframes
+ "connected", # is the bone connected to the parent bone?
+ )
+
+ def __init__(self, bone_name):
+ self.name = bone_name
+ self.rest_bone = arm.bones[bone_name]
+ self.pose_bone = obj.pose.bones[bone_name]
+
+ self.pose_mat = self.pose_bone.matrix
+
+ mat = self.rest_bone.matrix
+ self.rest_arm_mat = self.rest_bone.matrix_local
+ self.rest_local_mat = self.rest_bone.matrix
+
+ # inverted mats
+ self.pose_imat = self.pose_mat.inverted()
+ self.rest_arm_imat = self.rest_arm_mat.inverted()
+ self.rest_local_imat = self.rest_local_mat.inverted()
+
+ self.parent = None
+ self.prev_euler = Euler((0.0, 0.0, 0.0))
+ self.connected = (self.rest_bone.use_connect and self.rest_bone.parent)
+
+ def update_posedata(self):
+ self.pose_mat = self.pose_bone.matrix
+ self.pose_imat = self.pose_mat.inverted()
+
+ def __repr__(self):
+ if self.parent:
+ return "[\"%s\" child on \"%s\"]\n" % (self.name, self.parent.name)
+ else:
+ return "[\"%s\" root bone]\n" % (self.name)
+
+ bones_decorated = [decorated_bone(bone_name) for bone_name in serialized_names]
+
+ # Assign parents
+ bones_decorated_dict = {}
+ for dbone in bones_decorated:
+ bones_decorated_dict[dbone.name] = dbone
+
+ for dbone in bones_decorated:
+ parent = dbone.rest_bone.parent
+ if parent:
+ dbone.parent = bones_decorated_dict[parent.name]
+ del bones_decorated_dict
+ # finish assigning parents
+
+ scene = bpy.context.scene
+
+ file.write("MOTION\n")
+ file.write("Frames: %d\n" % (frame_end - frame_start + 1))
+ file.write("Frame Time: %.6f\n" % (1.0 / (scene.render.fps / scene.render.fps_base)))
+
+ for frame in range(frame_start, frame_end + 1):
+ scene.frame_set(frame)
+
+ for dbone in bones_decorated:
+ dbone.update_posedata()
+
+ for dbone in bones_decorated:
+ trans = Matrix.Translation(dbone.rest_bone.head_local)
+ itrans = Matrix.Translation(-dbone.rest_bone.head_local)
+
+ if dbone.parent:
+ mat_final = dbone.parent.rest_arm_mat * dbone.parent.pose_imat * dbone.pose_mat * dbone.rest_arm_imat
+ mat_final = itrans * mat_final * trans
+ loc = mat_final.to_translation() + (dbone.rest_bone.head_local - dbone.parent.rest_bone.head_local)
+ else:
+ mat_final = dbone.pose_mat * dbone.rest_arm_imat
+ mat_final = itrans * mat_final * trans
+ loc = mat_final.to_translation() + dbone.rest_bone.head
+
+ # keep eulers compatible, no jumping on interpolation.
+ rot = mat_final.to_3x3().inverted().to_euler('XYZ', dbone.prev_euler)
+
+ if not dbone.connected:
+ file.write("%.6f %.6f %.6f " % (loc * global_scale)[:])
+
+ file.write("%.6f %.6f %.6f " % (-degrees(rot[0]), -degrees(rot[1]), -degrees(rot[2])))
+
+ dbone.prev_euler = rot
+
+ file.write("\n")
+
+ file.close()
+
+ print("BVH Exported: %s frames:%d\n" % (filepath, frame_end - frame_start + 1))
+
+
+def save(operator, context, filepath="",
+ frame_start=-1,
+ frame_end=-1,
+ global_scale=1.0,
+ ):
+
+ write_armature(context, filepath,
+ frame_start=frame_start,
+ frame_end=frame_end,
+ global_scale=global_scale,
+ )
+
+ return {'FINISHED'}
+
+
+if __name__ == "__main__":
+ scene = bpy.context.scene
+ _read(bpy.data.filepath.rstrip(".blend") + ".bvh", bpy.context.object, scene.frame_start, scene.frame_end, 1.0)
diff --git a/io_anim_bvh/import_bvh.py b/io_anim_bvh/import_bvh.py
new file mode 100644
index 00000000..d0f3fde8
--- /dev/null
+++ b/io_anim_bvh/import_bvh.py
@@ -0,0 +1,552 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+# <pep8 compliant>
+
+# Script copyright (C) Campbell Barton
+
+import math
+from math import radians
+
+import bpy
+import mathutils
+from mathutils import Vector, Euler, Matrix
+
+
+class bvh_node_class(object):
+ __slots__ = (
+ 'name', # bvh joint name
+ 'parent', # bvh_node_class 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
+
+ _eul_order_lookup = {\
+ (0, 1, 2): 'XYZ',
+ (0, 2, 1): 'XZY',
+ (1, 0, 2): 'YXZ',
+ (1, 2, 0): 'YZX',
+ (2, 0, 1): 'ZXY',
+ (2, 1, 0): 'ZYX'}
+
+ def __init__(self, name, rest_head_world, rest_head_local, parent, channels, rot_order):
+ self.name = name
+ self.rest_head_world = rest_head_world
+ self.rest_head_local = rest_head_local
+ self.rest_tail_world = None
+ self.rest_tail_local = None
+ self.parent = parent
+ self.channels = channels
+ self.rot_order = tuple(rot_order)
+ self.rot_order_str = __class__._eul_order_lookup[self.rot_order]
+
+ # convenience functions
+ self.has_loc = channels[0] != -1 or channels[1] != -1 or channels[2] != -1
+ self.has_rot = channels[3] != -1 or channels[4] != -1 or channels[5] != -1
+
+ self.children = []
+
+ # list of 6 length tuples: (lx,ly,lz, rx,ry,rz)
+ # even if the channels arnt used they will just be zero
+ #
+ self.anim_data = [(0, 0, 0, 0, 0, 0)]
+
+ def __repr__(self):
+ return 'BVH name:"%s", rest_loc:(%.3f,%.3f,%.3f), rest_tail:(%.3f,%.3f,%.3f)' %\
+ (self.name,\
+ self.rest_head_world.x, self.rest_head_world.y, self.rest_head_world.z,\
+ self.rest_head_world.x, self.rest_head_world.y, self.rest_head_world.z)
+
+
+def read_bvh(context, file_path, rotate_mode='XYZ', global_scale=1.0):
+ # File loading stuff
+ # Open the file for importing
+ file = open(file_path, 'rU')
+
+ # Seperate into a list of lists, each line a list of words.
+ file_lines = file.readlines()
+ # Non standard carrage returns?
+ if len(file_lines) == 1:
+ file_lines = file_lines[0].split('\r')
+
+ # Split by whitespace.
+ file_lines = [ll for ll in [l.split() for l in file_lines] if ll]
+
+ # Create Hirachy as empties
+ if file_lines[0][0].lower() == 'hierarchy':
+ #print 'Importing the BVH Hierarchy for:', file_path
+ pass
+ else:
+ raise 'ERROR: This is not a BVH file'
+
+ bvh_nodes = {None: None}
+ bvh_nodes_serial = [None]
+
+ channelIndex = -1
+
+ lineIdx = 0 # An index for the file.
+ while lineIdx < len(file_lines) - 1:
+ #...
+ if file_lines[lineIdx][0].lower() == 'root' or file_lines[lineIdx][0].lower() == 'joint':
+
+ # Join spaces into 1 word with underscores joining it.
+ if len(file_lines[lineIdx]) > 2:
+ file_lines[lineIdx][1] = '_'.join(file_lines[lineIdx][1:])
+ file_lines[lineIdx] = file_lines[lineIdx][:2]
+
+ # MAY NEED TO SUPPORT MULTIPLE ROOT's HERE!!!, Still unsure weather multiple roots are possible.??
+
+ # Make sure the names are unique- Object names will match joint names exactly and both will be unique.
+ name = file_lines[lineIdx][1]
+
+ #print '%snode: %s, parent: %s' % (len(bvh_nodes_serial) * ' ', name, bvh_nodes_serial[-1])
+
+ lineIdx += 2 # Increment to the next line (Offset)
+ rest_head_local = Vector((float(file_lines[lineIdx][1]), float(file_lines[lineIdx][2]), float(file_lines[lineIdx][3]))) * global_scale
+ lineIdx += 1 # Increment to the next line (Channels)
+
+ # newChannel[Xposition, Yposition, Zposition, Xrotation, Yrotation, Zrotation]
+ # newChannel references indecies to the motiondata,
+ # if not assigned then -1 refers to the last value that will be added on loading at a value of zero, this is appended
+ # We'll add a zero value onto the end of the MotionDATA so this is always refers to a value.
+ my_channel = [-1, -1, -1, -1, -1, -1]
+ my_rot_order = [None, None, None]
+ rot_count = 0
+ for channel in file_lines[lineIdx][2:]:
+ channel = channel.lower()
+ channelIndex += 1 # So the index points to the right channel
+ if channel == 'xposition':
+ my_channel[0] = channelIndex
+ elif channel == 'yposition':
+ my_channel[1] = channelIndex
+ elif channel == 'zposition':
+ my_channel[2] = channelIndex
+
+ elif channel == 'xrotation':
+ my_channel[3] = channelIndex
+ my_rot_order[rot_count] = 0
+ rot_count += 1
+ elif channel == 'yrotation':
+ my_channel[4] = channelIndex
+ my_rot_order[rot_count] = 1
+ rot_count += 1
+ elif channel == 'zrotation':
+ my_channel[5] = channelIndex
+ my_rot_order[rot_count] = 2
+ rot_count += 1
+
+ channels = file_lines[lineIdx][2:]
+
+ my_parent = bvh_nodes_serial[-1] # account for none
+
+ # Apply the parents offset accumulatively
+ if my_parent is None:
+ rest_head_world = Vector(rest_head_local)
+ else:
+ rest_head_world = my_parent.rest_head_world + rest_head_local
+
+ bvh_node = bvh_nodes[name] = bvh_node_class(name, rest_head_world, rest_head_local, my_parent, my_channel, my_rot_order)
+
+ # If we have another child then we can call ourselves a parent, else
+ bvh_nodes_serial.append(bvh_node)
+
+ # Account for an end node
+ if file_lines[lineIdx][0].lower() == 'end' and file_lines[lineIdx][1].lower() == 'site': # There is sometimes a name after 'End Site' but we will ignore it.
+ lineIdx += 2 # Increment to the next line (Offset)
+ rest_tail = Vector((float(file_lines[lineIdx][1]), float(file_lines[lineIdx][2]), float(file_lines[lineIdx][3]))) * global_scale
+
+ bvh_nodes_serial[-1].rest_tail_world = bvh_nodes_serial[-1].rest_head_world + rest_tail
+ bvh_nodes_serial[-1].rest_tail_local = bvh_nodes_serial[-1].rest_head_local + rest_tail
+
+ # Just so we can remove the Parents in a uniform way- End has kids
+ # so this is a placeholder
+ bvh_nodes_serial.append(None)
+
+ if len(file_lines[lineIdx]) == 1 and file_lines[lineIdx][0] == '}': # == ['}']
+ bvh_nodes_serial.pop() # Remove the last item
+
+ if len(file_lines[lineIdx]) == 1 and file_lines[lineIdx][0].lower() == 'motion':
+ #print '\nImporting motion data'
+ lineIdx += 3 # Set the cursor to the first frame
+ break
+
+ lineIdx += 1
+
+ # Remove the None value used for easy parent reference
+ del bvh_nodes[None]
+ # Dont use anymore
+ del bvh_nodes_serial
+
+ bvh_nodes_list = bvh_nodes.values()
+
+ while lineIdx < len(file_lines):
+ line = file_lines[lineIdx]
+ for bvh_node in bvh_nodes_list:
+ #for bvh_node in bvh_nodes_serial:
+ lx = ly = lz = rx = ry = rz = 0.0
+ channels = bvh_node.channels
+ anim_data = bvh_node.anim_data
+ if channels[0] != -1:
+ lx = global_scale * float(line[channels[0]])
+
+ if channels[1] != -1:
+ ly = global_scale * float(line[channels[1]])
+
+ if channels[2] != -1:
+ lz = global_scale * float(line[channels[2]])
+
+ if channels[3] != -1 or channels[4] != -1 or channels[5] != -1:
+
+ rx = radians(float(line[channels[3]]))
+ ry = radians(float(line[channels[4]]))
+ rz = radians(float(line[channels[5]]))
+
+ # Done importing motion data #
+ anim_data.append((lx, ly, lz, rx, ry, rz))
+ lineIdx += 1
+
+ # Assign children
+ for bvh_node in bvh_nodes.values():
+ 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():
+
+ if not bvh_node.rest_tail_world:
+ if len(bvh_node.children) == 0:
+ # could just fail here, but rare BVH files have childless nodes
+ bvh_node.rest_tail_world = Vector(bvh_node.rest_head_world)
+ bvh_node.rest_tail_local = Vector(bvh_node.rest_head_local)
+ elif len(bvh_node.children) == 1:
+ bvh_node.rest_tail_world = Vector(bvh_node.children[0].rest_head_world)
+ bvh_node.rest_tail_local = bvh_node.rest_head_local + bvh_node.children[0].rest_head_local
+ else:
+ # allow this, see above
+ #if not bvh_node.children:
+ # raise 'error, bvh node has no end and no children. bad file'
+
+ # Removed temp for now
+ rest_tail_world = Vector((0.0, 0.0, 0.0))
+ rest_tail_local = Vector((0.0, 0.0, 0.0))
+ for bvh_node_child in bvh_node.children:
+ rest_tail_world += bvh_node_child.rest_head_world
+ rest_tail_local += bvh_node_child.rest_head_local
+
+ bvh_node.rest_tail_world = rest_tail_world * (1.0 / len(bvh_node.children))
+ bvh_node.rest_tail_local = rest_tail_local * (1.0 / len(bvh_node.children))
+
+ # Make sure tail isnt the same location as the head.
+ if (bvh_node.rest_tail_local - bvh_node.rest_head_local).length <= 0.001 * global_scale:
+ print("\tzero length node found:", bvh_node.name)
+ bvh_node.rest_tail_local.y = bvh_node.rest_tail_local.y + global_scale / 10
+ bvh_node.rest_tail_world.y = bvh_node.rest_tail_world.y + global_scale / 10
+
+ return bvh_nodes
+
+
+def bvh_node_dict2objects(context, bvh_name, bvh_nodes, rotate_mode='NATIVE', frame_start=1, IMPORT_LOOP=False):
+
+ if frame_start < 1:
+ frame_start = 1
+
+ scene = context.scene
+ for obj in scene.objects:
+ obj.select = False
+
+ objects = []
+
+ def add_ob(name):
+ obj = bpy.data.objects.new(name, None)
+ scene.objects.link(obj)
+ objects.append(obj)
+ obj.select = True
+
+ # nicer drawing.
+ obj.empty_draw_type = 'CUBE'
+ obj.empty_draw_size = 0.1
+
+ return obj
+
+ # Add objects
+ for name, bvh_node in bvh_nodes.items():
+ bvh_node.temp = add_ob(name)
+ bvh_node.temp.rotation_mode = bvh_node.rot_order_str[::-1]
+
+ # Parent the objects
+ for bvh_node in bvh_nodes.values():
+ for bvh_node_child in bvh_node.children:
+ bvh_node_child.temp.parent = bvh_node.temp
+
+ # Offset
+ for bvh_node in bvh_nodes.values():
+ # Make relative to parents offset
+ bvh_node.temp.location = bvh_node.rest_head_local
+
+ # Add tail objects
+ for name, bvh_node in bvh_nodes.items():
+ if not bvh_node.children:
+ ob_end = add_ob(name + '_end')
+ ob_end.parent = bvh_node.temp
+ ob_end.location = bvh_node.rest_tail_world - bvh_node.rest_head_world
+
+ for name, bvh_node in bvh_nodes.items():
+ obj = bvh_node.temp
+
+ for frame_current in range(len(bvh_node.anim_data)):
+
+ lx, ly, lz, rx, ry, rz = bvh_node.anim_data[frame_current]
+
+ if bvh_node.has_loc:
+ obj.delta_location = Vector((lx, ly, lz)) - bvh_node.rest_head_world
+ obj.keyframe_insert("delta_location", index=-1, frame=frame_start + frame_current)
+
+ if bvh_node.has_rot:
+ obj.delta_rotation_euler = rx, ry, rz
+ obj.keyframe_insert("delta_rotation_euler", index=-1, frame=frame_start + frame_current)
+
+ return objects
+
+
+def bvh_node_dict2armature(context, bvh_name, bvh_nodes, rotate_mode='XYZ', frame_start=1, IMPORT_LOOP=False):
+
+ if frame_start < 1:
+ frame_start = 1
+
+ # Add the new armature,
+ scene = context.scene
+ for obj in scene.objects:
+ obj.select = False
+
+ arm_data = bpy.data.armatures.new(bvh_name)
+ arm_ob = bpy.data.objects.new(bvh_name, arm_data)
+
+ scene.objects.link(arm_ob)
+
+ arm_ob.select = True
+ scene.objects.active = arm_ob
+
+ bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
+ bpy.ops.object.mode_set(mode='EDIT', toggle=False)
+
+ # 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():
+ l = (bvh_node.rest_head_local - bvh_node.rest_tail_local).length
+ if l:
+ average_bone_length += l
+ nonzero_count += 1
+
+ # Very rare cases all bones couldbe zero length???
+ if not average_bone_length:
+ average_bone_length = 0.1
+ else:
+ # Normal operation
+ average_bone_length = average_bone_length / nonzero_count
+
+ # XXX, annoying, remove bone.
+ while arm_data.edit_bones:
+ arm_ob.edit_bones.remove(arm_data.edit_bones[-1])
+
+ ZERO_AREA_BONES = []
+ for name, bvh_node in bvh_nodes.items():
+ # New editbone
+ bone = bvh_node.temp = arm_data.edit_bones.new(name)
+
+ bone.head = bvh_node.rest_head_world
+ bone.tail = bvh_node.rest_tail_world
+
+ # ZERO AREA BONES.
+ if (bone.head - bone.tail).length < 0.001:
+ print("\tzero length bone found:", bone.name)
+ if bvh_node.parent:
+ ofs = bvh_node.parent.rest_head_local - bvh_node.parent.rest_tail_local
+ if ofs.length: # is our parent zero length also?? unlikely
+ bone.tail = bone.tail - ofs
+ else:
+ bone.tail.y = bone.tail.y + average_bone_length
+ else:
+ bone.tail.y = bone.tail.y + average_bone_length
+
+ ZERO_AREA_BONES.append(bone.name)
+
+ for bvh_node in bvh_nodes.values():
+ if bvh_node.parent:
+ # bvh_node.temp is the Editbone
+
+ # Set the bone parent
+ bvh_node.temp.parent = bvh_node.parent.temp
+
+ # Set the connection state
+ if not bvh_node.has_loc and\
+ bvh_node.parent and\
+ bvh_node.parent.temp.name not in ZERO_AREA_BONES and\
+ bvh_node.parent.rest_tail_local == bvh_node.rest_head_local:
+ bvh_node.temp.use_connect = True
+
+ # Replace the editbone with the editbone name,
+ # to avoid memory errors accessing the editbone outside editmode
+ for bvh_node in bvh_nodes.values():
+ bvh_node.temp = bvh_node.temp.name
+
+ # Now Apply the animation to the armature
+
+ # Get armature animation data
+ bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
+
+ pose = arm_ob.pose
+ pose_bones = pose.bones
+
+ if rotate_mode == 'NATIVE':
+ for bvh_node in bvh_nodes.values():
+ 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
+
+ elif rotate_mode != 'QUATERNION':
+ for pose_bone in pose_bones:
+ pose_bone.rotation_mode = rotate_mode
+ else:
+ # Quats default
+ pass
+
+ context.scene.update()
+
+ arm_ob.animation_data_create()
+ action = bpy.data.actions.new(name=bvh_name)
+ arm_ob.animation_data.action = action
+
+ # 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():
+ 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]
+ bone_rest_matrix = rest_bone.matrix_local.to_3x3()
+
+ bone_rest_matrix_inv = Matrix(bone_rest_matrix)
+ bone_rest_matrix_inv.invert()
+
+ bone_rest_matrix_inv.resize_4x4()
+ bone_rest_matrix.resize_4x4()
+ bvh_node.temp = (pose_bone, bone, bone_rest_matrix, bone_rest_matrix_inv)
+
+ # Make a dict for fast access without rebuilding a list all the time.
+
+ # KEYFRAME METHOD, SLOW, USE IPOS DIRECT
+ # TODO: use f-point samples instead (Aligorith)
+ if rotate_mode != 'QUATERNION':
+ prev_euler = [Euler() for i in range(len(bvh_nodes))]
+
+ # Animate the data, the last used bvh_node will do since they all have the same number of frames
+ for frame_current in range(len(bvh_node.anim_data) - 1): # skip the first frame (rest frame)
+ # print frame_current
+
+ # if frame_current==40: # debugging
+ # break
+
+ scene.frame_set(frame_start + frame_current)
+
+ # Dont neet to set the current frame
+ for i, bvh_node in enumerate(bvh_nodes.values()):
+ 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]
+
+ if bvh_node.has_rot:
+ # apply rotation order and convert to XYZ
+ # note that the rot_order_str is reversed.
+ bone_rotation_matrix = Euler((rx, ry, rz), bvh_node.rot_order_str[::-1]).to_matrix().to_4x4()
+ bone_rotation_matrix = bone_rest_matrix_inv * bone_rotation_matrix * bone_rest_matrix
+
+ if rotate_mode == 'QUATERNION':
+ pose_bone.rotation_quaternion = bone_rotation_matrix.to_quaternion()
+ else:
+ euler = bone_rotation_matrix.to_euler(bvh_node.rot_order_str, prev_euler[i])
+ pose_bone.rotation_euler = euler
+ prev_euler[i] = euler
+
+ if bvh_node.has_loc:
+ pose_bone.location = (bone_rest_matrix_inv * Matrix.Translation(Vector((lx, ly, lz)) - bvh_node.rest_head_local)).to_translation()
+
+ if bvh_node.has_loc:
+ pose_bone.keyframe_insert("location")
+ if bvh_node.has_rot:
+ if rotate_mode == 'QUATERNION':
+ pose_bone.keyframe_insert("rotation_quaternion")
+ else:
+ pose_bone.keyframe_insert("rotation_euler")
+
+ for cu in action.fcurves:
+ if IMPORT_LOOP:
+ pass # 2.5 doenst have cyclic now?
+
+ for bez in cu.keyframe_points:
+ bez.interpolation = 'LINEAR'
+
+ return arm_ob
+
+
+def load(operator, context, filepath="", target='ARMATURE', rotate_mode='NATIVE', global_scale=1.0, use_cyclic=False, frame_start=1):
+ import time
+ t1 = time.time()
+ print('\tparsing bvh %r...' % filepath, end="")
+
+ bvh_nodes = read_bvh(context, filepath,
+ rotate_mode=rotate_mode,
+ global_scale=global_scale)
+
+ print('%.4f' % (time.time() - t1))
+
+ frame_orig = context.scene.frame_current
+
+ t1 = time.time()
+ print('\timporting to blender...', end="")
+
+ bvh_name = bpy.path.display_name_from_filepath(filepath)
+
+ if target == 'ARMATURE':
+ bvh_node_dict2armature(context, bvh_name, bvh_nodes,
+ rotate_mode=rotate_mode,
+ frame_start=frame_start,
+ IMPORT_LOOP=use_cyclic)
+
+ elif target == 'OBJECT':
+ bvh_node_dict2objects(context, bvh_name, bvh_nodes,
+ rotate_mode=rotate_mode,
+ frame_start=frame_start,
+ IMPORT_LOOP=use_cyclic)
+
+ else:
+ raise Exception("invalid type")
+
+ print('Done in %.4f\n' % (time.time() - t1))
+
+ context.scene.frame_set(frame_orig)
+
+ return {'FINISHED'}