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

bvh export

- add option to export root transform only (may help with secondlife compatibility)
- operators are now pep8 compliant.
- frame is set back to the original when export is done.
parent d8157903
No related branches found
No related tags found
No related merge requests found
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
# #
# ##### END GPL LICENSE BLOCK ##### # ##### END GPL LICENSE BLOCK #####
# <pep8 compliant> # <pep8-80 compliant>
bl_info = { bl_info = {
"name": "BioVision Motion Capture (BVH) format", "name": "BioVision Motion Capture (BVH) format",
...@@ -26,13 +26,12 @@ bl_info = { ...@@ -26,13 +26,12 @@ 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.5/Py/"\ "wiki_url": ("http://wiki.blender.org/index.php/Extensions:2.5/Py/"
"Scripts/Import-Export/MotionCapture_BVH", "Scripts/Import-Export/MotionCapture_BVH"),
"tracker_url": "", "tracker_url": "",
"support": 'OFFICIAL', "support": 'OFFICIAL',
"category": "Import-Export"} "category": "Import-Export"}
# To support reload properly, try to access a package var, if it's there, reload everything
if "bpy" in locals(): if "bpy" in locals():
import imp import imp
if "import_bvh" in locals(): if "import_bvh" in locals():
...@@ -41,7 +40,12 @@ if "bpy" in locals(): ...@@ -41,7 +40,12 @@ if "bpy" in locals():
imp.reload(export_bvh) imp.reload(export_bvh)
import bpy import bpy
from bpy.props import StringProperty, FloatProperty, IntProperty, BoolProperty, EnumProperty from bpy.props import (StringProperty,
FloatProperty,
IntProperty,
BoolProperty,
EnumProperty,
)
from bpy_extras.io_utils import ImportHelper, ExportHelper from bpy_extras.io_utils import ImportHelper, ExportHelper
...@@ -62,26 +66,45 @@ class ImportBVH(bpy.types.Operator, ImportHelper): ...@@ -62,26 +66,45 @@ class ImportBVH(bpy.types.Operator, ImportHelper):
description="Import target type.", description="Import target type.",
default='ARMATURE') 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) global_scale = FloatProperty(
frame_start = IntProperty(name="Start Frame", description="Starting frame for the animation", default=1) name="Scale",
use_cyclic = BoolProperty(name="Loop", description="Loop the animation playback", default=False) description="Scale the BVH by this value",
rotate_mode = EnumProperty(items=( min=0.0001, max=1000000.0,
('QUATERNION', "Quaternion", "Convert rotations to quaternions"), soft_min=0.001, soft_max=100.0,
('NATIVE', "Euler (Native)", "Use the rotation order defined in the BVH file"), default=1.0,
('XYZ', "Euler (XYZ)", "Convert rotations to euler XYZ"), )
('XZY', "Euler (XZY)", "Convert rotations to euler XZY"), frame_start = IntProperty(
('YXZ', "Euler (YXZ)", "Convert rotations to euler YXZ"), name="Start Frame",
('YZX', "Euler (YZX)", "Convert rotations to euler YZX"), description="Starting frame for the animation",
('ZXY', "Euler (ZXY)", "Convert rotations to euler ZXY"), default=1,
('ZYX', "Euler (ZYX)", "Convert rotations to euler ZYX"), )
), use_cyclic = BoolProperty(
name="Rotation", name="Loop",
description="Rotation conversion.", description="Loop the animation playback",
default='NATIVE') default=False,
)
rotate_mode = EnumProperty(
name="Rotation",
description="Rotation conversion.",
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"),
),
default='NATIVE',
)
def execute(self, context): def execute(self, context):
keywords = self.as_keywords(ignore=("filter_glob",))
from . import import_bvh from . import import_bvh
return import_bvh.load(self, context, **self.as_keywords(ignore=("filter_glob",))) return import_bvh.load(self, context, **keywords)
class ExportBVH(bpy.types.Operator, ExportHelper): class ExportBVH(bpy.types.Operator, ExportHelper):
...@@ -90,24 +113,47 @@ class ExportBVH(bpy.types.Operator, ExportHelper): ...@@ -90,24 +113,47 @@ class ExportBVH(bpy.types.Operator, ExportHelper):
bl_label = "Export BVH" bl_label = "Export BVH"
filename_ext = ".bvh" filename_ext = ".bvh"
filter_glob = StringProperty(default="*.bvh", options={'HIDDEN'}) filter_glob = StringProperty(
default="*.bvh",
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) options={'HIDDEN'},
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)
global_scale = FloatProperty(
rotate_mode = EnumProperty(items=( name="Scale",
('NATIVE', "Euler (Native)", "Use the rotation order defined in the BVH file"), description="Scale the BVH by this value",
('XYZ', "Euler (XYZ)", "Convert rotations to euler XYZ"), min=0.0001, max=1000000.0,
('XZY', "Euler (XZY)", "Convert rotations to euler XZY"), soft_min=0.001, soft_max=100.0,
('YXZ', "Euler (YXZ)", "Convert rotations to euler YXZ"), default=1.0,
('YZX', "Euler (YZX)", "Convert rotations to euler YZX"), )
('ZXY', "Euler (ZXY)", "Convert rotations to euler ZXY"), frame_start = IntProperty(
('ZYX', "Euler (ZYX)", "Convert rotations to euler ZYX"), name="Start Frame",
), description="Starting frame to export",
name="Rotation", default=0,
description="Rotation conversion.", )
default='NATIVE') frame_end = IntProperty(
name="End Frame",
description="End frame to export",
default=0,
)
rotate_mode = EnumProperty(
name="Rotation",
description="Rotation conversion.",
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"),
('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"),
),
default='NATIVE',
)
root_transform_only = BoolProperty(
name="Root Transform Only",
description="Only write out transform channels for the root bone",
default=False,
)
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
...@@ -125,8 +171,10 @@ class ExportBVH(bpy.types.Operator, ExportHelper): ...@@ -125,8 +171,10 @@ class ExportBVH(bpy.types.Operator, ExportHelper):
self.frame_start = context.scene.frame_start self.frame_start = context.scene.frame_start
self.frame_end = context.scene.frame_end self.frame_end = context.scene.frame_end
keywords = self.as_keywords(ignore=("check_existing", "filter_glob"))
from . import export_bvh from . import export_bvh
return export_bvh.save(self, context, **self.as_keywords(ignore=("check_existing", "filter_glob"))) return export_bvh.save(self, context, **keywords)
def menu_func_import(self, context): def menu_func_import(self, context):
......
...@@ -30,6 +30,7 @@ def write_armature(context, ...@@ -30,6 +30,7 @@ def write_armature(context,
frame_end, frame_end,
global_scale=1.0, global_scale=1.0,
rotate_mode='NATIVE', rotate_mode='NATIVE',
root_transform_only=False,
): ):
def ensure_rot_order(rot_order_str): def ensure_rot_order(rot_order_str):
...@@ -93,7 +94,7 @@ def write_armature(context, ...@@ -93,7 +94,7 @@ def write_armature(context,
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.x * global_scale, loc.y * global_scale, loc.z * global_scale))
if bone.use_connect 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[0], rot_order_str[1], rot_order_str[2]))
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[0], rot_order_str[1], rot_order_str[2]))
...@@ -153,7 +154,7 @@ def write_armature(context, ...@@ -153,7 +154,7 @@ def write_armature(context,
"rest_arm_imat", # rest_arm_mat inverted "rest_arm_imat", # rest_arm_mat inverted
"rest_local_imat", # rest_local_mat inverted "rest_local_imat", # rest_local_mat inverted
"prev_euler", # last used euler to preserve euler compability in between keyframes "prev_euler", # last used euler to preserve euler compability in between keyframes
"connected", # is the bone connected to the parent bone? "skip_position", # is the bone disconnected to the parent bone?
"rot_order", "rot_order",
"rot_order_str", "rot_order_str",
) )
...@@ -164,7 +165,8 @@ def write_armature(context, ...@@ -164,7 +165,8 @@ def write_armature(context,
'YXZ': (1, 0, 2), 'YXZ': (1, 0, 2),
'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
...@@ -191,7 +193,7 @@ def write_armature(context, ...@@ -191,7 +193,7 @@ def write_armature(context,
self.parent = None 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)
self.connected = (self.rest_bone.use_connect and self.rest_bone.parent) self.skip_position = ((self.rest_bone.use_connect or root_transform_only) and self.rest_bone.parent)
def update_posedata(self): def update_posedata(self):
self.pose_mat = self.pose_bone.matrix self.pose_mat = self.pose_bone.matrix
...@@ -218,6 +220,7 @@ def write_armature(context, ...@@ -218,6 +220,7 @@ def write_armature(context,
# finish assigning parents # finish assigning parents
scene = bpy.context.scene scene = bpy.context.scene
frame_current = scene.frame_current
file.write("MOTION\n") file.write("MOTION\n")
file.write("Frames: %d\n" % (frame_end - frame_start + 1)) file.write("Frames: %d\n" % (frame_end - frame_start + 1))
...@@ -245,7 +248,7 @@ def write_armature(context, ...@@ -245,7 +248,7 @@ def write_armature(context,
# keep eulers compatible, no jumping on interpolation. # 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_3x3().inverted().to_euler(dbone.rot_order_str, dbone.prev_euler)
if not dbone.connected: if not dbone.skip_position:
file.write("%.6f %.6f %.6f " % (loc * global_scale)[:]) 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]])))
...@@ -256,6 +259,8 @@ def write_armature(context, ...@@ -256,6 +259,8 @@ def write_armature(context,
file.close() file.close()
scene.frame_set(frame_current)
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))
...@@ -264,6 +269,7 @@ def save(operator, context, filepath="", ...@@ -264,6 +269,7 @@ def save(operator, context, filepath="",
frame_end=-1, frame_end=-1,
global_scale=1.0, global_scale=1.0,
rotate_mode="NATIVE", rotate_mode="NATIVE",
root_transform_only=False,
): ):
write_armature(context, filepath, write_armature(context, filepath,
...@@ -271,6 +277,7 @@ def save(operator, context, filepath="", ...@@ -271,6 +277,7 @@ def save(operator, context, filepath="",
frame_end=frame_end, frame_end=frame_end,
global_scale=global_scale, global_scale=global_scale,
rotate_mode=rotate_mode, rotate_mode=rotate_mode,
root_transform_only=root_transform_only,
) )
return {'FINISHED'} return {'FINISHED'}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment