MayaAPI学习笔记--Zurbrigg - Maya Python API (Volume1)

Arya Lv3

随手记

Maya是开放软件可以通过插件拓展。但是Maya是闭源的,它的核心代码不可以查看和更改,必须通过Maya提供的两个应用程序接口对Maya软件进行拓展。

1.Maya Commands

可以用mel或者python语言进行调用的程序接口。mel是maya编程的核心,maya的GUI就是由mel构建的。而maya.cmds(python)是mel代码的封装。

2.MayaAPI

MayaAPI最初是给C++进行调用的程序接口,但是从maya8.5之后已经对python和微软的.NET提供了支持。Python API目前有两个版本,Python API 1.0 和 Python API 2.0 。2.0对python更友好,性能也更好,但并不完全支持所有接口。Python API 1.0所用的库是maya.OpenMaya,Python API 2.0用的库是maya.api.OpenMaya。

为什么cmds被选择的更多呢?

1.cmds简单非常多,MayaAPI的学习曲线非常陡岖,很难学习,cmds就相对简单很多。MayaAPI可以操控的东西更多,对使用者对maya的基础知识了解的要求也就越多。需要使用者了解Maya“基于节点”的架构(包括DG和dag),以免使用的时候出现问题。

2.cmds是已经封装好的命令,里面已经包含了undo方法,而且已经被完全测试过可以使用。而用api写的命令,需要自己写undo方法,并且测试,如果在编写的时候破坏了堆栈会造成maya的崩溃。

3.同样的操作cmds所需的代码量更小

4.唯一能够对mayaUI界面进行操作的应用接口

给我个理由选择MayaAPI?

1.对maya架构进行扩展,扩展节点、上下文、发射器等等。

2.可以获得更多的控制。可以获得cmds做不到的控制。

3.性能更高。特别是在对大量点或面循环进行同样操作,MayaAPI的效率会高很多。

4.可以将python代码编译为C++插件,避免代码泄露。C++在处理多线程和进行效率最大化方面有更多优势。

python VS C++

C++的优缺点

1.C++的性能更优,它在加载到maya之前就已经编译优化好了。但是它对maya版本和操作系统版本具有依赖性,对每个不同版本的maya需要分别编译,对不同的平台也需要分别编译,这是很麻烦的事情。

2.C++可以实现多线程,更优化性能。 但他更难维护,而且写起来复杂。

3.mayaAPI本来就是针对C++写的,有一些python没有的方法。

PythonAPI的优缺点

1.开发更快,不用进行编译。更易于使用。但消耗资源比较多,还有全局锁(GIL),对多线程代码有限制。

2.python有很多第三方库可以解决很多复杂的问题。

3.对平台或者maya版本没有依赖性,除非不同maya版本的api发生了改变。

4.mayaAPI不是针对python开发的,不是所有的MayaAPI都有

3.MayaAPI 1.0 和 MayaAPI 2.0

一般会把 MayaAPI 1.0 和 MayaAPI 2.0 混合使用,只要它们不产生直接的交互是不会有什么问题的。但如果产生了直接交互(例如用1.0生成的节点再用2.0进行链接),会产生一些难以发觉的错误。特别是当代码量巨大的时候,很容易发生这类错误。

4.Python Scripts 和 Python Plugins

comparing python scripts and python plugins

加载 maya Plugins 可以通过插件管理器和用 MayaCommand(cmd)进行加载。在插件管理器中,有很多不同的文件夹分类,只有加入到maya环境变量中的文件夹路径才会出现在插件管理器中。而写成maya Plugins的代码,必须保存在maya环境变量的路径的文件夹下。一般的plugin代码可以放到maya默认的plugin文件夹下,即:

C:\Users\User_name\Documents\maya\version\plug-ins

5.Plugin template(插件编写模版)

一个插件的最基本结构是两个最基本的函数:initializePlugin() 和 uninitializePlugin() 。

initializePlugin函数是插件的起始点,在插件加载之后即可调用且只会被调用一次,它会向maya注册所有的命令、节点、菜单等等。有一个MObject参数用来注册插件内的这些组件

uninitializePlugin函数是插件的退出点,在插件取消加载的时候调用且只会被调用一次,它会注销掉所有它建立的命令、节点、菜单等等。也有一个MObject参数(这个MObject和initialize中的MObject是一样的),用于注销插件内的这些组件。

用maya API2.0编写插件时,需要先用maya_useNewAPI函数进行声明。也可以在头部使用 maya_useNewAPI = True 来声明。

插件基本结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import maya.api.OpenMaya as om2
import maya.cmds as cmds

#maya_unseNewAPI = True
def maya_useNewAPI():
pass

def initializePlugin(plugin):
vendor='AryaGala'
version='1.0.0'
om2.MFnPlugin(plugin, vendor, version) #用MFnPlugin类生成插件信息,在插件管理器中插件的右侧的🛈按钮中显示

def uninitializePlugin(plugin):
pass

命令插件基本结构:

通过插件将命令注册进maya中,可以通过cmds直接调用命令。在开发过程中,可以在代码下添加一段用于刷新的函数,方便测试和编写。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import maya.api.OpenMaya as om2
import maya.cmds as cmds

def maya_useNewAPI():
pass

class HelloOpenMayaCmd(om2.MPxCommand):
COMMAND_NAME = 'HelloOpenMaya'
def __init__(self):
super(HelloOpenMayaCmd,self).__init__()

def doIt(self,args):
#插件命令的核心函数,逻辑函数
print("Hello,OpenMaya!")

@classmethod #classmethod装饰类,不实例化也可以执行它所装饰的方法
def creator(cls):
#返回该类的实例,用于注册命令
return HelloOpenMayaCmd()


def initializePlugin(plugin):
vendor='AryaGala'
version='1.0.0'
plugin_fn = om2.MFnPlugin(plugin, vendor, version)
try:
#注册命令
plugin_fn.registerCommand(HelloOpenMayaCmd.COMMAND_NAME,HelloOpenMayaCmd.creator)
except:
om2.MGlobal.displayError("Failed to register command: {}".format(HelloOpenMayaCmd.COMMAND_NAME))

def uninitializePlugin(plugin):
plugin_fn = om2.MFnPlugin(plugin)
try:
#注销命令
plugin_fn.deregisterCommand(HelloOpenMayaCmd.COMMAND_NAME)
except:
om2.MGlobal.displayError("Failed to deregister command: {}".format(HelloOpenMayaCmd.COMMAND_NAME))

if __name__=="__main__":
plugin_name="empty_plugin.py"
cmds.evalDeferred('if cmds.pluginInfo(plugin_name,q=True,loaded=True): cmds.unloadPlugin(plugin_name)')
cmds.evalDeferred('if not cmds.pluginInfo(plugin_name,q=True,loaded=True): cmds.loadPlugin(plugin_name)')

节点插件基本结构:

通过插件将节点注册进maya中,插件启动后可以在maya中添加该节点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import maya.api.OpenMaya as om2
import maya.api.OpenMayaUI as omui2
import maya.cmds as cmds

def maya_useNewAPI():
pass

class HelloWorldNode(omui2.MPxLocatorNode):
TYPE_NAME = "helloworld"
TYPE_ID=om2.MTypeId(0x0007f7f7)

def __init__(self):
super(HelloWorldNode,self).__init__()

@classmethod
def creator(cls):
return HelloWorldNode()

@classmethod
def initialize(cls):
pass

def initializePlugin(plugin):
vendor='AryaGala'
version='1.0.0'
plugin_fn = om2.MFnPlugin(plugin, vendor, version)
try:
plugin_fn.registerNode(HelloWorldNode.TYPE_NAME,
HelloWorldNode.TYPE_ID,
HelloWorldNode.creator,
HelloWorldNode.initialize,
om2.MPxNode.kLocatorNode)
except:
om2.MGlobal.displayError("Failed to register node: {}".format(HelloWorldNode.TYPE_NAME))

def uninitializePlugin(plugin):
plugin_fn = om2.MFnPlugin(plugin)
try:
plugin_fn.deregisterNode(HelloWorldNode.TYPE_ID)
except:
om2.MGlobal.displayError("Failed to deregister command: {}".format(HelloWorldNode.TYPE_NAME))

if __name__=="__main__":
cmds.file(new=True, force=True)
plugin_name="empty_plugin.py"
cmds.evalDeferred('if cmds.pluginInfo(plugin_name,q=True,loaded=True): cmds.unloadPlugin(plugin_name)')
cmds.evalDeferred('if not cmds.pluginInfo(plugin_name,q=True,loaded=True): cmds.loadPlugin(plugin_name)')
cmds.evalDeferred('cmds.createNode("helloworld")')

*当节点在场景中已经存在时,是无法取消加载插件的,会弹出插件还在使用的报错。尽管删除了它,但它还存在于maya中的undo堆栈里,也会报错。所以在开发节点插件的过程中,可以通过新建场景对节点进行刷新。

关于文档

在Maya API的说明文档的Examples中,以py开头的文件是使用Maya API 2.0的。可以跟同名的 1.0 的文件作区分。这些例子可以在devkit包中的 “devkitBase\devkit\plug-ins\scripted” 路径下找到。
C++文档
python文档

基础概念

四大类

maya API由四个大类组成:MObject、Function Sets、Wrappers、Proxies。
MayaAPI Object Tyep

MObject


MObject是maya中的通用对象,可以用来表示maya中的任何物体(maya中的任何物体:节点、属性等等),它是maya对象的句柄(相当于存储了maya对象所在地址),我们并不能直接访问maya中的对象。在用MayaAPI进行开发的过程中,开发者是无权直接访问maya对象的,只能使用该物体对应的Function Sets类的方法来对该对象进行操纵。因为MObject只是一个句柄,所以它对应的类的函数很少,apiTypeStr和hasFn会比较常用(用于获取物体的类型(kTransform或者kCamera之类的)和判断是否是某一类(obj.hasFn(om.MFn.kTransform)))。

MObject Concept

Function Sets

Function_set Concept


因为无权直接对maya对象进行访问和操作,所以在对maya对象进行 操作 时需要用到Fuction Sets类。它以MFn开头,Fuction Sets 需要 和MObject进行配合来对maya中的物体进行操纵,而且不同的MObject类数据所对应的Fuction Sets类也不同。因为MObject的所有权是在maya那里,所以MObject无法在maya中被删除。当Function Sets对象被删除时,MObject对象也并不会被删除。

Wrappers

Function_set Concept


这种类一般是配置类,用于存储数据、计算、迭代数据等。通常以M开头。像MGlobal和MQtUtil,MMatrix等都是Wrappers类。另外,maya中的迭代器(以MIt开头)也是Wrappers类。

Proxies

Function_set Concept


代理类,以MPx开头,通常在扩展maya的时候会使用。主要用于继承扩展maya命令、节点之类的类。包括MPxCommand, MPxLocatorNode, MPxDrawOverride等。

关系依赖图(The Dependency Graph)

概念

关系依赖图(DG)是一个基于节点的架构的数据流模型,它构建了maya场景。有两个基本组成部分:节点和连接。maya中的场景就是通过把这些节点连接起来而构建的。
Dependency Graph Concept

关系依赖图的数据传递

DependencyGraph Updated
在maya中,节点是无法控制它们何时被更新的,它们只负责计算数据。DG一般是通过推拉模式(push-pull)进行数据的传递。
通过推,输入的属性更改会将脏点向前传播给所有它影响的输出属性和输出节点,而通过拉,沿着脏点传播路径找回最初标记为脏的节点,进行计算,然后将数值沿着脏点传播路径进行更新计算,并且把节点标记为干净的。

这样的方法避免了在数据更改时即刻计算数据,而是在需要数据的时候才进行计算,节省了大量的资源,提高效率。

节点(Nodes)

节点的基础结构

尽管maya中有很多种节点,但节点的基本结构都是相同的。基本组成就是属性和compute方法。属性用于存储数据,相当于代码中的变量,有很多不同类型(int、float、compound、array)通常被分为输入属性和输出属性。compute函数是节点的大脑,主要用于计算。使用自身节点的属性数据进行计算输出数值。还有一个节点中的重要方法是attributeAffects(),这个方法建立了输入属性与输出属性的影响关系,在传播脏点的时候有用(比如说输入属性改变将脏点传播给哪个输出属性,这个时候并不进入compute方法内进行关系查看,而是额外用一个函数指定关系)。

属性

Attributes
属性只是一个储存数据的数据结构,输入属性将数值传给compute函数进行计算,然后将输出数值传递给输出属性。

插点

插点和属性的通常概念其实差不多。按照更细分一点来说就是插点是存储和操纵属性数据的数据结构,而属性是个更为笼统的概念,它只定义了数据的类型和属性的名称(类似于MFuntionSet和MObject的区别),一般我们对属性进行操纵就是对插点进行操纵。

所以进行属性数值读取和写入的一般流程是:通过MSelectionList 找到 节点的MObject,得到对应的MFunctionSet,然后通过MFn来得到该节点的MPlugin

属性参数

一个属性插点通常是通过attr MFn 的create方法来建立的,(例如:om.MFnNumericAttribute),这个类还可以定义属性的参数。属性参数有很多,通常的例如 readable、 writable、 connectable等。另外还有storable用于决定是否将属性存储在场景中,cached属性用于决定属性是否要缓存到本地的datablock中(可以加快节点速度但是会消耗内存资源)、keyable用于控制通道盒中属性是否显示以及节点编辑器中节点的插点是否显示(默认为False)以及channelBox、hidden和array(如果属性值是一个序列的话,这个参数值要设置为True)。

注册节点

注册节点通常是通过MPxPlugin.registerNode()进行注册,有四个参数:
Node Name、Unique Node ID、creator方法、initialize方法。
其中,creator方法和initialize方法是节点类的类方法,这两个方法可以随意命名。creator方法返回节点类的实例。而initialize方法初始化节点类的所有属性,并且这个方法只在插件被加载时被调用一次。

编写节点

在编写节点时,需要实现上面提到的creator和initialize两个类方法,然后要重写compute方法。这个方法根据输入数值计算输出数值。compute方法有两个参数,plug和data_block, plug代表了需要计算的输入属性的插点(MPlug)。data_block(MDataBlock)提供了节点所有属性值的接口。还有一些方法,例如addAttribute()和attributeAffects()方法,是添加属性和针对属性之间关系设定的方法,一般会在init方法进行初始化,只有在attributeAffects()被调用之后,maya节点编辑器中的节点上才会出现输入属性的插点。

其他一些值得了解的方法还有postConstructor和connectionMade和connectionBroken方法。其中postConstructor方法在该节点类初始化之后调用(相当于一个初始化方法)。因为节点内的方法不能在init中进行调用,所以要在postConstructor中进行调用。后面两个connection方法分别是在节点进行链接和断开链接的时候进行调用。

  • 标题: MayaAPI学习笔记--Zurbrigg - Maya Python API (Volume1)
  • 作者: Arya
  • 创建于 : 2023-12-06 08:00:00
  • 更新于 : 2024-11-22 15:45:52
  • 链接: https://aryagala0.github.io/2023/12/06/Maya/MayaAPI学习笔记01/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
 评论