Skip to content

Commit 42250cb

Browse files
authored
Merge pull request #5 from GPLgithub/master
Cross platform and basic config support
2 parents 0f67da8 + 3086cbb commit 42250cb

File tree

5 files changed

+419
-85
lines changed

5 files changed

+419
-85
lines changed

engine.py

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -106,19 +106,36 @@ def init_engine(self):
106106

107107
def init_qt_app(self):
108108
self.logger.debug("%s: Initializing QtApp for Unreal", self)
109-
110109
from sgtk.platform.qt5 import QtWidgets
111110

112111
if not QtWidgets.QApplication.instance():
113112
self._qt_app = QtWidgets.QApplication(sys.argv)
114113
self._qt_app.setQuitOnLastWindowClosed(False)
115-
unreal.log("Created QApplication instance: {0}".format(self._qt_app))
116114
else:
117115
self._qt_app = QtWidgets.QApplication.instance()
118116

117+
# On other platforms than Windows, we need to process the Qt events otherwise
118+
# UIs are "frozen". We use a slate tick callback to do that on a regular basis.
119+
# It is not clear why this is not needed on Windows, possibly because a
120+
# dedicated Windows event dispatcher is used instead of a regular
121+
# QAbstractEventDispatcher
122+
if sys.platform != "win32":
123+
unreal.register_slate_post_tick_callback(self._process_qt_events_cb)
119124
# Make the QApplication use the dark theme. Must be called after the QApplication is instantiated
120125
self._initialize_dark_look_and_feel()
121126

127+
@staticmethod
128+
def _process_qt_events_cb(delta_time):
129+
"""
130+
An Unreal tick callback to process QT events.
131+
132+
:param float delta_time: delta time since the last run.
133+
"""
134+
from sgtk.platform.qt5 import QtWidgets
135+
qapp = QtWidgets.QApplication.instance()
136+
if qapp:
137+
qapp.processEvents()
138+
122139
def post_app_init(self):
123140
"""
124141
Called when all apps have initialized
@@ -251,12 +268,13 @@ def _create_dialog(self, title, bundle, widget, parent):
251268

252269
from sgtk.platform.qt import QtGui
253270

254-
unreal_icon = os.path.realpath(os.path.join(
255-
os.path.dirname(__file__),
256-
"icon_256.png"))
257-
271+
unreal_icon = os.path.realpath(
272+
os.path.join(
273+
os.path.dirname(__file__),
274+
"icon_256.png"
275+
)
276+
)
258277
dialog.setWindowIcon(QtGui.QIcon(unreal_icon))
259-
260278
return dialog
261279

262280
def _define_qt_base(self):

hooks/tk-multi-publish2/basic/publish_asset.py

Lines changed: 179 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,20 @@
44

55
import sgtk
66
import os
7+
import sys
78
import unreal
89
import datetime
910

11+
12+
# Local storage path field for known Oses.
13+
_OS_LOCAL_STORAGE_PATH_FIELD = {
14+
"darwin": "mac_path",
15+
"win32": "windows_path",
16+
"linux": "linux_path",
17+
"linux2": "linux_path",
18+
}[sys.platform]
19+
20+
1021
HookBaseClass = sgtk.get_hook_baseclass()
1122

1223

@@ -70,7 +81,12 @@ def settings(self):
7081
"description": "Template path for published work files. Should"
7182
"correspond to a template defined in "
7283
"templates.yml.",
73-
}
84+
},
85+
"Publish Folder": {
86+
"type": "string",
87+
"default": None,
88+
"description": "Optional folder to use as a root for publishes"
89+
},
7490
}
7591

7692
# update the base settings
@@ -89,6 +105,151 @@ def item_filters(self):
89105
"""
90106
return ["unreal.asset.StaticMesh"]
91107

108+
def create_settings_widget(self, parent):
109+
"""
110+
Creates a Qt widget, for the supplied parent widget (a container widget
111+
on the right side of the publish UI).
112+
113+
:param parent: The parent to use for the widget being created.
114+
:returns: A :class:`QtGui.QFrame` that displays editable widgets for
115+
modifying the plugin's settings.
116+
"""
117+
# defer Qt-related imports
118+
from sgtk.platform.qt import QtGui, QtCore
119+
120+
# Create a QFrame with all our widgets
121+
settings_frame = QtGui.QFrame(parent)
122+
# Create our widgets, we add them as properties on the QFrame so we can
123+
# retrieve them easily. Qt uses camelCase so our xxxx_xxxx names can't
124+
# clash with existing Qt properties.
125+
126+
# Show this plugin description
127+
settings_frame.description_label = QtGui.QLabel(self.description)
128+
settings_frame.description_label.setWordWrap(True)
129+
settings_frame.description_label.setOpenExternalLinks(True)
130+
settings_frame.description_label.setTextFormat(QtCore.Qt.RichText)
131+
132+
# Unreal setttings
133+
settings_frame.unreal_publish_folder_label = QtGui.QLabel("Publish folder:")
134+
storage_roots = self.parent.shotgun.find(
135+
"LocalStorage",
136+
[],
137+
["code", _OS_LOCAL_STORAGE_PATH_FIELD]
138+
)
139+
settings_frame.storage_roots_widget = QtGui.QComboBox()
140+
settings_frame.storage_roots_widget.addItem("Current Unreal Project")
141+
for storage_root in storage_roots:
142+
if storage_root[_OS_LOCAL_STORAGE_PATH_FIELD]:
143+
settings_frame.storage_roots_widget.addItem(
144+
"%s (%s)" % (
145+
storage_root["code"],
146+
storage_root[_OS_LOCAL_STORAGE_PATH_FIELD]
147+
),
148+
userData=storage_root,
149+
)
150+
# Create the layout to use within the QFrame
151+
settings_layout = QtGui.QVBoxLayout()
152+
settings_layout.addWidget(settings_frame.description_label)
153+
settings_layout.addWidget(settings_frame.unreal_publish_folder_label)
154+
settings_layout.addWidget(settings_frame.storage_roots_widget)
155+
156+
settings_layout.addStretch()
157+
settings_frame.setLayout(settings_layout)
158+
return settings_frame
159+
160+
def get_ui_settings(self, widget):
161+
"""
162+
Method called by the publisher to retrieve setting values from the UI.
163+
164+
:returns: A dictionary with setting values.
165+
"""
166+
# defer Qt-related imports
167+
from sgtk.platform.qt import QtCore
168+
169+
self.logger.info("Getting settings from UI")
170+
171+
# Please note that we don't have to return all settings here, just the
172+
# settings which are editable in the UI.
173+
storage_index = widget.storage_roots_widget.currentIndex()
174+
publish_folder = None
175+
if storage_index > 0: # Something selected and not the first entry
176+
storage = widget.storage_roots_widget.itemData(storage_index, role=QtCore.Qt.UserRole)
177+
publish_folder = storage[_OS_LOCAL_STORAGE_PATH_FIELD]
178+
179+
settings = {
180+
"Publish Folder": publish_folder,
181+
}
182+
return settings
183+
184+
def set_ui_settings(self, widget, settings):
185+
"""
186+
Method called by the publisher to populate the UI with the setting values.
187+
188+
:param widget: A QFrame we created in `create_settings_widget`.
189+
:param settings: A list of dictionaries.
190+
:raises NotImplementedError: if editing multiple items.
191+
"""
192+
# defer Qt-related imports
193+
from sgtk.platform.qt import QtCore
194+
195+
self.logger.info("Setting UI settings")
196+
if len(settings) > 1:
197+
# We do not allow editing multiple items
198+
raise NotImplementedError
199+
cur_settings = settings[0]
200+
# Note: the template is validated in the accept method, no need to check it here.
201+
publish_template_setting = cur_settings.get("Publish Template")
202+
publisher = self.parent
203+
publish_template = publisher.get_template_by_name(publish_template_setting)
204+
if isinstance(publish_template, sgtk.TemplatePath):
205+
widget.unreal_publish_folder_label.setEnabled(False)
206+
widget.storage_roots_widget.setEnabled(False)
207+
folder_index = 0
208+
publish_folder = cur_settings["Publish Folder"]
209+
if publish_folder:
210+
for i in range(widget.storage_roots_widget.count()):
211+
data = widget.storage_roots_widget.itemData(i, role=QtCore.Qt.UserRole)
212+
if data and data[_OS_LOCAL_STORAGE_PATH_FIELD] == publish_folder:
213+
folder_index = i
214+
break
215+
self.logger.debug("Index for %s is %s" % (publish_folder, folder_index))
216+
widget.storage_roots_widget.setCurrentIndex(folder_index)
217+
218+
def load_saved_ui_settings(self, settings):
219+
"""
220+
Load saved settings and update the given settings dictionary with them.
221+
222+
:param settings: A dictionary where keys are settings names and
223+
values Settings instances.
224+
"""
225+
# Retrieve SG utils framework settings module and instantiate a manager
226+
fw = self.load_framework("tk-framework-shotgunutils_v5.x.x")
227+
module = fw.import_module("settings")
228+
settings_manager = module.UserSettings(self.parent)
229+
230+
# Retrieve saved settings
231+
settings["Publish Folder"].value = settings_manager.retrieve(
232+
"publish2.publish_folder",
233+
settings["Publish Folder"].value,
234+
settings_manager.SCOPE_PROJECT
235+
)
236+
self.logger.debug("Loaded settings %s" % settings["Publish Folder"])
237+
238+
def save_ui_settings(self, settings):
239+
"""
240+
Save UI settings.
241+
242+
:param settings: A dictionary of Settings instances.
243+
"""
244+
# Retrieve SG utils framework settings module and instantiate a manager
245+
fw = self.load_framework("tk-framework-shotgunutils_v5.x.x")
246+
module = fw.import_module("settings")
247+
settings_manager = module.UserSettings(self.parent)
248+
249+
# Save settings
250+
publish_folder = settings["Publish Folder"].value
251+
settings_manager.store("publish2.publish_folder", publish_folder, settings_manager.SCOPE_PROJECT)
252+
92253
def accept(self, settings, item):
93254
"""
94255
Method called by the publisher to determine if an item is of any
@@ -132,6 +293,7 @@ def accept(self, settings, item):
132293
# for use in subsequent methods
133294
item.properties["publish_template"] = publish_template
134295

296+
self.load_saved_ui_settings(settings)
135297
return {
136298
"accepted": accepted,
137299
"checked": True
@@ -172,10 +334,7 @@ def validate(self, settings, item):
172334
self.logger.debug("Asset path or name not configured.")
173335
return False
174336

175-
publish_template = item.properties.get("publish_template")
176-
if not publish_template:
177-
self.logger.debug("No publish template configured.")
178-
return False
337+
publish_template = item.properties["publish_template"]
179338

180339
# Add the Unreal asset name to the fields
181340
fields = {"name": asset_name}
@@ -194,10 +353,22 @@ def validate(self, settings, item):
194353
# which should be project root + publish template
195354
publish_path = publish_template.apply_fields(fields)
196355
publish_path = os.path.normpath(publish_path)
356+
if not os.path.isabs(publish_path):
357+
# If the path is not absolute, prepend the publish folder setting.
358+
publish_folder = settings["Publish Folder"].value
359+
if not publish_folder:
360+
publish_folder = unreal.Paths.project_saved_dir()
361+
publish_path = os.path.abspath(
362+
os.path.join(
363+
publish_folder,
364+
publish_path
365+
)
366+
)
367+
item.properties["publish_path"] = publish_path
197368
item.properties["path"] = publish_path
198369

199-
# Remove the filename from the work path
200-
destination_path = os.path.split(publish_path)[0]
370+
# Remove the filename from the publish path
371+
destination_path = os.path.dirname(publish_path)
201372

202373
# Stash the destination path in properties
203374
item.properties["destination_path"] = destination_path
@@ -207,7 +378,7 @@ def validate(self, settings, item):
207378

208379
# run the base class validation
209380
# return super(UnrealAssetPublishPlugin, self).validate(settings, item)
210-
381+
self.save_ui_settings(settings)
211382
return True
212383

213384
def publish(self, settings, item):

0 commit comments

Comments
 (0)