Maya Python 插件开发:
用 PySide2/PySide6 实现完美的停靠式工具栏(附源码)
# -*- coding: utf-8 -*-
"""
Maya 停靠工具栏示例 (支持 PySide2 / PySide6 双版本)
作者: [fangyuan/wfy.wang]
描述: 利用 MayaQWidgetDockableMixin 和 workspaceControl 的 tabToControl 属性,
把自定义的 PySide2 界面完美嵌套到 Maya 原生的 Attribute Editor(属性编辑器)或 Channel Box(通道盒)标签页中。
这是目前 Maya 插件开发中非常标准且优雅的停靠方案。
"""
import maya.cmds as cmds
from maya.app.general.mayaMixin import MayaQWidgetDockableMixin
# 自动处理 PySide2 (Maya 2024及以下) 和 PySide6 (Maya 2025及以上) 的兼容
try:
from PySide2 import QtWidgets, QtCore
except ImportError:
from PySide6 import QtWidgets, QtCore
class MyMayaPipelineToolbar(MayaQWidgetDockableMixin, QtWidgets.QWidget):
def __init__(self, parent=None):
super(MyMayaPipelineToolbar, self).__init__(parent=parent)
self.setObjectName("MyCustomToolbar")
self.setWindowTitle("我的工具集")
# 主布局
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()
self.tab_bar.setShape(QtWidgets.QTabBar.Shape.RoundedWest if hasattr(QtWidgets.QTabBar, "Shape") else QtWidgets.QTabBar.RoundedWest)
self.tab_bar.addTab("通用")
self.tab_bar.addTab("模型")
self.tab_bar.currentChanged.connect(self.on_tab_changed)
self.main_layout.addWidget(self.tab_bar)
# 2. 右侧内容区
self.stack = QtWidgets.QStackedWidget()
self.main_layout.addWidget(self.stack)
# 填充页面内容
for text in ["通用工具页面", "模型工具页面"]:
pg = QtWidgets.QWidget()
lay = QtWidgets.QVBoxLayout(pg)
lay.addWidget(QtWidgets.QPushButton(text))
lay.addStretch()
self.stack.addWidget(pg)
def on_tab_changed(self, index):
self.stack.setCurrentIndex(index)
def show_toolbar():
object_name = "MyCustomToolbar"
workspace_control_name = object_name + "WorkspaceControl"
# 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)
# 声明全局变量防止被 Python 垃圾回收机制误杀
global my_tool_instance
my_tool_instance = MyMayaPipelineToolbar()
# 2. 先以停靠模式显示
my_tool_instance.show(dockable=True, area='right', floating=False)
# 3. 强行将其合并到原生的 Tab 组中(通道盒或属性编辑器)
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) # -1 表示追加到最后一个标签页
)
break
# 4. 激活当前工具标签
cmds.workspaceControl(workspace_control_name, edit=True, raise_=True)
# 提示:在 Maya 中代码执行 show_toolbar() 即可
-
痛点引出:传统的 cmds.window 会悬浮在 Maya 上方,经常被遮挡,体验极差。而原生的 workspaceControl 如果不用 tabToControl,每次打开都会在右侧强行挤占一整列空间,让视口越来越小。
-
核心原理揭秘:
-
MayaQWidgetDockableMixin:让 PySide 的 WIDGET 具备 Maya 停靠窗口的基因。
-
tabToControl=(target, -1):这是“白嫖”原生面板位置的神器,直接把我们的工具塞进通道盒或属性编辑器的标签页里。
-
避坑指南(干货):
-
生命周期问题:为什么要用 global 变量?(解释:如果不加 global,函数执行完后局部变量被回收,窗口可能会闪退或变成空白)。
- 版本兼容:提到 Maya 2025 升级 Python 3 和 PySide6 的改动,解释代码里 try...except 的必要性。