第二集面向对象编程
类,对象和模块。
类是一个蓝图,是一个数据的集合,根据类创建对象,也可以称为类的实例化,对象是基于类的结构的数据的集合,模块是类的集合。
第三集maya api 术语
dag path
dag=directed acyclic graph有向非循环图
树是一种很典型的有向非循环图 在maya中创建的物体对象,具有两个节点,一个transform节点,一个shape节点,transform节点定义了变换(xyz位置)旋转,缩放,shape节点定义了物体的形状。
dg=dependency graph依赖图
dag path
dag path是从根到特定对象的路径,没有dag path,maya就不知道那个特定对象在世界空间的哪里。 因为当一个物体是另一个物体的子物体时,它的原点坐标就会变成它的父物体的当前坐标位置。 maya是通过dag path获得这些位置的。
MObject
MObject是model object的缩写 MObject是可以访问maya的专门的处理程序,可以用来处理模型,渲染,灯光
Selection List
方法有creating、add/remove、walking(遍历)等
案例
说明:首先创建一个mSelectionList对象(mselectionList)(第一行),将obj放入到0的位置(第二行),然后创建一个mdagPath对象(mdagPath)(目前是空的)(第三行),然后将mSelectionList对象中的索引为0的obj的dagpath放入到MDagPath对象(mdagPath)中(现在mdagPath对象中拥有了obj的dagpath)(第四行),创建一个MObject对象(第五行),获得mSelectionList中索引为0的obj中的依赖节点给MObject对象(mobj)。 看那个手的图案下面有个obj我想大概意思是将obj对象与MObject对象建立关系,理解为指针就可以了。
这里的pPlane1为maya中的模型名字
1 2 3 4 5 6 7 8 9 10 11 12 13 import maya.OpenMaya as OpenMayamSel = OpenMaya.MSelectionList() mSel.add("pPlane1" ) mObj = OpenMaya.MObject() mDagPath = OpenMaya.MDagPath() mSel.getDependNode(0 ,mObj) mSel.getDagPath(0 ,mDagPath) print mDagPath.fullPathName()
第四集获取和更改场景物体的属性
先了解节点属性
例如创建一个平面,节点连接关系图如图所示: 然后双击黄色的线就弹出窗口这样就可以进行左边的属性与右边的属性进行连接 。
MFnMesh
首先需要理解一下什么是MFn,MFn是model function set 的缩写意思是模型函数集(函数集是函数的集合的意思)。 然后MFnMesh接受一个网格类型,然后可以执行创建,修改, 更改边,面,细分等方法
MFnDependencyNode
MFnDependencyNode有创建修改检索等功能 针对的是dependency graph,接受的是dependency node 类型
MFnMesh和MFnDependencyNode接受的输入
它们接受的输入通常是MObject或者MDagPath(可以通过看官方文档查询) 这个上面的英语是 inputs to function set MFnMesh接受的输入是MObject或者MDagPath 而MFnDependencyNode能接受的输入只有MObject CSDN上的一些知识点: API文档的坑:MStatus(其实是很多人没仔细看文档的锅……) 如果在学习python api前没有认真看过maya官方文档的相关文档,就会对api文档中的mstatus感觉困惑,这个东西是嘛,我们怎么用,为什么到处都是。我就说一句:api方法中的mstatus,你用python时候就当它不存在!是的,就是无视,这设计到maya软件的整体设计问题。Maya中的程序异常处理是使用的status code而非exception(早年C++异常处理的锅,大家都不敢用,吃性能还不讨好,但是现在maya想换也做不到了,历史包袱严重),也就是说本身需要语言处理的事情,maya的系统自行设计了一套处理机制,而异常的消息传递,就是靠的status code,也就是mstatus,这也是maya api中mstatus无处不在的原因。但是python不需要啊!Python有完备的异常处理机制,干嘛用status code。所以python api就没有mstatus,我们也就不需要管它。
MPlug与MPlugArray
通过观看视频我理解的plug的意思应该就是模型对象的属性,分两种类型 network plug : dependency node plug 意思是在DG中建立连接的属性 non-network plug : user-defined plug 意思应该是用户自定义的属性,还没有建立连接。 看这里,一个节点有多种属性,然后如果我们要操控这些属性,需要通过这个手图案来管理属性,进行创建修改访问的操作。handle 代表了属性名字例如weight,height,subdivisionsWidth 理解了plug的意思后,接下来理解plug array的意思: plug array通过看了视频我理解的是: 一个节点的plug array 是一个列表 ,列表中存在着这个节点的所有连接,包括输入和输出。 例如pPlaneShape1的输入 pPlaneShpae1.inMesh是plug array中的一个值。
案例
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 49 50 51 52 53 import maya.OpenMaya as OpenMayamSel = OpenMaya.MSelectionList() mSel.add("pPlane1" ) mObj = OpenMaya.MObject() mDagPath = OpenMaya.MDagPath() mSel.getDependNode(0 ,mObj) mSel.getDagPath(0 ,mDagPath) print mDagPath.fullPathName() mFnMesh = OpenMaya.MFnMesh(mDagPath) print mFnMesh.fullPathName() mFnDependNode = OpenMaya.MFnDependencyNode(mObj) print mFnDependNode.name() mPlugArray = OpenMaya.MPlugArray() mFnMesh.getConnections(mPlugArray) mPlugArray.length() print mPlugArray[0 ].name() print mPlugArray[1 ].name() mPlugArray2 = OpenMaya.MPlugArray() mPlugArray[1 ].connectedTo(mPlugArray2,True ,False ) print mPlugArray2.length() print mPlugArray2[0 ].name() mObj2 = mPlugArray2[0 ].node() mFnDependNode2 = OpenMaya.MFnDependencyNode(mObj2) print mFnDependNode2.name() mPlug_width = mFnDependNode2.findPlug("width" ) mPlug_height = mFnDependNode2.findPlug("height" ) print mPlug_width.asInt() print mPlug_height.asInt() mPlug_subWidth = mFnDependNode2.findPlug("subdivisionsWidth" ) mPlug_subHeight = mFnDependNode2.findPlug("subdivisionsHeight" ) mPlug_subWidth.setInt(10 ) mPlug_subHeight.setInt(10 ) print mPlug_subWidth.asInt() print mPlug_subHeight.asInt()
第五集 命令通信流
编写命令插件有三个重要的事情 1、function 函数 2、initialization 初始化 / registration 注册 3、 Un-initialization 取消初始化/ De-registration取消注册什么是initialization/registration,什么是Un-initialization/De-registration? 给maya core发送内容叫initialization或者叫registration 从maya core 移除内容叫Un-initialization或者叫De-registration命令通信流 流程:写了一个Class,然后根据Class创建对象,然后我们会赋予指向这个实例的指针(这对于注册这个命令插件是有很大帮助的),然后我们会要求maya生成一个Handle (MObject)抓住指针,然后这个handle会从maya core 中带来实例
第六集 编写命令插件(跟第五集相关联)
目的:通过配合MPxCommand写一个命令,然后保存好并加载进maya的插件中以后,就可以通过maya.cmds直接使用这个命令。比如通过MpxCommand写了一个输出hello world的功能,写好保存加载好以后,就可以直接通过cmds使用那个功能来输出hello world。总之,这节课的目的就是教我们如何扩展maya的命令也就是编写命令插件。 经过上一集的介绍,我们也大概了解了maya的内部流程,因此我们通过这个流程来编写命令插件。 所需要的编写规范可以参考一下官方给的范例:https://help.autodesk.com/view/MAYAUL/2019/CHS/?guid=Maya_SDK_MERGED_py_ref_scripted_2hello_world_cmd_8py_example_html
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 import maya.OpenMaya as OpenMayaimport maya.OpenMayaMPx as OpenMayaMPx import sys commandName = "pluginCommand" class pluginCommand (OpenMayaMPx.MPxCommand): def __init__ (self ): OpenMayaMPx.MPxCommand.__init__(self ) def doIt (self, argList ): print "doIt..." def cmdCreator (): return OpenMayaMPx.asMPxPtr(pluginCommand()) def initializePlugin (mobject ): mplugin = OpenMayaMPx.MFnPlugin(mobject) try : mplugin.registerCommand(commandName, cmdCreator) except : sys.stderr.write("Failed to register command :" + commandName) def uninitializePlugin (mobject ): mplugin = OpenMayaMPx.MFnPlugin(mobject) try : mplugin.deregisterCommand(commandName) except : sys.stderr.write("Failed to de-register command:" + commandName)
代码完成后需要通过脚本编辑器将它们保存为脚本文件。通过UI加载这个脚本文件 进入插件管理器,选择浏览,选择我们刚才保存的脚本文件。记载后就可以在脚本编辑器中使用这个自定义的命令了。 范例: import maya.cmds as cmds cmds.pluginCommand() 结果输出:doIt…通过python命令加载脚本文件 其中参数为脚本文件的路径
第七集 迭代器
一些概念在TD技能学院上面介绍了,这里就不介绍了。
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 import maya.OpenMaya as OpenMayadagIterator = OpenMaya.MItDag(OpenMaya.MItDag.kBreadthFirst, OpenMaya.MFn.kInvalid) dagNodeFn = OpenMaya.MFnDagNode() while (not dagIterator.isDone()): currentObj = dagIterator.currentItem() depth = dagIterator.depth() dagNodeFn.setObject(currentObj) name = dagNodeFn.name() type = currentObj.apiTypeStr() path = dagNodeFn.fullPathName() printOut = "" for i in range (0 , depth): printOut += "------>" printOut += name + " : " + type print printOut dagIterator.next ()
再复习一下上一集的编写命令插件 将制作输出一个字符串命令改为输出当前场景的完整层次结构。 就是将print "doIt…"更改为这节的功能代码然后更改一下当时定义命令的名字。
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 49 50 51 52 53 54 55 56 57 import maya.OpenMaya as OpenMayaimport maya.OpenMayaMPx as OpenMayaMPx import sys commandName = "printHierarchy" class pluginCommand (OpenMayaMPx.MPxCommand): def __init__ (self ): OpenMayaMPx.MPxCommand.__init__(self ) def doIt (self, argList ): dagIterator = OpenMaya.MItDag(OpenMaya.MItDag.kBreadthFirst, OpenMaya.MFn.kInvalid) dagNodeFn = OpenMaya.MFnDagNode() while (not dagIterator.isDone()): currentObj = dagIterator.currentItem() depth = dagIterator.depth() dagNodeFn.setObject(currentObj) name = dagNodeFn.name() type = currentObj.apiTypeStr() path = dagNodeFn.fullPathName() printOut = "" for i in range (0 , depth): printOut += "------>" printOut += name + " : " + type print printOut dagIterator.next () def cmdCreator (): return OpenMayaMPx.asMPxPtr(pluginCommand()) def initializePlugin (mobject ): mplugin = OpenMayaMPx.MFnPlugin(mobject) try : mplugin.registerCommand(commandName, cmdCreator) except : sys.stderr.write("Failed to register command :" + commandName) def uninitializePlugin (mobject ): mplugin = OpenMayaMPx.MFnPlugin(mobject) try : mplugin.deregisterCommand(commandName) except : sys.stderr.write("Failed to de-register command:" + commandName)
第八集 带参数的自定义命令
命令与参数:
MSyntax和MArgDatabase
parsing:语法分析 storing:储存 retrieving:检索 通过MSyntax定义我们的脚本命令接受什么类型的flag还有arguments还有object,然后maya会判断这个传入的参数是否可以接受。 MArgDatabase是一个类,它可以分析储存与检索flag和flag arguments 和object 如果我们更加深入地了解MArgDatabase,我们会发现它派生了MArgParser类 通过MSyntax和MArgDatabase我们可以接受标志参数和对象并解析他们并存储他们,基于这些值我们可以执行某些操作。
maya 的undo 和redo
maya在执行一个命令时,undo(撤销)框架下会存储相同的命令,当执行undo命令时,undo框架下的命令将会移动到redo框架上面。 当我们自己自定义一个命令时也要同时定义一个undo和redo函数。
新增代码过程
1.首先在新建的命令类之外定义一个syntaxCreator函数,写一个MSyntax类的对象,为这个对象定义新增的标志(flag),然后返回这个对象。 2.在初始化注册函数中的注册方法中新增刚才定义的带标志的语法对象(通过synaxCreator函数来得到(不需要在函数后面加括号))。 3.在命令类之内新建一个argmentParser(参数解析器)函数用来解析argList,通过MArgDatabase类中的方法,来判断传入的标志与参数。 4.在doIt函数中首先使用这个argmentParser函数来解析参数,然后根据参数的内容有无(默认参数为None)来决定执行redoIt(真正的实现执行功能的函数),之所以使用将实现功能的命令都放到redoIt函数中是因为当我们执行了undo(撤销)操作后会调用undoIt函数,然后如果再使用redo(重做;取消撤销)操作后会执行redoIt函数,redo后就需要再次实现功能了,因此把功能命令都放到redoIt函数中,doIt函数调用redoIt就好了。 5.redoIt函数中的逻辑如下: 首先读取所选择的物体,然后读取物体的点的位置信息,然后创建一个粒子发射系统,然后将粒子系统发射的粒子移动到物体点的位置上去。 6.定义isUndoable函数,返回True,意思是定义我们要新加的功能是可以撤销的。 7.定义undoIt函数,当我们执行撤销操作时会调用这个函数。 8.Maya api中MStatus的更新https://zhuanlan.zhihu.com/p/508453168 在2013后的版本中,Maya_api将MStatus从库中删除了,在编写脚本时要用python自带的异常捕获机制来代替。 若继续使用MStatus会报错‘module’ object has no attribute ‘MStatus’ 简单的代替方法: 原:OpenMaya.MStatus.kUnKonwnParameter更改为return ‘unknown’ 原OpenMaya.MStatus.kSuccess的,直接删掉就好了。
全部代码
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 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 import maya.OpenMaya as OpenMayaimport maya.OpenMayaMPx as OpenMayaMPx import sys import maya.OpenMayaFX as OpenMayaFXcommandName = "vertexPartial" kHelpFlag = "-h" kHelpLongFlag = "-help" kSparseFlag = "-s" kSparseLongFlag = "-sparse" helpMessage = "This command is used to attach a particle on each vertex of a poly mesh" class pluginCommand (OpenMayaMPx.MPxCommand): sparse = None def __init__ (self ): OpenMayaMPx.MPxCommand.__init__(self ) def argumentParser (self, argList ): syntax = self .syntax() parsedArguments = OpenMaya.MArgDatabase(syntax, argList) if parsedArguments.isFlagSet(kSparseFlag): self .sparse = parsedArguments.flagArgumentDouble(kSparseFlag, 0 ) if parsedArguments.isFlagSet(kSparseLongFlag): self .sparse = parsedArguments.flagArgumentDouble(kSparseFlag, 0 ) if parsedArguments.isFlagSet(kHelpFlag): self .setResult(helpMessage) if parsedArguments.isFlagSet(kHelpLongFlag): self .setResult(helpMessage) def isUndoable (self ): return True def undoIt (self ): print "undo" mFnDagNode = OpenMaya.MFnDagNode(self .mobj_particle) mDagMod = OpenMaya.MDagModifier() mDagMod.deleteNode(mFnDagNode.parent(0 )) mDagMod.doIt() def redoIt (self ): mSel = OpenMaya.MSelectionList() mDagPath = OpenMaya.MDagPath() mFnMesh = OpenMaya.MFnMesh() OpenMaya.MGlobal.getActiveSelectionList(mSel) if mSel.length() >= 1 : try : mSel.getDagPath(0 , mDagPath) mFnMesh.setObject(mDagPath) except : print "Select a poly mesh" return 'unknown' else : print "Select a poly mesh" return 'unknown' mPointArray = OpenMaya.MPointArray() mFnMesh.getPoints(mPointArray, OpenMaya.MSpace.kWorld) mFnParticle = OpenMayaFX.MFnParticleSystem() self .mobj_particle = mFnParticle.create() mFnParticle = OpenMayaFX.MFnParticleSystem(self .mobj_particle) counter = 0 for i in xrange(mPointArray.length()): if i % self .sparse == 0 : mFnParticle.emit(mPointArray[i]) counter += 1 print "Total Points :" + str (counter) mFnParticle.saveInitialState() def doIt (self, argList ): self .argumentParser(argList) if self .sparse != None : self .redoIt() def cmdCreator (): return OpenMayaMPx.asMPxPtr(pluginCommand()) def syntaxCreator (): syntax = OpenMaya.MSyntax() syntax.addFlag(kHelpFlag, kHelpLongFlag) syntax.addFlag(kSparseFlag, kSparseLongFlag, OpenMaya.MSyntax.kDouble) return syntax def initializePlugin (mobject ): mplugin = OpenMayaMPx.MFnPlugin(mobject) try : mplugin.registerCommand(commandName, cmdCreator, syntaxCreator) except : sys.stderr.write("Failed to register command :" + commandName) def uninitializePlugin (mobject ): mplugin = OpenMayaMPx.MFnPlugin(mobject) try : mplugin.deregisterCommand(commandName) except : sys.stderr.write("Failed to de-register command:" + commandName)
第九集 Dependency graph
Dirty Propagation 、Push & Pull Mechanism、 Lazy Evaluation
这三个术语通过举例来理解:假如你必须洗衣服,然后走进洗衣房,你会看到那里有几个洗衣机几个干衣机和几个与干衣机相关的桶,它们上面还有计时器记录剩余的时间,你根据它们的剩余时间将它们标记为红色,当你真正去洗衣烘干放入桶中后你会将它们标记为绿色。这是Dirty propagation(红色) 以及 Push&Pull Mechanism(绿色),这个过程叫LazyEvaluation 这样的好处是,当我们做出更新节点的操作时,maya只会更新操作所需要的节点,而不会对其他节点做操作,不会更新场景中的所有节点,这样减轻了性能消耗。
第十集 Writing Custom/DG Nodes
什么是Dependency graph node:
就是一个节点,有数据,有连接接口,有处理数据的能力。
设计一个Dependency graph node
设计一个Dependency graph node 的过程和创建一个自定义命令差不多: 自定义的类需要继承MPxNode,__init__以及compute函数(类似与doIt函数),compute函数为节点最核心的方法,我们所有的算法都要存到这里通过计算返回不同的结果。 然后类外需要定义的函数: 1.creator function 2.initialize函数(初始化属性)3.initialize plugin(注册) 4.uninitialize plugin(取消注册)
针对不同的属性类型(数字,灯光,信息)使用不同的属性函数库
initialize 函数的构建过程
首先创建一个MFnNumericAttribute函数库,通过这个函数库来创建属性并设置属性的名字初始值(传递给自定义类中创建的对应属性名字的MObject)然后定义特征(是否可读可写可存储可k关键帧),然后将属性绑定到节点上(因为通过MFn得到的属性是MObject类型)(因为我们的类继承了对应的MPx,因此使用继承的类中的addAttribute方法来讲MObject类型的属性附加到节点(类)上),最后设置电路(属性之间的联系,即定义输入(Input)输出(Output),通过使用继承的类中的attributeAffects方法,意思是方法内的属性参数,左边的属性影响右边的属性。Affects是影响的意思)
nodeInitializer函数中的create Attribute 创建属性的流程:
通过MFn创建MObject类型的属性,然后设置属性,有readable(设置此属性是否可读。如果一个属性是可读的,那么它可以用作依赖图连接中的源。),writable(如果是可写的,那么这个属性就可以作为其他属性的目标(下游)),storable(设置此属性是否可存储。如果属性是可存储的,那么当节点存储到文件中时,它将被写入。这应该只在节点创建者的初始化调用中调用。),keyable(设置此属性是否应接受关键帧数据。这应该只在节点创建者的初始化调用中调用。可键属性将由AutoKey和Set Keyframe UI进行键控。非键属性可以防止用户通过为键控提供的明显UI设置键。不可键性并不会阻碍向属性添加键。)我们也可以设置属性的最小值与最大值。通过使用函数集中的setMin和setMax来达到。 这是创建属性后的默认的设置的属性
实例:制作一个轮子节点
通过平移使轮子自动旋转 公式与节点大致形状:根据平移的距离来控制旋转的角度的多少(一个圆的周长的距离为360度) nodeId: 每个节点都应该有对应的ID来对应它,无论是maya的还是用户自定义的(UUID)
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 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 import maya.OpenMaya as OpenMayaimport maya.OpenMayaMPx as OpenMayaMPx import sys nodeName = "WheelNode" nodeId = OpenMaya.MTypeId(0x100fff ) class WheelNode (OpenMayaMPx.MPxNode): inRadius = OpenMaya.MObject() inTranslate = OpenMaya.MObject() outRotate = OpenMaya.MObject() def __init__ (self ): OpenMayaMPx.MPxNode.__init__(self ) def compute (self, plug, dataBlock ): ''' rotate = translate/(2*3.14*radius)*(-360) :param plug: 节点属性 :param dataBlock:节点属性中的数据 :return: ''' if plug == WheelNode.outRotate: dataHandleRadius = dataBlock.inputValue(WheelNode.inRadius) dataHandleTranslate = dataBlock.inputValue(WheelNode.inTranslate) inRadiusVal = dataHandleRadius.asFloat() inTranslateVal = dataHandleTranslate.asFloat() outRotate = float (inTranslateVal) / float (2 * 3.14 * inRadiusVal) * (-360 ) dataHandleRotate = dataBlock.outputValue(WheelNode.outRotate) dataHandleRotate.setFloat(outRotate) dataBlock.setClean(plug) else : return 'unknown' def doIt (self, argList ): self .argumentParser(argList) if self .sparse != None : self .redoIt() def nodeCreator (): return OpenMayaMPx.asMPxPtr(WheelNode()) def nodeInitializer (): mFnAttr = OpenMaya.MFnNumericAttribute() WheelNode.inTranslate = mFnAttr.create("translate" , "t" , OpenMaya.MFnNumericData.kFloat, 0.0 ) mFnAttr.setReadable(1 ) mFnAttr.setWritable(1 ) mFnAttr.setStorable(1 ) mFnAttr.setKeyable(1 ) WheelNode.inRadius = mFnAttr.create("radius" , "r" , OpenMaya.MFnNumericData.kFloat, 0.0 ) mFnAttr.setReadable(1 ) mFnAttr.setWritable(1 ) mFnAttr.setStorable(1 ) mFnAttr.setKeyable(1 ) WheelNode.outRotate = mFnAttr.create("rotate" , "rot" , OpenMaya.MFnNumericData.kFloat) mFnAttr.setReadable(1 ) mFnAttr.setWritable(0 ) mFnAttr.setStorable(0 ) mFnAttr.setKeyable(0 ) WheelNode.addAttribute(WheelNode.inRadius) WheelNode.addAttribute(WheelNode.inTranslate) WheelNode.addAttribute(WheelNode.outRotate) WheelNode.attributeAffects(WheelNode.inRadius, WheelNode.outRotate) WheelNode.attributeAffects(WheelNode.inTranslate, WheelNode.outRotate) def initializePlugin (mobject ): mplugin = OpenMayaMPx.MFnPlugin(mobject) try : mplugin.registerNode(nodeName, nodeId, nodeCreator, nodeInitializer, OpenMayaMPx.MPxNode.kDeformerNode) except : sys.stderr.write("Failed to register command :" + nodeName) def uninitializePlugin (mobject ): mplugin = OpenMayaMPx.MFnPlugin(mobject) try : mplugin.deregisterNode(nodeName) except : sys.stderr.write("Failed to de-register command:" + nodeName)
创建自定义节点后的使用流程
通过这个命令加载自定义节点 import maya.cmds as cmds cmds.loadPlugin(r’D:\ZhangRuiChen\pythonProject\wheelNode.py ’) 然后就可以在节点编辑器中通过自定义节点名字搜索到这个节点了。 这里的unitConversion1是当我们的WheelNode节点的Rotate属性连接到pCylinder1中的RotateZ上后maya自动生成的。 连接好后就可以实现功能了。
第十一集 Deformer 变形器
什么是变形器 变形器是一个节点,下图是一个变形器所必须带有的属性 Input是几何体的输入,envelope控制变形的程度,范围是0~1,为0时不变形,为1时完全按照变形器的计算来变形。 我们的物体内部存在两个东西,一个groupId,一个inputGeom(这个是我们需要的) 编写代码的框架:
第十二~十五集 编写自定义变形器
可以参考的网站https://www.xingyulei.com/post/maya-api-deformer/ 自定义变形器的设计: 除默认自带的属性外,额外添加的自定义属性:amplitude(振幅,浮点型,范围是0~1),Displace(位移,浮点型,范围是0~10) 其中实现初始化功能的函数的内容步骤和自定义节点的步骤是相同的,有一点区别就是变形器节点具有默认的outputGeom属性,因此我们没必要再创建一个输出的属性,我们可以直接利用这个默认的outputGemo属性,那么怎么使用这个outputGemo属性呢? 教程中首先介绍了SWIG,然后使用了语句 SWIG - Simplified Wrapper Interface Generator 简化 包装器 接口 生成器 是允许开发人员使用脚本语言包装C++代码的工具,因为maya核心是由C++编写的 Autodesk 为我们提供了一种使用swig使用这些属性的方法 通过这个语句:outputGemo = OpenMayaMPx.cvar.MPxDeformerNode_outputGeom
从左到右依次是self, dataBlock, geoIterator, matrix, geometryIndex
第一步将特殊的指针(dataHandileInputArray)指向变形器的Input数组,第二步通过geometryIndex跳到相应的元素,第三步(dataHandleInputElement)指向其中的特殊的dataBlock,第四步通过OpenMayaMPx.cvar.MPxDeformerNode_inputValue()找到它的inputGeom(变形器的默认节点),然后通过.child方法将inputGeom的属性令dataHandleInputGeom指向。
整体代码第一版
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 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 import mathimport maya.OpenMaya as OpenMayaimport maya.OpenMayaMPx as OpenMayaMPx import sys nodeName = "RippleDeformer" nodeId = OpenMaya.MTypeId(0x102fff ) class Ripple (OpenMayaMPx.MPxDeformerNode): """ Commands ----> MPxCommand Custom ----> MPxNode Deformer ----> MPxDeformerNode """ mObj_Displace = OpenMaya.MObject() mObj_Amplitude = OpenMaya.MObject() def __init__ (self ): OpenMayaMPx.MPxDeformerNode.__init__(self ) def deform (self, dataBlock, geoIterator, matrix, geometryIndex ): """ 变形器的核心参数 :param dataBlock:数据块 :param geoIterator: 迭代器,例如遍历一个几何体的所有顶点时用的到 :param matrix: 矩阵 几何体的世界矩阵或受影响的网格 :param geometryIndex: 几何索引,当使用多个几何体时,所有几何体都进入这个属性 :return: """ input = OpenMayaMPx.cvar.MPxGeometryFilter_input dataHandleInputArray = dataBlock.inputArrayValue(input ) dataHandleInputArray.jumpToElement(geometryIndex) dataHandleInputElement = dataHandleInputArray.inputValue() inputGeom = OpenMayaMPx.cvar.MPxGeometryFilter_inputGeom dataHandleInputGeom = dataHandleInputElement.child(inputGeom) inMesh = dataHandleInputGeom.asMesh() envelope = OpenMayaMPx.cvar.MPxGeometryFilter_envelope dataHandleEnvelope = dataBlock.inputValue(envelope) envelopeValue = dataHandleEnvelope.asFloat() dataHandleAmplitude = dataBlock.inputValue(Ripple.mObj_Amplitude) amplitudeValue = dataHandleAmplitude.asFloat() dataHandleDisplace = dataBlock.inputValue(Ripple.mObj_Displace) displaceValue = dataHandleDisplace.asFloat() mFloatVectorArray_normal = OpenMaya.MFloatVectorArray() mFnMesh = OpenMaya.MFnMesh(inMesh) mFnMesh.getVertexNormals(False , mFloatVectorArray_normal, OpenMaya.MSpace.kObject) while not geoIterator.isDone(): pointPosition = geoIterator.position() pointPosition.x = pointPosition.x + math.sin(geoIterator.index() + displaceValue) * amplitudeValue * \ mFloatVectorArray_normal[geoIterator.index()].x * envelopeValue pointPosition.y = pointPosition.y + math.sin(geoIterator.index() + displaceValue) * amplitudeValue * \ mFloatVectorArray_normal[geoIterator.index()].y * envelopeValue pointPosition.z = pointPosition.z + math.sin(geoIterator.index() + displaceValue) * amplitudeValue * \ mFloatVectorArray_normal[geoIterator.index()].z * envelopeValue geoIterator.setPosition(pointPosition) geoIterator.next () def deformerCreator (): nodePtr = OpenMayaMPx.asMPxPtr(Ripple()) return nodePtr def nodeInitializer (): """ 写功能前先将流程写下来,然后每完成一个流程就标记一下那个流程 Create Attributes 创建属性 - check Attach Attributes 附加属性 - check Design Circuitry 设计电路图 - check """ mFnAttr = OpenMaya.MFnNumericAttribute() Ripple.mObj_Amplitude = mFnAttr.create("AmplitudeValue" , "AmplitudeVal" , OpenMaya.MFnNumericData.kFloat, 0.0 ) mFnAttr.setKeyable(1 ) mFnAttr.setMin(0.0 ) mFnAttr.setMax(1.0 ) Ripple.mObj_Displace = mFnAttr.create("DisplaceValue" , "DispVal" , OpenMaya.MFnNumericData.kFloat, 0.0 ) mFnAttr.setKeyable(1 ) mFnAttr.setMin(0.0 ) mFnAttr.setMax(10.0 ) Ripple.addAttribute(Ripple.mObj_Amplitude) Ripple.addAttribute(Ripple.mObj_Displace) ''' SWIG - Simplified Wrapper Interface Generator 简化 包装器 接口 生成器 是允许开发人员使用脚本语言包装C++代码的工具,因为maya核心是由C++编写的 Autodesk 为我们提供了一种使用swig使用这些属性的方法 ''' outputGeom = OpenMayaMPx.cvar.MPxGeometryFilter_outputGeom Ripple.attributeAffects(Ripple.mObj_Amplitude, outputGeom) Ripple.attributeAffects(Ripple.mObj_Displace, outputGeom) def initializePlugin (mobject ): mplugin = OpenMayaMPx.MFnPlugin(mobject, "Violet" , "1.0" ) try : mplugin.registerNode(nodeName, nodeId, nodeCreator, nodeInitializer) except : sys.stderr.write("Failed to register node: " + nodeName) raise def uninitializePlugin (mobject ): mplugin = OpenMayaMPx.MFnPlugin(mobject) try : mplugin.deregisterCommand(nodeId) except : sys.stderr.write("Failed to de-register node: " + nodeName) raise
优化后的代码
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 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 import mathimport maya.OpenMaya as OpenMayaimport maya.OpenMayaMPx as OpenMayaMPx import sys nodeName = "RippleDeformer" nodeId = OpenMaya.MTypeId(0x102fff ) class Ripple (OpenMayaMPx.MPxDeformerNode): """ Commands ----> MPxCommand Custom ----> MPxNode Deformer ----> MPxDeformerNode """ mObj_Displace = OpenMaya.MObject() mObj_Amplitude = OpenMaya.MObject() def __init__ (self ): OpenMayaMPx.MPxDeformerNode.__init__(self ) def deform (self, dataBlock, geoIterator, matrix, geometryIndex ): """ 变形器的核心参数 :param dataBlock:数据块 :param geoIterator: 迭代器,例如遍历一个几何体的所有顶点时用的到 :param matrix: 矩阵 几何体的世界矩阵或受影响的网格 :param geometryIndex: 几何索引,当使用多个几何体时,所有几何体都进入这个属性 :return: """ input = OpenMayaMPx.cvar.MPxGeometryFilter_input dataHandleInputArray = dataBlock.outputArrayValue(input ) dataHandleInputArray.jumpToElement(geometryIndex) dataHandleInputElement = dataHandleInputArray.outputValue() inputGeom = OpenMayaMPx.cvar.MPxGeometryFilter_inputGeom dataHandleInputGeom = dataHandleInputElement.child(inputGeom) inMesh = dataHandleInputGeom.asMesh() envelope = OpenMayaMPx.cvar.MPxGeometryFilter_envelope dataHandleEnvelope = dataBlock.inputValue(envelope) envelopeValue = dataHandleEnvelope.asFloat() dataHandleAmplitude = dataBlock.inputValue(Ripple.mObj_Amplitude) amplitudeValue = dataHandleAmplitude.asFloat() dataHandleDisplace = dataBlock.inputValue(Ripple.mObj_Displace) displaceValue = dataHandleDisplace.asFloat() mFloatVectorArray_normal = OpenMaya.MFloatVectorArray() mFnMesh = OpenMaya.MFnMesh(inMesh) mFnMesh.getVertexNormals(False , mFloatVectorArray_normal, OpenMaya.MSpace.kObject) mPointArray_meshVert = OpenMaya.MPointArray() while not geoIterator.isDone(): pointPosition = geoIterator.position() pointPosition.x = pointPosition.x + math.sin(geoIterator.index() + displaceValue) * amplitudeValue * \ mFloatVectorArray_normal[geoIterator.index()].x * envelopeValue pointPosition.y = pointPosition.y + math.sin(geoIterator.index() + displaceValue) * amplitudeValue * \ mFloatVectorArray_normal[geoIterator.index()].y * envelopeValue pointPosition.z = pointPosition.z + math.sin(geoIterator.index() + displaceValue) * amplitudeValue * \ mFloatVectorArray_normal[geoIterator.index()].z * envelopeValue mPointArray_meshVert.append(pointPosition) geoIterator.next () geoIterator.setAllPosiions(mPointArray_meshVert) def deformerCreator (): nodePtr = OpenMayaMPx.asMPxPtr(Ripple()) return nodePtr def nodeInitializer (): """ 写功能前先将流程写下来,然后每完成一个流程就标记一下那个流程 Create Attributes 创建属性 - check Attach Attributes 附加属性 - check Design Circuitry 设计电路图 - check """ mFnAttr = OpenMaya.MFnNumericAttribute() Ripple.mObj_Amplitude = mFnAttr.create("AmplitudeValue" , "AmplitudeVal" , OpenMaya.MFnNumericData.kFloat, 0.0 ) mFnAttr.setKeyable(1 ) mFnAttr.setMin(0.0 ) mFnAttr.setMax(1.0 ) Ripple.mObj_Displace = mFnAttr.create("DisplaceValue" , "DispVal" , OpenMaya.MFnNumericData.kFloat, 0.0 ) mFnAttr.setKeyable(1 ) mFnAttr.setMin(0.0 ) mFnAttr.setMax(10.0 ) Ripple.addAttribute(Ripple.mObj_Amplitude) Ripple.addAttribute(Ripple.mObj_Displace) ''' SWIG - Simplified Wrapper Interface Generator 简化 包装器 接口 生成器 是允许开发人员使用脚本语言包装C++代码的工具,因为maya核心是由C++编写的 Autodesk 为我们提供了一种使用swig使用这些属性的方法 ''' outputGeom = OpenMayaMPx.cvar.MPxGeometryFilter_outputGeom Ripple.attributeAffects(Ripple.mObj_Amplitude, outputGeom) Ripple.attributeAffects(Ripple.mObj_Displace, outputGeom) def initializePlugin (mobject ): mplugin = OpenMayaMPx.MFnPlugin(mobject, "Violet" , "1.0" ) try : mplugin.registerNode(nodeName, nodeId, deformerCreator, nodeInitializer, OpenMayaMPx.MPxNode.kDeformerNode) except : sys.stderr.write("Failed to register node: " + nodeName) raise def uninitializePlugin (mobject ): mplugin = OpenMayaMPx.MFnPlugin(mobject) try : mplugin.deregisterCommand(nodeId) except : sys.stderr.write("Failed to de-register node: " + nodeName) raise
增加了绘制功能的代码
增加绘制功能需要两个步骤,第一步是读取几何体的所有顶点,第二步是使用权重值去计算。 跟教程中的代码一样 教程中也就加了个weight = self.weightValue(dataBlock, geometryIndex, geoIterator.index()),然后通过weight来改变计算步骤,但是我的2018.5的版本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 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 import mathimport maya.OpenMaya as OpenMayaimport maya.OpenMayaMPx as OpenMayaMPx import sys nodeName = "RippleDeformer" nodeId = OpenMaya.MTypeId(0x102fff ) class Ripple (OpenMayaMPx.MPxDeformerNode): """ Commands ----> MPxCommand Custom ----> MPxNode Deformer ----> MPxDeformerNode """ mObj_Displace = OpenMaya.MObject() mObj_Amplitude = OpenMaya.MObject() def __init__ (self ): OpenMayaMPx.MPxDeformerNode.__init__(self ) def deform (self, dataBlock, geoIterator, matrix, geometryIndex ): """ 变形器的核心参数 :param dataBlock:数据块 :param geoIterator: 迭代器,例如遍历一个几何体的所有顶点时用的到 :param matrix: 矩阵 几何体的世界矩阵或受影响的网格 :param geometryIndex: 几何索引,当使用多个几何体时,所有几何体都进入这个属性 :return: """ input = OpenMayaMPx.cvar.MPxGeometryFilter_input dataHandleInputArray = dataBlock.outputArrayValue(input ) dataHandleInputArray.jumpToElement(geometryIndex) dataHandleInputElement = dataHandleInputArray.outputValue() inputGeom = OpenMayaMPx.cvar.MPxGeometryFilter_inputGeom dataHandleInputGeom = dataHandleInputElement.child(inputGeom) inMesh = dataHandleInputGeom.asMesh() envelope = OpenMayaMPx.cvar.MPxGeometryFilter_envelope dataHandleEnvelope = dataBlock.inputValue(envelope) envelopeValue = dataHandleEnvelope.asFloat() dataHandleAmplitude = dataBlock.inputValue(Ripple.mObj_Amplitude) amplitudeValue = dataHandleAmplitude.asFloat() dataHandleDisplace = dataBlock.inputValue(Ripple.mObj_Displace) displaceValue = dataHandleDisplace.asFloat() mFloatVectorArray_normal = OpenMaya.MFloatVectorArray() mFnMesh = OpenMaya.MFnMesh(inMesh) mFnMesh.getVertexNormals(False , mFloatVectorArray_normal, OpenMaya.MSpace.kObject) mPointArray_meshVert = OpenMaya.MPointArray() while not geoIterator.isDone(): pointPosition = geoIterator.position() weight = self .weightValue(dataBlock, geometryIndex, geoIterator.index()) pointPosition.x = pointPosition.x + math.sin(geoIterator.index() + displaceValue) * amplitudeValue * \ mFloatVectorArray_normal[geoIterator.index()].x * weight * envelopeValue * 0.1 pointPosition.y = pointPosition.y + math.sin(geoIterator.index() + displaceValue) * amplitudeValue * \ mFloatVectorArray_normal[geoIterator.index()].y * weight * envelopeValue * 0.1 pointPosition.z = pointPosition.z + math.sin(geoIterator.index() + displaceValue) * amplitudeValue * \ mFloatVectorArray_normal[geoIterator.index()].z * weight * envelopeValue * 0.1 mPointArray_meshVert.append(pointPosition) geoIterator.next () geoIterator.setAllPositions(mPointArray_meshVert) def deformerCreator (): nodePtr = OpenMayaMPx.asMPxPtr(Ripple()) return nodePtr def nodeInitializer (): """ 写功能前先将流程写下来,然后每完成一个流程就标记一下那个流程 Create Attributes 创建属性 - check Attach Attributes 附加属性 - check Design Circuitry 设计电路图 - check """ mFnAttr = OpenMaya.MFnNumericAttribute() Ripple.mObj_Amplitude = mFnAttr.create("AmplitudeValue" , "AmplitudeVal" , OpenMaya.MFnNumericData.kFloat, 0.0 ) mFnAttr.setKeyable(1 ) mFnAttr.setMin(0.0 ) mFnAttr.setMax(1.0 ) Ripple.mObj_Displace = mFnAttr.create("DisplaceValue" , "DispVal" , OpenMaya.MFnNumericData.kFloat, 0.0 ) mFnAttr.setKeyable(1 ) mFnAttr.setMin(0.0 ) mFnAttr.setMax(10.0 ) Ripple.addAttribute(Ripple.mObj_Amplitude) Ripple.addAttribute(Ripple.mObj_Displace) ''' SWIG - Simplified Wrapper Interface Generator 简化 包装器 接口 生成器 是允许开发人员使用脚本语言包装C++代码的工具,因为maya核心是由C++编写的 Autodesk 为我们提供了一种使用swig使用这些属性的方法 ''' outputGeom = OpenMayaMPx.cvar.MPxGeometryFilter_outputGeom Ripple.attributeAffects(Ripple.mObj_Amplitude, outputGeom) Ripple.attributeAffects(Ripple.mObj_Displace, outputGeom) def initializePlugin (mobject ): mplugin = OpenMayaMPx.MFnPlugin(mobject, "Violet" , "1.0" ) try : mplugin.registerNode(nodeName, nodeId, deformerCreator, nodeInitializer, OpenMayaMPx.MPxNode.kDeformerNode) except : sys.stderr.write("Failed to register node: " + nodeName) raise def uninitializePlugin (mobject ): mplugin = OpenMayaMPx.MFnPlugin(mobject) try : mplugin.deregisterNode(nodeId) except : sys.stderr.write("Failed to de-register node: " + nodeName) raise
为自定义变形器添加辅助对象
什么是辅助对象/节点 Accessary object/nodes 创建它是为了方便用户使用变形功能用的。 辅助对象节点需要有两个特征: 1.它是帮助变形器变形的。2.当辅助对象被删除时,变形器节点也应当一起被删除 因为它是帮助变形器获得变形的,因此我们需要将它用在deformer函数中的transform中 为了实现当辅助对象被删除时,变形器也一起被删除,应当将辅助对象的世界度量属性与变形器节点的矩阵属性关联起来。 在本例中辅助节点就是一个定位器,可以通过定位器x轴的移动从而改变变形器中的变形效果。 添加过程:首先需要为变形器创建矩阵属性, 刚开始和之前的节点属性添加过程一样,在节点初始化中新建矩阵属性函数库,通过函数库创建属性(创建的同时由类中定义的空的MObject对象来接收)并设置权限(mFnMatrixAttr.setStorable(0)mFnMatrixAttr.setConnectable(1)),然后设计连接线路。初始化完成后在类中通过dataBlock得到指向数据的handle(MObject),然后通过这个handle得到Matrix的值。 然后为了完成创建辅助对象节点,我们需要定义Deformer类中的两个函数(AccessoryNodeSetup,AccessoryAttribute)。然后通过在变形器类中读取辅助对象的矩阵属性中的变换属性的值来改变变形器的算法,从而实现辅助对象影响变形效果。
AccessoryNodeSetup,这个函数是用来创建辅助对象物体,以及连接辅助对象的世界度量属性与变形器的矩阵属性
属性之间的连接: 2.AccessoryAttribute,这个函数用来返回类中的MObject对象(Ripple.mObj_Matrix)
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 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 import mathimport maya.OpenMaya as OpenMayaimport maya.OpenMayaMPx as OpenMayaMPx import sys nodeName = "RippleDeformer" nodeId = OpenMaya.MTypeId(0x102fff ) class Ripple (OpenMayaMPx.MPxDeformerNode): """ Commands ----> MPxCommand Custom ----> MPxNode Deformer ----> MPxDeformerNode """ mObj_Displace = OpenMaya.MObject() mObj_Amplitude = OpenMaya.MObject() mObj_Matrix = OpenMaya.MObject() def __init__ (self ): OpenMayaMPx.MPxDeformerNode.__init__(self ) def deform (self, dataBlock, geoIterator, matrix, geometryIndex ): """ 变形器的核心参数 :param dataBlock:数据块 :param geoIterator: 迭代器,例如遍历一个几何体的所有顶点时用的到 :param matrix: 矩阵 几何体的世界矩阵或受影响的网格 :param geometryIndex: 几何索引,当使用多个几何体时,所有几何体都进入这个属性 :return: """ input = OpenMayaMPx.cvar.MPxGeometryFilter_input dataHandleInputArray = dataBlock.outputArrayValue(input ) dataHandleInputArray.jumpToElement(geometryIndex) dataHandleInputElement = dataHandleInputArray.outputValue() inputGeom = OpenMayaMPx.cvar.MPxGeometryFilter_inputGeom dataHandleInputGeom = dataHandleInputElement.child(inputGeom) inMesh = dataHandleInputGeom.asMesh() envelope = OpenMayaMPx.cvar.MPxGeometryFilter_envelope dataHandleEnvelope = dataBlock.inputValue(envelope) envelopeValue = dataHandleEnvelope.asFloat() dataHandleAmplitude = dataBlock.inputValue(Ripple.mObj_Amplitude) amplitudeValue = dataHandleAmplitude.asFloat() dataHandleDisplace = dataBlock.inputValue(Ripple.mObj_Displace) displaceValue = dataHandleDisplace.asFloat() dataHandleMatrix = dataBlock.inputValue(Ripple.mObj_Matrix) matrixValue = dataHandleMatrix.asMatrix() mTransMatrix = OpenMaya.MTransformationMatrix(matrixValue) translationValue = mTransMatrix.getTranslation(OpenMaya.MSpace.kObject) mFloatVectorArray_normal = OpenMaya.MFloatVectorArray() mFnMesh = OpenMaya.MFnMesh(inMesh) mFnMesh.getVertexNormals(False , mFloatVectorArray_normal, OpenMaya.MSpace.kObject) mPointArray_meshVert = OpenMaya.MPointArray() while not geoIterator.isDone(): pointPosition = geoIterator.position() weight = self .weightValue(dataBlock, geometryIndex, geoIterator.index()) pointPosition.x = pointPosition.x + math.sin( geoIterator.index() + displaceValue + translationValue[0 ]) * amplitudeValue * mFloatVectorArray_normal[ geoIterator.index()].x * weight * envelopeValue * 0.1 pointPosition.y = pointPosition.y + math.sin( geoIterator.index() + displaceValue + translationValue[0 ]) * amplitudeValue * mFloatVectorArray_normal[ geoIterator.index()].y * weight * envelopeValue * 0.1 pointPosition.z = pointPosition.z + math.sin( geoIterator.index() + displaceValue + translationValue[0 ]) * amplitudeValue * mFloatVectorArray_normal[ geoIterator.index()].z * weight * envelopeValue * 0.1 mPointArray_meshVert.append(pointPosition) geoIterator.next () geoIterator.setAllPositions(mPointArray_meshVert) def accessoryNodeSetup (self, dagModifier ): mObjLocator = dagModifier.createNode('locator' ) mFnDependLocator = OpenMaya.MFnDependencyNode(mObjLocator) mPlugWorld = mFnDependLocator.findPlug('worldMatrix' ) mObj_WorldAttr = mPlugWorld.attribute() mStatusConnect = dagModifier.connect(mObjLocator, mObj_WorldAttr, self .thisMObject(), Ripple.mObj_Matrix) return mStatusConnect def accessoryAttribute (self ): return Ripple.mObj_Matrix def deformerCreator (): nodePtr = OpenMayaMPx.asMPxPtr(Ripple()) return nodePtr def nodeInitializer (): """ 写功能前先将流程写下来,然后每完成一个流程就标记一下那个流程 Create Attributes 创建属性 - check Attach Attributes 附加属性 - check Design Circuitry 设计电路图 - check """ mFnAttr = OpenMaya.MFnNumericAttribute() Ripple.mObj_Amplitude = mFnAttr.create("AmplitudeValue" , "AmplitudeVal" , OpenMaya.MFnNumericData.kFloat, 0.0 ) mFnAttr.setKeyable(1 ) mFnAttr.setMin(0.0 ) mFnAttr.setMax(1.0 ) Ripple.mObj_Displace = mFnAttr.create("DisplaceValue" , "DispVal" , OpenMaya.MFnNumericData.kFloat, 0.0 ) mFnAttr.setKeyable(1 ) mFnAttr.setMin(0.0 ) mFnAttr.setMax(10.0 ) mFnMatrixAttr = OpenMaya.MFnMatrixAttribute() Ripple.mObj_Matrix = mFnMatrixAttr.create('MatrixAttribute' , 'matAttr' ) mFnMatrixAttr.setStorable(0 ) mFnMatrixAttr.setConnectable(1 ) Ripple.addAttribute(Ripple.mObj_Amplitude) Ripple.addAttribute(Ripple.mObj_Displace) Ripple.addAttribute(Ripple.mObj_Matrix) ''' SWIG - Simplified Wrapper Interface Generator 简化 包装器 接口 生成器 是允许开发人员使用脚本语言包装C++代码的工具,因为maya核心是由C++编写的 Autodesk 为我们提供了一种使用swig使用这些属性的方法 ''' outputGeom = OpenMayaMPx.cvar.MPxGeometryFilter_outputGeom Ripple.attributeAffects(Ripple.mObj_Amplitude, outputGeom) Ripple.attributeAffects(Ripple.mObj_Displace, outputGeom) Ripple.attributeAffects(Ripple.mObj_Matrix, outputGeom) def initializePlugin (mobject ): mplugin = OpenMayaMPx.MFnPlugin(mobject, "Violet" , "1.0" ) try : mplugin.registerNode(nodeName, nodeId, deformerCreator, nodeInitializer, OpenMayaMPx.MPxNode.kDeformerNode) except : sys.stderr.write("Failed to register node: " + nodeName) raise def uninitializePlugin (mobject ): mplugin = OpenMayaMPx.MFnPlugin(mobject) try : mplugin.deregisterNode(nodeId) except : sys.stderr.write("Failed to de-register node: " + nodeName) raise
第十六集 创建自定义定位器
参考网站:https://blog.csdn.net/u013148608/article/details/105694909 因为新版本已经不支持这个了glRenderer = OpenMayaRender.MHardwareRenderer.theRenderer(),因此就这吧,看看就好。 步骤:1.定义定位器的形状2.定义边界框3.定义边界框边界的大小
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 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 import maya.OpenMaya as OpenMayaimport maya.OpenMayaMPx as OpenMayaMPx import sys import maya.OpenMayaRender as OpenMayaRendernodeName = "LeftFoot" nodeId = OpenMaya.MTypeId(0x100fff ) glRenderer = OpenMayaRender.MHardwareRenderer.theRenderer() glFT = glRenderer.glFunctionTable() class LocatorNode (OpenMayaMPx.MPxLocatorNode): def __init__ (self ): OpenMayaMPx.MPxLocatorNode.__init__(self ) def compute (self, plug, dataBlock ): return OpenMaya.kUnknownParameter def draw (self, view, path, style, status ): view.beginGL() glFT.glPushAttrib(OpenMayaRender.MGL_CURRENT_BIT) glFT.glEnable(OpenMayaRender.MGL_BLEND) glFT.glBlendFunc(OpenMayaRender.MGL_SRC_ALPHA, OpenMayaRender.MGL_ONE_MINUS_SRC_ALPHA) if status == view.kActive: glFT.glColor4f(0.2 , 0.5 , 0.1 , 0.3 ) elif status == view.kLead: glFT.glColor4f(0.5 , 0.2 , 0.1 , 0.3 ) elif status == view.kDormant: glFT.glColor4f(0.1 , 0.1 , 0.1 , 0.3 ) glFT.glBegin() glFT.glVertex3f(-0.031 , 0 , -2.875 ) glFT.glVertex3f(-0.939 , 0.1 , -2.370 ) glFT.glVertex3f(-1.175 , 0.2 , -1.731 ) glFT.glVertex3f(-0.603 , 0.3 , 1.060 ) glFT.glVertex3f(-0.473 , 0.3 , 1.026 ) glFT.glVertex3f(-0.977 , 0.2 , -1.731 ) glFT.glVertex3f(-0.809 , 0.1 , -2.337 ) glFT.glVertex3f(-0.035 , 0 , -2.807 ) glFT.glEnd() glFT.glBegin() glFT.glVertex3f(-0.587 , 0.3 , 1.33 ) glFT.glVertex3f(0.442 , 0.3 , 1.33 ) glFT.glVertex3f(0.442 , 0.3 , 1.92 ) glFT.glVertex3f(0.230 , 0.3 , 2.24 ) glFT.glVertex3f(-0.442 , 0.3 , 2.25 ) glFT.glVertex3f(-0.635 , 0.3 , 1.92 ) glFT.glVertex3f(-0.567 , 0.3 , 1.35 ) glFT.glEnd() if status == view.kActive: glFT.glColor4f(0.2 , 0.5 , 0.1 , 1 ) elif status == view.kLead: glFT.glColor4f(0.5 , 0.2 , 0.1 , 1 ) elif status == view.kDormant: glFT.glColor4f(0.1 , 0.1 , 0.1 , 1 ) view.drawText("Left Foot" , OpenMaya.MPoint(0 , 0 , 0 ), view.kLeft) glFT.glDisable(OpenMayaRender.MGL_BLEND) glFT.glPopAttrib() view.endGL() def nodeCreator (): return OpenMayaMPx.asMPxPtr(LocatorNode()) def nodeInitializer (): pass def initializePlugin (mobject ): mplugin = OpenMayaMPx.MFnPlugin(mobject, "Violet" , "1.0" ) try : mplugin.registerNode(nodeName, nodeId, nodeCreator, nodeInitializer, OpenMayaMPx.MPxNode.kLocatorNode) except : sys.stderr.write("Failed to register node: " + nodeName) def uninitializePlugin (mobject ): mplugin = OpenMayaMPx.MFnPlugin(mobject) try : mplugin.deregisterCommand(nodeId) except : sys.stderr.write("Failed to de-register node: " + nodeName)
第十七~十九集 无缝IKFK混合与回调函数
先解释一下回调函数,回调函数可以理解为把函数当成变量传递给另一个函数,当变量的函数改变时也会同样的影响把函数当变量的那个函数。作为参数传递的那个函数叫做回调函数 我们通常通过MMessage来实现事件触发,当事件触发时会调用回调函数。 MMessage的种类与事件:
什么是IK FK ,IK是反向运动学,它是子关节影响父关节,FK是正向运动学,它是父关节影响子关节。 当我们制作IK时当添加了极向量约束时,我们将不能够通过父关节的旋转而影响子关节,因此制作一个在极向量约束的功能存在的情况下依然能够正确进行子关节影响父关节并且父关节也可以旋转影响子关节。 这个功能制作的关键是当添加IK后,关节会出现一个属性IKBlend 这个属性为1时则子关节移动会影响父关节,父关节不能主动移动,当IKBlend属性为0时,子关节的移动就不会影响父关节,父关节的旋转会带动子关节。以及如果父关节旋转后中间的那个受极向量约束的定位器位置也应该随着改变。
代码实现细节
我们要制作一个节点来实现这些功能,因此使用最开始的自定义轮子节点来写代码。 将轮子结点复制过来后更改一下初始设置:更改节点名字,将类名通过shift+F6统一改成CVG,然后因为节点不需要属性以及算法,将compute函数和nodeInitializer函数内容统统改为pass 定义两个模式,一个是FK,一个是IK,默认模式是FK,当我们选择IK控制柄或者选择joint3,或者选择极向量控制器,都会使模式切换成IK。 模式为FK时IKBlend的值设为0,并且将极向量控制器设置为不显示,因为FK模式下用不到极向量控制器,模式为IK时IKBlend的值设为1.并将极向量控制器约束在joint2的位置上。
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 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 import maya.OpenMaya as OpenMayaimport maya.OpenMayaMPx as OpenMayaMPx import sys nodeName = "CVGKNode" nodeId = OpenMaya.MTypeId(0x100fff ) class CVG (OpenMayaMPx.MPxNode): idCallback = [] joint1 = OpenMaya.MObject() joint2 = OpenMaya.MObject() joint3 = OpenMaya.MObject() activeEffector = OpenMaya.MObject() activeHandle = OpenMaya.MObject() activePoleVector = OpenMaya.MObject() activePoleVectorControl = OpenMaya.MObject() def __init__ (self ): OpenMayaMPx.MPxNode.__init__(self ) self .idCallback.append(OpenMaya.MEventMessage.addEventCallback("SelectionChanged" , self .callbackFunc)) self .idCallback.append(OpenMaya.MDGMessage.addNodeRemovedCallback(self .remove, "dependNode" )) def callbackFunc (self, *args ): print "Callback Function" mSel = OpenMaya.MSelectionList() OpenMaya.MGlobal.getActiveSelectionList(mSel) mItSelectionList = OpenMaya.MItSelectionList(mSel, OpenMaya.MFn.kDagNode) mode = "fk" mFnDependencyNode = OpenMaya.MFnDependencyNode() while not mItSelectionList.isDone(): mObj = OpenMaya.MObject() mItSelectionList.getDependNode(mObj) if mObj.apiTypeStr() == "kIkEffector" : self .activeEffector = mObj mode = "ik" break if self .activePoleVectorControl.apiTypeStr() != "kInvalid" : if OpenMaya.MFnDependencyNode(mObj).name() == OpenMaya.MFnDependencyNode( self .activePoleVectorControl).name(): mode = "ik" break mFnDependencyNode.setObject(mObj) mPlugArray_joint = OpenMaya.MPlugArray() mFnDependencyNode.getConnections((mPlugArray_joint)) for i in xrange(mPlugArray_joint.length()): mPlug_joint = mPlugArray_joint[i] mPlugArray2 = OpenMaya.MPlugArray() mPlug_joint.connectedTo(mPlugArray2, True , True ) mPlug2 = mPlugArray2[0 ] if mPlug2.node().apiTypeStr() == "kIkEffector" : self .activeEffector = mPlug2.node() mode = "ik" break mItSelectionList.next () ''' If IK-Effector is found then : - find IK-Handle ''' if self .activeEffector.apiTypeStr() == "kIkEffector" : mFnDependencyNode.setObject(self .activeEffector) mPlugArray_effector = OpenMaya.MPlugArray() mFnDependencyNode.getConnections(mPlugArray_effector) for i in xrange(mPlugArray_effector.length()): mPlug_effector = mPlugArray_effector[i] mPlugArray2 = OpenMaya.MPlugArray() mPlug_effector.connectedTo(mPlugArray2, True , True ) mPlug2 = mPlugArray2[0 ] if mPlug2.node().apiTypeStr() == "kIkHandle" : self .activeHandle = mPlug2.node() break """ If IK-Handle is found then: -find IK-Blend Plug -find IK-PoleVector """ if self .activeHandle.apiTypeStr() == "kIkHandle" : mFnDependencyNodeHandle = OpenMaya.MFnDependencyNode(self .activeHandle) mPlug_blendAttr = mFnDependencyNodeHandle.findPlug("ikBlend" ) mAttr_blendAttr = mPlug_blendAttr.attribute() mMFnAttribute = OpenMaya.MFnAttribute(mAttr_blendAttr) mMFnAttribute.setKeyable(0 ) mMFnAttribute.setChannelBox(0 ) mFnDependencyNode.setObject(self .activeHandle) mPlugArray_handle = OpenMaya.MPlugArray() mFnDependencyNode.getConnections(mPlugArray_handle) for i in xrange(mPlugArray_handle.length()): mPlug_handle = mPlugArray_handle[i] mPlugArray2 = OpenMaya.MPlugArray() mPlug_handle.connectedTo(mPlugArray2, True , True ) mPlug2 = mPlugArray2[0 ] if mPlug2.node().apiTypeStr() == "kPoleVectorConstraint" : self .activePoleVector = mPlug2.node() break """ If IK-PoleVector is found then: - find IK-PoleVector Control Curve """ if self .activePoleVector.apiTypeStr() == "kPoleVectorConstraint" : mFnDependencyNode.setObject(self .activePoleVector) mPlugArray_handle = OpenMaya.MPlugArray() mFnDependencyNode.getConnections(mPlugArray_handle) for i in xrange(mPlugArray_handle.length()): mPlug_handle = mPlugArray_handle[i] mPlugArray2 = OpenMaya.MPlugArray() mPlug_handle.connectedTo(mPlugArray2, True , True ) mPlug2 = mPlugArray2[0 ] if mPlug2.node().apiTypeStr() == "kTransform" : self .activePoleVectorControl = mPlug2.node() break """ If IK-PoleVector Control Curve is found then: - find middle joint of joint change, to whick this control should be attached. """ if self .activePoleVectorControl.apiTypeStr() == "kTransform" : mFnDependencyNode.setObject(self .activeEffector) mPlugArray_effector = OpenMaya.MPlugArray() mFnDependencyNode.getConnections(mPlugArray_effector) for i in xrange(mPlugArray_effector.length()): mPlug_effector = mPlugArray_effector[i] mPlugArray2 = OpenMaya.MPlugArray() mPlug_effector.connectedTo(mPlugArray2,True ,True ) mPlug2 = mPlugArray2[0 ] if mPlug2.node().apiTypeStr() == "kJoint" : self .joint3 = mPlug2.node() break mFnDependencyNode.setObject(self .activeHandle) mPlugArray_handle = OpenMaya.MPlugArray() mFnDependencyNode.getConnections(mPlugArray_handle) for i in xrange(mPlugArray_handle.length()): mPlug_handle = mPlugArray_handle[i] mPlugArray2 = OpenMaya.MPlugArray() mPlug_handle.connectedTo(mPlugArray2, True , True ) mPlug2 = mPlugArray2[0 ] if mPlug2.node().apiTypeStr() == "kJoint" : self .joint1 = mPlug2.node() break """ Find joints connected to Joint1 , and if any joint which is connected to Joint1 is also connected to Joint3 then it is Joint2 """ mObj_joint1Connections = OpenMaya.MObjectArray() mObj_joint3Connections = OpenMaya.MObjectArray() mFnDependencyNode.setObject(self .joint1) mPlugArray_joint1 = OpenMaya.MPlugArray() mPlug_joint1Scale = mFnDependencyNode.findPlug("scale" ) mPlugArray_joint1 = OpenMaya.MPlugArray() mPlug_joint1Scale.connectedTo(mPlugArray_joint1,True ,True ) for i in xrange(mPlugArray_joint1.length()): if mPlugArray_joint1[i].node().apiTypeStr() == "kJoint" : mObj_joint1Connections.append(mPlugArray_joint1[i].node()) mFnDependencyNode.setObject(self .joint3) mPlugArray_joint3 = OpenMaya.MPlugArray() mPlug_joint3Scale = mFnDependencyNode.findPlug("inverseScale" ) mPlugArray_joint3 = OpenMaya.MPlugArray() mPlug_joint3Scale.connectedTo(mPlugArray_joint3,True ,True ) for i in xrange(mPlugArray_joint3.length()): if mPlugArray_joint3[i].node().apiTypeStr() == "kJoint" : mObj_joint3Connections.append(mPlugArray_joint3[i].node()) mFnDependencyNode_temp1 = OpenMaya.MFnDependencyNode() mFnDependencyNode_temp3 = OpenMaya.MFnDependencyNode() for i in xrange(mObj_joint3Connections.length()): for j in xrange(mObj_joint3Connections.length()): mFnDependencyNode_temp1.setObject(mObj_joint1Connections[i]) mFnDependencyNode_temp3.setObject(mObj_joint3Connections[j]) if mFnDependencyNode_temp1.name() == mFnDependencyNode_temp3.name(): self .joint2 = mObj_joint3Connections[j] break def remove (self, *args ): try : OpenMaya.MSelectionList.add(self .thisMObject()) except : for i in xrange(len (self .idCallback)): try : OpenMaya.MEventMessage.removeCallback(self .idCallback[i]) except : pass try : OpenMaya.MDGMessage.removeCallback(self .idCallback[i]) except : pass def compute (self, plug, dataBlock ): pass def nodeCreator (): return OpenMayaMPx.asMPxPtr(CVG()) def nodeInitializer (): pass def initializePlugin (mobject ): mplugin = OpenMayaMPx.MFnPlugin(mobject, "Violet" , "1.0" ) try : mplugin.registerNode(nodeName, nodeId, nodeCreator, nodeInitializer) except : sys.stderr.write("Failed to register node: " + nodeName) def uninitializePlugin (mobject ): mplugin = OpenMayaMPx.MFnPlugin(mobject) try : mplugin.deregisterCommand(nodeId) except : sys.stderr.write("Failed to de-register node: " + nodeName)