maya工具集(new)
** 界面数据化 ** 用 YAML 配置界面,Python 只负责读取并渲染。
这样做有三个巨大的好处:
- 零代码加工具:美术或非技术人员想加个新按钮,只需要改改文本,完全不用动 Python 代码。
- 极易维护:UI 的排列组合一目了然,不用在密密麻麻的 PySide 代码里找布局。
- 动态更新:甚至可以把 YAML 放在服务器上,实现全团队工具箱的远程实时更新。
引入 Python 原生的 yaml 库,并把 Maya 的执行命令直接作为字符串写进配置里。
📂 完美的项目文件结构
建立如下的文件夹结构:
my_maya_pipeline/
│
├── config.yml # 存放工具按钮和分类的配置文件
└── my_pipeline_gui.py # 读取配置并生成 Maya 停靠面板的 Python 主脚本
📝 1. 配置文件:config.yml
这里定义了左侧的 Tab 分类、右侧按钮的名称,以及点击按钮时真正触发的 Maya Python 命令。
# my_maya_pipeline/config.yml
# Maya 动态工具箱配置文件
tabs:
- name: "通用"
label: "通用工具"
tools:
- label: "🧼 一键优化场景大小"
command: "import maya.mel as mel; mel.eval('cleanUpScene(1);')"
- label: "📂 打开当前工程目录"
command: "import maya.cmds as cmds; import os; os.startfile(cmds.workspace(q=True, rootDirectory=True))"
- name: "模型"
label: "模型常用操作"
tools:
- label: "🧊 冻结变换 + 删除历史"
command: "import maya.cmds as cmds; sel = cmds.ls(sl=True); cmds.makeIdentity(sel, apply=True, t=1, r=1, s=1); cmds.delete(sel, ch=True)"
- label: "📐 重置轴心到中心"
command: "import maya.cmds as cmds; cmds.xform(cp=True)"
🐍 2. 主脚本:my_pipeline_gui.py
在这里,我们引入 yaml 模块。核心逻辑是用 exec() 函数去动态执行配置文件里写好的 command 字符串。
⚠️ 注意:Maya 自带的 Python 环境可能没有预装 pyyaml。在运行此脚本前,需要在 Maya 的 Output Window 或脚本编辑器中运行 pip 安装: import os; import sys; os.system(f'"{sys.executable}" -m pip install pyyaml')
# -*- coding: utf-8 -*-
"""
核心渲染逻辑:读取 config.yml 并自动生成 Maya 停靠界面
"""
import os
import yaml
import maya.cmds as cmds
from maya.app.general.mayaMixin import MayaQWidgetDockableMixin
try:
from PySide2 import QtWidgets, QtCore
except ImportError:
from PySide6 import QtWidgets, QtCore
class MyYamlPipelineToolbar(MayaQWidgetDockableMixin, QtWidgets.QWidget):
def __init__(self, config_path, parent=None):
super(MyYamlPipelineToolbar, self).__init__(parent=parent)
self.setObjectName("MyYamlToolbar")
self.setWindowTitle("YAML 配置工具箱")
# 加载 YAML 配置数据
self.config_data = self.load_config(config_path)
# 主布局
self.main_layout = QtWidgets.QHBoxLayout(self)
self.main_layout.setContentsMargins(2, 2, 2, 2)
self.main_layout.setSpacing(2)
# 1. 左侧垂直 Tab
self.tab_bar = QtWidgets.QTabBar()
if hasattr(QtWidgets.QTabBar, "Shape"):
self.tab_bar.setShape(QtWidgets.QTabBar.Shape.RoundedWest)
else:
self.tab_bar.setShape(QtWidgets.QTabBar.RoundedWest)
self.tab_bar.currentChanged.connect(self.stack.setCurrentIndex)
self.main_layout.addWidget(self.tab_bar)
# 2. 右侧内容层叠窗口
self.stack = QtWidgets.QStackedWidget()
self.main_layout.addWidget(self.stack)
# 3. 根据 YAML 数据动态解析并构建 UI
if self.config_data and "tabs" in self.config_data:
self.build_ui_from_config()
def load_config(self, path):
"""安全读取 YAML 配置文件"""
if not os.path.exists(path):
cmds.warning(f"找不到配置文件: {path}")
return None
with open(path, 'r', encoding='utf-8') as f:
return yaml.safe_load(f)
def build_ui_from_config(self):
"""核心解析函数:遍历 YAML 自动生成按钮"""
for tab_info in self.config_data["tabs"]:
# 添加左侧标签
self.tab_bar.addTab(tab_info.get("name", "未命名"))
# 创建右侧对应的页面
page = QtWidgets.QWidget()
page_layout = QtWidgets.QVBoxLayout(page)
page_layout.setContentsMargins(6, 6, 6, 6)
page_layout.setSpacing(4)
# 页面标题
title_label = QtWidgets.QLabel(f"<b>{tab_info.get('label', '')}</b>")
page_layout.addWidget(title_label)
# 动态循环生成按钮
for tool in tab_info.get("tools", []):
btn = QtWidgets.QPushButton(tool.get("label", "未知按钮"))
btn.setMinimumHeight(30)
# 获取该按钮对应的 Python 执行命令
cmd_str = tool.get("command", "")
# 关键:使用 Python 的 lambda 和 exec 动态绑定命令
# 用 default 参数绑定当前的 cmd_str,防止闭包导致的变量污染
btn.clicked.connect(lambda checked=False, c=cmd_str: exec(c, globals()))
page_layout.addWidget(btn)
page_layout.addStretch()
self.stack.addWidget(page)
def show_toolbar():
object_name = "MyYamlToolbar"
workspace_control_name = object_name + "WorkspaceControl"
# 获取当前脚本所在目录下的 config.yml
current_dir = os.path.dirname(__file__) if "__file__" in locals() else "D:/my_maya_pipeline" # 备用绝对路径
config_path = os.path.join(current_dir, "config.yml")
# 1. 清理旧 UI
if cmds.workspaceControl(workspace_control_name, exists=True):
cmds.workspaceControl(workspace_control_name, edit=True, close=True)
cmds.deleteUI(workspace_control_name, control=True)
# 2. 生成实例
global my_yaml_tool_instance
my_yaml_tool_instance = MyYamlPipelineToolbar(config_path)
# 3. 停靠模式显示
my_yaml_tool_instance.show(dockable=True, area='right', floating=False)
# 4. 吸附到右侧标签组
target_controls = ["AttributeEditor", "ChannelBoxLayerEditor"]
for target in target_controls:
if cmds.workspaceControl(target, exists=True):
cmds.workspaceControl(workspace_control_name, edit=True, tabToControl=(target, -1))
break
cmds.workspaceControl(workspace_control_name, edit=True, raise_=True)
if __name__ == "__main__":
show_toolbar()
🎯 技术要点
btn.clicked.connect(lambda checked=False, c=cmd_str: exec(c, globals()))
💡 为什么要这样写? 1. exec(c, globals()):YAML 里的命令是字符串,Python 的 exec() 可以直接把字符串当做代码来执行,完美解决了解耦问题。 2. c=cmd_str:这是为了解决 Python 循环中的闭包(Closure)陷阱。如果不写 c=cmd_str,所有按钮被点击时,都只会执行循环里最后一个工具的命令。通过把当前循环的字符串作为默认参数传给 lambda,就能把命令死死地“锁”在各自的按钮上!