Merge branch 'develop' into chore/resolve_to_new_style_host

This commit is contained in:
Roy Nieterau 2023-10-06 14:17:15 +02:00 committed by GitHub
commit 0760e4f394
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 259 additions and 97 deletions

View file

@ -1,14 +1,13 @@
import os
import logging
from functools import partial
from qtpy import QtWidgets, QtGui
import maya.utils
import maya.cmds as cmds
from openpype.settings import get_project_settings
from openpype.pipeline import (
get_current_project_name,
get_current_asset_name,
get_current_task_name
)
@ -46,12 +45,12 @@ def get_context_label():
)
def install():
def install(project_settings):
if cmds.about(batch=True):
log.info("Skipping openpype.menu initialization in batch mode..")
return
def deferred():
def add_menu():
pyblish_icon = host_tools.get_pyblish_icon()
parent_widget = get_main_window()
cmds.menu(
@ -191,7 +190,7 @@ def install():
cmds.setParent(MENU_NAME, menu=True)
def add_scripts_menu():
def add_scripts_menu(project_settings):
try:
import scriptsmenu.launchformaya as launchformaya
except ImportError:
@ -201,9 +200,6 @@ def install():
)
return
# load configuration of custom menu
project_name = get_current_project_name()
project_settings = get_project_settings(project_name)
config = project_settings["maya"]["scriptsmenu"]["definition"]
_menu = project_settings["maya"]["scriptsmenu"]["name"]
@ -225,8 +221,9 @@ def install():
# so that it only gets called after Maya UI has initialized too.
# This is crucial with Maya 2020+ which initializes without UI
# first as a QCoreApplication
maya.utils.executeDeferred(deferred)
cmds.evalDeferred(add_scripts_menu, lowestPriority=True)
maya.utils.executeDeferred(add_menu)
cmds.evalDeferred(partial(add_scripts_menu, project_settings),
lowestPriority=True)
def uninstall():

View file

@ -28,8 +28,6 @@ from openpype.lib import (
from openpype.pipeline import (
legacy_io,
get_current_project_name,
get_current_asset_name,
get_current_task_name,
register_loader_plugin_path,
register_inventory_action_path,
register_creator_plugin_path,
@ -108,7 +106,7 @@ class MayaHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost):
_set_project()
self._register_callbacks()
menu.install()
menu.install(project_settings)
register_event_callback("save", on_save)
register_event_callback("open", on_open)

View file

@ -603,6 +603,13 @@ class RenderlayerCreator(NewCreator, MayaCreatorBase):
class Loader(LoaderPlugin):
hosts = ["maya"]
load_settings = {} # defined in settings
@classmethod
def apply_settings(cls, project_settings, system_settings):
super(Loader, cls).apply_settings(project_settings, system_settings)
cls.load_settings = project_settings['maya']['load']
def get_custom_namespace_and_group(self, context, options, loader_key):
"""Queries Settings to get custom template for namespace and group.
@ -615,12 +622,9 @@ class Loader(LoaderPlugin):
loader_key (str): key to get separate configuration from Settings
('reference_loader'|'import_loader')
"""
options["attach_to_root"] = True
asset = context['asset']
subset = context['subset']
settings = get_project_settings(context['project']['name'])
custom_naming = settings['maya']['load'][loader_key]
options["attach_to_root"] = True
custom_naming = self.load_settings[loader_key]
if not custom_naming['namespace']:
raise LoadError("No namespace specified in "
@ -629,6 +633,8 @@ class Loader(LoaderPlugin):
self.log.debug("No custom group_name, no group will be created.")
options["attach_to_root"] = False
asset = context['asset']
subset = context['subset']
formatting_data = {
"asset_name": asset['name'],
"asset_type": asset['type'],

View file

@ -69,11 +69,8 @@ class ValidateUnrealStaticMeshName(pyblish.api.InstancePlugin,
invalid = []
project_settings = get_project_settings(
legacy_io.Session["AVALON_PROJECT"]
)
collision_prefixes = (
project_settings
instance.context.data["project_settings"]
["maya"]
["create"]
["CreateUnrealStaticMesh"]

View file

@ -33,11 +33,13 @@ class ExtractReviewIntermediates(publish.Extractor):
"""
nuke_publish = project_settings["nuke"]["publish"]
deprecated_setting = nuke_publish["ExtractReviewDataMov"]
current_setting = nuke_publish["ExtractReviewIntermediates"]
current_setting = nuke_publish.get("ExtractReviewIntermediates")
if deprecated_setting["enabled"]:
# Use deprecated settings if they are still enabled
cls.viewer_lut_raw = deprecated_setting["viewer_lut_raw"]
cls.outputs = deprecated_setting["outputs"]
elif current_setting is None:
pass
elif current_setting["enabled"]:
cls.viewer_lut_raw = current_setting["viewer_lut_raw"]
cls.outputs = current_setting["outputs"]

View file

@ -6,6 +6,7 @@ import requests
import pyblish.api
from openpype import AYON_SERVER_ENABLED
from openpype.pipeline import legacy_io
from openpype.pipeline.publish import (
OpenPypePyblishPluginMixin
@ -34,6 +35,8 @@ class FusionSubmitDeadline(
targets = ["local"]
# presets
plugin = None
priority = 50
chunk_size = 1
concurrent_tasks = 1
@ -173,7 +176,7 @@ class FusionSubmitDeadline(
"SecondaryPool": instance.data.get("secondaryPool"),
"Group": self.group,
"Plugin": "Fusion",
"Plugin": self.plugin,
"Frames": "{start}-{end}".format(
start=int(instance.data["frameStartHandle"]),
end=int(instance.data["frameEndHandle"])
@ -216,16 +219,29 @@ class FusionSubmitDeadline(
# Include critical variables with submission
keys = [
# TODO: This won't work if the slaves don't have access to
# these paths, such as if slaves are running Linux and the
# submitter is on Windows.
"PYTHONPATH",
"OFX_PLUGIN_PATH",
"FUSION9_MasterPrefs"
"FTRACK_API_KEY",
"FTRACK_API_USER",
"FTRACK_SERVER",
"AVALON_PROJECT",
"AVALON_ASSET",
"AVALON_TASK",
"AVALON_APP_NAME",
"OPENPYPE_DEV",
"OPENPYPE_LOG_NO_COLORS",
"IS_TEST"
]
environment = dict({key: os.environ[key] for key in keys
if key in os.environ}, **legacy_io.Session)
# to recognize render jobs
if AYON_SERVER_ENABLED:
environment["AYON_BUNDLE_NAME"] = os.environ["AYON_BUNDLE_NAME"]
render_job_label = "AYON_RENDER_JOB"
else:
render_job_label = "OPENPYPE_RENDER_JOB"
environment[render_job_label] = "1"
payload["JobInfo"].update({
"EnvironmentKeyValue%d" % index: "{key}={value}".format(
key=key,

View file

@ -25,6 +25,7 @@ def _get_template_id(renderer):
:rtype: int
"""
# TODO: Use settings from context?
templates = get_system_settings()["modules"]["muster"]["templates_mapping"]
if not templates:
raise RuntimeError(("Muster template mapping missing in "

View file

@ -83,10 +83,6 @@ class OpenTaskPath(LauncherAction):
if os.path.exists(valid_workdir):
return valid_workdir
# If task was selected, try to find asset path only to asset
if not task_name:
raise AssertionError("Folder does not exist.")
data.pop("task", None)
workdir = anatomy.templates_obj["work"]["folder"].format(data)
valid_workdir = self._find_first_filled_path(workdir)
@ -95,7 +91,7 @@ class OpenTaskPath(LauncherAction):
valid_workdir = os.path.normpath(valid_workdir)
if os.path.exists(valid_workdir):
return valid_workdir
raise AssertionError("Folder does not exist.")
raise AssertionError("Folder does not exist yet.")
@staticmethod
def open_in_explorer(path):

View file

@ -271,12 +271,6 @@ class PypeCommands:
if mongo_url:
args.extend(["--mongo_url", mongo_url])
else:
msg = (
"Either provide uri to MongoDB through environment variable"
" OPENPYPE_MONGO or the command flag --mongo_url"
)
assert not os.environ.get("OPENPYPE_MONGO"), msg
print("run_tests args: {}".format(args))
import pytest

View file

@ -748,15 +748,17 @@ def _convert_nuke_project_settings(ayon_settings, output):
)
new_review_data_outputs = {}
outputs_settings = None
outputs_settings = []
# Check deprecated ExtractReviewDataMov
# settings for backwards compatibility
deprecrated_review_settings = ayon_publish["ExtractReviewDataMov"]
current_review_settings = (
ayon_publish["ExtractReviewIntermediates"]
ayon_publish.get("ExtractReviewIntermediates")
)
if deprecrated_review_settings["enabled"]:
outputs_settings = deprecrated_review_settings["outputs"]
elif current_review_settings is None:
pass
elif current_review_settings["enabled"]:
outputs_settings = current_review_settings["outputs"]

View file

@ -52,7 +52,8 @@
"priority": 50,
"chunk_size": 10,
"concurrent_tasks": 1,
"group": ""
"group": "",
"plugin": "Fusion"
},
"NukeSubmitDeadline": {
"enabled": true,

View file

@ -289,6 +289,15 @@
"type": "text",
"key": "group",
"label": "Group Name"
},
{
"type": "enum",
"key": "plugin",
"label": "Deadline Plugin",
"enum_items": [
{"Fusion": "Fusion"},
{"FusionCmd": "FusionCmd"}
]
}
]
},

View file

@ -272,7 +272,7 @@ class AbstractLauncherFrontEnd(AbstractLauncherCommon):
@abstractmethod
def set_application_force_not_open_workfile(
self, project_name, folder_id, task_id, action_id, enabled
self, project_name, folder_id, task_id, action_ids, enabled
):
"""This is application action related to force not open last workfile.
@ -280,7 +280,7 @@ class AbstractLauncherFrontEnd(AbstractLauncherCommon):
project_name (Union[str, None]): Project name.
folder_id (Union[str, None]): Folder id.
task_id (Union[str, None]): Task id.
action_id (str): Action identifier.
action_id (Iterable[str]): Action identifiers.
enabled (bool): New value of force not open workfile.
"""
@ -295,3 +295,13 @@ class AbstractLauncherFrontEnd(AbstractLauncherCommon):
"""
pass
@abstractmethod
def refresh_actions(self):
"""Refresh actions and all related data.
Triggers 'controller.refresh.actions.started' event at the beginning
and 'controller.refresh.actions.finished' at the end.
"""
pass

View file

@ -121,10 +121,10 @@ class BaseLauncherController(
project_name, folder_id, task_id)
def set_application_force_not_open_workfile(
self, project_name, folder_id, task_id, action_id, enabled
self, project_name, folder_id, task_id, action_ids, enabled
):
self._actions_model.set_application_force_not_open_workfile(
project_name, folder_id, task_id, action_id, enabled
project_name, folder_id, task_id, action_ids, enabled
)
def trigger_action(self, project_name, folder_id, task_id, identifier):
@ -145,5 +145,17 @@ class BaseLauncherController(
self._emit_event("controller.refresh.finished")
def refresh_actions(self):
self._emit_event("controller.refresh.actions.started")
# Refresh project settings (used for actions discovery)
self._project_settings = {}
# Refresh projects - they define applications
self._projects_model.reset()
# Refresh actions
self._actions_model.refresh()
self._emit_event("controller.refresh.actions.finished")
def _emit_event(self, topic, data=None):
self.emit_event(topic, data, "controller")

View file

@ -326,13 +326,14 @@ class ActionsModel:
return output
def set_application_force_not_open_workfile(
self, project_name, folder_id, task_id, action_id, enabled
self, project_name, folder_id, task_id, action_ids, enabled
):
no_workfile_reg_data = self._get_no_last_workfile_reg_data()
project_data = no_workfile_reg_data.setdefault(project_name, {})
folder_data = project_data.setdefault(folder_id, {})
task_data = folder_data.setdefault(task_id, {})
task_data[action_id] = enabled
for action_id in action_ids:
task_data[action_id] = enabled
self._launcher_tool_reg.set_item(
self._not_open_workfile_reg_key, no_workfile_reg_data
)
@ -359,7 +360,10 @@ class ActionsModel:
project_name, folder_id, task_id
)
force_not_open_workfile = per_action.get(identifier, False)
action.data["start_last_workfile"] = force_not_open_workfile
if force_not_open_workfile:
action.data["start_last_workfile"] = False
else:
action.data.pop("start_last_workfile", None)
action.process(session)
except Exception as exc:
self.log.warning("Action trigger failed.", exc_info=True)

View file

@ -19,6 +19,21 @@ ANIMATION_STATE_ROLE = QtCore.Qt.UserRole + 6
FORCE_NOT_OPEN_WORKFILE_ROLE = QtCore.Qt.UserRole + 7
def _variant_label_sort_getter(action_item):
"""Get variant label value for sorting.
Make sure the output value is a string.
Args:
action_item (ActionItem): Action item.
Returns:
str: Variant label or empty string.
"""
return action_item.variant_label or ""
class ActionsQtModel(QtGui.QStandardItemModel):
"""Qt model for actions.
@ -31,10 +46,6 @@ class ActionsQtModel(QtGui.QStandardItemModel):
def __init__(self, controller):
super(ActionsQtModel, self).__init__()
controller.register_event_callback(
"controller.refresh.finished",
self._on_controller_refresh_finished,
)
controller.register_event_callback(
"selection.project.changed",
self._on_selection_project_changed,
@ -51,6 +62,7 @@ class ActionsQtModel(QtGui.QStandardItemModel):
self._controller = controller
self._items_by_id = {}
self._action_items_by_id = {}
self._groups_by_id = {}
self._selected_project_name = None
@ -72,8 +84,12 @@ class ActionsQtModel(QtGui.QStandardItemModel):
def get_item_by_id(self, action_id):
return self._items_by_id.get(action_id)
def get_action_item_by_id(self, action_id):
return self._action_items_by_id.get(action_id)
def _clear_items(self):
self._items_by_id = {}
self._action_items_by_id = {}
self._groups_by_id = {}
root = self.invisibleRootItem()
root.removeRows(0, root.rowCount())
@ -101,12 +117,14 @@ class ActionsQtModel(QtGui.QStandardItemModel):
groups_by_id = {}
for action_items in items_by_label.values():
action_items.sort(key=_variant_label_sort_getter, reverse=True)
first_item = next(iter(action_items))
all_action_items_info.append((first_item, len(action_items) > 1))
groups_by_id[first_item.identifier] = action_items
new_items = []
items_by_id = {}
action_items_by_id = {}
for action_item_info in all_action_items_info:
action_item, is_group = action_item_info
icon = get_qt_icon(action_item.icon)
@ -132,6 +150,7 @@ class ActionsQtModel(QtGui.QStandardItemModel):
action_item.force_not_open_workfile,
FORCE_NOT_OPEN_WORKFILE_ROLE)
items_by_id[action_item.identifier] = item
action_items_by_id[action_item.identifier] = action_item
if new_items:
root_item.appendRows(new_items)
@ -139,19 +158,14 @@ class ActionsQtModel(QtGui.QStandardItemModel):
to_remove = set(self._items_by_id.keys()) - set(items_by_id.keys())
for identifier in to_remove:
item = self._items_by_id.pop(identifier)
self._action_items_by_id.pop(identifier)
root_item.removeRow(item.row())
self._groups_by_id = groups_by_id
self._items_by_id = items_by_id
self._action_items_by_id = action_items_by_id
self.refreshed.emit()
def _on_controller_refresh_finished(self):
context = self._controller.get_selected_context()
self._selected_project_name = context["project_name"]
self._selected_folder_id = context["folder_id"]
self._selected_task_id = context["task_id"]
self.refresh()
def _on_selection_project_changed(self, event):
self._selected_project_name = event["project_name"]
self._selected_folder_id = None
@ -336,6 +350,9 @@ class ActionsWidget(QtWidgets.QWidget):
self._set_row_height(1)
def refresh(self):
self._model.refresh()
def _set_row_height(self, rows):
self.setMinimumHeight(rows * 75)
@ -387,9 +404,15 @@ class ActionsWidget(QtWidgets.QWidget):
checkbox.setChecked(True)
action_id = index.data(ACTION_ID_ROLE)
is_group = index.data(ACTION_IS_GROUP_ROLE)
if is_group:
action_items = self._model.get_group_items(action_id)
else:
action_items = [self._model.get_action_item_by_id(action_id)]
action_ids = {action_item.identifier for action_item in action_items}
checkbox.stateChanged.connect(
lambda: self._on_checkbox_changed(
action_id, checkbox.isChecked()
action_ids, checkbox.isChecked()
)
)
action = QtWidgets.QWidgetAction(menu)
@ -402,7 +425,7 @@ class ActionsWidget(QtWidgets.QWidget):
menu.exec_(global_point)
self._context_menu = None
def _on_checkbox_changed(self, action_id, is_checked):
def _on_checkbox_changed(self, action_ids, is_checked):
if self._context_menu is not None:
self._context_menu.close()
@ -410,7 +433,7 @@ class ActionsWidget(QtWidgets.QWidget):
folder_id = self._model.get_selected_folder_id()
task_id = self._model.get_selected_task_id()
self._controller.set_application_force_not_open_workfile(
project_name, folder_id, task_id, action_id, is_checked)
project_name, folder_id, task_id, action_ids, is_checked)
self._model.refresh()
def _on_clicked(self, index):

View file

@ -92,6 +92,10 @@ class HierarchyPage(QtWidgets.QWidget):
if visible and project_name:
self._projects_combobox.set_selection(project_name)
def refresh(self):
self._folders_widget.refresh()
self._tasks_widget.refresh()
def _on_back_clicked(self):
self._controller.set_selected_project(None)

View file

@ -73,6 +73,9 @@ class ProjectIconView(QtWidgets.QListView):
class ProjectsWidget(QtWidgets.QWidget):
"""Projects Page"""
refreshed = QtCore.Signal()
def __init__(self, controller, parent=None):
super(ProjectsWidget, self).__init__(parent=parent)
@ -104,6 +107,7 @@ class ProjectsWidget(QtWidgets.QWidget):
main_layout.addWidget(projects_view, 1)
projects_view.clicked.connect(self._on_view_clicked)
projects_model.refreshed.connect(self.refreshed)
projects_filter_text.textChanged.connect(
self._on_project_filter_change)
refresh_btn.clicked.connect(self._on_refresh_clicked)
@ -119,6 +123,15 @@ class ProjectsWidget(QtWidgets.QWidget):
self._projects_model = projects_model
self._projects_proxy_model = projects_proxy_model
def has_content(self):
"""Model has at least one project.
Returns:
bool: True if there is any content in the model.
"""
return self._projects_model.has_content()
def _on_view_clicked(self, index):
if index.isValid():
project_name = index.data(QtCore.Qt.DisplayRole)

View file

@ -99,8 +99,8 @@ class LauncherWindow(QtWidgets.QWidget):
message_timer.setInterval(self.message_interval)
message_timer.setSingleShot(True)
refresh_timer = QtCore.QTimer()
refresh_timer.setInterval(self.refresh_interval)
actions_refresh_timer = QtCore.QTimer()
actions_refresh_timer.setInterval(self.refresh_interval)
page_slide_anim = QtCore.QVariantAnimation(self)
page_slide_anim.setDuration(self.page_side_anim_interval)
@ -108,8 +108,10 @@ class LauncherWindow(QtWidgets.QWidget):
page_slide_anim.setEndValue(1.0)
page_slide_anim.setEasingCurve(QtCore.QEasingCurve.OutQuad)
projects_page.refreshed.connect(self._on_projects_refresh)
message_timer.timeout.connect(self._on_message_timeout)
refresh_timer.timeout.connect(self._on_refresh_timeout)
actions_refresh_timer.timeout.connect(
self._on_actions_refresh_timeout)
page_slide_anim.valueChanged.connect(
self._on_page_slide_value_changed)
page_slide_anim.finished.connect(self._on_page_slide_finished)
@ -132,6 +134,7 @@ class LauncherWindow(QtWidgets.QWidget):
self._is_on_projects_page = True
self._window_is_active = False
self._refresh_on_activate = False
self._selected_project_name = None
self._pages_widget = pages_widget
self._pages_layout = pages_layout
@ -143,7 +146,7 @@ class LauncherWindow(QtWidgets.QWidget):
# self._action_history = action_history
self._message_timer = message_timer
self._refresh_timer = refresh_timer
self._actions_refresh_timer = actions_refresh_timer
self._page_slide_anim = page_slide_anim
hierarchy_page.setVisible(not self._is_on_projects_page)
@ -152,14 +155,14 @@ class LauncherWindow(QtWidgets.QWidget):
def showEvent(self, event):
super(LauncherWindow, self).showEvent(event)
self._window_is_active = True
if not self._refresh_timer.isActive():
self._refresh_timer.start()
if not self._actions_refresh_timer.isActive():
self._actions_refresh_timer.start()
self._controller.refresh()
def closeEvent(self, event):
super(LauncherWindow, self).closeEvent(event)
self._window_is_active = False
self._refresh_timer.stop()
self._actions_refresh_timer.stop()
def changeEvent(self, event):
if event.type() in (
@ -170,15 +173,15 @@ class LauncherWindow(QtWidgets.QWidget):
self._window_is_active = is_active
if is_active and self._refresh_on_activate:
self._refresh_on_activate = False
self._on_refresh_timeout()
self._refresh_timer.start()
self._on_actions_refresh_timeout()
self._actions_refresh_timer.start()
super(LauncherWindow, self).changeEvent(event)
def _on_refresh_timeout(self):
def _on_actions_refresh_timeout(self):
# Stop timer if widget is not visible
if self._window_is_active:
self._controller.refresh()
self._controller.refresh_actions()
else:
self._refresh_on_activate = True
@ -191,12 +194,26 @@ class LauncherWindow(QtWidgets.QWidget):
def _on_project_selection_change(self, event):
project_name = event["project_name"]
self._selected_project_name = project_name
if not project_name:
self._go_to_projects_page()
elif self._is_on_projects_page:
self._go_to_hierarchy_page(project_name)
def _on_projects_refresh(self):
# There is nothing to do, we're on projects page
if self._is_on_projects_page:
return
# No projects were found -> go back to projects page
if not self._projects_page.has_content():
self._go_to_projects_page()
return
self._hierarchy_page.refresh()
self._actions_widget.refresh()
def _on_action_trigger_started(self, event):
self._echo("Running action: {}".format(event["full_label"]))

View file

@ -199,13 +199,18 @@ class HierarchyModel(object):
Hierarchy items are folders and tasks. Folders can have as parent another
folder or project. Tasks can have as parent only folder.
"""
lifetime = 60 # A minute
def __init__(self, controller):
self._folders_items = NestedCacheItem(levels=1, default_factory=dict)
self._folders_by_id = NestedCacheItem(levels=2, default_factory=dict)
self._folders_items = NestedCacheItem(
levels=1, default_factory=dict, lifetime=self.lifetime)
self._folders_by_id = NestedCacheItem(
levels=2, default_factory=dict, lifetime=self.lifetime)
self._task_items = NestedCacheItem(levels=2, default_factory=dict)
self._tasks_by_id = NestedCacheItem(levels=2, default_factory=dict)
self._task_items = NestedCacheItem(
levels=2, default_factory=dict, lifetime=self.lifetime)
self._tasks_by_id = NestedCacheItem(
levels=2, default_factory=dict, lifetime=self.lifetime)
self._folders_refreshing = set()
self._tasks_refreshing = set()

View file

@ -56,11 +56,21 @@ class FoldersModel(QtGui.QStandardItemModel):
return self._has_content
def clear(self):
def refresh(self):
"""Refresh folders for last selected project.
Force to update folders model from controller. This may or may not
trigger query from server, that's based on controller's cache.
"""
self.set_project_name(self._last_project_name)
def _clear_items(self):
self._items_by_id = {}
self._parent_id_by_id = {}
self._has_content = False
super(FoldersModel, self).clear()
root_item = self.invisibleRootItem()
root_item.removeRows(0, root_item.rowCount())
def get_index_by_id(self, item_id):
"""Get index by folder id.
@ -90,7 +100,7 @@ class FoldersModel(QtGui.QStandardItemModel):
self._is_refreshing = True
if self._last_project_name != project_name:
self.clear()
self._clear_items()
self._last_project_name = project_name
thread = self._refresh_threads.get(project_name)
@ -135,7 +145,7 @@ class FoldersModel(QtGui.QStandardItemModel):
def _fill_items(self, folder_items_by_id):
if not folder_items_by_id:
if folder_items_by_id is not None:
self.clear()
self._clear_items()
self._is_refreshing = False
self.refreshed.emit()
return
@ -247,6 +257,7 @@ class FoldersWidget(QtWidgets.QWidget):
folders_model = FoldersModel(controller)
folders_proxy_model = RecursiveSortFilterProxyModel()
folders_proxy_model.setSourceModel(folders_model)
folders_proxy_model.setSortCaseSensitivity(QtCore.Qt.CaseInsensitive)
folders_view.setModel(folders_proxy_model)
@ -293,6 +304,14 @@ class FoldersWidget(QtWidgets.QWidget):
self._folders_proxy_model.setFilterFixedString(name)
def refresh(self):
"""Refresh folders model.
Force to update folders model from controller.
"""
self._folders_model.refresh()
def _on_project_selection_change(self, event):
project_name = event["project_name"]
self._set_project_name(project_name)
@ -300,9 +319,6 @@ class FoldersWidget(QtWidgets.QWidget):
def _set_project_name(self, project_name):
self._folders_model.set_project_name(project_name)
def _clear(self):
self._folders_model.clear()
def _on_folders_refresh_finished(self, event):
if event["sender"] != SENDER_NAME:
self._set_project_name(event["project_name"])

View file

@ -44,14 +44,20 @@ class TasksModel(QtGui.QStandardItemModel):
# Initial state
self._add_invalid_selection_item()
def clear(self):
def _clear_items(self):
self._items_by_name = {}
self._has_content = False
self._remove_invalid_items()
super(TasksModel, self).clear()
root_item = self.invisibleRootItem()
root_item.removeRows(0, root_item.rowCount())
def refresh(self, project_name, folder_id):
"""Refresh tasks for folder.
def refresh(self):
"""Refresh tasks for last project and folder."""
self._refresh(self._last_project_name, self._last_folder_id)
def set_context(self, project_name, folder_id):
"""Set context for which should be tasks showed.
Args:
project_name (Union[str]): Name of project.
@ -121,7 +127,7 @@ class TasksModel(QtGui.QStandardItemModel):
return self._empty_tasks_item
def _add_invalid_item(self, item):
self.clear()
self._clear_items()
root_item = self.invisibleRootItem()
root_item.appendRow(item)
@ -299,6 +305,7 @@ class TasksWidget(QtWidgets.QWidget):
tasks_model = TasksModel(controller)
tasks_proxy_model = QtCore.QSortFilterProxyModel()
tasks_proxy_model.setSourceModel(tasks_model)
tasks_proxy_model.setSortCaseSensitivity(QtCore.Qt.CaseInsensitive)
tasks_view.setModel(tasks_proxy_model)
@ -334,8 +341,14 @@ class TasksWidget(QtWidgets.QWidget):
self._handle_expected_selection = handle_expected_selection
self._expected_selection_data = None
def _clear(self):
self._tasks_model.clear()
def refresh(self):
"""Refresh folders for last selected project.
Force to update folders model from controller. This may or may not
trigger query from server, that's based on controller's cache.
"""
self._tasks_model.refresh()
def _on_tasks_refresh_finished(self, event):
"""Tasks were refreshed in controller.
@ -353,13 +366,13 @@ class TasksWidget(QtWidgets.QWidget):
or event["folder_id"] != self._selected_folder_id
):
return
self._tasks_model.refresh(
self._tasks_model.set_context(
event["project_name"], self._selected_folder_id
)
def _folder_selection_changed(self, event):
self._selected_folder_id = event["folder_id"]
self._tasks_model.refresh(
self._tasks_model.set_context(
event["project_name"], self._selected_folder_id
)

View file

@ -237,6 +237,7 @@
},
{
"name": "13-0",
"label": "13.0",
"use_python_2": false,
"executables": {
"windows": [
@ -319,6 +320,7 @@
},
{
"name": "13-0",
"label": "13.0",
"use_python_2": false,
"executables": {
"windows": [
@ -405,6 +407,7 @@
},
{
"name": "13-0",
"label": "13.0",
"use_python_2": false,
"executables": {
"windows": [
@ -491,6 +494,7 @@
},
{
"name": "13-0",
"label": "13.0",
"use_python_2": false,
"executables": {
"windows": [
@ -577,6 +581,7 @@
},
{
"name": "13-0",
"label": "13.0",
"use_python_2": false,
"executables": {
"windows": [

View file

@ -124,6 +124,24 @@ class LimitGroupsSubmodel(BaseSettingsModel):
)
def fusion_deadline_plugin_enum():
"""Return a list of value/label dicts for the enumerator.
Returning a list of dicts is used to allow for a custom label to be
displayed in the UI.
"""
return [
{
"value": "Fusion",
"label": "Fusion"
},
{
"value": "FusionCmd",
"label": "FusionCmd"
}
]
class FusionSubmitDeadlineModel(BaseSettingsModel):
enabled: bool = Field(True, title="Enabled")
optional: bool = Field(False, title="Optional")
@ -132,6 +150,9 @@ class FusionSubmitDeadlineModel(BaseSettingsModel):
chunk_size: int = Field(10, title="Frame per Task")
concurrent_tasks: int = Field(1, title="Number of concurrent tasks")
group: str = Field("", title="Group Name")
plugin: str = Field("Fusion",
enum_resolver=fusion_deadline_plugin_enum,
title="Deadline Plugin")
class NukeSubmitDeadlineModel(BaseSettingsModel):

View file

@ -1 +1 @@
__version__ = "0.1.1"
__version__ = "0.1.2"