前言
前段时间研究使用Maya重定向动画,但一个一个手动操作还是有点心烦,所以我花了2天时间学习了Pyhton并写了这个插件(本人有c++、qt、JavaScript经验所以学的快),在这个过程也积累了一些心得在此分享给大家。另外祝大家劳动节快乐。
以下是我写的插件,一个通过HumanIK批量重定向动画的工具:
https://github.com/blueroseslol/DccTool
可以帮助动画公司将biped骨骼动画批量重定向到Ue4或者其他骨骼上。里面有很多HumanIK控制代码以及文件导入与导出代码可以参考。如果有什么问题欢迎交流。
前期准备
开发环境搭建推荐看这篇文章 https://www.jianshu.com/p/813b2cc71ca2
本人是通过《Maya Python游戏与影视编程指南》一书来学习实用Python开发Maya插件,书中也介绍了Python的语法。使得没有Python基础的人也可以很好的学习。同时他也介绍了Maya插件开发的命令模式与API模式。通俗的说就是用Maya的内置命令与实用Maya的API。Maya API更加适合专业插件开发者使用。举个书中例子,API中的基础对象MObject是一个指针对象。所以使用c#或者c++会更加适合API模式的开发吧。
不过这本书的很多翻译都感觉怪怪,这是它位移的缺点。
我使用Vscode进行开发,除了必须的Python(打开一个Py文件就会提示安装)插件外,我还使用MayaPy与MayaCode。为了能让MayaPy将代码发送到Maya中执行,还需要再Maya中执行一段开启端口的命令:
Mel
commandPort -name "localhost:7001" -sourceType "mel" -echoOutput;
Python
import maya.cmds as cmds
cmds.commandPort(name=":7001", sourceType="mel",echoOutput=True)
端口与MayaPy中设置的端口有关。如果你不想每次启动Maya都手动执行命令,那么可以新建一个脚本文件并将代码填入。之后放到指定目录中:
Windows: <drive>:\Documents and Settings\<你的windows用户名>\My Documents\maya\<你maya的版本号>\scripts
(其实就是我的文档下面maya文件夹)
MacOSX: ~/Library/Preferences/Autodesk/maya/<你maya的版本号>/scripts.
Linux: ~/maya/<你maya的版本号>/scripts.
Maya Python路径设置及代码自动补全
VS Code中按Ctrl+Shift+P,输入Settings打开settings.json配置文件,在大括号里加入下面代码:
//python.pythonPath是指定Python命令路径,请根据你maya的安装路径来做修改
"python.pythonPath": "C:/Program Files/Autodesk/Maya2019/bin/mayapy.exe",
//python.autoComplete.extraPaths是代码自动补全路径,同样根据你自己的maya安装路径来写
"python.autoComplete.extraPaths": "C:/Program Files/Autodesk/Maya2019/devkit/other/pymel/extras/completion/py"
注意:settings.json文件中,每一项设置用”,”隔开,最后一项设置后面没有”,”,如果报错,检查一下是不是这里出现了问题。
编码篇
初次学习可以参考YivanLee的文章
https://zhuanlan.zhihu.com/p/76957745
我认为首先你需要了解Maya中的物体都是节点式的,当然我个人认为Maya的节点更加偏向于组件,而非Houdini那样的流程节点。
文档与搜索技巧
请使用谷歌进行搜索(不推荐bing以及baidu),包括搜索API,这样可以节约大量时间。
官方文档:http://help.autodesk.com/view/MAYAUL/2018/ENU/
Pyside2是python版本的Qt库。你只要看一下它的模块数目就能明白它的强大。另外百度的时候请搜索PyQt5,虽然Pyside才是官方正版。
Pyside2:https://doc.qt.io/qtforpython/modules.html
PyMel与maya.cmds不同在于,它返回的不是字符串,而是一个PyNode对象。它可以直接修改节点属性值,无需调用getAttr与setAttr。更加适合于习惯了OOP语言人士使用。同时PyMel可以简化GUI的构建。但你看了文档就会明白这个玩意就是个残废。
Pymel文档:http://help.autodesk.com/cloudhelp/2018/JPN/Maya-Tech-Docs/PyMel/modules.html
pyside2与GUI
实用Python构建Maya插件UI有3种方式:
- 调用Maya内置命令创建。
- 直接调用Pyside2函数创建。
本人使用第三种,使用Qt的界面设计师工具创建。虽然本质上就是第二种方法,但效率高。需要注意的是maya目录下的designer.exe是不能直接使用的。需要将安装目录下的qt-plugins中的所有文件都复制到bin所在目录中。之后的步骤就是用Qt的界面设计师工具设计界面了。
但本人电脑上有Qt,所有直接就用自己这个版本了。推荐还是用Maya目录下的版本(自己去下个5.6版本的也是可以的)原因后面会说。
Qt的界面设计师工具可以输出.ui文件,但Python是无法直接使用的,(虽然可以通过loadUI载入,但只能调用Mel命令,无法关联python函数)所以之后需要安装Pyside2,目的是为了使用 pyside-uic.exe工具,它可以将 .ui文件转化为python代码。
Pyside2对应python3.x,所以你需要下载3.0的版本并安装。安装完之后,打开CMD切换到安装目录下的script文件夹。执行
pip install PySide2
时候再执行
//请注意文件路径,推荐奖*.ui文件复制到script文件夹中
pyside-uic -o output.py input.ui
可能是我用的Qt版本与Maya的不同,最后生成出来的python存在一些小错误:按钮上的setText函数中会多出一个莫名其妙的函数。还有一个问题我倒最后也没搞懂:Pyside2不是对应python3.x与Qt5么,那为什么Maya使用python2.7却可以调用Pyside2呢?是因为预编译了对应的库么?
学习建议
我个人建议,如果你想深入地使用pyside2开发插件,强烈推荐先去学习一到两个月的Qt。之后再来学习pyside2你就会非常的顺利。尤其需要了解的是Qt的信号与槽机制、GUI绘制与线程、Qt事件传递机制。
实用代码
防止窗口重复创建
def main():
global win
try:
win.close() # 为了不让窗口出现多个,因为第一次运行还没初始化,所以要try,在这里尝试先关闭,再重新新建一个窗口
except:
pass
//MainWindow为窗口类
win = MainWindow()
win.show()
信号槽与解决生命周期问题
//其中SIGNAL需要先导入
from PySide2.QtCore import SIGNAL, QObject
class MainWindow(QWidget, Ui_Form):
def slotBtnClicked(self):
//为了防止消息框易一出现就被回收,需要给它设置父对象
msgBox = QMessageBox(self)
msgBox.setText(u"The document has been modified.")
msgBox.setInformativeText(u"Do you want to save your changes?")
msgBox.setStandardButtons(QMessageBox.Save)
msgBox.setDefaultButton(QMessageBox.Save)
msgBox.show()
def __init__(self, parent=None):
self.pushButton_stop.clicked.connect(self.slotBtnClicked)
QObject.connect(self.pushButton_targetSkin,SIGNAL('clicked()'), self.slotBtnClicked)
python HumanIK
相关的控制代码可以在 安装目录\scripts\others下搜索hik找到,主要在hikCharacterControlsUI.mel与hikGlobalUtils.mel文件中,也可以参考我插件中的代码。
FBX导出命令
导入文件可以只用cmds.file命令,但是导出就不太好用了,比如需要烘焙动画什么的,所以需要调用以下Mel命令。
# FBX Exporter options. Set as required.
# You can find a reference guide here: http://download.autodesk.com/us/fbx/20112/Maya/_index.html
# Just add/change what you need.
# Geometry
mm.eval("FBXExportSmoothingGroups -v true")
mm.eval("FBXExportHardEdges -v false")
mm.eval("FBXExportTangents -v false")
mm.eval("FBXExportSmoothMesh -v true")
mm.eval("FBXExportInstances -v false")
mm.eval("FBXExportReferencedContainersContent -v false")
# Animation
mm.eval("FBXExportBakeComplexAnimation -v true")
mm.eval("FBXExportBakeComplexStart -v "+str(exportStartFrame[x]))
mm.eval("FBXExportBakeComplexEnd -v "+str(exportEndFrame[x]))
mm.eval("FBXExportBakeComplexStep -v 1")
# mm.eval("FBXExportBakeResampleAll -v true")
mm.eval("FBXExportUseSceneName -v false")
mm.eval("FBXExportQuaternion -v euler")
mm.eval("FBXExportShapes -v true")
mm.eval("FBXExportSkins -v true")
# Constraints
mm.eval("FBXExportConstraints -v false")
# Cameras
mm.eval("FBXExportCameras -v false")
# Lights
mm.eval("FBXExportLights -v false")
# Embed Media
mm.eval("FBXExportEmbeddedTextures -v false")
# Connections
mm.eval("FBXExportInputConnections -v false")
# Axis Conversion
mm.eval("FBXExportUpAxis y")
# Export!
mm.eval("FBXExport -f "+exportNames[x]+".fbx -s")