观看笔记 - Pluralsight - Python for Maya Fundamentals

极力推荐Maya Python入门的教程


前言

  本来我是想先看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 Python

  delano的教程更多的是针对于Maya自身提供的Python API进行教学,不过年代受限,使用的方法也是比较老。
  这套教程不仅仅分析了Maya自身的代码编辑器,也从Python的多个角度进行拓展展望。

相关概念讲解

  • 开头讲解如何使用Maya脚本编辑器
  • 分析了Maya中可以使用的三种编程语言 MEL Python C++ 比较了三门语言在 Maya 的优劣
    • MEL上手容易,但是执行效率低,只能开发脚本,无法开发插件。
    • Python覆盖面很广,上手容易,能够胜任各种情况。
    • C++比较复杂,上手困难,执行效率高。
      观看笔记 - Pluralsight - Python for Maya Fundamentals_第1张图片
  • 演示如何讲MEL转成Python代码
  • Maya编辑器可以快速通过网络查询相关函数的文档
  • 下面就是Python的基础概念
    • 变量(variable)的概念
    • 节点(node)的概念
    • Python2 VS Python3
      观看笔记 - Pluralsight - Python for Maya Fundamentals_第2张图片

Python基础教学

  • Maya Python语言基础教学
    • for 循环
    • while 循环
    • if/else
    • getAttr & setAttr
    • function
    • selection
    • string
    • Scope 域
      观看笔记 - Pluralsight - Python for Maya Fundamentals_第3张图片
    • reload函数 方便外部修改

外部编辑器使用

  • 使用外部编辑器 - 指出为什么使用VScode
    观看笔记 - Pluralsight - Python for Maya Fundamentals_第4张图片
    观看笔记 - Pluralsight - Python for Maya Fundamentals_第5张图片
    观看笔记 - Pluralsight - Python for Maya Fundamentals_第6张图片
  • 搭建VScode autocomplete 链接
  • 如何让VScode和Maya联动使用

UI搭建

  • Maya新的界面组成 - Qt
    观看笔记 - Pluralsight - Python for Maya Fundamentals_第7张图片
  • 在Maya中创建UI 可以选择的方案
    观看笔记 - Pluralsight - Python for Maya Fundamentals_第8张图片

编程实战前 - 讲解分析

  搭建 Randomizer 和 Alinger 两个插件,演示Python插件的制作流程。
  本教程只使用了Maya的cmd创建窗口,非常适合新手入门。
  Maya cmd 提供的窗口布局
观看笔记 - Pluralsight - Python for Maya Fundamentals_第9张图片
  中间也穿插了Python CLass的概念(学过面向对象的都知道,过于复杂的部分也很少会用到)

  • class的概念
    观看笔记 - Pluralsight - Python for Maya Fundamentals_第10张图片
  • 实例的概念
    观看笔记 - Pluralsight - Python for Maya Fundamentals_第11张图片
  • self的作用
    观看笔记 - Pluralsight - Python for Maya Fundamentals_第12张图片
  • 继承
    观看笔记 - Pluralsight - Python for Maya Fundamentals_第13张图片
  • 内置Magic方法
    观看笔记 - Pluralsight - Python for Maya Fundamentals_第14张图片
  • 超类继承
    观看笔记 - Pluralsight - Python for Maya Fundamentals_第15张图片

编程实战

文件组成架构
观看笔记 - Pluralsight - Python for Maya Fundamentals_第16张图片

  classdemo.py 的内容只是代码演示 不再赘述
  我将英文注释去掉,加入自己的理解 (面向对象相关的知识建议看其他教程,这个教程的篇幅受限,讲得很浅)

randomizer Python 代码分析

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)


观看笔记 - Pluralsight - Python for Maya Fundamentals_第17张图片

Aligner Python 代码分析

  代码的英文注释已经写得非常详细,我就算是翻译一下吧

# 导入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)
                
                

观看笔记 - Pluralsight - Python for Maya Fundamentals_第18张图片

总结

  Python For Maya - Artist Friendly Programming这套教程我也差不多看完了,这一部讲得比较浅显,非常适合入门,而最近看得这一部涉及的内容更多,还包含了pyqt和pymel,进阶的话可以看最近看的这部教程。
  掌握了JavaScript之后,感觉Python其实也没什么难度,编程很多地方都是想通的。
  如今要加快对 Maya 库的学习,除此之外,插件开发很重要的一点是要对Maya足够熟悉。

你可能感兴趣的:(Maya,Python,TD,Maya,Python,TD)