diff --git a/release/scripts/op/io_anim_bvh/__init__.py b/release/scripts/op/io_anim_bvh/__init__.py index 211db8b9cb3..9282c4f22c9 100644 --- a/release/scripts/op/io_anim_bvh/__init__.py +++ b/release/scripts/op/io_anim_bvh/__init__.py @@ -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() diff --git a/release/scripts/op/io_anim_bvh/export_bvh.py b/release/scripts/op/io_anim_bvh/export_bvh.py index 6123990d370..c02da070121 100644 --- a/release/scripts/op/io_anim_bvh/export_bvh.py +++ b/release/scripts/op/io_anim_bvh/export_bvh.py @@ -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) diff --git a/release/scripts/op/io_anim_bvh/import_bvh.py b/release/scripts/op/io_anim_bvh/import_bvh.py index f3d781448f8..e0ff5dd5673 100644 --- a/release/scripts/op/io_anim_bvh/import_bvh.py +++ b/release/scripts/op/io_anim_bvh/import_bvh.py @@ -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)