目录
maya打开脚本编辑器
运行打开bvh脚本
maya导出bvh脚本
maya打开脚本编辑器
打开Maya软件,点击右下角 “脚本编辑器”
运行打开bvh脚本
https://github.com/jhoolmans/mayaImporterBVH/blob/master/bvh_importer.py
import os
import re
from typing import Optionalimport maya.cmds as mcspace_re = re.compile(r"\s+")# This maps the BVH naming convention to Maya
translationDict = {"Xposition": "translateX","Yposition": "translateY","Zposition": "translateZ","Xrotation": "rotateX","Yrotation": "rotateY","Zrotation": "rotateZ"
}class TinyDAG(object):"""Tiny DAG class for storing the hierarchy of the BVH file."""def __init__(self, obj: str, parent: Optional["TinyDAG"] = None):"""Constructor"""self.obj = objself.__parent = parent@propertydef parent(self):"""Returns the parent of the object"""return self.__parentdef __str__(self) -> str:"""String representation of the object"""return str(self.obj)def full_path(self) -> str:"""Returns the full path of the object"""if self.parent is not None:return "%s|%s" % (self.parent.full_path(), str(self))return str(self.obj)class BVHImporterDialog(object):"""BVH Importer DialogThis class is the main dialog for the BVH importer."""def __init__(self, debug=False):self._name = "bvhImportDialog"self._title = "BVH Importer v2.0"if debug:print("Debug is deprecated.")# UI relatedself._textfield = ""self._scale_field = ""self._frame_field = ""self._rotation_order = ""self._reload = ""# Otherself._root_node = None # Used for targeting# BVH specific stuffself._filename = ""self._channels = []self.setup_ui()def setup_ui(self):"""Builds the UI"""win = self._nameif mc.window(win, ex=True):mc.deleteUI(win)# Non sizeable dialogwin = mc.window(self._name, title=self._title, w=200, rtf=True,sizeable=False)mc.columnLayout(adj=1, rs=5)mc.separator()mc.text("Options")mc.separator()mc.rowColumnLayout(numberOfColumns=2,columnWidth=[(1, 80), (2, 150)],cal=[(1, "right"), (2, "center")],cs=[(1, 5), (2, 5)],rs=[(1, 5), (2, 5)])mc.text("Rig scale")self._scale_field = mc.floatField(minValue=0.01, maxValue=2, value=1)mc.text("Frame offset")self._frame_field = mc.intField(minValue=0)mc.text("Rotation Order")self._rotation_order = mc.optionMenu()mc.menuItem(label='XYZ')mc.menuItem(label='YZX')mc.menuItem(label='ZXY')mc.menuItem(label='XZY')mc.menuItem(label='YXZ')mc.menuItem(label='ZYX')mc.setParent("..")mc.separator()# Targeting UImc.text("Skeleton Targeting")mc.text("(Select the hips)")mc.separator()mc.rowColumnLayout(numberOfColumns=2,columnWidth=[(1, 150), (2, 80)],cs=[(1, 5), (2, 5)],rs=[(1, 5), (2, 5)])self._textfield = mc.textField(editable=False)mc.button("Select/Clear", c=self._on_select_root)mc.setParent("..")mc.separator()mc.button("Import..", c=self._on_select_file)self._reload = mc.button("Reload", enable=False, c=self._read_bvh)# Footermc.text("by Jeroen Hoolmans")mc.window(win, e=True, rtf=True, sizeable=False)mc.showWindow(win)def _on_select_file(self, e):"""Callback for the import button."""file_filter = "All Files (*.*);;Motion Capture (*.bvh)"result = mc.fileDialog2(fileFilter=file_filter, dialogStyle=1, fm=1)if result is None or not len(result):returnself._filename = result[0]mc.button(self._reload, e=True, enable=True)# Action!self._read_bvh()def load_bvh(self, filename):self._filename = filenameself._read_bvh()def _read_bvh(self, *_args):# Safe close is needed for End Site part to keep from setting new# parent.safe_close = False# Once motion is active, animate.motion = False# Clear channels before appendingself._channels = []# Scale the entire rig and animationrig_scale = mc.floatField(self._scale_field, q=True, value=True)frame = mc.intField(self._frame_field, q=True, value=True)rot_order = mc.optionMenu(self._rotation_order, q=True, select=True) - 1with open(self._filename) as f:# Check to see if the file is valid (sort of)if not f.readline().startswith("HIERARCHY"):mc.error("No valid .bvh file selected.")return Falseif self._root_node is None:# Create a group for the rig, easier to scale.# (Freeze transform when ungrouping please..)mocap_name = os.path.basename(self._filename)grp = mc.group(em=True, name="_mocap_%s_grp" % mocap_name)mc.setAttr("%s.scale" % grp, rig_scale, rig_scale, rig_scale)# The group is now the 'root'my_parent = TinyDAG(grp, None)else:my_parent = TinyDAG(self._root_node, None)self._clear_animation()for line in f:line = line.replace(" ", " ") # force spacesif not motion:# root jointif line.startswith("ROOT"):# Set the Hip joint as rootif self._root_node:my_parent = TinyDAG(str(self._root_node), None)else:my_parent = TinyDAG(line[5:].rstrip(), my_parent)# Update root node in case we want to reload.self._root_node = my_parentmc.textField(self._textfield,e=True,text=my_parent.full_path())if "JOINT" in line:jnt = space_re.split(line.strip())# Create the jointmy_parent = TinyDAG(jnt[1], my_parent)if "End Site" in line:# Finish up a hierarchy and ignore a closing bracketsafe_close = Trueif "}" in line:# Ignore when safeClose is onif safe_close:safe_close = Falsecontinue# Go up one levelif my_parent is not None:my_parent = my_parent.parentif my_parent is not None:mc.select(my_parent.full_path())if "CHANNELS" in line:chan = line.strip()chan = space_re.split(chan)# Append the channels that are animatedfor i in range(int(chan[1])):self._channels.append("%s.%s" % (my_parent.full_path(),translationDict[chan[2 + i]]))if "OFFSET" in line:offset = line.strip()offset = space_re.split(offset)jnt_name = str(my_parent)# When End Site is reached, name it "_tip"if safe_close:jnt_name += "_tip"# skip if existsif mc.objExists(my_parent.full_path()):jnt = my_parent.full_path()else:# Build a new jointjnt = mc.joint(name=jnt_name, p=(0, 0, 0))mc.setAttr(jnt + ".rotateOrder", rot_order)mc.setAttr(jnt + ".translate",float(offset[1]),float(offset[2]),float(offset[3]))if "MOTION" in line:# Animate!motion = Trueelse:# We don't really need to use Frame count and time# (since Python handles file reads nicely)if "Frame" not in line:data = space_re.split(line.strip())# Set the values to channelsfor index, value in enumerate(data):mc.setKeyframe(self._channels[index],time=frame,value=float(value))frame = frame + 1def _clear_animation(self):if self._root_node is None:mc.error("Could not find root node to clear animation.")return# Select hierarchymc.select(str(self._root_node), hi=True)nodes = mc.ls(sl=True)trans_attrs = ["translateX", "translateY", "translateZ"]rot_attrs = ["rotateX", "rotateY", "rotateZ"]for node in nodes:for attr in trans_attrs + rot_attrs:# Delete input connectionsconnections = mc.listConnections("%s.%s" % (node, attr),s=True,d=False)if connections is not None:mc.delete(connections)for attr in rot_attrs:# Reset rotationmc.setAttr("%s.%s" % (node, attr), 0)def _on_select_root(self, *_args):# When targeting, set the root joint (Hips)selection = mc.ls(sl=True, type="joint", l=True)if len(selection) == 0:self._root_node = Nonemc.textField(self._textfield, e=True, text="")else:self._root_node = selection[0]mc.textField(self._textfield, e=True, text=self._root_node)if __name__ == "__main__":dialog = BVHImporterDialog()
maya导出bvh脚本
https://github.com/zhaozigu/maya-export-bvh/blob/main/export_bvh.py
import os
import mathimport maya.cmds as cmds
import maya.api.OpenMaya as omdef get_bone_rotation(bone):cur_mat = om.MMatrix(cmds.xform(bone, q=True, ws=True, m=True))parent = cmds.listRelatives(bone, p=True)[0]parent_mat = om.MMatrix(cmds.xform(parent, q=True, ws=True, m=True))local_mat = cur_mat * parent_mat.inverse()cur_xfo_mat = om.MTransformationMatrix(local_mat)rotation = [math.degrees(x) for x in cur_xfo_mat.rotation().asVector()]return rotationdef export_motion(joints, start_frame, end_frame, rot_order: tuple):motion_str = ""root_joint = joints[0]for frame in range(start_frame, end_frame + 1):cmds.currentTime(frame)for joint in joints:joint_name = cmds.ls(joint, long=True)[0]rot = get_bone_rotation(joint_name)if joint == root_joint:loc = cmds.xform(joint_name, q=True, translation=True)motion_str += "%.6f %.6f %.6f " % (loc[0], loc[1], loc[2])motion_str += "%.6f %.6f %.6f " % (rot[rot_order[0]], rot[rot_order[1]], rot[rot_order[2]])motion_str += "\n"return motion_strdef export_hierarchy(joints, rot_order: str):hierarchy_str = "HIERARCHY\n"def _process_joint(joint, indent):nonlocal hierarchy_strjoint_name_raw = cmds.ls(joint, long=True)[0]joint_name = joint_name_raw.split("|")[-1].split(":")[-1]if indent == 0:hierarchy_str += "{}ROOT {}\n".format('\t' * indent, joint_name)else:hierarchy_str += "{}JOINT {}\n".format('\t' * indent, joint_name)loc = cmds.xform(joint_name_raw, q=True, translation=True)hierarchy_str += "{}{{\n".format('\t' * indent)hierarchy_str += "{}OFFSET {:.6f} {:.6f} {:.6f}\n".format('\t' * (indent + 1), loc[0], loc[1], loc[2])if indent == 0:hierarchy_str += "{}CHANNELS 6 Xposition Yposition Zposition {}rotation {}rotation {}rotation\n".format('\t' * (indent + 1), rot_order[0], rot_order[1], rot_order[2])else:hierarchy_str += "{}CHANNELS 3 {}rotation {}rotation {}rotation\n".format('\t' * (indent + 1), rot_order[0], rot_order[1], rot_order[2])children = cmds.listRelatives(joint, children=True, type="joint")if children:for child in children:_process_joint(child, indent + 1)else:hierarchy_str += "{}End Site\n".format('\t' * (indent + 1))hierarchy_str += "{}{{\n".format('\t' * (indent + 1))hierarchy_str += "{}OFFSET 0.0 0.0 0.0\n".format('\t' * (indent + 2))hierarchy_str += "{}}}\n".format('\t' * (indent + 1))hierarchy_str += "{}}}\n".format('\t' * indent)root_joint = joints[0]_process_joint(root_joint, 0)return hierarchy_strdef export_bvh(joints, output_file_path, start_frame, end_frame, rot_order="ZXY"):_order = {"XYZ": (0, 1, 2),"XZY": (0, 2, 1),"YXZ": (1, 0, 2),"YZX": (1, 2, 0),"ZXY": (2, 0, 1),"ZYX": (2, 1, 0),}assert rot_order in _order, "The parameters of the rotation order are incorrect"hierarchy = export_hierarchy(joints, rot_order)motion = export_motion(joints, start_frame, end_frame, _order[rot_order])num_frames = end_frame - start_frame + 1frame_rate = cmds.playbackOptions(query=True, framesPerSecond=True)if frame_rate == 0:frame_rate = 24.0frame_time = 1.0 / frame_ratewith open(output_file_path, "w") as output_file:output_file.write(hierarchy)output_file.write(f"MOTION\nFrames: {num_frames}\nFrame Time: {frame_time:.6f}\n")output_file.write(motion)def get_ordered_joints(joint):ordered_joints = [joint]children = cmds.listRelatives(joint, children=True, type="joint")if children:for child in children:ordered_joints.extend(get_ordered_joints(child))return ordered_jointsif __name__ == "__main__":root_joint_name = "root"root_joint = Nonechildren = cmds.listRelatives(root_joint_name, children=True, type="joint")if children:root_joint = children[0]else:raise ValueError(f"No joint found under {root_joint_name}")joints = get_ordered_joints(root_joint)print(joints)start_frame = int(cmds.playbackOptions(query=True, minTime=True))end_frame = int(cmds.playbackOptions(query=True, maxTime=True))# Set the output file pathoutput_file_path = os.path.join(os.path.expanduser("~"), "maya_body_test.bvh")export_bvh(joints, output_file_path, start_frame, end_frame, "ZYX")