在 maya 中创建操纵器的示例,根据官方文档描述,所有要创建的操纵器,都应该满足以下要求:
- 所有基本操纵器都应该在实例化的 OpenMayaMPx.MPxManipContainer() 容器中生成
- 在容器中实现 createChildren(self) 函数功能,使用与容器对应的方法生成指定的操纵器
- connectToDependNode(self, node) 函数用于链接操纵器到选中的节点的属性接口
- 完成 addPlugToManipConversion(theIndex) 和 manipToPlugConversion(index) 并通过其对应的回调函数,分别可以进行节点属性接口控制容器值,以及容器值控制节点属性接口的目的
以下示例插件完成类似Maya默认快捷键“T”的方向操纵器的功能:
# -*- coding:utf-8 -*-
# !/usr/bin/env python2
# Author: Mirror
# File: MayaPlugins_AimMinapulator.py
# Time: 2022-12-03 21:43
# Update: 2022-12-11 22:05
# Environment:PyCharm
# Blog: www.mirrorcg.com
# Description: 旨在给 arnold 面光源添加一个方向操控器,在大纲中不产生节点,可用于任何节点
# 插件工具架使用示例:
# import maya.mel as mm
# import maya.cmds as cmds
#
# if not cmds.shelfLayout("Shelf1",q=1,ex=1) :
# mainTopShelfTab = mm.eval('global string $gShelfTopLevel;string $a=$gShelfTopLevel;') # 获取工具架顶级布局
# newShelfLayout = cmds.shelfLayout("Shelf1",p=mainTopShelfTab) # 创建工具架分页
# cmds.loadPlugin("MayaPlugins_AimMinapulator.py")
# if not cmds.aimManipCtxCmd("aimManipCtxCmd1", ex=1, q=1):
# cmds.aimManipCtxCmd( 'spAimManipContext1' )
# cmds.setParent( 'AimMinap' )
# cmds.toolButton( 'aimManip', cl='toolCluster', t='spAimManipContext1', i1="aimManip.xpm" )
# 这将在工具架的"Shelf1"选项卡中创建一个名为"AimMinap"的新按钮。创建一个arnold 面光源,然后单击工具架上的按钮。选择对象时,将出现Z轴方向操纵器。
#
# 插件快捷键使用示例:
# 在快捷键中设置以下命令,并设置快捷键为 Ctrl+Shift+T(可自行设置):
# import maya.mel as mm
# import maya.cmds as cmds
# pluginNmae = 'Maya_pythonPlugin.py'
# try:
# if not cmds.pluginInfo(pluginNmae,l=True,q=1):
# cmds.loadPlugin(pluginNmae)
# except:
# cmds.warning(u"插件路径中没有名为'%s'的插件" % pluginNmae)
# raise
# if not cmds.aimManipCtxCmd("aimManipCtxCmd1", ex=1, q=1):
# cmds.aimManipCtxCmd( 'aimManipCtxCmd1' )
# mm.eval("setToolTo aimManipCtxCmd1")
# =========================================
import sys
import maya.OpenMaya as OpenMaya
import maya.OpenMayaUI as OpenMayaUI
import maya.OpenMayaMPx as OpenMayaMPx
aimManipId = OpenMaya.MTypeId(0x8, 133) # 自定义ID 防止冲突
contextCmdName = "aimManipCtxCmd"
nodeName = "aimManip"
class AimManip(OpenMayaMPx.MPxManipContainer):
def __init__(self):
OpenMayaMPx.MPxManipContainer.__init__(self)
self.fFreePointManip = OpenMaya.MDagPath()
self.fDirectionManip = OpenMaya.MDagPath()
self.fScaleManip = OpenMaya.MDagPath()
self.fNodePath = OpenMaya.MDagPath()
self.initiDirection = OpenMaya.MVector()
self.startDirection = OpenMaya.MVector() # 记录物体的初始位置
self.endDirection = OpenMaya.MVector() # 记录物体移动后的位置
def createChildren(self):
u"""添加基础操纵器"""
# FreePointTriadManip
self.fFreePointManip = self.addFreePointTriadManip("pointManip", "freePoint")
freePointTriadManipFn = OpenMayaUI.MFnFreePointTriadManip(self.fFreePointManip)
# # ScaleManip
# self.fScaleManip = self.addScaleManip("scaleManip", "scale")
# scaleManipFn = OpenMayaUI.MFnScaleManip(self.fScaleManip)
# DirectionManip
self.fDirectionManip = self.addDirectionManip("directionManip", "direction")
directionManipFn = OpenMayaUI.MFnDirectionManip(self.fDirectionManip)
directionManipFn.setNormalizeDirection(False)
def addPlug(self, node):
u"""添加记录属性保存操作历史"""
dagNodeFn = OpenMaya.MFnDagNode(node)
dagNodeFn.getPath(self.fNodePath)
nodeFn = OpenMaya.MFnDependencyNode()
nodeFn.setObject(node)
# 获取选中节点的朝向
_Matrix = OpenMaya.MTransformationMatrix(self.fNodePath.inclusiveMatrix()).asMatrix() # .asRotateMatrix()
self.initiDirection = OpenMaya.MVector(_Matrix(2, 0), _Matrix(2, 1), _Matrix(2, 2))
if not nodeFn.hasAttribute("arrowDirection"):
numericFn = OpenMaya.MFnNumericAttribute()
aArrow2DirectionX = numericFn.create("arrowDirectionX", "ax", OpenMaya.MFnNumericData.kDouble,
self.initiDirection(0) * -5)
aArrow2DirectionY = numericFn.create("arrowDirectionY", "ay", OpenMaya.MFnNumericData.kDouble,
self.initiDirection(1) * -5)
aArrow2DirectionZ = numericFn.create("arrowDirectionZ", "az", OpenMaya.MFnNumericData.kDouble,
self.initiDirection(2) * -5)
aArrow2Direction = numericFn.create("arrowDirection", "dir", aArrow2DirectionX, aArrow2DirectionY,
aArrow2DirectionZ)
nodeFn.addAttribute(aArrow2Direction)
else:
directionX = nodeFn.findPlug("arrowDirectionX").asFloat()
directionY = nodeFn.findPlug("arrowDirectionY").asFloat()
directionZ = nodeFn.findPlug("arrowDirectionZ").asFloat()
length = (directionX ** 2 + directionY ** 2 + directionZ ** 2) ** 0.5
self.initiDirection = self.initiDirection * (-length)
nodeFn.findPlug("arrowDirectionX").setFloat(self.initiDirection(0))
nodeFn.findPlug("arrowDirectionY").setFloat(self.initiDirection(1))
nodeFn.findPlug("arrowDirectionZ").setFloat(self.initiDirection(2))
def connectToDependNode(self, node):
u"""链接操纵器到选中的节点的属性接口"""
# 获取 DAG path
dagNodeFn = OpenMaya.MFnDagNode(node)
dagNodeFn.getPath(self.fNodePath)
parentNode = dagNodeFn.parent(0)
parentNodeFn = OpenMaya.MFnDagNode(parentNode)
nodeFn = OpenMaya.MFnDependencyNode()
nodeFn.setObject(node)
# 链接操纵器到对应的接口
# FreePointTriadManip
freePointManipFn = OpenMayaUI.MFnFreePointTriadManip(self.fFreePointManip)
try:
tPlug = nodeFn.findPlug("translate")
freePointManipFn.connectToPointPlug(tPlug)
except:
sys.stdout.write(u"移动操纵器链接 translate 属性失败\n")
# # ScaleManip
# scaleManipFn = OpenMayaUI.MFnScaleManip(self.fScaleManip)
# try:
# scalePlug = nodeFn.findPlug("scale")
# scaleManipFn.connectToScalePlug(scalePlug)
# scaleManipFn.displayWithNode(node)
# except:
# sys.stdout.write(u"缩放操纵器链接 scale 属性失败\n")
# 以下有自定义接口
# DirectionManip
directionManipFn = OpenMayaUI.MFnDirectionManip()
directionManipFn.setObject(self.fDirectionManip)
self.addPlug(node)
self.startLocate = self.nodeTranslation() # 获取位置初值
try:
sys.stdout.write(u"生成方向操纵器\n")
directionPlug = nodeFn.findPlug("arrowDirection")
directionManipFn.connectToDirectionPlug(directionPlug)
startPointIndex = directionManipFn.startPointIndex()
self.addPlugToManipConversion(startPointIndex)
except:
sys.stdout.write(u"方向操作器链接 arrowDirection 属性失败\n")
try:
rPlugin = nodeFn.findPlug("rotate")
self.initiDirection = OpenMaya.MVector(0, 0, -6)
directionManipFn.setDirection(self.initiDirection) # 每次生成操纵器都需要设置初始方向
self.addManipToPlugConversion(rPlugin)
except:
sys.stdout.write(u"物体旋转方向链接方向操作器 direction 属性失败\n")
OpenMayaMPx.MPxManipContainer.finishAddingManips(self)
OpenMayaMPx.MPxManipContainer.connectToDependNode(self, node)
def draw(self, view, path, style, status):
u"""绘制提示语"""
# todo VP2.0
OpenMayaMPx.MPxManipContainer.draw(self, view, path, style, status)
view.beginGL()
textPos = OpenMaya.MPoint(self.nodeTranslation())
sys.stdout.write("draw,1111111111111111111111111111111")
view.drawText("Swiss Army Manipulator", textPos, OpenMayaUI.M3dView.kLeft)
view.endGL()
def manipToPlugConversion(self, index):
u"""链接选中容器指定属性到物体接口"""
numData = OpenMaya.MFnNumericData()
numDataObj = numData.create(OpenMaya.MFnNumericData.k3Float)
directionManipFn = OpenMayaUI.MFnDirectionManip(self.fDirectionManip)
direction = OpenMaya.MVector()
self.getConverterManipValue(directionManipFn.directionIndex(), direction) # 获取容器方向
quaternion = self.initiDirection.rotateTo(direction)
euler = OpenMaya.MTransformationMatrix(quaternion.asMatrix()).eulerRotation() # 通过四元数转旋转角度
numData.setData3Float(euler.x, euler.y, euler.z)
return OpenMayaUI.MManipData(numDataObj)
def plugToManipConversion(self, theIndex):
u"""链接选定物体的中心点到操纵器起始点接口"""
if theIndex == 8:
numData = OpenMaya.MFnNumericData()
numDataObj = numData.create(OpenMaya.MFnNumericData.k3Float)
vec = self.nodeTranslation()
numData.setData3Float(vec.x, vec.y, vec.z)
manipData = OpenMayaUI.MManipData(numDataObj)
self.updateArrowDirectionValue(vec)
return manipData
else:
sys.stdout.write(theIndex)
sys.stdout.write("\n^index error\n")
def nodeTranslation(self):
u"""获取选中对象的中心点"""
dagFn = OpenMaya.MFnDagNode(self.fNodePath)
path = OpenMaya.MDagPath()
dagFn.getPath(path)
# path.pop() # pop from the shape to the transform 禁用,报错对象和方法不兼容
transformFn = OpenMaya.MFnTransform(path)
return transformFn.getTranslation(OpenMaya.MSpace.kWorld)
def updateArrowDirectionValue(self, endLocate):
u"""更新接口值,移动物体位置时更新物体朝向的向量"""
self.endLocate = endLocate
relativeLocate = self.endLocate - self.startLocate
self.startLocate = self.endLocate
dagFn = OpenMaya.MFnDagNode(self.fNodePath)
directionPlugX = dagFn.findPlug("arrowDirectionX").asFloat()
directionPlugY = dagFn.findPlug("arrowDirectionY").asFloat()
directionPlugZ = dagFn.findPlug("arrowDirectionZ").asFloat()
directLocate = OpenMaya.MVector(directionPlugX, directionPlugY, directionPlugZ)
newLacate = directLocate - relativeLocate
dagFn.findPlug("arrowDirectionX").setFloat(newLacate(0))
dagFn.findPlug("arrowDirectionY").setFloat(newLacate(1))
dagFn.findPlug("arrowDirectionZ").setFloat(newLacate(2))
# sys.stdout.write("change arrorDirectionZ \n")
directionManipFn = OpenMayaUI.MFnDirectionManip()
directionManipFn.setObject(self.fDirectionManip)
directionManipFn.setDirection(newLacate) # 更新方向操纵器的方向,以同步数据
def aimManipCreator():
return OpenMayaMPx.asMPxPtr(AimManip())
def aimManipInitialize():
OpenMayaMPx.MPxManipContainer.initialize()
class aimManipContext(OpenMayaMPx.MPxSelectionContext):
def __init__(self):
OpenMayaMPx.MPxSelectionContext.__init__(self)
self.updateManipulatorsCallbackID = None
def toolOnSetup(self, event):
updateManipulators(self)
self.updateManipulatorsCallbackID = OpenMaya.MModelMessage.addCallback(
OpenMaya.MModelMessage.kActiveListModified, updateManipulators, self)
def toolOffCleanup(self):
self.deleteManipulators()
try:
if self.updateManipulatorsCallbackID != None:
OpenMaya.MModelMessage.removeCallback(self.updateManipulatorsCallbackID)
except:
sys.stderr.write(u"安装前调用清理失败.\n")
super(aimManipContext, self).toolOffCleanup()
def updateManipulators(clientData):
clientData.deleteManipulators()
selectionList = OpenMaya.MSelectionList()
OpenMaya.MGlobal.getActiveSelectionList(selectionList)
selectionIter = OpenMaya.MItSelectionList(selectionList, OpenMaya.MFn.kInvalid)
while not selectionIter.isDone():
dependNode = OpenMaya.MObject()
selectionIter.getDependNode(dependNode)
if dependNode.isNull() or not dependNode.hasFn(OpenMaya.MFn.kDependencyNode):
print(u"depend node is null")
continue
dependNodeFn = OpenMaya.MFnDependencyNode(dependNode)
tPlug = dependNodeFn.findPlug("translate", False)
rPlug = dependNodeFn.findPlug("rotate", False)
sPlug = dependNodeFn.findPlug("scale", False)
if tPlug.isNull() or rPlug.isNull()or sPlug.isNull():
print(u"translate and/or rotate and/or scale plugs are null")
selectionIter.next()
continue
manipObject = OpenMaya.MObject()
manipulator = OpenMayaMPx.MPxManipContainer.newManipulator(nodeName, manipObject)
if manipulator is not None:
clientData.addManipulator(manipObject)
manipulator.connectToDependNode(dependNode)
selectionIter.next()
class aimManipCtxCmd(OpenMayaMPx.MPxContextCommand):
def __init__(self):
OpenMayaMPx.MPxContextCommand.__init__(self)
def makeObj(self):
return OpenMayaMPx.asMPxPtr(aimManipContext())
def contextCmdCreator():
return OpenMayaMPx.asMPxPtr(aimManipCtxCmd())
# 注册插件
def initializePlugin(mobject):
mplugin = OpenMayaMPx.MFnPlugin(mobject, "www.mirrorcg.com", "1.0.0", "Any")
try:
mplugin.registerContextCommand(contextCmdName, contextCmdCreator)
except:
print(u"该上下文命令注册失败: %s" % contextCmdName)
raise
try:
mplugin.registerNode(nodeName, aimManipId, aimManipCreator, aimManipInitialize,
OpenMayaMPx.MPxNode.kManipContainer)
except:
print(u"该节点注册失败: %s" % nodeName)
raise
# 取消注册插件
def uninitializePlugin(mobject):
mplugin = OpenMayaMPx.MFnPlugin(mobject)
try:
mplugin.deregisterContextCommand(contextCmdName)
except:
print(u"该上下文命令取消注册失败: %s" % contextCmdName)
raise
try:
mplugin.deregisterNode(aimManipId)
except:
print(u"该节点取消注册失败: %s" % nodeName)
raise