Maya Python游戏与影视编程指南阅读笔记——第八章
程序员文章站
2022-07-12 19:40:30
...
创建依附窗口
posemgr.py文件
import maya.cmds as cmds
import maya.mel as mel
import os, cPickle, sys, time
kPoseFileExtension = 'pse'
def showUI():
"""实例化姿势管理器窗口的函数"""
return AR_PoseManagerWindow.showUI()
class AR_PoseManagerWindow(object):
"""基本姿势管理器窗口的类"""
@classmethod
def showUI(cls):
"""实例化姿势管理器窗口的函数"""
win = cls()
win.create()
return win
def __init__(self):
"""初始化数据属性"""
## 独特的窗把手
self.window = 'ar_poseManagerWindow'
## 窗口标题
self.title = 'Pose Manager'
## 窗口大小
self.size = (300, 174)
if mel.eval('getApplicationVersionAsFloat()') > 2010.0:
self.size = (300, 150)
## 在可写位置存储姿势的临时文件
self.tempFile = os.path.join(
os.path.expanduser('~'),
'temp_pose.%s'%kPoseFileExtension
)
## 当前剪贴板状态消息
self.clipboardStat = 'No pose currently copied.'
if (os.path.exists(self.tempFile)):
self.clipboardStat = 'Old pose currently copied to clipboard.'
## 要在文件浏览器中显示的文件筛选器
self.fileFilter = 'Pose (*.%s)'%kPoseFileExtension
def create(self):
"""绘制窗口"""
# 如果窗口存在句柄,则删除该窗口
if(cmds.window(self.window, exists=True)):
cmds.deleteUI(self.window, window=True)
# 初始化窗口
self.window = cmds.window(self.window, title=self.title, wh=self.size, s=False)
# 主窗体布局
self.mainForm = cmds.formLayout()
# 复制/粘贴框架
self.copyPasteFrame = cmds.frameLayout(l='Copy and Paste Poses')
# 框架内的窗体布局
self.copyPasteForm = cmds.formLayout()
# 在两列网格中创建按钮
self.copyPasteGrid = cmds.gridLayout(cw=self.size[0]/2-2, nc=2)
self.copyBtn = cmds.button(l='Copy Pose', c=self.copyBtnCmd)
self.pasteBtn = cmds.button(l='Paste Pose', c=self.pasteBtnCmd)
# 带剪贴板状态标签的滚动视图
cmds.setParent(self.copyPasteForm)
self.clipboardLayout = cmds.scrollLayout(h=42, w=self.size[0]-4)
self.clipboardLbl = cmds.text(l=self.clipboardStat)
# 在复制粘贴窗体中附加控件
ac = []; af = []
ac.append([self.clipboardLayout,'top',0,self.copyPasteGrid])
af.append([self.copyPasteGrid,'top',0])
af.append([self.clipboardLayout,'bottom',0])
cmds.formLayout(
self.copyPasteForm, e=True,
attachControl=ac, attachForm=af
)
# 保存/加载框架
cmds.setParent(self.mainForm)
self.loadSaveFrame = cmds.frameLayout(l='Save and Load Poses')
# 在两列网格中创建按钮
self.loadSaveBtnLayout = cmds.gridLayout(cw=self.size[0]/2-2, nc=2)
self.saveBtn = cmds.button(l='Save Pose', c=self.saveBtnCmd)
self.loadBtn = cmds.button(l='Load Pose', c=self.loadBtnCmd)
# 将框架附加到主窗体
ac = []; af = []
ac.append([self.loadSaveFrame,'top',0,self.copyPasteFrame])
af.append([self.copyPasteFrame,'top',0])
af.append([self.copyPasteFrame,'left',0])
af.append([self.copyPasteFrame,'right',0])
af.append([self.loadSaveFrame,'bottom',0])
af.append([self.loadSaveFrame,'left',0])
af.append([self.loadSaveFrame,'right',0])
cmds.formLayout(
self.mainForm, e=True,
attachControl=ac, attachForm=af
)
# 显示窗口
cmds.showWindow(self.window)
# 强制窗口大小
cmds.window(self.window, e=True, wh=self.size)
def getSelection(self):
rootNodes = cmds.ls(sl=True, type='transform')
if rootNodes is None or len(rootNodes) < 1:
cmds.confirmDialog(t='Error', b=['OK'],
m='Please select one or more transform nodes.')
return None
else: return rootNodes
def copyBtnCmd(self, *args):
"""当按下复制姿势按钮时调用"""
rootNodes = self.getSelection()
if rootNodes is None: return
cmds.text(
self.clipboardLbl, e=True,
l='Pose copied at %s for %s.'%(
time.strftime('%I:%M'),
''.join('%s, '%t for t in rootNodes)[:-3]
)
)
exportPose(self.tempFile, rootNodes)
def pasteBtnCmd(self, *args):
"""当按下粘贴姿势按钮时调用"""
if not os.path.exists(self.tempFile): return
importPose(self.tempFile)
def saveBtnCmd(self, *args):
"""当按下保存姿势按钮时调用"""
rootNodes = self.getSelection()
if rootNodes is None: return
filePath = ''
# 2011及更新版本的使用 fileDialog2
try:
filePath = cmds.fileDialog2(
ff=self.fileFilter, fileMode=0
)
# BUG: 在某些版本的OS X上,Maya 2008和更早的版本可能会返回目录名和文件名之间没有分隔符的路径:e.g., /users/adam/Desktopuntitled.pse
except:
filePath = cmds.fileDialog(
dm='*.%s'%kPoseFileExtension, mode=1
)
# 提前退出对话被取消
if filePath is None or len(filePath) < 1: return
if isinstance(filePath, list): filePath = filePath[0]
exportPose(filePath, cmds.ls(sl=True, type='transform'))
def loadBtnCmd(self, *args):
"""按下加载姿势按钮时调用"""
filePath = ''
# 玛雅2011及更新版本的使用 fileDialog2
try:
filePath = cmds.fileDialog2(
ff=self.fileFilter, fileMode=1
)
except:
filePath = cmds.fileDialog(
dm='*.%s'%kPoseFileExtension, mode=0
)
# 提前退出对话被取消
if filePath is None or len(filePath) < 1: return
if isinstance(filePath, list): filePath = filePath[0]
importPose(filePath)
def exportPose(filePath, rootNodes):
"""在文件路径处为根节点及其子节点保存姿势文件"""
# try to open the file
try: f = open(filePath, 'w')
except:
cmds.confirmDialog(
t='Error', b=['OK'],
m='Unable to write file: %s'%filePath
)
raise
# 建立层次结构数据列表
data = saveHiearchy(rootNodes, [])
# 保存序列化数据
cPickle.dump(data, f)
# 关闭文件
f.close()
def saveHiearchy(rootNodes, data):
"""将所有可设置关键帧属性的属性值附加到数据数组"""
# 遍历提供的节点
for node in rootNodes:
# 跳过非转换节点
nodeType = cmds.nodeType(node)
if not (nodeType=='transform' or
nodeType=='joint'): continue
# 获取动画属性
keyableAttrs = cmds.listAttr(node, keyable=True)
if keyableAttrs is not None:
for attr in keyableAttrs:
data.append(['%s.%s'%(node,attr),
cmds.getAttr('%s.%s'%(node,attr))])
# 如果有子级,则重复相同的过程并附加其数据
children = cmds.listRelatives(node, children=True)
if children is not None: saveHiearchy(children, data)
return data
def importPose(filePath):
"""导入存储在filePath中的姿势数据"""
# 尝试打开文件
try: f = open(filePath, 'r')
except:
cmds.confirmDialog(
t='Error', b=['OK'],
m='Unable to open file: %s'%filePath
)
raise
# 取消pickle数据
pose = cPickle.load(f)
# 关闭文件
f.close()
# 将属性设置为存储的姿势
errAttrs = []
for attrValue in pose:
try: cmds.setAttr(attrValue[0], attrValue[1])
except:
try: errAttrs.append(attrValue[0])
except: errAttrs.append(attrValue)
# 如果需要,显示错误消息
if len(errAttrs) > 0:
importErrorWindow(errAttrs)
sys.stderr.write('Not all attributes could be loaded.')
def importErrorWindow(errAttrs):
"""导入姿势时,如果有未知属性,将显示一个错误窗口"""
win='ar_errorWindow'
# 关闭窗口的函数
def dismiss(*args):
cmds.deleteUI(win, window=True)
# 如果存在该窗口,则将其销毁
if cmds.window(win, exists=True):
dismiss()
# 创建窗口
size = (300, 200)
cmds.window(
win, wh=size, s=False,
t='Unknown Attributes'
)
mainForm = cmds.formLayout()
# 信息标签
infoLbl = cmds.text(l='The following attributes could not be found.\nThey are being ignored.', al='left')
# 显示无法加载的属性列表
scroller = cmds.scrollLayout(w=size[0])
errStr = ''.join('\t- %s\n'%a for a in errAttrs).rstrip()
cmds.text(l=errStr, al='left')
# 关闭按钮
btn = cmds.button(l='OK', c=dismiss, p=mainForm, h=26)
# 附加控件
ac = []; af=[];
ac.append([scroller,'top',5,infoLbl])
ac.append([scroller,'bottom',5,btn])
af.append([infoLbl,'top',5])
af.append([infoLbl,'left',5])
af.append([infoLbl,'right',5])
af.append([scroller,'left',0])
af.append([scroller,'right',0])
af.append([btn,'left',5])
af.append([btn,'right',5])
af.append([btn,'bottom',5])
cmds.formLayout(
mainForm, e=True,
attachControl=ac, attachForm=af
)
# 显示窗口
cmds.window(win, e=True, wh=size)
cmds.showWindow(win)
example.py文件
import maya.cmds as cmds
import posemgr
#创建窗口
pwin = posemgr.AR_PoseManagerWindow()
pwin.create()
#设置可见
cmds.toggleWindowVisibility(pwin.window)
#设置布局
layout = cmds.paneLayout(configuration='single')
dock = cmds.dockControl(
label=pwin.title,
width=pwin.size[0],
allowedArea='all',
area='right',
floating=False,
content=layout
)
#添加窗口
cmds.control(pwin.window, e=True, p=layout)
输出结果:
加载ui界面,创建窗口
cone.ui文件
<ui version="4.0">
<class>ar_conePtrWindow</class>
<widget class="QMainWindow" name="ar_conePtrWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>300</width>
<height>200</height>
</rect>
</property>
<property name="windowTitle">
<string>Create Cone Pointer</string>
</property>
<widget class="QWidget" name="centralwidget">
<widget class="QPushButton" name="pushButton">
<property name="geometry">
<rect>
<x>69</x>
<y>130</y>
<width>161</width>
<height>32</height>
</rect>
</property>
<property name="text">
<string>Create Cone Pointer</string>
</property>
<property name="+command" stdset="0">
<string>AR_QtConePtrWindow.use.createBtnCmd</string>
</property>
</widget>
<widget class="QLabel" name="label">
<property name="geometry">
<rect>
<x>20</x>
<y>20</y>
<width>81</width>
<height>16</height>
</rect>
</property>
<property name="text">
<string>Y-Rotation:</string>
</property>
</widget>
<widget class="QLineEdit" name="inputRotation">
<property name="geometry">
<rect>
<x>110</x>
<y>20</y>
<width>110</width>
<height>22</height>
</rect>
</property>
<property name="text">
<string>0.0</string>
</property>
</widget>
<widget class="QDial" name="dialRotation">
<property name="geometry">
<rect>
<x>240</x>
<y>10</y>
<width>41</width>
<height>41</height>
</rect>
</property>
<property name="maximum">
<number>360</number>
</property>
</widget>
<widget class="QSpinBox" name="spinBox">
<property name="geometry">
<rect>
<x>250</x>
<y>20</y>
<width>20</width>
<height>20</height>
</rect>
</property>
<property name="maximum">
<number>360</number>
</property>
</widget>
<zorder>spinBox</zorder>
<zorder>pushButton</zorder>
<zorder>label</zorder>
<zorder>inputRotation</zorder>
<zorder>dialRotation</zorder>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>300</width>
<height>22</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<resources/>
<connections>
<connection>
<sender>dialRotation</sender>
<signal>valueChanged(int)</signal>
<receiver>spinBox</receiver>
<slot>setValue(int)</slot>
<hints>
<hint type="sourcelabel">
<x>265</x>
<y>47</y>
</hint>
<hint type="destinationlabel">
<x>167</x>
<y>105</y>
</hint>
</hints>
</connection>
<connection>
<sender>spinBox</sender>
<signal>valueChanged(QString)</signal>
<receiver>inputRotation</receiver>
<slot>setText(QString)</slot>
<hints>
<hint type="sourcelabel">
<x>150</x>
<y>99</y>
</hint>
<hint type="destinationlabel">
<x>148</x>
<y>48</y>
</hint>
</hints>
</connection>
</connections>
</ui>
loadUI.py文件
import maya.cmds as cmds
import os
class AR_QtConePtrWindow(object):
"""一个创建定向圆锥体的窗口类"""
## 引用最近的实例
use = None
@classmethod
def showUI(cls, uiFile):
"""实例化窗口函数"""
win = cls(uiFile)
win.create()
return win
def __init__(self, filePath):
"""实例化属性"""
## 允许控件使用类属性初始化
AR_QtConePtrWindow.use = self
## 唯一的窗口句柄
self.window = 'ar_conePtrWindow'
## 旋转输入名称
self.rotField = 'inputRotation'
## ui文件路径
self.uiFile = filePath
def create(self, verbose=False):
"""绘制窗口"""
# 如果窗口已经存在,删除窗口
if cmds.window(self.window, exists=True):
cmds.deleteUI(self.window)
# 加载ui文件,创建窗口
self.window = cmds.loadUI(
uiFile=self.uiFile,
verbose=verbose
)
cmds.showWindow(self.window)
def createBtnCmd(self, *args):
"""按下创建按钮时执行的函数"""
rotation = None
# 以浮点形式获取输入
try:
ctrlPath = '|'.join(
[self.window, 'centralwidget', self.rotField]
)
rotation = float(
cmds.textField(ctrlPath, q=True, text=True)
)
except: raise
# 创建一个圆锥体并将其旋转到正确的方向
cone = cmds.polyCone()
cmds.setAttr('%s.rx'%cone[0], 90)
cmds.rotate(0, rotation, 0, cone[0], relative=True)
#创建窗口并显示
win = AR_QtConePtrWindow(
os.path.join(
os.getenv('HOME'),
'cone.ui'
)
)
win.create(verbose = True)
输出结果:
上一篇: Spring MVC自定义类型转换器
下一篇: 单向数据流和双向数据流及双向数据绑定