IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Python知识库 -> PyQt编写Maya Dockable Window问题汇总 -> 正文阅读

[Python知识库]PyQt编写Maya Dockable Window问题汇总

最近在研究用PyQt写Maya插件的界面,遇到不少的疑难杂症,在这里汇总一下,便于日后查询。


与Maya界面融合

首先最主要的目标是想让PyQt写好的界面与Maya完美融合,不会在操作Maya界面的时候把我们自己的窗口压到后面,而网上传统的方法便是使用OpenMayaUI库以及shiboken2库中的wrapInstance方法,将我们的窗口parent到已经存在的Maya窗口中。

官网的Maya开发人员帮助中也给了以下代码示例:

from maya import OpenMayaUI as omui 

from PySide2.QtCore import * 
from PySide2.QtGui import * 
from PySide2.QtWidgets import *
from PySide2 import __version__
from shiboken2 import wrapInstance 

mayaMainWindowPtr = omui.MQtUtil.mainWindow() 
mayaMainWindow= wrapInstance(long(mayaMainWindowPtr), QWidget) 

# WORKS: Widget is fine 
hello = QLabel("Hello, World", parent=mayaMainWindow) 
hello.setObjectName('MyLabel') 
hello.setWindowFlags(Qt.Window) # Make this widget a standalone window even though it is parented 
hello.show() 
hello = None # the "hello" widget is parented, so it will not be destroyed. 

# BROKEN: Widget is destroyed 
hello = QLabel("Hello, World", parent=None) 
hello.setObjectName('MyLabel') 
hello.show() 
hello = None # the "hello" widget is not parented, so it will be destroyed.

代码中对比了parent = mayaMainWindowparent = None两种情况,后者在使用show()方法后窗口会由于Python的GC机制在创建后瞬间消失,但前者由于parent到了Maya的主窗口中,就会由Maya来维持其生命周期。另外,此时在操作Maya界面的时候,我们的窗口也会一直保持置顶不会被挡住。


Dock窗口

为了让我们的窗口能够dock在Maya的UI中,网上常见的方法是结合上面的wrapInstance方法,再通过内置库的cmds.workspaceControl来实现。(注:Maya2017之前的版本为 cmds.dockControl

例如Dhruv Govil大神的Python For Maya: Artist Friendly Programming教程中的一个案例就是如此:

def getDock(name='LightingManagerDock'):
    # Delete existing window first
    deleteDock(name)
    
    ctrl = pm.workspaceControl(name,dockToMainWindow=('right',1),label="Lighting Manager")
    
    qtCtrl = omui.MQtUtil.findControl(ctrl)
    ptr = wrapInstance(long(qtCtrl),QtWidgets.QWidget)
    return ptr

def deleteDock(name='LightingManagerDock'):
    if pm.workspaceControl(name,query=True,exists=True) :
        pm.deleteUI(name)
  • 其中,大神这里用的是PyMel库pm.,也可替换成cmds.
  • 另外omui.MQtUtil.findControl方法的官方解释为:Auto-naming a widget so that it can be looked up as a string.

虽然以上方法可行,但根据教程还需要再额外添加多行代码才能实现dock的逻辑,比较麻烦,于是我又去调研了其他方案。

后来发现Maya提供了maya.app.general.mayaMixin模块,其中包含的类可以方便将基于PyQt创建的控件融合进Maya UI中,其中用于dock窗口的就是MayaQWidgetDockableMixin类。

官方示例:

from maya.app.general.mayaMixin import MayaQWidgetDockableMixin
from PySide.QtGui import QPushButton, QSizePolicy

class MyDockableButton(MayaQWidgetDockableMixin, QPushButton):
    def __init__(self, parent=None):
        super(MyDockableButton, self).__init__(parent=parent)
        self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred )
        self.setText('Push Me')

# Create an instance of the button and display it.
button = MyDockableButton()
# Show the button as a non-dockable floating window.
button.show(dockable=False)
# A valid Maya control name has been automatically assigned to the button.
buttonName = button.objectName()
print('# ' + buttonName)
# MyDockableButton_368fe1d8-5bc3-4942-a1bf-597d1b5d3b83

# showRepr() can be used to display the current dockable settings.
print('# ' + button.showRepr())
# show(dockable=False, height=23, width=70, y=610, x=197, floating=True)

# Change it to a dockable floating window.
button.show(dockable=True)
print('# ' + button.showRepr())
# show(dockable=True, area='none', height=23, width=70, y=610, x=197, floating=True)
  • 在继承了MayaQWidgetBaseMixin类后,如果没有明确指定parent,则可以直接将我们的控件parent到main Maya window,无需再用wrapInstance方法,非常方便。
  • 另外要注意MayaQWidgetBaseMixin应作为第一继承,否则在使用show(dockable=True)语句时会报错。
  • 继承该类后,PyQt的setWindowIcon方法会失效,窗口无法自定义图标。

保持窗口唯一

当我们用上述方法写完窗口后,会发现重复执行show方法时,会弹出多个窗口,这个原因就是我们每实例化一次,Maya都会自动给我们的窗口起一个独一无二的Object Name,如上面案例中的MyDockableButton_368fe1d8-5bc3-4942-a1bf-597d1b5d3b83

解决该问题的方法也很简单,可以手动用setObjectName方法给我们的窗口命名,避免可以同时实例化多个窗口。当然我们在后续重复执行代码的时候,Maya会因为重名问题报错:Object’s name is not unique,所以应该在实例化之前先用cmds.deleteUI删除已存在的窗口。

于是我们得到了一个很简单的方式来写Maya Dockable Window:

class MyDockableWindow(MayaQWidgetDockableMixin, QtWidgets.QWidget):

    def __init__(self):
        super(MyDockableWindow, self).__init__()

        # Delete existing UI
        try:
            cmds.deleteUI('MDWWorkspaceControl')
        except RuntimeError:
            pass

        self.setWindowTitle('My Dockable Window')
        self.resize(500, 400)
        self.setObjectName('MDW')
        self.show(dockable=True)

MyWin = MyDockableWindow()

界面尺寸问题

由于我写的界面宽高并不是Fixed,而是使用QSizePolicy.Expanding允许用户随意缩放窗口,但我又希望每次用户打开界面的时候能复原我的初始布局,于是我在__init__时用了resize方法来确保界面大小固定。

而当继承了MayaQWidgetDockableMixin后,我发现我的界面经过用户手动大小调整后每次关闭再打开,Maya始终记得界面关闭前的大小,resize方法失效了。

经过研究,我发现Maya会将每个界面布局(Workspace)存储在一个对应的JSON文件中,其中记录着各个控件(如Outliner、ToolBox、ArnoldRenderView)的布局信息,所以在每次打开某一个控件的时候,Maya都会记得它上次关闭前的一些属性,包括窗口大小。

这个JSON文件位于C:\Users\<用户名>\Documents\maya\<版本>\prefs\workspaces文件夹中,内容参考如下:

"closedControls": [
	{
        "objectName": "UVToolkitDockControl",
        "posX": 1901,
        "posY": 697,
        "controlHeight": 930,
        "controlWidth": 315,
        "widthProperty": "preferred",
        "heightProperty": "free"
    },
    {
        "objectName": "hyperShadePanel1Window",
        "posX": 2610,
        "posY": 333,
        "controlHeight": 870,
        "controlWidth": 1365,
        "widthProperty": "free",
        "heightProperty": "free"
    },
    {
        "objectName": "ArnoldRenderView",
        "posX": 765,
        "posY": 758,
        "controlHeight": 750,
        "controlWidth": 1450,
        "widthProperty": "free",
        "heightProperty": "free"
    },
]
  • closedControls是没有dock在Maya界面中,已经被关闭的Workspace Control,如果我们的窗口在关闭Maya时没有dock,则可以在这里面找到。
  • 其中controlHeight以及controlWidth就记录了这个窗口的宽高属性,会在下次打开时调用。
  • 在关闭软件时,Maya会自动调用saveShelf命令来记录界面布局信息,该JSON文件也会被重写。

保存Dock窗口

如果我们在关闭Maya时,我们的窗口已经dock在了Maya界面布局上,会发现下次启动Maya后窗口消失。

如果想要创建一个能够跨会话永久保持的窗口,则可以参考Kaine van Gemert大神的方法,代价就是比较复杂。

原文链接:link


If you’re designing a tool where you would like a persistent interface across multiple sessions, that remembers its location within Maya such as the Outliner or Modelling Toolkit, we need to make use of the uiScript argument in our show method.

The uiScript argument takes a string representing Python code that will be executed to construct the UI when Maya is launched. This argument is part of the workspaceControl command and the show method in MayaQWidgetDockableMixin will pass this along when it creates the control.

To make use of this, we need to create a method for Maya that it can call to construct our UI. The one caveat is we will also need to handle parenting our UI to the Workspace Control ourselves.

The final self-restoring class looks like this:

import inspect

import maya.OpenMayaUI as omui
from maya.app.general.mayaMixin import MayaQWidgetDockableMixin

from PySide2 import QtWidgets
import shiboken2

mixinWindows = {}


class DockableBase(MayaQWidgetDockableMixin):
    """
    Convenience class for creating dockable Maya windows.
    """
    def __init__(self, controlName, **kwargs):
        super(DockableBase, self).__init__(**kwargs)
        self.setObjectName(controlName)   
                                    
    def show(self, *args, **kwargs):
        """
        Show UI with generated uiScript argument
        """
        modulePath = inspect.getmodule(self).__name__
        className = self.__class__.__name__
        super(DockableBase, self).show(dockable=True,
                                        uiScript="import {0}; {0}.{1}._restoreUI()".format(modulePath, className), **kwargs)
        
    @classmethod
    def _restoreUI(cls):
        """
        Internal method to restore the UI when Maya is opened.
        """
        # Create UI instance
        instance = cls()
        # Get the empty WorkspaceControl created by Maya
        workspaceControl = omui.MQtUtil.getCurrentParent()
        # Grab the pointer to our instance as a Maya object
        mixinPtr = omui.MQtUtil.findControl(instance.objectName())
        # Add our UI to the WorkspaceControl
        omui.MQtUtil.addWidgetToMayaLayout(long(mixinPtr), long(workspaceControl))
        # Store reference to UI
        global mixinWindows
        mixinWindows[instance.objectName()] = instance

To create a dockable window using our new MayaQWidgetDockableMixin wrapper class, inherit it along with the QWidget you wish to use. Remember, the inheritance order is still crucial!

class MyDockableWindow(DockableBase, QtWidgets.QDialog):
    def __init__(self):
        super(MyDockableWindow, self).__init__(controlName="MyWindow")
        self.setWindowTitle("My Window")

        self.pushButton = QtWidgets.QPushButton("Push me or else!", parent=self)
        
        self.setLayout(QtWidgets.QVBoxLayout())
        self.layout().addWidget(self.pushButton)

Due to the method of automatically generating the uiScript argument, if you execute this code directly from the Maya Script Editor, it won’t be able to determine the module where your class resides. You will need to make sure you have saved the file to one of Maya’s scripts directories, so it can be found when Maya launches, and then import your class.

For example, if you save your code to ‘scripts/dockableWindow.py’, then you would create an instance in the following manner:

from dockableWindow import MyDockableWindow

myWindow = MyDockableWindow()
myWindow.show()

You will now have a window that not only docks, but saves its location and restores itself successfully each time Maya is opened.

  Python知识库 最新文章
Python中String模块
【Python】 14-CVS文件操作
python的panda库读写文件
使用Nordic的nrf52840实现蓝牙DFU过程
【Python学习记录】numpy数组用法整理
Python学习笔记
python字符串和列表
python如何从txt文件中解析出有效的数据
Python编程从入门到实践自学/3.1-3.2
python变量
上一篇文章      下一篇文章      查看所有文章
加:2021-09-03 11:50:39  更:2021-09-03 11:52:14 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 -2024/12/26 23:33:15-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码
数据统计