mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-25 21:32:15 +01:00
Merge branch 'develop' into chore/resolve_to_new_style_host
This commit is contained in:
commit
0760e4f394
25 changed files with 259 additions and 97 deletions
|
|
@ -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():
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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'],
|
||||
|
|
|
|||
|
|
@ -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"]
|
||||
|
|
|
|||
|
|
@ -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"]
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 "
|
||||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"]
|
||||
|
||||
|
|
|
|||
|
|
@ -52,7 +52,8 @@
|
|||
"priority": 50,
|
||||
"chunk_size": 10,
|
||||
"concurrent_tasks": 1,
|
||||
"group": ""
|
||||
"group": "",
|
||||
"plugin": "Fusion"
|
||||
},
|
||||
"NukeSubmitDeadline": {
|
||||
"enabled": true,
|
||||
|
|
|
|||
|
|
@ -289,6 +289,15 @@
|
|||
"type": "text",
|
||||
"key": "group",
|
||||
"label": "Group Name"
|
||||
},
|
||||
{
|
||||
"type": "enum",
|
||||
"key": "plugin",
|
||||
"label": "Deadline Plugin",
|
||||
"enum_items": [
|
||||
{"Fusion": "Fusion"},
|
||||
{"FusionCmd": "FusionCmd"}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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"]))
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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"])
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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": [
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
__version__ = "0.1.1"
|
||||
__version__ = "0.1.2"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue