Skip to content
Snippets Groups Projects
Commit f5fd8087 authored by Campbell Barton's avatar Campbell Barton
Browse files

Cleanup: BVH import/export

- Use tuple unpacking.
- Remove unused operator argument.
parent 6d62e07f
Branches
Tags
No related merge requests found
...@@ -26,10 +26,13 @@ bl_info = { ...@@ -26,10 +26,13 @@ bl_info = {
"location": "File > Import-Export", "location": "File > Import-Export",
"description": "Import-Export BVH from armature objects", "description": "Import-Export BVH from armature objects",
"warning": "", "warning": "",
"wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/" "wiki_url": (
"Scripts/Import-Export/BVH_Importer_Exporter", "http://wiki.blender.org/index.php/Extensions:2.6/Py/"
"Scripts/Import-Export/BVH_Importer_Exporter"
),
"support": 'OFFICIAL', "support": 'OFFICIAL',
"category": "Import-Export"} "category": "Import-Export",
}
if "bpy" in locals(): if "bpy" in locals():
import importlib import importlib
...@@ -40,18 +43,18 @@ if "bpy" in locals(): ...@@ -40,18 +43,18 @@ if "bpy" in locals():
import bpy import bpy
from bpy.props import ( from bpy.props import (
StringProperty, StringProperty,
FloatProperty, FloatProperty,
IntProperty, IntProperty,
BoolProperty, BoolProperty,
EnumProperty, EnumProperty,
) )
from bpy_extras.io_utils import ( from bpy_extras.io_utils import (
ImportHelper, ImportHelper,
ExportHelper, ExportHelper,
orientation_helper_factory, orientation_helper_factory,
axis_conversion, axis_conversion,
) )
ImportBVHOrientationHelper = orientation_helper_factory("ImportBVHOrientationHelper", axis_forward='-Z', axis_up='Y') ImportBVHOrientationHelper = orientation_helper_factory("ImportBVHOrientationHelper", axis_forward='-Z', axis_up='Y')
...@@ -66,74 +69,85 @@ class ImportBVH(bpy.types.Operator, ImportHelper, ImportBVHOrientationHelper): ...@@ -66,74 +69,85 @@ class ImportBVH(bpy.types.Operator, ImportHelper, ImportBVHOrientationHelper):
filename_ext = ".bvh" filename_ext = ".bvh"
filter_glob = StringProperty(default="*.bvh", options={'HIDDEN'}) filter_glob = StringProperty(default="*.bvh", options={'HIDDEN'})
target = EnumProperty(items=( target = EnumProperty(
items=(
('ARMATURE', "Armature", ""), ('ARMATURE', "Armature", ""),
('OBJECT', "Object", ""), ('OBJECT', "Object", ""),
), ),
name="Target", name="Target",
description="Import target type", description="Import target type",
default='ARMATURE') default='ARMATURE',
)
global_scale = FloatProperty( global_scale = FloatProperty(
name="Scale", name="Scale",
description="Scale the BVH by this value", description="Scale the BVH by this value",
min=0.0001, max=1000000.0, min=0.0001, max=1000000.0,
soft_min=0.001, soft_max=100.0, soft_min=0.001, soft_max=100.0,
default=1.0, default=1.0,
) )
frame_start = IntProperty( frame_start = IntProperty(
name="Start Frame", name="Start Frame",
description="Starting frame for the animation", description="Starting frame for the animation",
default=1, default=1,
) )
use_fps_scale = BoolProperty( use_fps_scale = BoolProperty(
name="Scale FPS", name="Scale FPS",
description=("Scale the framerate from the BVH to the current scenes, " description=(
"otherwise each BVH frame maps directly to a Blender frame"), "Scale the framerate from the BVH to the current scenes, "
default=False, "otherwise each BVH frame maps directly to a Blender frame"
) ),
default=False,
)
update_scene_fps = BoolProperty( update_scene_fps = BoolProperty(
name="Update Scene FPS", name="Update Scene FPS",
description="Set the scene framerate to that of the BVH file (note that this " description=(
"nullifies the 'Scale FPS' option, as the scale will be 1:1)", "Set the scene framerate to that of the BVH file (note that this "
default=False "nullifies the 'Scale FPS' option, as the scale will be 1:1)"
) ),
default=False
)
update_scene_duration = BoolProperty( update_scene_duration = BoolProperty(
name="Update Scene Duration", name="Update Scene Duration",
description="Extend the scene's duration to the BVH duration (never shortens the scene)", description="Extend the scene's duration to the BVH duration (never shortens the scene)",
default=False, default=False,
) )
use_cyclic = BoolProperty( use_cyclic = BoolProperty(
name="Loop", name="Loop",
description="Loop the animation playback", description="Loop the animation playback",
default=False, default=False,
) )
rotate_mode = EnumProperty( rotate_mode = EnumProperty(
name="Rotation", name="Rotation",
description="Rotation conversion", description="Rotation conversion",
items=(('QUATERNION', "Quaternion", items=(
"Convert rotations to quaternions"), ('QUATERNION', "Quaternion",
('NATIVE', "Euler (Native)", "Convert rotations to quaternions"),
"Use the rotation order defined in the BVH file"), ('NATIVE', "Euler (Native)",
('XYZ', "Euler (XYZ)", "Convert rotations to euler XYZ"), "Use the rotation order defined in the BVH file"),
('XZY', "Euler (XZY)", "Convert rotations to euler XZY"), ('XYZ', "Euler (XYZ)", "Convert rotations to euler XYZ"),
('YXZ', "Euler (YXZ)", "Convert rotations to euler YXZ"), ('XZY', "Euler (XZY)", "Convert rotations to euler XZY"),
('YZX', "Euler (YZX)", "Convert rotations to euler YZX"), ('YXZ', "Euler (YXZ)", "Convert rotations to euler YXZ"),
('ZXY', "Euler (ZXY)", "Convert rotations to euler ZXY"), ('YZX', "Euler (YZX)", "Convert rotations to euler YZX"),
('ZYX', "Euler (ZYX)", "Convert rotations to euler ZYX"), ('ZXY', "Euler (ZXY)", "Convert rotations to euler ZXY"),
), ('ZYX', "Euler (ZYX)", "Convert rotations to euler ZYX"),
default='NATIVE', ),
) default='NATIVE',
)
def execute(self, context): def execute(self, context):
keywords = self.as_keywords(ignore=("axis_forward", keywords = self.as_keywords(
"axis_up", ignore=(
"filter_glob", "axis_forward",
)) "axis_up",
"filter_glob",
)
)
global_matrix = axis_conversion(from_forward=self.axis_forward, global_matrix = axis_conversion(
from_up=self.axis_up, from_forward=self.axis_forward,
).to_4x4() from_up=self.axis_up,
).to_4x4()
keywords["global_matrix"] = global_matrix keywords["global_matrix"] = global_matrix
...@@ -148,46 +162,47 @@ class ExportBVH(bpy.types.Operator, ExportHelper): ...@@ -148,46 +162,47 @@ class ExportBVH(bpy.types.Operator, ExportHelper):
filename_ext = ".bvh" filename_ext = ".bvh"
filter_glob = StringProperty( filter_glob = StringProperty(
default="*.bvh", default="*.bvh",
options={'HIDDEN'}, options={'HIDDEN'},
) )
global_scale = FloatProperty( global_scale = FloatProperty(
name="Scale", name="Scale",
description="Scale the BVH by this value", description="Scale the BVH by this value",
min=0.0001, max=1000000.0, min=0.0001, max=1000000.0,
soft_min=0.001, soft_max=100.0, soft_min=0.001, soft_max=100.0,
default=1.0, default=1.0,
) )
frame_start = IntProperty( frame_start = IntProperty(
name="Start Frame", name="Start Frame",
description="Starting frame to export", description="Starting frame to export",
default=0, default=0,
) )
frame_end = IntProperty( frame_end = IntProperty(
name="End Frame", name="End Frame",
description="End frame to export", description="End frame to export",
default=0, default=0,
) )
rotate_mode = EnumProperty( rotate_mode = EnumProperty(
name="Rotation", name="Rotation",
description="Rotation conversion", description="Rotation conversion",
items=(('NATIVE', "Euler (Native)", items=(
"Use the rotation order defined in the BVH file"), ('NATIVE', "Euler (Native)",
('XYZ', "Euler (XYZ)", "Convert rotations to euler XYZ"), "Use the rotation order defined in the BVH file"),
('XZY', "Euler (XZY)", "Convert rotations to euler XZY"), ('XYZ', "Euler (XYZ)", "Convert rotations to euler XYZ"),
('YXZ', "Euler (YXZ)", "Convert rotations to euler YXZ"), ('XZY', "Euler (XZY)", "Convert rotations to euler XZY"),
('YZX', "Euler (YZX)", "Convert rotations to euler YZX"), ('YXZ', "Euler (YXZ)", "Convert rotations to euler YXZ"),
('ZXY', "Euler (ZXY)", "Convert rotations to euler ZXY"), ('YZX', "Euler (YZX)", "Convert rotations to euler YZX"),
('ZYX', "Euler (ZYX)", "Convert rotations to euler ZYX"), ('ZXY', "Euler (ZXY)", "Convert rotations to euler ZXY"),
), ('ZYX', "Euler (ZYX)", "Convert rotations to euler ZYX"),
default='NATIVE', ),
) default='NATIVE',
)
root_transform_only = BoolProperty( root_transform_only = BoolProperty(
name="Root Translation Only", name="Root Translation Only",
description="Only write out translation channels for the root bone", description="Only write out translation channels for the root bone",
default=False, default=False,
) )
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
...@@ -208,7 +223,7 @@ class ExportBVH(bpy.types.Operator, ExportHelper): ...@@ -208,7 +223,7 @@ class ExportBVH(bpy.types.Operator, ExportHelper):
keywords = self.as_keywords(ignore=("check_existing", "filter_glob")) keywords = self.as_keywords(ignore=("check_existing", "filter_glob"))
from . import export_bvh from . import export_bvh
return export_bvh.save(self, context, **keywords) return export_bvh.save(context, **keywords)
def menu_func_import(self, context): def menu_func_import(self, context):
...@@ -232,5 +247,6 @@ def unregister(): ...@@ -232,5 +247,6 @@ def unregister():
bpy.types.INFO_MT_file_import.remove(menu_func_import) bpy.types.INFO_MT_file_import.remove(menu_func_import)
bpy.types.INFO_MT_file_export.remove(menu_func_export) bpy.types.INFO_MT_file_export.remove(menu_func_export)
if __name__ == "__main__": if __name__ == "__main__":
register() register()
...@@ -24,14 +24,15 @@ ...@@ -24,14 +24,15 @@
import bpy import bpy
def write_armature(context, def write_armature(
filepath, context,
frame_start, filepath,
frame_end, frame_start,
global_scale=1.0, frame_end,
rotate_mode='NATIVE', global_scale=1.0,
root_transform_only=False, rotate_mode='NATIVE',
): root_transform_only=False,
):
def ensure_rot_order(rot_order_str): def ensure_rot_order(rot_order_str):
if set(rot_order_str) != {'X', 'Y', 'Z'}: if set(rot_order_str) != {'X', 'Y', 'Z'}:
...@@ -91,11 +92,11 @@ def write_armature(context, ...@@ -91,11 +92,11 @@ def write_armature(context,
file.write("%sROOT %s\n" % (indent_str, bone_name)) file.write("%sROOT %s\n" % (indent_str, bone_name))
file.write("%s{\n" % indent_str) 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)) file.write("%s\tOFFSET %.6f %.6f %.6f\n" % (indent_str, *(loc * global_scale)))
if (bone.use_connect or root_transform_only) and bone.parent: if (bone.use_connect or root_transform_only) and bone.parent:
file.write("%s\tCHANNELS 3 %srotation %srotation %srotation\n" % (indent_str, rot_order_str[0], rot_order_str[1], rot_order_str[2])) file.write("%s\tCHANNELS 3 %srotation %srotation %srotation\n" % (indent_str, *rot_order_str))
else: else:
file.write("%s\tCHANNELS 6 Xposition Yposition Zposition %srotation %srotation %srotation\n" % (indent_str, rot_order_str[0], rot_order_str[1], rot_order_str[2])) file.write("%s\tCHANNELS 6 Xposition Yposition Zposition %srotation %srotation %srotation\n" % (indent_str, *rot_order_str))
if my_children: if my_children:
# store the location for the children # store the location for the children
...@@ -111,7 +112,7 @@ def write_armature(context, ...@@ -111,7 +112,7 @@ def write_armature(context,
file.write("%s\tEnd Site\n" % indent_str) file.write("%s\tEnd Site\n" % indent_str)
file.write("%s\t{\n" % indent_str) file.write("%s\t{\n" % indent_str)
loc = bone.tail_local - node_locations[bone_name] 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\tOFFSET %.6f %.6f %.6f\n" % (indent_str, *(loc * global_scale)))
file.write("%s\t}\n" % indent_str) file.write("%s\t}\n" % indent_str)
file.write("%s}\n" % indent_str) file.write("%s}\n" % indent_str)
...@@ -149,22 +150,34 @@ def write_armature(context, ...@@ -149,22 +150,34 @@ def write_armature(context,
class DecoratedBone: class DecoratedBone:
__slots__ = ( __slots__ = (
"name", # bone name, used as key in many places # Bone name, used as key in many places.
"name",
"parent", # decorated bone parent, set in a later loop "parent", # decorated bone parent, set in a later loop
"rest_bone", # blender armature bone # Blender armature bone.
"pose_bone", # blender pose bone "rest_bone",
"pose_mat", # blender pose matrix # Blender pose bone.
"rest_arm_mat", # blender rest matrix (armature space) "pose_bone",
"rest_local_mat", # blender rest batrix (local space) # Blender pose matrix.
"pose_imat", # pose_mat inverted "pose_mat",
"rest_arm_imat", # rest_arm_mat inverted # Blender rest matrix (armature space).
"rest_local_imat", # rest_local_mat inverted "rest_arm_mat",
"prev_euler", # last used euler to preserve euler compability in between keyframes # Blender rest batrix (local space).
"skip_position", # is the bone disconnected to the parent bone? "rest_local_mat",
# Pose_mat inverted.
"pose_imat",
# Rest_arm_mat inverted.
"rest_arm_imat",
# Rest_local_mat inverted.
"rest_local_imat",
# Last used euler to preserve euler compability in between keyframes.
"prev_euler",
# Is the bone disconnected to the parent bone?
"skip_position",
"rot_order", "rot_order",
"rot_order_str", "rot_order_str",
"rot_order_str_reverse", # needed for the euler order when converting from a matrix # Needed for the euler order when converting from a matrix.
) "rot_order_str_reverse",
)
_eul_order_lookup = { _eul_order_lookup = {
'XYZ': (0, 1, 2), 'XYZ': (0, 1, 2),
...@@ -173,7 +186,7 @@ def write_armature(context, ...@@ -173,7 +186,7 @@ def write_armature(context,
'YZX': (1, 2, 0), 'YZX': (1, 2, 0),
'ZXY': (2, 0, 1), 'ZXY': (2, 0, 1),
'ZYX': (2, 1, 0), 'ZYX': (2, 1, 0),
} }
def __init__(self, bone_name): def __init__(self, bone_name):
self.name = bone_name self.name = bone_name
...@@ -216,10 +229,7 @@ def write_armature(context, ...@@ -216,10 +229,7 @@ def write_armature(context,
bones_decorated = [DecoratedBone(bone_name) for bone_name in serialized_names] bones_decorated = [DecoratedBone(bone_name) for bone_name in serialized_names]
# Assign parents # Assign parents
bones_decorated_dict = {} bones_decorated_dict = {dbone.name: dbone for dbone in bones_decorated}
for dbone in bones_decorated:
bones_decorated_dict[dbone.name] = dbone
for dbone in bones_decorated: for dbone in bones_decorated:
parent = dbone.rest_bone.parent parent = dbone.rest_bone.parent
if parent: if parent:
...@@ -227,7 +237,7 @@ def write_armature(context, ...@@ -227,7 +237,7 @@ def write_armature(context,
del bones_decorated_dict del bones_decorated_dict
# finish assigning parents # finish assigning parents
scene = bpy.context.scene scene = context.scene
frame_current = scene.frame_current frame_current = scene.frame_current
file.write("MOTION\n") file.write("MOTION\n")
...@@ -244,7 +254,7 @@ def write_armature(context, ...@@ -244,7 +254,7 @@ def write_armature(context,
trans = Matrix.Translation(dbone.rest_bone.head_local) trans = Matrix.Translation(dbone.rest_bone.head_local)
itrans = Matrix.Translation(-dbone.rest_bone.head_local) itrans = Matrix.Translation(-dbone.rest_bone.head_local)
if dbone.parent: if dbone.parent:
mat_final = dbone.parent.rest_arm_mat * dbone.parent.pose_imat * dbone.pose_mat * dbone.rest_arm_imat mat_final = dbone.parent.rest_arm_mat * dbone.parent.pose_imat * dbone.pose_mat * dbone.rest_arm_imat
mat_final = itrans * mat_final * trans mat_final = itrans * mat_final * trans
loc = mat_final.to_translation() + (dbone.rest_bone.head_local - dbone.parent.rest_bone.head_local) loc = mat_final.to_translation() + (dbone.rest_bone.head_local - dbone.parent.rest_bone.head_local)
...@@ -272,20 +282,21 @@ def write_armature(context, ...@@ -272,20 +282,21 @@ def write_armature(context,
print("BVH Exported: %s frames:%d\n" % (filepath, frame_end - frame_start + 1)) print("BVH Exported: %s frames:%d\n" % (filepath, frame_end - frame_start + 1))
def save(operator, context, filepath="", def save(
frame_start=-1, context, filepath="",
frame_end=-1, frame_start=-1,
global_scale=1.0, frame_end=-1,
rotate_mode="NATIVE", global_scale=1.0,
root_transform_only=False, rotate_mode="NATIVE",
): root_transform_only=False,
):
write_armature(context, filepath, write_armature(
frame_start=frame_start, context, filepath,
frame_end=frame_end, frame_start=frame_start,
global_scale=global_scale, frame_end=frame_end,
rotate_mode=rotate_mode, global_scale=global_scale,
root_transform_only=root_transform_only, rotate_mode=rotate_mode,
) root_transform_only=root_transform_only,
)
return {'FINISHED'} return {'FINISHED'}
...@@ -28,22 +28,41 @@ from mathutils import Vector, Euler, Matrix ...@@ -28,22 +28,41 @@ from mathutils import Vector, Euler, Matrix
class BVH_Node: class BVH_Node:
__slots__ = ( __slots__ = (
'name', # bvh joint name # Bvh joint name.
'parent', # BVH_Node type or None for no parent 'name',
'children', # a list of children of this type. # BVH_Node type or None for no parent.
'rest_head_world', # worldspace rest location for the head of this node 'parent',
'rest_head_local', # localspace rest location for the head of this node # A list of children of this type..
'rest_tail_world', # worldspace rest location for the tail of this node 'children',
'rest_tail_local', # worldspace rest location for the tail of this node # Worldspace rest location for the head of this node.
'channels', # list of 6 ints, -1 for an unused channel, otherwise an index for the BVH motion data lines, loc triple then rot triple 'rest_head_world',
'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. # Localspace rest location for the head of this node.
'rot_order_str', # same as above but a string 'XYZ' format. 'rest_head_local',
'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. # Worldspace rest location for the tail of this node.
'has_loc', # Convenience function, bool, same as (channels[0]!=-1 or channels[1]!=-1 or channels[2]!=-1) 'rest_tail_world',
'has_rot', # Convenience function, bool, same as (channels[3]!=-1 or channels[4]!=-1 or channels[5]!=-1) # Worldspace rest location for the tail of this node.
'index', # index from the file, not strictly needed but nice to maintain order 'rest_tail_local',
'temp', # use this for whatever you want # List of 6 ints, -1 for an unused channel,
) # otherwise an index for the BVH motion data lines,
# loc triple then rot triple.
'channels',
# 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',
# Same as above but a string 'XYZ' format..
'rot_order_str',
# 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.
'anim_data',
# Convenience function, bool, same as: (channels[0] != -1 or channels[1] != -1 or channels[2] != -1).
'has_loc',
# Convenience function, bool, same as: (channels[3] != -1 or channels[4] != -1 or channels[5] != -1).
'has_rot',
# Index from the file, not strictly needed but nice to maintain order.
'index',
# Use this for whatever you want.
'temp',
)
_eul_order_lookup = { _eul_order_lookup = {
(None, None, None): 'XYZ', # XXX Dummy one, no rotation anyway! (None, None, None): 'XYZ', # XXX Dummy one, no rotation anyway!
...@@ -53,7 +72,7 @@ class BVH_Node: ...@@ -53,7 +72,7 @@ class BVH_Node:
(1, 2, 0): 'YZX', (1, 2, 0): 'YZX',
(2, 0, 1): 'ZXY', (2, 0, 1): 'ZXY',
(2, 1, 0): 'ZYX', (2, 1, 0): 'ZYX',
} }
def __init__(self, name, rest_head_world, rest_head_local, parent, channels, rot_order, index): def __init__(self, name, rest_head_world, rest_head_local, parent, channels, rot_order, index):
self.name = name self.name = name
...@@ -73,16 +92,18 @@ class BVH_Node: ...@@ -73,16 +92,18 @@ class BVH_Node:
self.children = [] self.children = []
# list of 6 length tuples: (lx,ly,lz, rx,ry,rz) # List of 6 length tuples: (lx, ly, lz, rx, ry, rz)
# even if the channels aren't used they will just be zero # even if the channels aren't used they will just be zero.
#
self.anim_data = [(0, 0, 0, 0, 0, 0)] self.anim_data = [(0, 0, 0, 0, 0, 0)]
def __repr__(self): def __repr__(self):
return ("BVH name: '%s', rest_loc:(%.3f,%.3f,%.3f), rest_tail:(%.3f,%.3f,%.3f)" % return (
(self.name, "BVH name: '%s', rest_loc:(%.3f,%.3f,%.3f), rest_tail:(%.3f,%.3f,%.3f)" % (
self.rest_head_world.x, self.rest_head_world.y, self.rest_head_world.z, self.name,
self.rest_head_world.x, self.rest_head_world.y, self.rest_head_world.z)) *self.rest_head_world,
*self.rest_head_world,
)
)
def sorted_nodes(bvh_nodes): def sorted_nodes(bvh_nodes):
...@@ -107,7 +128,7 @@ def read_bvh(context, file_path, rotate_mode='XYZ', global_scale=1.0): ...@@ -107,7 +128,7 @@ def read_bvh(context, file_path, rotate_mode='XYZ', global_scale=1.0):
# Create hierarchy as empties # Create hierarchy as empties
if file_lines[0][0].lower() == 'hierarchy': if file_lines[0][0].lower() == 'hierarchy':
#print 'Importing the BVH Hierarchy for:', file_path # print 'Importing the BVH Hierarchy for:', file_path
pass pass
else: else:
raise Exception("This is not a BVH file") raise Exception("This is not a BVH file")
...@@ -121,8 +142,7 @@ def read_bvh(context, file_path, rotate_mode='XYZ', global_scale=1.0): ...@@ -121,8 +142,7 @@ def read_bvh(context, file_path, rotate_mode='XYZ', global_scale=1.0):
lineIdx = 0 # An index for the file. lineIdx = 0 # An index for the file.
while lineIdx < len(file_lines) - 1: while lineIdx < len(file_lines) - 1:
#... if file_lines[lineIdx][0].lower() in {'root', 'joint'}:
if file_lines[lineIdx][0].lower() == 'root' or file_lines[lineIdx][0].lower() == 'joint':
# Join spaces into 1 word with underscores joining it. # Join spaces into 1 word with underscores joining it.
if len(file_lines[lineIdx]) > 2: if len(file_lines[lineIdx]) > 2:
...@@ -134,7 +154,7 @@ def read_bvh(context, file_path, rotate_mode='XYZ', global_scale=1.0): ...@@ -134,7 +154,7 @@ def read_bvh(context, file_path, rotate_mode='XYZ', global_scale=1.0):
# Make sure the names are unique - Object names will match joint names exactly and both will be unique. # Make sure the names are unique - Object names will match joint names exactly and both will be unique.
name = file_lines[lineIdx][1] name = file_lines[lineIdx][1]
#print '%snode: %s, parent: %s' % (len(bvh_nodes_serial) * ' ', name, bvh_nodes_serial[-1]) # print '%snode: %s, parent: %s' % (len(bvh_nodes_serial) * ' ', name, bvh_nodes_serial[-1])
lineIdx += 2 # Increment to the next line (Offset) 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 rest_head_local = Vector((float(file_lines[lineIdx][1]), float(file_lines[lineIdx][2]), float(file_lines[lineIdx][3]))) * global_scale
...@@ -185,16 +205,18 @@ def read_bvh(context, file_path, rotate_mode='XYZ', global_scale=1.0): ...@@ -185,16 +205,18 @@ def read_bvh(context, file_path, rotate_mode='XYZ', global_scale=1.0):
# If we have another child then we can call ourselves a parent, else # If we have another child then we can call ourselves a parent, else
bvh_nodes_serial.append(bvh_node) bvh_nodes_serial.append(bvh_node)
# Account for an end 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. # There is sometimes a name after 'End Site' but we will ignore it.
lineIdx += 2 # Increment to the next line (Offset) if file_lines[lineIdx][0].lower() == 'end' and file_lines[lineIdx][1].lower() == 'site':
# Increment to the next line (Offset)
lineIdx += 2
rest_tail = Vector((float(file_lines[lineIdx][1]), float(file_lines[lineIdx][2]), float(file_lines[lineIdx][3]))) * global_scale 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_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 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 # Just so we can remove the parents in a uniform way,
# so this is a placeholder # the end has kids so this is a placeholder.
bvh_nodes_serial.append(None) bvh_nodes_serial.append(None)
if len(file_lines[lineIdx]) == 1 and file_lines[lineIdx][0] == '}': # == ['}'] if len(file_lines[lineIdx]) == 1 and file_lines[lineIdx][0] == '}': # == ['}']
...@@ -208,15 +230,16 @@ def read_bvh(context, file_path, rotate_mode='XYZ', global_scale=1.0): ...@@ -208,15 +230,16 @@ def read_bvh(context, file_path, rotate_mode='XYZ', global_scale=1.0):
if len(file_lines[lineIdx]) == 1 and file_lines[lineIdx][0].lower() == 'motion': if len(file_lines[lineIdx]) == 1 and file_lines[lineIdx][0].lower() == 'motion':
lineIdx += 1 # Read frame count. lineIdx += 1 # Read frame count.
if (len(file_lines[lineIdx]) == 2 and if (len(file_lines[lineIdx]) == 2 and
file_lines[lineIdx][0].lower() == 'frames:'): file_lines[lineIdx][0].lower() == 'frames:'):
bvh_frame_count = int(file_lines[lineIdx][1]) bvh_frame_count = int(file_lines[lineIdx][1])
lineIdx += 1 # Read frame rate. lineIdx += 1 # Read frame rate.
if (len(file_lines[lineIdx]) == 3 and if (
len(file_lines[lineIdx]) == 3 and
file_lines[lineIdx][0].lower() == 'frame' and file_lines[lineIdx][0].lower() == 'frame' and
file_lines[lineIdx][1].lower() == 'time:'): file_lines[lineIdx][1].lower() == 'time:'
):
bvh_frame_time = float(file_lines[lineIdx][2]) bvh_frame_time = float(file_lines[lineIdx][2])
lineIdx += 1 # Set the cursor to the first frame lineIdx += 1 # Set the cursor to the first frame
...@@ -227,7 +250,7 @@ def read_bvh(context, file_path, rotate_mode='XYZ', global_scale=1.0): ...@@ -227,7 +250,7 @@ def read_bvh(context, file_path, rotate_mode='XYZ', global_scale=1.0):
# Remove the None value used for easy parent reference # Remove the None value used for easy parent reference
del bvh_nodes[None] del bvh_nodes[None]
# Dont use anymore # Don't use anymore
del bvh_nodes_serial del bvh_nodes_serial
# importing world with any order but nicer to maintain order # importing world with any order but nicer to maintain order
...@@ -237,7 +260,7 @@ def read_bvh(context, file_path, rotate_mode='XYZ', global_scale=1.0): ...@@ -237,7 +260,7 @@ def read_bvh(context, file_path, rotate_mode='XYZ', global_scale=1.0):
while lineIdx < len(file_lines): while lineIdx < len(file_lines):
line = file_lines[lineIdx] line = file_lines[lineIdx]
for bvh_node in bvh_nodes_list: for bvh_node in bvh_nodes_list:
#for bvh_node in bvh_nodes_serial: # for bvh_node in bvh_nodes_serial:
lx = ly = lz = rx = ry = rz = 0.0 lx = ly = lz = rx = ry = rz = 0.0
channels = bvh_node.channels channels = bvh_node.channels
anim_data = bvh_node.anim_data anim_data = bvh_node.anim_data
...@@ -279,7 +302,7 @@ def read_bvh(context, file_path, rotate_mode='XYZ', global_scale=1.0): ...@@ -279,7 +302,7 @@ def read_bvh(context, file_path, rotate_mode='XYZ', global_scale=1.0):
bvh_node.rest_tail_local = bvh_node.rest_head_local + bvh_node.children[0].rest_head_local bvh_node.rest_tail_local = bvh_node.rest_head_local + bvh_node.children[0].rest_head_local
else: else:
# allow this, see above # allow this, see above
#if not bvh_node.children: # if not bvh_node.children:
# raise Exception("bvh node has no end and no children. bad file") # raise Exception("bvh node has no end and no children. bad file")
# Removed temp for now # Removed temp for now
...@@ -364,16 +387,17 @@ def bvh_node_dict2objects(context, bvh_name, bvh_nodes, rotate_mode='NATIVE', fr ...@@ -364,16 +387,17 @@ def bvh_node_dict2objects(context, bvh_name, bvh_nodes, rotate_mode='NATIVE', fr
return objects return objects
def bvh_node_dict2armature(context, def bvh_node_dict2armature(
bvh_name, context,
bvh_nodes, bvh_name,
bvh_frame_time, bvh_nodes,
rotate_mode='XYZ', bvh_frame_time,
frame_start=1, rotate_mode='XYZ',
IMPORT_LOOP=False, frame_start=1,
global_matrix=None, IMPORT_LOOP=False,
use_fps_scale=False, global_matrix=None,
): use_fps_scale=False,
):
if frame_start < 1: if frame_start < 1:
frame_start = 1 frame_start = 1
...@@ -521,7 +545,7 @@ def bvh_node_dict2armature(context, ...@@ -521,7 +545,7 @@ def bvh_node_dict2armature(context,
for frame_i in range(1, num_frame): for frame_i in range(1, num_frame):
time[frame_i] += float(frame_i) time[frame_i] += float(frame_i)
#print("bvh_frame_time = %f, dt = %f, num_frame = %d" # print("bvh_frame_time = %f, dt = %f, num_frame = %d"
# % (bvh_frame_time, dt, num_frame])) # % (bvh_frame_time, dt, num_frame]))
for i, bvh_node in enumerate(bvh_nodes_list): for i, bvh_node in enumerate(bvh_nodes_list):
...@@ -537,7 +561,7 @@ def bvh_node_dict2armature(context, ...@@ -537,7 +561,7 @@ def bvh_node_dict2armature(context,
bvh_loc = bvh_node.anim_data[frame_i + skip_frame][:3] bvh_loc = bvh_node.anim_data[frame_i + skip_frame][:3]
bone_translate_matrix = Matrix.Translation( bone_translate_matrix = Matrix.Translation(
Vector(bvh_loc) - bvh_node.rest_head_local) Vector(bvh_loc) - bvh_node.rest_head_local)
location[frame_i] = (bone_rest_matrix_inv * location[frame_i] = (bone_rest_matrix_inv *
bone_translate_matrix).to_translation() bone_translate_matrix).to_translation()
...@@ -549,7 +573,7 @@ def bvh_node_dict2armature(context, ...@@ -549,7 +573,7 @@ def bvh_node_dict2armature(context,
for frame_i in range(num_frame): for frame_i in range(num_frame):
keyframe_points[frame_i].co = \ keyframe_points[frame_i].co = \
(time[frame_i], location[frame_i][axis_i]) (time[frame_i], location[frame_i][axis_i])
if bvh_node.has_rot: if bvh_node.has_rot:
data_path = None data_path = None
...@@ -580,7 +604,7 @@ def bvh_node_dict2armature(context, ...@@ -580,7 +604,7 @@ def bvh_node_dict2armature(context,
rotate[frame_i] = bone_rotation_matrix.to_quaternion() rotate[frame_i] = bone_rotation_matrix.to_quaternion()
else: else:
rotate[frame_i] = bone_rotation_matrix.to_euler( rotate[frame_i] = bone_rotation_matrix.to_euler(
pose_bone.rotation_mode, prev_euler) pose_bone.rotation_mode, prev_euler)
prev_euler = rotate[frame_i] prev_euler = rotate[frame_i]
# For each Euler angle x, y, z (or Quaternion w, x, y, z). # For each Euler angle x, y, z (or Quaternion w, x, y, z).
...@@ -591,7 +615,7 @@ def bvh_node_dict2armature(context, ...@@ -591,7 +615,7 @@ def bvh_node_dict2armature(context,
for frame_i in range(0, num_frame): for frame_i in range(0, num_frame):
keyframe_points[frame_i].co = \ keyframe_points[frame_i].co = \
(time[frame_i], rotate[frame_i][axis_i]) (time[frame_i], rotate[frame_i][axis_i])
for cu in action.fcurves: for cu in action.fcurves:
if IMPORT_LOOP: if IMPORT_LOOP:
...@@ -607,28 +631,30 @@ def bvh_node_dict2armature(context, ...@@ -607,28 +631,30 @@ def bvh_node_dict2armature(context,
return arm_ob return arm_ob
def load(context, def load(
filepath, context,
*, filepath,
target='ARMATURE', *,
rotate_mode='NATIVE', target='ARMATURE',
global_scale=1.0, rotate_mode='NATIVE',
use_cyclic=False, global_scale=1.0,
frame_start=1, use_cyclic=False,
global_matrix=None, frame_start=1,
use_fps_scale=False, global_matrix=None,
update_scene_fps=False, use_fps_scale=False,
update_scene_duration=False, update_scene_fps=False,
report=print update_scene_duration=False,
): report=print
):
import time import time
t1 = time.time() t1 = time.time()
print("\tparsing bvh %r..." % filepath, end="") print("\tparsing bvh %r..." % filepath, end="")
bvh_nodes, bvh_frame_time, bvh_frame_count = read_bvh(context, filepath, bvh_nodes, bvh_frame_time, bvh_frame_count = read_bvh(
rotate_mode=rotate_mode, context, filepath,
global_scale=global_scale) rotate_mode=rotate_mode,
global_scale=global_scale,
)
print("%.4f" % (time.time() - t1)) print("%.4f" % (time.time() - t1))
...@@ -637,9 +663,12 @@ def load(context, ...@@ -637,9 +663,12 @@ def load(context,
# Broken BVH handling: guess frame rate when it is not contained in the file. # Broken BVH handling: guess frame rate when it is not contained in the file.
if bvh_frame_time is None: if bvh_frame_time is None:
report({'WARNING'}, "The BVH file does not contain frame duration in its MOTION " report(
"section, assuming the BVH and Blender scene have the same " {'WARNING'},
"frame rate") "The BVH file does not contain frame duration in its MOTION "
"section, assuming the BVH and Blender scene have the same "
"frame rate"
)
bvh_frame_time = scene.render.fps_base / scene.render.fps bvh_frame_time = scene.render.fps_base / scene.render.fps
# No need to scale the frame rate, as they're equal now anyway. # No need to scale the frame rate, as they're equal now anyway.
use_fps_scale = False use_fps_scale = False
...@@ -652,8 +681,7 @@ def load(context, ...@@ -652,8 +681,7 @@ def load(context,
use_fps_scale = False use_fps_scale = False
if update_scene_duration: if update_scene_duration:
_update_scene_duration(context, report, bvh_frame_count, bvh_frame_time, frame_start, _update_scene_duration(context, report, bvh_frame_count, bvh_frame_time, frame_start, use_fps_scale)
use_fps_scale)
t1 = time.time() t1 = time.time()
print("\timporting to blender...", end="") print("\timporting to blender...", end="")
...@@ -661,21 +689,23 @@ def load(context, ...@@ -661,21 +689,23 @@ def load(context,
bvh_name = bpy.path.display_name_from_filepath(filepath) bvh_name = bpy.path.display_name_from_filepath(filepath)
if target == 'ARMATURE': if target == 'ARMATURE':
bvh_node_dict2armature(context, bvh_name, bvh_nodes, bvh_frame_time, bvh_node_dict2armature(
rotate_mode=rotate_mode, context, bvh_name, bvh_nodes, bvh_frame_time,
frame_start=frame_start, rotate_mode=rotate_mode,
IMPORT_LOOP=use_cyclic, frame_start=frame_start,
global_matrix=global_matrix, IMPORT_LOOP=use_cyclic,
use_fps_scale=use_fps_scale, global_matrix=global_matrix,
) use_fps_scale=use_fps_scale,
)
elif target == 'OBJECT': elif target == 'OBJECT':
bvh_node_dict2objects(context, bvh_name, bvh_nodes, bvh_node_dict2objects(
rotate_mode=rotate_mode, context, bvh_name, bvh_nodes,
frame_start=frame_start, rotate_mode=rotate_mode,
IMPORT_LOOP=use_cyclic, frame_start=frame_start,
# global_matrix=global_matrix, # TODO IMPORT_LOOP=use_cyclic,
) # global_matrix=global_matrix, # TODO
)
else: else:
report({'ERROR'}, "Invalid target %r (must be 'ARMATURE' or 'OBJECT')" % target) report({'ERROR'}, "Invalid target %r (must be 'ARMATURE' or 'OBJECT')" % target)
...@@ -693,8 +723,11 @@ def _update_scene_fps(context, report, bvh_frame_time): ...@@ -693,8 +723,11 @@ def _update_scene_fps(context, report, bvh_frame_time):
# Broken BVH handling: prevent division by zero. # Broken BVH handling: prevent division by zero.
if bvh_frame_time == 0.0: if bvh_frame_time == 0.0:
report({'WARNING'}, "Unable to update scene frame rate, as the BVH file " report(
"contains a zero frame duration in its MOTION section") {'WARNING'},
"Unable to update scene frame rate, as the BVH file "
"contains a zero frame duration in its MOTION section",
)
return return
scene = context.scene scene = context.scene
...@@ -707,13 +740,17 @@ def _update_scene_fps(context, report, bvh_frame_time): ...@@ -707,13 +740,17 @@ def _update_scene_fps(context, report, bvh_frame_time):
scene.render.fps_base = 1.0 scene.render.fps_base = 1.0
def _update_scene_duration(context, report, bvh_frame_count, bvh_frame_time, frame_start, def _update_scene_duration(
use_fps_scale): context, report, bvh_frame_count, bvh_frame_time, frame_start,
use_fps_scale):
"""Extend the scene's duration so that the BVH file fits in its entirety.""" """Extend the scene's duration so that the BVH file fits in its entirety."""
if bvh_frame_count is None: if bvh_frame_count is None:
report({'WARNING'}, "Unable to extend the scene duration, as the BVH file does not " report(
"contain the number of frames in its MOTION section") {'WARNING'},
"Unable to extend the scene duration, as the BVH file does not "
"contain the number of frames in its MOTION section",
)
return return
# Not likely, but it can happen when a BVH is just used to store an armature. # Not likely, but it can happen when a BVH is just used to store an armature.
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment