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 = {
"location": "File > Import-Export",
"description": "Import-Export BVH from armature objects",
"warning": "",
"wiki_url": "http://wiki.blender.org/index.php/Extensions:2.6/Py/"
"Scripts/Import-Export/BVH_Importer_Exporter",
"wiki_url": (
"http://wiki.blender.org/index.php/Extensions:2.6/Py/"
"Scripts/Import-Export/BVH_Importer_Exporter"
),
"support": 'OFFICIAL',
"category": "Import-Export"}
"category": "Import-Export",
}
if "bpy" in locals():
import importlib
......@@ -66,13 +69,15 @@ class ImportBVH(bpy.types.Operator, ImportHelper, ImportBVHOrientationHelper):
filename_ext = ".bvh"
filter_glob = StringProperty(default="*.bvh", options={'HIDDEN'})
target = EnumProperty(items=(
target = EnumProperty(
items=(
('ARMATURE', "Armature", ""),
('OBJECT', "Object", ""),
),
name="Target",
description="Import target type",
default='ARMATURE')
default='ARMATURE',
)
global_scale = FloatProperty(
name="Scale",
......@@ -88,14 +93,18 @@ class ImportBVH(bpy.types.Operator, ImportHelper, ImportBVHOrientationHelper):
)
use_fps_scale = BoolProperty(
name="Scale FPS",
description=("Scale the framerate from the BVH to the current scenes, "
"otherwise each BVH frame maps directly to a Blender frame"),
description=(
"Scale the framerate from the BVH to the current scenes, "
"otherwise each BVH frame maps directly to a Blender frame"
),
default=False,
)
update_scene_fps = BoolProperty(
name="Update Scene FPS",
description="Set the scene framerate to that of the BVH file (note that this "
"nullifies the 'Scale FPS' option, as the scale will be 1:1)",
description=(
"Set the scene framerate to that of the BVH file (note that this "
"nullifies the 'Scale FPS' option, as the scale will be 1:1)"
),
default=False
)
update_scene_duration = BoolProperty(
......@@ -111,7 +120,8 @@ class ImportBVH(bpy.types.Operator, ImportHelper, ImportBVHOrientationHelper):
rotate_mode = EnumProperty(
name="Rotation",
description="Rotation conversion",
items=(('QUATERNION', "Quaternion",
items=(
('QUATERNION', "Quaternion",
"Convert rotations to quaternions"),
('NATIVE', "Euler (Native)",
"Use the rotation order defined in the BVH file"),
......@@ -126,12 +136,16 @@ class ImportBVH(bpy.types.Operator, ImportHelper, ImportBVHOrientationHelper):
)
def execute(self, context):
keywords = self.as_keywords(ignore=("axis_forward",
keywords = self.as_keywords(
ignore=(
"axis_forward",
"axis_up",
"filter_glob",
))
)
)
global_matrix = axis_conversion(from_forward=self.axis_forward,
global_matrix = axis_conversion(
from_forward=self.axis_forward,
from_up=self.axis_up,
).to_4x4()
......@@ -172,7 +186,8 @@ class ExportBVH(bpy.types.Operator, ExportHelper):
rotate_mode = EnumProperty(
name="Rotation",
description="Rotation conversion",
items=(('NATIVE', "Euler (Native)",
items=(
('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"),
......@@ -208,7 +223,7 @@ class ExportBVH(bpy.types.Operator, ExportHelper):
keywords = self.as_keywords(ignore=("check_existing", "filter_glob"))
from . import export_bvh
return export_bvh.save(self, context, **keywords)
return export_bvh.save(context, **keywords)
def menu_func_import(self, context):
......@@ -232,5 +247,6 @@ def unregister():
bpy.types.INFO_MT_file_import.remove(menu_func_import)
bpy.types.INFO_MT_file_export.remove(menu_func_export)
if __name__ == "__main__":
register()
......@@ -24,7 +24,8 @@
import bpy
def write_armature(context,
def write_armature(
context,
filepath,
frame_start,
frame_end,
......@@ -91,11 +92,11 @@ def write_armature(context,
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))
file.write("%s\tOFFSET %.6f %.6f %.6f\n" % (indent_str, *(loc * global_scale)))
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:
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:
# store the location for the children
......@@ -111,7 +112,7 @@ def write_armature(context,
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\tOFFSET %.6f %.6f %.6f\n" % (indent_str, *(loc * global_scale)))
file.write("%s\t}\n" % indent_str)
file.write("%s}\n" % indent_str)
......@@ -149,21 +150,33 @@ def write_armature(context,
class DecoratedBone:
__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
"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
"skip_position", # is the bone disconnected to the parent bone?
# Blender armature bone.
"rest_bone",
# Blender pose bone.
"pose_bone",
# Blender pose matrix.
"pose_mat",
# Blender rest matrix (armature space).
"rest_arm_mat",
# Blender rest batrix (local space).
"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_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 = {
......@@ -216,10 +229,7 @@ def write_armature(context,
bones_decorated = [DecoratedBone(bone_name) for bone_name in serialized_names]
# Assign parents
bones_decorated_dict = {}
for dbone in bones_decorated:
bones_decorated_dict[dbone.name] = dbone
bones_decorated_dict = {dbone.name: dbone for dbone in bones_decorated}
for dbone in bones_decorated:
parent = dbone.rest_bone.parent
if parent:
......@@ -227,7 +237,7 @@ def write_armature(context,
del bones_decorated_dict
# finish assigning parents
scene = bpy.context.scene
scene = context.scene
frame_current = scene.frame_current
file.write("MOTION\n")
......@@ -272,15 +282,16 @@ def write_armature(context,
print("BVH Exported: %s frames:%d\n" % (filepath, frame_end - frame_start + 1))
def save(operator, context, filepath="",
def save(
context, filepath="",
frame_start=-1,
frame_end=-1,
global_scale=1.0,
rotate_mode="NATIVE",
root_transform_only=False,
):
write_armature(context, filepath,
write_armature(
context, filepath,
frame_start=frame_start,
frame_end=frame_end,
global_scale=global_scale,
......
......@@ -28,21 +28,40 @@ from mathutils import Vector, Euler, Matrix
class BVH_Node:
__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, loc 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', # Convenience function, bool, same as (channels[0]!=-1 or channels[1]!=-1 or channels[2]!=-1)
'has_rot', # Convenience function, bool, same as (channels[3]!=-1 or channels[4]!=-1 or channels[5]!=-1)
'index', # index from the file, not strictly needed but nice to maintain order
'temp', # use this for whatever you want
# Bvh joint name.
'name',
# BVH_Node type or None for no parent.
'parent',
# A list of children of this type..
'children',
# Worldspace rest location for the head of this node.
'rest_head_world',
# Localspace rest location for the head of this node.
'rest_head_local',
# Worldspace rest location for the tail of this node.
'rest_tail_world',
# Worldspace rest location for the tail of this node.
'rest_tail_local',
# 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 = {
......@@ -73,16 +92,18 @@ class BVH_Node:
self.children = []
# list of 6 length tuples: (lx,ly,lz, rx,ry,rz)
# even if the channels aren't used they will just be zero
#
# List of 6 length tuples: (lx, ly, lz, rx, ry, rz)
# even if the channels aren't 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))
return (
"BVH name: '%s', rest_loc:(%.3f,%.3f,%.3f), rest_tail:(%.3f,%.3f,%.3f)" % (
self.name,
*self.rest_head_world,
*self.rest_head_world,
)
)
def sorted_nodes(bvh_nodes):
......@@ -121,8 +142,7 @@ def read_bvh(context, file_path, rotate_mode='XYZ', global_scale=1.0):
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':
if file_lines[lineIdx][0].lower() in {'root', 'joint'}:
# Join spaces into 1 word with underscores joining it.
if len(file_lines[lineIdx]) > 2:
......@@ -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
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)
# Account for an end node.
# There is sometimes a name after 'End Site' but we will ignore it.
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
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
# Just so we can remove the parents in a uniform way,
# the end has kids so this is a placeholder.
bvh_nodes_serial.append(None)
if len(file_lines[lineIdx]) == 1 and file_lines[lineIdx][0] == '}': # == ['}']
......@@ -213,10 +235,11 @@ def read_bvh(context, file_path, rotate_mode='XYZ', global_scale=1.0):
bvh_frame_count = int(file_lines[lineIdx][1])
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][1].lower() == 'time:'):
file_lines[lineIdx][1].lower() == 'time:'
):
bvh_frame_time = float(file_lines[lineIdx][2])
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):
# Remove the None value used for easy parent reference
del bvh_nodes[None]
# Dont use anymore
# Don't use anymore
del bvh_nodes_serial
# importing world with any order but nicer to maintain order
......@@ -364,7 +387,8 @@ def bvh_node_dict2objects(context, bvh_name, bvh_nodes, rotate_mode='NATIVE', fr
return objects
def bvh_node_dict2armature(context,
def bvh_node_dict2armature(
context,
bvh_name,
bvh_nodes,
bvh_frame_time,
......@@ -607,7 +631,8 @@ def bvh_node_dict2armature(context,
return arm_ob
def load(context,
def load(
context,
filepath,
*,
target='ARMATURE',
......@@ -621,14 +646,15 @@ def load(context,
update_scene_duration=False,
report=print
):
import time
t1 = time.time()
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(
context, filepath,
rotate_mode=rotate_mode,
global_scale=global_scale)
global_scale=global_scale,
)
print("%.4f" % (time.time() - t1))
......@@ -637,9 +663,12 @@ def load(context,
# Broken BVH handling: guess frame rate when it is not contained in the file.
if bvh_frame_time is None:
report({'WARNING'}, "The BVH file does not contain frame duration in its MOTION "
report(
{'WARNING'},
"The BVH file does not contain frame duration in its MOTION "
"section, assuming the BVH and Blender scene have the same "
"frame rate")
"frame rate"
)
bvh_frame_time = scene.render.fps_base / scene.render.fps
# No need to scale the frame rate, as they're equal now anyway.
use_fps_scale = False
......@@ -652,8 +681,7 @@ def load(context,
use_fps_scale = False
if update_scene_duration:
_update_scene_duration(context, report, bvh_frame_count, bvh_frame_time, frame_start,
use_fps_scale)
_update_scene_duration(context, report, bvh_frame_count, bvh_frame_time, frame_start, use_fps_scale)
t1 = time.time()
print("\timporting to blender...", end="")
......@@ -661,7 +689,8 @@ def load(context,
bvh_name = bpy.path.display_name_from_filepath(filepath)
if target == 'ARMATURE':
bvh_node_dict2armature(context, bvh_name, bvh_nodes, bvh_frame_time,
bvh_node_dict2armature(
context, bvh_name, bvh_nodes, bvh_frame_time,
rotate_mode=rotate_mode,
frame_start=frame_start,
IMPORT_LOOP=use_cyclic,
......@@ -670,7 +699,8 @@ def load(context,
)
elif target == 'OBJECT':
bvh_node_dict2objects(context, bvh_name, bvh_nodes,
bvh_node_dict2objects(
context, bvh_name, bvh_nodes,
rotate_mode=rotate_mode,
frame_start=frame_start,
IMPORT_LOOP=use_cyclic,
......@@ -693,8 +723,11 @@ def _update_scene_fps(context, report, bvh_frame_time):
# Broken BVH handling: prevent division by zero.
if bvh_frame_time == 0.0:
report({'WARNING'}, "Unable to update scene frame rate, as the BVH file "
"contains a zero frame duration in its MOTION section")
report(
{'WARNING'},
"Unable to update scene frame rate, as the BVH file "
"contains a zero frame duration in its MOTION section",
)
return
scene = context.scene
......@@ -707,13 +740,17 @@ def _update_scene_fps(context, report, bvh_frame_time):
scene.render.fps_base = 1.0
def _update_scene_duration(context, report, bvh_frame_count, bvh_frame_time, frame_start,
def _update_scene_duration(
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."""
if bvh_frame_count is None:
report({'WARNING'}, "Unable to extend the scene duration, as the BVH file does not "
"contain the number of frames in its MOTION section")
report(
{'WARNING'},
"Unable to extend the scene duration, as the BVH file does not "
"contain the number of frames in its MOTION section",
)
return
# 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