0%

Unreal中使用Pyside2

摘要

Unreal Engine这几次更新后, 强化了Python(DCC之王)的使用.

在Editor使用情况下, 用PySide2 写UI远远比原生的SlateUI好用太多…

但是Unreal创建Qt窗口时, 会导致窗口无法前台, 一闪而过, 使用以下方法,可以快速附加Qt窗口到Unreal.


Easy To Copy!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# coding=utf-8
# Script created by MineClever


import re as re
import unreal as unreal
import os as os

################################
# Build Ui for tool.
################################
# UI Part
from functools import partial
import PySide2 as PySide2
from PySide2 import QtWidgets, QtUiTools, QtWidgets, QtCore
####################################

class UnrealPyUiLoaderBase (QtWidgets.QWidget):
"""
Create a default tool window. Base Class
"""
# store ref to window to prevent garbage collection
window = None

def __init__(self, parent=None, ui_file_name=""):
"""
Import UI and connect components
"""
super().__init__(parent)

# load the created UI widget
current_file_dir = current_file_dir = os.path.dirname(__file__)
ui_file_path = os.path.join(current_file_dir, ui_file_name).replace("\\", "/")
# NOTE: path to .ui file
ui_file_object = QtCore.QFile(ui_file_path)
if not ui_file_object.open(QtCore.QIODevice.ReadOnly):
raise Exception("Can not load ui file {}".format(ui_file_path))

qt_ui_loader = QtUiTools.QUiLoader()
loaded_ui_window = qt_ui_loader.load(ui_file_object) # type: QtWidgets.QWidget
self.widget = loaded_ui_window
# attach the widget to the instance of this class (aka self)
self.widget.setParent(self)
self._modifyUi()

def findButton(self, name, clicked_callback=None):
# type: (str, ...) -> QtWidgets.QPushButton | None
if self.widget == None : return None
btn = self.widget.findChild(QtWidgets.QPushButton, name) # type: QtWidgets.QPushButton
if clicked_callback and btn:
btn.clicked.connect(clicked_callback)
return btn

def findTextEdit(self, name):
if self.widget == None: return None
txt_edit = self.widget.findChild(QtWidgets.QTextEdit, name) # type: QtWidgets.QTextEdit
return txt_edit

def _modifyUi(self):
"""Modify current widget load from ui file"""
pass

def resizeEvent(self, event):
"""
Called on automatically generated resize event
"""
self.widget.resize(self.width(), self.height())

def closewindow(self):
"""
Close the window.
"""
self.destroy()


class QtUiStartHelper():
u"""
Always calling "start_qt_app()" firstly. Then init this class with a widget.

---
Example:
---

QtUiStartHelper.start_qt_app()

qt_ui_widget = UnrealMeshTexExportUiLoader(ui_file_name = "unreal_tool_ui_path.ui")
unreal_qt_ui_helper = QtUiStartHelper(qt_ui_widget)
unreal_qt_ui_helper.windowTitleName = u"Unreal Tool Name " + unreal_qt_ui_helper.version
__file__
unreal_qt_ui_helper.build_unreal_window()

"""

@staticmethod
def _registerUnrealQtAppWindow():
unreal_app = QtWidgets.QApplication([])
tick_handle = unreal.register_slate_post_tick_callback(
lambda delta: QtWidgets.QApplication.sendPostedEvents())
__QtAppQuit__ = partial(unreal.unregister_slate_post_tick_callback, tick_handle)
unreal_app.aboutToQuit.connect(__QtAppQuit__)

@staticmethod
def startQtApp():
unreal_app = QtWidgets.QApplication.instance()
if not unreal_app:
QtUiStartHelper._registerUnrealQtAppWindow()


def __init__ (self, widget):
# type: (QtWidgets.QWidget) -> None
if not isinstance(widget, QtWidgets.QWidget):
raise
self._widget = widget
self.version = "0.0.1"
self.windowObjName = "UnrealLevelUsdExport"
self.windowTitleName = u"{win_name} {ver}".format(win_name = self.windowObjName, ver=self.version)
if widget.windowTitle():
self.windowTitleName = widget.windowTitle()


def _bindWidgetToUnreal(self):
# NOTE: must show firstly, then bind to unreal, or would not parent into unreal main window...
widget = self._widget
widget.setObjectName(self.windowObjName)
widget.setWindowTitle(self.windowTitleName)
widget.show()
win_id = widget.winId()
unreal.parent_external_window_to_slate(win_id)


def buildUnrealWindow (self):

# Id any current instances of tool and destroy
all_qt_windows = QtWidgets.QApplication.topLevelWidgets() # type: list[QtWidgets.QWidget]
for qt_win in all_qt_windows:
if self.windowObjName in qt_win.objectName(): # update this name to match name below
qt_win.deleteLater()
qt_win.close()
qt_win.destroy()

self._bindWidgetToUnreal()


class UnrealDemoPyUiLoader(UnrealPyUiLoaderBase):
"""
Create tool window.
"""

def __init__(self, parent=None, ui_file_name=""):
self.file_dir_path = unreal.Paths.project_saved_dir()
super().__init__(parent, ui_file_name)

################################
# Your code goes here.
################################

def _modifyUi(self):
# -----------------------------------------
# find interactive elements of UI
self.btn_run = self.findButton("Btn_run", clicked_callback=self.openFileDialog)
self.txt_edit_export_file_dir_path = self.findTextEdit("Text_FilePath")


@QtCore.Slot()
def openFileDialog(self):
"""
open get file path window
"""
chosen_dir_path = QtWidgets.QFileDialog.getExistingDirectory(
self,
options =QtWidgets.QFileDialog.ShowDirsOnly,
caption=u"Choose Export Folder",
dir=unreal.Paths.project_saved_dir())
if not chosen_dir_path: return

# NOTE: update UI and dir path
self.file_dir_path = chosen_dir_path
if self.txt_edit_export_file_dir_path:
self.txt_edit_export_file_dir_path.setText(chosen_dir_path)


if __name__ == "__main__":
QtUiStartHelper.startQtApp()

qt_ui_widget = UnrealDemoPyUiLoader(ui_file_name="demo_ui_file.ui")
unreal_qt_ui_helper = QtUiStartHelper(qt_ui_widget)

unreal_qt_ui_helper.buildUnrealWindow()

歡迎關注我的其它發布渠道