added bvh export operator & menu item, now user accessible.

This commit is contained in:
Campbell Barton 2011-01-01 09:44:13 +00:00
parent 631745ab9b
commit f932371d1e
3 changed files with 89 additions and 103 deletions

@ -27,18 +27,18 @@ if "bpy" in locals():
import bpy
from bpy.props import *
from io_utils import ImportHelper
from io_utils import ImportHelper, ExportHelper
class BvhImporter(bpy.types.Operator, ImportHelper):
'''Load a OBJ Motion Capture File'''
'''Load a BVH motion capture file'''
bl_idname = "import_anim.bvh"
bl_label = "Import BVH"
filename_ext = ".bvh"
filter_glob = StringProperty(default="*.bvh", options={'HIDDEN'})
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=0.1)
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=(
@ -53,23 +53,52 @@ class BvhImporter(bpy.types.Operator, ImportHelper):
),
name="Rotation",
description="Rotation conversion.",
default='NATIVE')
default='QUATERNION') # XXX, eulers are broken!
def execute(self, context):
from . import import_bvh
return import_bvh.load(self, context, **self.as_keywords(ignore=("filter_glob",)))
def menu_func(self, context):
class BvhExporter(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")
frame_end = IntProperty(name="End Frame", description="End frame to export")
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):
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(BvhImporter.bl_idname, text="Motion Capture (.bvh)")
def menu_func_export(self, context):
self.layout.operator(BvhExporter.bl_idname, text="Motion Capture (.bvh)")
def register():
bpy.types.INFO_MT_file_import.append(menu_func)
bpy.types.INFO_MT_file_import.append(menu_func_import)
bpy.types.INFO_MT_file_export.append(menu_func_export)
def unregister():
bpy.types.INFO_MT_file_import.remove(menu_func)
bpy.types.INFO_MT_file_import.remove(menu_func_import)
bpy.types.INFO_MT_file_export.remove(menu_func_export)
if __name__ == "__main__":
register()

@ -22,44 +22,32 @@
# fixes from Andrea Rugliancich
import bpy
from mathutils import Matrix, Vector
from math import degrees
def bvh_export(filepath, obj, pref_startframe, pref_endframe, pref_scale=1.0):
def _read(context, filepath, frame_start, frame_end, global_scale=1.0):
# Window.EditMode(0)
from mathutils import Matrix, Vector
from math import degrees
file = open(filepath, "w")
# bvh_nodes = {}
arm_data = obj.data
bones = arm_data.bones.values()
obj = context.object
arm = obj.data
# Build a dictionary of bone children.
# None is for parentless bones
bone_children = {None: []}
# initialize with blank lists
for bone in bones:
for bone in arm.bones:
bone_children[bone.name] = []
for bone in bones:
parent = bone.parent
bone_name = bone.name
if parent:
bone_children[parent.name].append(bone_name)
else: # root node
bone_children[None].append(bone_name)
for bone in arm.bones:
bone_children[getattr(bone.parent, "name", None)].append(bone.name)
# sort the children
for children_list in bone_children.values():
children_list.sort()
# build a (name:bone) mapping dict
bone_dict = {}
for bone in bones:
bone_dict[bone.name] = bone
# bone name list in the order that the bones are written
bones_serialized_names = []
@ -72,7 +60,7 @@ def bvh_export(filepath, obj, pref_startframe, pref_endframe, pref_scale=1.0):
indent_str = "\t" * indent
bone = bone_dict[bone_name]
bone = arm.bones[bone_name]
loc = bone.head_local
bone_locs[bone_name] = loc
@ -86,7 +74,7 @@ def bvh_export(filepath, obj, pref_startframe, pref_endframe, pref_scale=1.0):
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 * pref_scale, loc.y * pref_scale, loc.z * pref_scale))
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\tCHANNELS 6 Xposition Yposition Zposition Xrotation Yrotation Zrotation\n" % indent_str)
if my_bone_children:
@ -103,7 +91,7 @@ def bvh_export(filepath, obj, pref_startframe, pref_endframe, pref_scale=1.0):
file.write("%s\tEnd Site\n" % indent_str)
file.write("%s\t{\n" % indent_str)
loc = bone.tail_local - bone_locs[bone_name]
file.write("%s\t\tOFFSET %.6f %.6f %.6f\n" % (indent_str, loc.x * pref_scale, loc.y * pref_scale, loc.z * pref_scale))
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)
@ -129,9 +117,7 @@ def bvh_export(filepath, obj, pref_startframe, pref_endframe, pref_scale=1.0):
file.write("}\n")
# redefine bones as sorted by bones_serialized_names
# se we can write motion
pose_dict = obj.pose.bones
#pose_bones = [(pose_dict[bone_name], bone_dict[bone_name].matrix_local.copy().invert()) for bone_name in bones_serialized_names]
# so we can write motion
class decorated_bone(object):
__slots__ = (\
@ -148,8 +134,8 @@ def bvh_export(filepath, obj, pref_startframe, pref_endframe, pref_scale=1.0):
def __init__(self, bone_name):
self.name = bone_name
self.rest_bone = bone_dict[bone_name]
self.pose_bone = pose_dict[bone_name]
self.rest_bone = arm.bones[bone_name]
self.pose_bone = obj.pose.bones[bone_name]
self.pose_mat = self.pose_bone.matrix
@ -189,13 +175,13 @@ def bvh_export(filepath, obj, pref_startframe, pref_endframe, pref_scale=1.0):
# finish assigning parents
file.write("MOTION\n")
file.write("Frames: %d\n" % (pref_endframe - pref_startframe + 1))
file.write("Frames: %d\n" % (frame_end - frame_start + 1))
file.write("Frame Time: %.6f\n" % 0.03)
scene = bpy.context.scene
triple = "%.6f %.6f %.6f "
for frame in range(pref_startframe, pref_endframe + 1):
for frame in range(frame_start, frame_end + 1):
scene.frame_set(frame)
obj.update(scene, 1,1,1)
scene.update()
@ -205,9 +191,6 @@ def bvh_export(filepath, obj, pref_startframe, pref_endframe, pref_scale=1.0):
if dbone.parent:
trans = Matrix.Translation(dbone.rest_bone.head_local)
itrans = Matrix.Translation(-dbone.rest_bone.head_local)
# mat2 = dbone.rest_arm_imat * dbone.pose_mat * dbone.parent.pose_imat * dbone.parent.rest_arm_mat
# mat2 = trans * mat2 * itrans
mat2 = dbone.parent.rest_arm_mat * dbone.parent.pose_imat * dbone.pose_mat * dbone.rest_arm_imat
mat2 = itrans * mat2 * trans
@ -217,63 +200,37 @@ def bvh_export(filepath, obj, pref_startframe, pref_endframe, pref_scale=1.0):
trans = Matrix.Translation(dbone.rest_bone.head_local)
itrans = Matrix.Translation(-dbone.rest_bone.head_local)
# mat2 = dbone.rest_arm_imat * dbone.pose_mat
# mat2 = trans * mat2 * itrans
mat2 = dbone.pose_mat * dbone.rest_arm_imat
mat2 = itrans * mat2 * trans
myloc = mat2.translation_part() + dbone.rest_bone.head_local
rot = mat2.copy().transpose().to_euler()
file.write(triple % (myloc[0] * pref_scale, myloc[1] * pref_scale, myloc[2] * pref_scale))
file.write(triple % (myloc[0] * global_scale, myloc[1] * global_scale, myloc[2] * global_scale))
file.write(triple % (-degrees(rot[0]), -degrees(rot[1]), -degrees(rot[2])))
file.write("\n")
numframes = pref_endframe - pref_startframe + 1
file.close()
print("BVH Exported: %s frames:%d\n" % (filepath, numframes))
print("BVH Exported: %s frames:%d\n" % (filepath, frame_end - frame_start + 1))
bvh_export("/foo.bvh", bpy.context.object, 1, 190, 1.0)
'''
def bvh_export_ui(filepath):
# Dont overwrite
if not BPyMessages.Warning_SaveOver(filepath):
return
def save(operator, context, filepath="",
frame_start=-1,
frame_end=-1,
global_scale=1.0,
):
scn = Scene.GetCurrent()
ob_act = scn.objects.active
if not ob_act or ob_act.type != 'Armature':
BPyMessages.Error_NoArmatureActive()
_read(context, filepath,
frame_start=frame_start,
frame_end=frame_end,
global_scale=global_scale,
)
arm_ob = scn.objects.active
return {'FINISHED'}
if not arm_ob or arm_ob.type != 'Armature':
Blender.Draw.PupMenu('No Armature object selected.')
return
ctx = scn.getRenderingContext()
orig_frame = Blender.Get('curframe')
pref_startframe = Blender.Draw.Create(int(ctx.startFrame()))
pref_endframe = Blender.Draw.Create(int(ctx.endFrame()))
block = [\
("Start Frame: ", pref_startframe, 1, 30000, "Start Bake from what frame?: Default 1"),\
("End Frame: ", pref_endframe, 1, 30000, "End Bake on what Frame?"),\
]
if not Blender.Draw.PupBlock("Export MDD", block):
return
pref_startframe, pref_endframe = \
min(pref_startframe.val, pref_endframe.val),\
max(pref_startframe.val, pref_endframe.val)
bvh_export(filepath, ob_act, pref_startframe, pref_endframe)
Blender.Set('curframe', orig_frame)
if __name__ == '__main__':
Blender.Window.FileSelector(bvh_export_ui, 'EXPORT BVH', sys.makename(ext='.bvh'))
'''
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)

@ -90,7 +90,7 @@ def eulerRotate(x, y, z, rot_order):
'''
def read_bvh(context, file_path, ROT_MODE='XYZ', GLOBAL_SCALE=1.0):
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')
@ -134,7 +134,7 @@ def read_bvh(context, file_path, ROT_MODE='XYZ', GLOBAL_SCALE=1.0):
#print '%snode: %s, parent: %s' % (len(bvh_nodes_serial) * ' ', name, bvh_nodes_serial[-1])
lineIdx += 2 # Incriment 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
lineIdx += 1 # Incriment to the next line (Channels)
# newChannel[Xposition, Yposition, Zposition, Xrotation, Yrotation, Zrotation]
@ -185,7 +185,7 @@ def read_bvh(context, file_path, ROT_MODE='XYZ', GLOBAL_SCALE=1.0):
# Account for an end node
if file_lines[lineIdx][0].lower() == 'end' and file_lines[lineIdx][1].lower() == 'site': # There is somtimes a name after 'End Site' but we will ignore it.
lineIdx += 2 # Incriment 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
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
@ -220,18 +220,18 @@ def read_bvh(context, file_path, ROT_MODE='XYZ', GLOBAL_SCALE=1.0):
channels = bvh_node.channels
anim_data = bvh_node.anim_data
if channels[0] != -1:
lx = GLOBAL_SCALE * float(line[channels[0]])
lx = global_scale * float(line[channels[0]])
if channels[1] != -1:
ly = GLOBAL_SCALE * float(line[channels[1]])
ly = global_scale * float(line[channels[1]])
if channels[2] != -1:
lz = GLOBAL_SCALE * float(line[channels[2]])
lz = global_scale * float(line[channels[2]])
if channels[3] != -1 or channels[4] != -1 or channels[5] != -1:
rx, ry, rz = float(line[channels[3]]), float(line[channels[4]]), float(line[channels[5]])
if ROT_MODE != 'NATIVE':
if rotate_mode != 'NATIVE':
rx, ry, rz = eulerRotate(radians(rx), radians(ry), radians(rz), bvh_node.rot_order)
else:
rx, ry, rz = radians(rx), radians(ry), radians(rz)
@ -273,9 +273,9 @@ def read_bvh(context, file_path, ROT_MODE='XYZ', GLOBAL_SCALE=1.0):
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:
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
if (bvh_node.rest_tail_local - bvh_node.rest_head_local).length <= 0.001 * global_scale:
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
@ -334,7 +334,7 @@ def bvh_node_dict2objects(context, bvh_nodes, IMPORT_START_FRAME=1, IMPORT_LOOP=
return objects
def bvh_node_dict2armature(context, bvh_nodes, ROT_MODE='XYZ', IMPORT_START_FRAME=1, IMPORT_LOOP=False):
def bvh_node_dict2armature(context, bvh_nodes, rotate_mode='XYZ', IMPORT_START_FRAME=1, IMPORT_LOOP=False):
if IMPORT_START_FRAME < 1:
IMPORT_START_FRAME = 1
@ -430,7 +430,7 @@ def bvh_node_dict2armature(context, bvh_nodes, ROT_MODE='XYZ', IMPORT_START_FRAM
pose = arm_ob.pose
pose_bones = pose.bones
if ROT_MODE == 'NATIVE':
if rotate_mode == 'NATIVE':
eul_order_lookup = {\
(0, 1, 2): 'XYZ',
(0, 2, 1): 'XZY',
@ -444,9 +444,9 @@ def bvh_node_dict2armature(context, bvh_nodes, ROT_MODE='XYZ', IMPORT_START_FRAM
pose_bone = pose_bones[bone_name]
pose_bone.rotation_mode = eul_order_lookup[tuple(bvh_node.rot_order)]
elif ROT_MODE != 'QUATERNION':
elif rotate_mode != 'QUATERNION':
for pose_bone in pose_bones:
pose_bone.rotation_mode = ROT_MODE
pose_bone.rotation_mode = rotate_mode
else:
# Quats default
pass
@ -488,7 +488,7 @@ def bvh_node_dict2armature(context, bvh_nodes, ROT_MODE='XYZ', IMPORT_START_FRAM
# KEYFRAME METHOD, SLOW, USE IPOS DIRECT
# TODO: use f-point samples instead (Aligorith)
if ROT_MODE != 'QUATERNION':
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
@ -507,7 +507,7 @@ def bvh_node_dict2armature(context, bvh_nodes, ROT_MODE='XYZ', IMPORT_START_FRAM
bone_rotation_matrix = Euler((rx, ry, rz)).to_matrix().resize4x4()
bone_rotation_matrix = bone_rest_matrix_inv * bone_rotation_matrix * bone_rest_matrix
if ROT_MODE == 'QUATERNION':
if rotate_mode == 'QUATERNION':
pose_bone.rotation_quaternion = bone_rotation_matrix.to_quat()
else:
euler = bone_rotation_matrix.to_euler(pose_bone.rotation_mode, prev_euler[i])
@ -520,7 +520,7 @@ def bvh_node_dict2armature(context, bvh_nodes, ROT_MODE='XYZ', IMPORT_START_FRAM
if bvh_node.has_loc:
pose_bone.keyframe_insert("location")
if bvh_node.has_rot:
if ROT_MODE == 'QUATERNION':
if rotate_mode == 'QUATERNION':
pose_bone.keyframe_insert("rotation_quaternion")
else:
pose_bone.keyframe_insert("rotation_euler")
@ -539,21 +539,21 @@ def bvh_node_dict2armature(context, bvh_nodes, ROT_MODE='XYZ', IMPORT_START_FRAM
return arm_ob
def load(operator, context, filepath="", rotate_mode='NATIVE', scale=1.0, use_cyclic=False, frame_start=1):
def load(operator, context, filepath="", 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,
ROT_MODE=rotate_mode,
GLOBAL_SCALE=scale)
rotate_mode=rotate_mode,
global_scale=global_scale)
print('%.4f' % (time.time() - t1))
t1 = time.time()
print('\timporting to blender...', end="")
bvh_node_dict2armature(context, bvh_nodes,
ROT_MODE=rotate_mode,
rotate_mode=rotate_mode,
IMPORT_START_FRAME=frame_start,
IMPORT_LOOP=use_cyclic)