欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

3dsmax不同版本 pyside qt UI 设置max窗口为父窗口的方法

程序员文章站 2022-06-26 20:02:44
3dsmax不同版本 pyside qt widget 设置 max 窗口为父窗口的方法 前言: 3dsmax 在 2014 extension 之后开始集成 Python 和 PySide,但是在版本2014 extension - 2015 中,当设置 qt UI 的父窗口为 max 主窗口的时 ......

3dsmax不同版本 pyside qt widget 设置 max 窗口为父窗口的方法

前言:

3dsmax 在 2014 extension 之后开始集成 python 和 pyside,但是在版本2014 extension - 2015 中,当设置 qt ui 的父窗口为 max 主窗口的时候会报错,3dsmax2016 修复了这个bug,2017 和 2018 对 parenting qt widget to max main window 的方式都有所更新,下面来看看每个版本的具体方式。

3dsmax2014 extension - 2015:

  下面是报错的代码:(在maxscript listener中运行 python.executefile @"[path]\maxpygui.py",[path]改为文件的所在路径)

3dsmax不同版本 pyside qt UI 设置max窗口为父窗口的方法
# -*- coding: utf-8 -*-
"""
在maxscript listener中运行 python.executefile @"[path]\maxpygui.py"
[path]改为 maxpygui.py 所在的路径
"""
from pyside import qtgui
from pyside import shiboken
import maxplus

class _gcprotector(object):
    widgets = []

app = qtgui.qapplication.instance()
if not app:
    app = qtgui.qapplication([])
    
def main():
    maxplus.filemanager.reset(true)
    w = qtgui.qwidget()
    w.resize(250, 100)
    w.setwindowtitle('window')
    _gcprotector.widgets.append(w)
    
    main_layout = qtgui.qvboxlayout()
    label = qtgui.qlineedit()
    main_layout.addwidget(label)

    cylinder_btn = qtgui.qpushbutton("test")
    main_layout.addwidget(cylinder_btn)
    w.setlayout(main_layout)
    
    # 这是会报错的方式
    maxwinhwd = maxplus.core.getwindowhandle()
    parent = shiboken.wrapinstance(long(maxwinhwd), qtgui.qwidget)
    w.setparent(parent)#报错在这里,如果你的窗口继承了qtgui.qwidget,parent = parent 也会报错,如果想正常运行,请注释这行
    
    """max2016的修正方式
    maxplus.attachqwidgettomax(w)
    """
    
    """不太好的方式
    hwnd = w.winid()
    import ctypes
    ctypes.pythonapi.pycobject_asvoidptr.restype = ctypes.c_void_p
    ctypes.pythonapi.pycobject_asvoidptr.argtypes = [ctypes.py_object]
    int_hwnd = ctypes.pythonapi.pycobject_asvoidptr(hwnd)
    maxplus.win32_set3dsmaxasparentwindow(int_hwnd)
    """
    w.show()

if __name__ == '__main__':
    main()
maxpygui.py

注意:如果运行报错syntaxerror: encoding declaration in unicode string (maxpygui.py, line 0),请去掉第一行的 # -*- coding: utf-8 -*-,在命令行中运行不需要指定,下面的代码例子也一样。

  很多人建议不要 parenting qt widget to max main window ,不过还是有人尝试了很多方法,autodesk 官方 也给出了 pyqt4 的方式,链接:https://area.autodesk.com/blogs/chris/pyqt-ui-in-3ds-max-2014-extension,我使用的是pyside,所以没有验证过,也有人把这种方式改为 pyside ,有兴趣的可以试试。

一种比较理想的代替方式:

  下面是在:https://github.com/alfalfasprossen/qtinwin 上找到的代码,下载后有以下文件:

  3dsmax不同版本 pyside qt UI 设置max窗口为父窗口的方法

  在这里只关注 maxparenting.py 和 maxparenting_example.py,在maxscript listener中运行 python.executefile @"maxparenting_example.py",这是以owner的方式来实现的,具体描述请看代码里面的注释。

  下面附上代码:

3dsmax不同版本 pyside qt UI 设置max窗口为父窗口的方法
"""this is a quite well working experiment of setting the **owner**
(not the **parent**) of the qt widget to be the 3dsmax main window.

effectively the qt widget will behave like a natively spawned window,
with correct z-order behaviour concerning its sibling windows.
"""

import ctypes

from pyside import qtgui
from pyside import qtcore
import maxplus

gwl_hwndparent = -8
setwindowlongptr = ctypes.windll.user32.setwindowlongptrw

class focusfilter(qtcore.qobject):
    def eventfilter(self, obj, event):
        # todo: fix focus filter not releasing on defocus
        maxplus.cui.disableaccelerators()
        return false

class maxwidget(qtgui.qwidget):
    def __init__(self, title):
        super(maxwidget, self).__init__(none)
        self.parent_hwnd = maxplus.win32.getmaxhwnd()
        self.hwnd = self.get_hwnd()
        self._parent_to_main_window()
        self.show()
        app = qtgui.qapplication.instance()
        self._focus_filter = focusfilter()
        self.event_filter = app.installeventfilter(self._focus_filter)

    def get_hwnd(self):
        """get the hwnd window handle from this qtwidget."""
        ctypes.pythonapi.pycobject_asvoidptr.restype = ctypes.c_void_p
        ctypes.pythonapi.pycobject_asvoidptr.argtypes = [ctypes.py_object]
        wdgt_ptr = ctypes.pythonapi.pycobject_asvoidptr(self.winid())
        return wdgt_ptr

    def _parent_to_main_window(self):
        """ parent the widget to the 3dsmax main window.

        technically this is not setting the **parent** of the window,
        but the **owner**.
        there is a huge difference, that is hardly documented in the
        win32 api.
        https://msdn.microsoft.com/en-us/library/windows/desktop/ms644898(v=vs.85).aspx  # noqa
        setting the parent would make this a child or mdi-
        child window. setting the owner, makes this a top-level,
        overlapped window that is controlled by the main window, but
        not confined to its client area.
        http://*.com/questions/133122/
        """
        setwindowlongptr(self.hwnd, gwl_hwndparent, self.parent_hwnd)

    def closeevent(self, event):
        app = qtgui.qapplication.instance()
        app.removeeventfilter(self.event_filter)
        event.accept()
maxparenting.py
3dsmax不同版本 pyside qt UI 设置max窗口为父窗口的方法
from pyside import qtgui

import maxparenting
reload(maxparenting)

class examplewidget(maxparenting.maxwidget):
    """this is a test that ui interaction works correctly with a more
    or less complex ui.
    """
    def __init__(self):
        super(examplewidget, self).__init__("example widget")
        self.build_ui()
        self.connect_ui()

    def build_ui(self):
        self.setlayout(qtgui.qvboxlayout())
        self.label = qtgui.qlabel("some label")
        self.btn = qtgui.qpushbutton("button")
        self.lineedit = qtgui.qlineedit()
        self.textedit = qtgui.qtextedit()

        self.grp = qtgui.qgroupbox("group box grid layout")
        self.grp.setlayout(qtgui.qgridlayout())
        self.chkbx_1 = qtgui.qcheckbox("chkbx_1")
        self.chkbx_2 = qtgui.qcheckbox("chkbx_2l")
        self.chkbx_2.setdisabled(true)
        self.chkbx_3 = qtgui.qcheckbox("chkbx_2r")
        self.chkbx_4 = qtgui.qcheckbox("chkbx_3")
        self.chkbx_5 = qtgui.qcheckbox("chkbx_4")
        self.grp.layout().addwidget(self.chkbx_1, 0, 0)
        self.grp.layout().addwidget(self.chkbx_2, 1, 0)
        self.grp.layout().addwidget(self.chkbx_3, 1, 1)
        self.grp.layout().addwidget(self.chkbx_4, 2, 0)
        self.grp.layout().addwidget(self.chkbx_5, 3, 0)
        self.grp.layout().setcolumnstretch(2,1)

        self.lrbox = qtgui.qhboxlayout()
        self.lrbox.addwidget(self.textedit)
        self.lrbox.addwidget(self.grp)

        self.layout().addwidget(self.label)
        self.layout().addwidget(self.btn)
        self.layout().addwidget(self.lineedit)
        self.layout().addlayout(self.lrbox)

    def connect_ui(self):
        self.btn.clicked.connect(self.on_btn_clicked)

    def on_btn_clicked(self):
        print "btn clicked"

global qtwdgt
qtwdgt = examplewidget()
maxparenting_example.py

  其它.py文件有兴趣的可以自己尝试。

3dsmax2016:

  在2016中,终于做出了修正,在模块 maxplus 中增加了attachqwidgettomax(),不过这只是一种简单的指定方式,我们还是没办法获得 max main window 的qt对象,没办法以继承 qtgui.qwidget 来指定parent,but, it's not a bid deal。

3dsmax不同版本 pyside qt UI 设置max窗口为父窗口的方法
# -*- coding: utf-8 -*-
"""
在maxscript listener中运行 python.executefile @"[path]\maxpygui.py"
[path]改为 maxpygui.py 所在的路径
"""
from pyside import qtgui
from pyside import shiboken
import maxplus

class _gcprotector(object):
    widgets = []

app = qtgui.qapplication.instance()
if not app:
    app = qtgui.qapplication([])
    
def main():
    maxplus.filemanager.reset(true)
    w = qtgui.qwidget()
    w.resize(250, 100)
    w.setwindowtitle('window')
    _gcprotector.widgets.append(w)
    
    main_layout = qtgui.qvboxlayout()
    label = qtgui.qlineedit()
    main_layout.addwidget(label)

    cylinder_btn = qtgui.qpushbutton("test")
    main_layout.addwidget(cylinder_btn)
    w.setlayout(main_layout)
    
    """这是会报错的方式
    maxwinhwd = maxplus.core.getwindowhandle()
    parent = shiboken.wrapinstance(long(maxwinhwd), qtgui.qwidget)
    w.setparent(parent)#报错在这里,如果你的窗口继承了qtgui.qwidget,parent = parent 也会报错,如果想正常运行,请注释这行
    """
    
    #max2016的修正方式
    maxplus.attachqwidgettomax(w)
    
    """不太好的方式
    hwnd = w.winid()
    import ctypes
    ctypes.pythonapi.pycobject_asvoidptr.restype = ctypes.c_void_p
    ctypes.pythonapi.pycobject_asvoidptr.argtypes = [ctypes.py_object]
    int_hwnd = ctypes.pythonapi.pycobject_asvoidptr(hwnd)
    maxplus.win32_set3dsmaxasparentwindow(int_hwnd)
    """
    w.show()

if __name__ == '__main__':
    main()
maxpygui.py

3dsmax2017:

  所以,在2017中,为了解决2016的问题,在 maxplus 中增加了getqmaxwindow(),这个方法直接返回 max main window 的 pyside.qtgui.qwidget object:

3dsmax不同版本 pyside qt UI 设置max窗口为父窗口的方法

3dsmax不同版本 pyside qt UI 设置max窗口为父窗口的方法
"""
在maxscript listener中运行 python.executefile @"[path]\maxpygui.py"
[path]改为 maxpygui.py 所在的路径
"""
from pyside import qtgui
from pyside import shiboken
import maxplus

class _gcprotector(object):
    widgets = []

app = qtgui.qapplication.instance()
if not app:
    app = qtgui.qapplication([])
    
def main():
    maxplus.filemanager.reset(true)
    w = qtgui.qwidget()
    w.resize(250, 100)
    w.setwindowtitle('window')
    _gcprotector.widgets.append(w)
    
    main_layout = qtgui.qvboxlayout()
    label = qtgui.qlineedit()
    main_layout.addwidget(label)

    cylinder_btn = qtgui.qpushbutton(u"我们")
    main_layout.addwidget(cylinder_btn)
    w.setlayout(main_layout)
    
    #max2017的改进方式
    parent = maxplus.getqmaxwindow()
    w.setparent(parent)#上面返回的parent直接是pyside.qtgui.qwidget object,可以不通过wrapping,直接设置为父窗口
    
    """这是会报错的方式
    maxwinhwd = maxplus.core.getwindowhandle()
    parent = shiboken.wrapinstance(long(maxwinhwd), qtgui.qwidget)
    w.setparent(parent)#报错在这里,如果你的窗口继承了qtgui.qwidget,parent = parent 也会报错,如果想正常运行,请注释这行
    """
    
    """max2016的修正方式
    maxplus.attachqwidgettomax(w)
    """
    
    """不太好的方式
    hwnd = w.winid()
    import ctypes
    ctypes.pythonapi.pycobject_asvoidptr.restype = ctypes.c_void_p
    ctypes.pythonapi.pycobject_asvoidptr.argtypes = [ctypes.py_object]
    int_hwnd = ctypes.pythonapi.pycobject_asvoidptr(hwnd)
    maxplus.win32_set3dsmaxasparentwindow(int_hwnd)
    """
    w.show()

if __name__ == '__main__':
    main()
maxpygui.py

3dsmax2018 (pyside2):

  在2018中,去掉了 maxplus 中的getqmaxwindow(),所以没办法直接获得 max main window 的 pyside.qtgui.qwidget object,但是增加了qthelpers 类,里面有静态方法getqmaxmainwindow() 获得 maxmainwindow 的指针,然后我们可以通过传统方式来进行转换:

  maxwinhwd = maxplus.qthelpers.getqmaxmainwindow()

  parent = shiboken2.wrapinstance(long(maxwinhwd), qtgui.qwidget)

  例子代码在这里就不提供了,只是要注意的是2018开始,集成的是pyside2,所以要用shiboken2,而且 shiboken2 是在 pyside2 下的一个模块,所以导入方式为(和maya的不一样):

  from pyside2 import shiboken2