3dsmax不同版本 pyside qt

发布时间:2019-03-16 22:30:22编辑:auto阅读(2181)

    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]改为文件的所在路径)

    # -*- 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 上找到的代码,下载后有以下文件:

      

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

      下面附上代码:

    """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://stackoverflow.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
    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。

    # -*- 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:

    """
    在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

关键字