本来我是想先看Udemy的Python For Maya - Artist Friendly Programming,不过看到那套教程使用pycharm 之后,我就迫不及待地想了解一下pluralsight最新的Python教程使用哪一个IDE
没想到pluralsight这套居然使用了VScode,果断转来看这套了。
不过看了一下才发现,原来两套教程的作者是同一个人_(:з」∠)_
后面还是觉得这套新教程讲得比较浅,先看完在说吧。
如果将这套教程和Delano出的Python三教程比较的话,这套教程更加偏向编程本身。
Digital-Tutors Getting Started with Python Scripting in Maya
Digital-Tutors Artist’s Guide to Python Scripting in Maya
Digital Tutors - Enhancing Maya Toolsets with Pythondelano的教程更多的是针对于Maya自身提供的Python API进行教学,不过年代受限,使用的方法也是比较老。
这套教程不仅仅分析了Maya自身的代码编辑器,也从Python的多个角度进行拓展展望。
搭建 Randomizer 和 Alinger 两个插件,演示Python插件的制作流程。
本教程只使用了Maya的cmd创建窗口,非常适合新手入门。
Maya cmd 提供的窗口布局
中间也穿插了Python CLass的概念(学过面向对象的都知道,过于复杂的部分也很少会用到)
classdemo.py 的内容只是代码演示 不再赘述
我将英文注释去掉,加入自己的理解 (面向对象相关的知识建议看其他教程,这个教程的篇幅受限,讲得很浅)
from maya import cmds
class Window(object):
# 构造函数 实例化的时候调用
def __init__(self, name):
# 如果窗口已经存在 则删除存在的窗口
if cmds.window(name, query=True, exists=True):
cmds.deleteUI(name)
# 创建窗口 并且窗口名为name变量
cmds.window(name)
# 执行BuildUI 构建窗口相关内容
self.buildUI()
# 显示当前窗口(如果buildUI没有执行构建,就是一个空的窗口)
cmds.showWindow()
def buildUI(self):
print "No UI is defined"
from maya import cmds
# 导入random库 从而可以调用random函数
import random
# 导用上面写的 baseWindow(务必放在 文档->maya版本->script 文件夹)
import baseWindow
# RandomizerUI 继承 baseWindow的Window类
class RandomizerUI(baseWindow.Window):
# 创建构造函数
def __init__(self, name='Randomizer'):
# 执行Window的构造函数 父类有执行BuildUI函数 因此执行此函数可以执行buildUI函数
super(RandomizerUI, self).__init__(name)
# 重写buildUI函数
def buildUI(self):
# 标题分区
column = cmds.columnLayout()
cmds.frameLayout(label="Choose an object type")
# 复选框
cmds.columnLayout()
self.objType = cmds.radioCollection("objectCreationType")
cmds.radioButton(label="Sphere")
cmds.radioButton(label="Cube", select=True)
cmds.radioButton(label="Cone")
# 个数输入框
self.intField = cmds.intField("numObjects", value=3)
# 标题分区
cmds.setParent(column)
frame = cmds.frameLayout("Choose your max ranges")
# 网格布局
cmds.gridLayout(numberOfColumns=2, cellWidth=100)
# for循环生成 XYZ 输入
for axis in 'xyz':
cmds.text(label='%s axis' % axis)
cmds.floatField('%sAxisField' % axis, value=random.uniform(0, 10))
# 设置
cmds.setParent(frame)
# 横向布局 柱子分段为2 用于容纳两个复选框
cmds.rowLayout(numberOfColumns=2)
cmds.radioCollection("randomMode")
cmds.radioButton(label='Absolute', select=True)
cmds.radioButton(label='Relative')
cmds.setParent(column)
# 和上面一样 容纳两个按钮
cmds.rowLayout(numberOfColumns=2)
# 指定按钮的名称和调用的函数
cmds.button(label="Create", command=self.onCreateClick)
cmds.button(label="Randomize", command=self.onRandomClick)
# 点击 Create 调用的函数
# *args 是因为Maya cmd 无法让函数的参数为零
def onCreateClick(self, *args):
# 获取插件窗口的选项
radio = cmds.radioCollection(self.objType, query=True, select=True)
mode = cmds.radioButton(radio, query=True, label=True)
# 获取生成的个数
numObjects = cmds.intField(self.intField, query=True, value=True)
# 传入变量 执行生成物体函数
createObjects(mode, numObjects)
# 顺带执行随机位置函数
onRandomClick()
# 随机位置函数
def onRandomClick(self, *args):
# 获取插件窗口的选项
radio = cmds.radioCollection("randomMode", query=True, select=True)
mode = cmds.radioButton(radio, query=True, label=True)
# for循环处理 xyz 三个轴向
for axis in 'xyz':
# 获取插件窗口的数值
val = cmds.floatField("%sAxisField" % axis, query=True, value=True)
# 传入变量 执行随机化的函数
randomize(minValue=val*-1, maxValue=val, mode=mode, axes=axis)
def createObjects(mode, numObjects=5):
# 数组
objList = []
# 根据传入的参数 设置生成的物体
for n in range(numObjects):
if mode == 'Cube':
obj = cmds.polyCube()
elif mode == 'Sphere':
obj = cmds.polySphere()
elif mode == "Cylinder":
obj = cmds.polyCylinder()
elif mode == 'Cone':
obj = cmds.polyCone()
else: cmds.error("I don't know what to create")
# 将生成的物体赋予到数组中
objList.append(obj[0])
# 选择这个数组中的对象
cmds.select(objList)
return objList
def randomize(objList=None, minValue=0, maxValue=10, axes='xyz', mode='Absolute'):
# objList如果是空 也就是直接按Randomize按钮
if objList is None:
# 获取选中的物体
objList = cmds.ls(selection=True)
# 循环选中的物体
for obj in objList:
# 循环三个轴向(参数'xyz')
for axis in axes:
# current归零
current = 0
# 如果是相对的情况就获取当前物体的位置
if mode == 'Relative':
current = cmds.getAttr(obj+'.t%s' % axis)
# uniform可以生成相应区间的随机值
val = current + random.uniform(minValue, maxValue)
# 将随机的变量设置到相应的轴向中 .t 为.translate 简写
cmds.setAttr(obj+'.t%s' % axis, val)
代码的英文注释已经写得非常详细,我就算是翻译一下吧
# 导入cmd库
from maya import cmds
# 导入partial库,可以预先加载函数参数
from functools import partial
def align(nodes=None, axis='x', mode='mid'):
# 如果没有传入nodes参数 获取当前选中的物体
if not nodes:
nodes = cmds.ls(sl=True)
# 如果当前没有任何选择 弹出错误
if not nodes:
cmds.error('Nothing selected or provided')
# 这里需要将选择的部分转换为点,并且需要将范围flatten
# 没有flatten的话 范围取值 polyCube1.vtx[1:5]
# 这种形式不利于编程调用,需要转换为 polyCube1.vtx[1], polyCube1.vtx[2], polyCube1.vtx[3]
# 创建一个临时的数组
_nodes = []
for node in nodes:
# 面的选择形式: polyCube1.f[2]
# 可以寻找'.f[' 来确定是否选中了面
if '.f[' in node:
# 如果有面选择起来就转换为点
node = cmds.polyListComponentConversion(node, fromFace=True, toVertex=True)
elif '.e[' in node:
# 同理 边的处理也是一样的
node = cmds.polyListComponentConversion(node, fromEdge=True, toVertex=True)
# flatten点范围 需要先将点选择起来
cmds.select(node)
# 使用fl命令进行 flatten
node = cmds.ls(sl=True, fl=True)
# 然后将当前的数组添加到_nodes中
_nodes.extend(node)
# 重新选择我们选中的物体
cmds.select(nodes)
# 将临时数组赋值给nodes
nodes = _nodes
# 这里检测当前选中的模式
# 三个变量将会获取到布尔值
# 布尔值为True的就是当前选择的模式
minMode = mode == 'min'
maxMode = mode == 'max'
midMode = mode == 'mid'
# 数组是从零开始的
# 后面轴向是数组延续的
# start变量就是xyz转成数组的形式
if axis == 'x':
start = 0
elif axis == 'y':
start = 1
elif axis == 'z':
start = 2
else:
# 如果全都不符合 就报错
cmds.error('Unknown Axis')
# 用来保存 碰撞盒 和 数值 的变量
bboxes = {}
values = []
# 获取数组中的元素
for node in nodes:
# 如果对象是点的话
if '.vtx[' in node:
# 点是没有碰撞盒的 所以直接获取点的世界坐标
ws = cmds.xform(node, q=True, t=True, ws=True)
# 点是没有体积的 所以三个值都是相等的
minValue = midValue = maxValue = ws[start]
else:
# 如果是个完整的物体 就获取它的碰撞盒
# 碰撞盒会返回一下的数组
# [x-min, y-min, z-min, x-max, y-max, z-max]
bbox = cmds.exactWorldBoundingBox(node)
# 通过start获取相应轴向的 最大最小值 及 中间值
minValue = bbox[start]
maxValue = bbox[start+3]
midValue = (maxValue+minValue)/2
# 将这些值存进上面声明的 bboxes 字典变量中
bboxes[node] = (minValue, midValue, maxValue)
# 根据选择的模式将相应的数值存入变量数组中
if minMode:
values.append(minValue)
elif maxMode:
values.append(maxValue)
else:
values.append(midValue)
# 更具选中的模式进行不同的计算
if minMode:
# 返回数组最小的值
target=min(values)
elif maxMode:
# 返回数组最大的值
target = max(values)
else:
# 获取中间的平均值
target = sum(values)/len(values)
# for循环计算每一个物体需要移动的距离
for node in nodes:
# 获取相应的bboxes字典的数据
bbox = bboxes[node]
# 分离出相应的变量
minValue, midValue, maxValue = bbox
# 获取选中物体的世界坐标
ws = cmds.xform(node, query=True,
translation=True,
ws=True)
# 计算出移动的距离
width = maxValue - minValue
if minMode:
distance = minValue - target
ws[start] = (minValue-distance) + width/2
elif maxMode:
distance = target-maxValue
ws[start] = (maxValue + distance) - width/2
else:
distance = target - midValue
ws[start] = midValue + distance
# 根据计算的值移动物体
cmds.xform(node, translation=ws, ws=True)
class Aligner(object):
def __init__(self):
# 创建窗口 并且让它只有一个存在。
name = "Aligner"
if cmds.window(name, query=True, exists=True):
cmds.deleteUI(name)
window = cmds.window(name)
self.buildUI()
cmds.showWindow()
cmds.window(window, e=True, resizeToFitChildren=True)
def buildUI(self):
column = cmds.columnLayout()
# 添加 radioButton 进行选择
cmds.frameLayout(label="Choose an axis")
cmds.gridLayout(numberOfColumns=3, cellWidth=50)
cmds.radioCollection()
self.xAxis = cmds.radioButton(label='x', select=True)
self.yAxis = cmds.radioButton(label='y')
self.zAxis = cmds.radioButton(label='z')
# 创建图片按钮
# partial实现点击图片也可以改变当前选项
createIconButton('XAxis.png', command=partial(self.onOptionClick, self.xAxis))
createIconButton('YAxis.png', command=partial(self.onOptionClick, self.yAxis))
createIconButton('ZAxis.png', command=partial(self.onOptionClick, self.zAxis))
# 给模式选择添加按钮
cmds.setParent(column)
cmds.frameLayout(label="Choose where to align")
cmds.gridLayout(numberOfColumns=3, cellWidth=50)
cmds.radioCollection()
self.minMode = cmds.radioButton(label='min')
self.midMode = cmds.radioButton(label='mid', select=True)
self.maxMode = cmds.radioButton(label='max')
createIconButton('MinAxis.png', command=partial(self.onOptionClick, self.minMode))
createIconButton('MidAxis.png', command=partial(self.onOptionClick, self.midMode))
createIconButton('MaxAxis.png', command=partial(self.onOptionClick, self.maxMode))
# 添加执行按钮
cmds.setParent(column)
# bgc是backgroundcolor的缩写
# 它对应的值是 rgb
cmds.button(label='Align', command=self.onApplyClick, bgc=(0.2, 0.5, 0.9))
def onOptionClick(self, opt):
# 获取传入的参数
# 改变当前按钮的选择
cmds.radioButton(opt, edit=True, select=True)
def onApplyClick(self, *args):
# 获取当前轴向
if cmds.radioButton(self.xAxis, q=True, select=True):
axis = 'x'
elif cmds.radioButton(self.yAxis, q=True, select=True):
axis = 'y'
else:
axis = 'z'
# 获取当前模式
if cmds.radioButton(self.minMode, q=True, select=True):
mode = 'min'
elif cmds.radioButton(self.midMode, q=True, select=True):
mode = 'mid'
else:
mode = 'max'
# 执行对齐功能函数
align(axis=axis, mode=mode)
def getIcon(icon):
import os
# 当前脚本路径应该在 script 文件夹中
# __file__ 是当前脚本的路径
# os.path.dirname 返回完整的路径名
scripts = os.path.dirname(__file__)
# 获取 icons 文件夹路径
icons = os.path.join(scripts, 'icons')
# 最后找到相应的 icon 并返回相应的路径
icon = os.path.join(icons, icon)
return icon
# 创建图标函数
def createIconButton(icon, command=None):
if command:
cmds.iconTextButton(image1=getIcon(icon), width=50, height=50, command=command)
else:
cmds.iconTextButton(image1=getIcon(icon), width=50, height=50)
Python For Maya - Artist Friendly Programming这套教程我也差不多看完了,这一部讲得比较浅显,非常适合入门,而最近看得这一部涉及的内容更多,还包含了pyqt和pymel,进阶的话可以看最近看的这部教程。
掌握了JavaScript之后,感觉Python其实也没什么难度,编程很多地方都是想通的。
如今要加快对 Maya 库的学习,除此之外,插件开发很重要的一点是要对Maya足够熟悉。