From ada23f369b7e6946d675a7c3bb267394cb4dbb18 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 26 Aug 2021 15:02:17 +0200 Subject: [PATCH 01/18] added base of anatomy template enum entity --- openpype/settings/entities/__init__.py | 4 ++- openpype/settings/entities/enum_entity.py | 42 +++++++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/openpype/settings/entities/__init__.py b/openpype/settings/entities/__init__.py index 9cda702e9af..8c30d5044c6 100644 --- a/openpype/settings/entities/__init__.py +++ b/openpype/settings/entities/__init__.py @@ -106,7 +106,8 @@ ToolsEnumEntity, TaskTypeEnumEntity, ProvidersEnum, - DeadlineUrlEnumEntity + DeadlineUrlEnumEntity, + AnatomyTemplatesEnumEntity ) from .list_entity import ListEntity @@ -162,6 +163,7 @@ "TaskTypeEnumEntity", "ProvidersEnum", "DeadlineUrlEnumEntity", + "AnatomyTemplatesEnumEntity", "ListEntity", diff --git a/openpype/settings/entities/enum_entity.py b/openpype/settings/entities/enum_entity.py index 5db31959a52..17915d99480 100644 --- a/openpype/settings/entities/enum_entity.py +++ b/openpype/settings/entities/enum_entity.py @@ -494,3 +494,45 @@ def set_override_state(self, *args, **kwargs): if key in self.valid_keys: new_value.append(key) self._current_value = new_value + + +class AnatomyTemplatesEnumEntity(BaseEnumEntity): + schema_types = ["anatomy-templates-enum"] + + def _item_initalization(self): + self.multiselection = False + + self.enum_items = [] + self.valid_keys = set() + + enum_default = self.schema_data.get("default") or "work" + + self.value_on_not_set = enum_default + self.valid_value_types = (STRING_TYPE,) + + # GUI attribute + self.placeholder = self.schema_data.get("placeholder") + + def _get_enum_values(self): + templates_entity = self.get_entity_from_path( + "project_anatomy/templates" + ) + + valid_keys = set() + enum_items_list = [] + + for key, value in templates_entity.items(): + print(key, value) + enum_items_list.append( + {key: key}) + valid_keys.add(key) + return enum_items_list, valid_keys + + def set_override_state(self, *args, **kwargs): + super(AnatomyTemplatesEnumEntity, self).set_override_state( + *args, **kwargs + ) + + self.enum_items, self.valid_keys = self._get_enum_values() + if self._current_value not in self.valid_keys: + self._current_value = self.value_on_not_set From a06ab7e0ef08c5cb0a90bbdceddd4681cf9b5288 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 26 Aug 2021 15:03:54 +0200 Subject: [PATCH 02/18] added `workfile_template_profiles` to settings --- .../defaults/project_settings/global.json | 7 +++++ openpype/settings/entities/enum_entity.py | 1 - .../schemas/schema_global_tools.json | 31 +++++++++++++++++++ 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index aab8c2196c5..63c4bc50911 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -249,6 +249,13 @@ ] }, "Workfiles": { + "workfile_template_profiles": [ + { + "task_types": [], + "hosts": [], + "workfile_template": "work" + } + ], "last_workfile_on_startup": [ { "hosts": [], diff --git a/openpype/settings/entities/enum_entity.py b/openpype/settings/entities/enum_entity.py index 17915d99480..d174b6a3df0 100644 --- a/openpype/settings/entities/enum_entity.py +++ b/openpype/settings/entities/enum_entity.py @@ -522,7 +522,6 @@ def _get_enum_values(self): enum_items_list = [] for key, value in templates_entity.items(): - print(key, value) enum_items_list.append( {key: key}) valid_keys.add(key) diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_tools.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_tools.json index 9e39eeb39e7..245560f115d 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_tools.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_tools.json @@ -65,6 +65,37 @@ "key": "Workfiles", "label": "Workfiles", "children": [ + { + "type": "list", + "key": "workfile_template_profiles", + "label": "Workfile template profiles", + "use_label_wrap": true, + "object_type": { + "type": "dict", + "children": [ + { + "key": "task_types", + "label": "Task types", + "type": "task-types-enum" + }, + { + "type": "hosts-enum", + "key": "hosts", + "label": "Hosts", + "multiselection": true + }, + { + "type": "splitter" + }, + { + "key": "workfile_template", + "label": "Workfile template", + "type": "anatomy-templates-enum", + "multiselection": false + } + ] + } + }, { "type": "list", "key": "last_workfile_on_startup", From 59cff86ce4ea05f7ff1161cf274c1b5bbc336546 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 26 Aug 2021 15:17:04 +0200 Subject: [PATCH 03/18] fixed templates enum entity --- openpype/settings/entities/enum_entity.py | 32 ++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/openpype/settings/entities/enum_entity.py b/openpype/settings/entities/enum_entity.py index d174b6a3df0..c35a8008831 100644 --- a/openpype/settings/entities/enum_entity.py +++ b/openpype/settings/entities/enum_entity.py @@ -521,10 +521,36 @@ def _get_enum_values(self): valid_keys = set() enum_items_list = [] - for key, value in templates_entity.items(): - enum_items_list.append( - {key: key}) + others_entity = None + for key, entity in templates_entity.items(): + # Skip defaults key + if key == "defaults": + continue + + if key == "others": + others_entity = entity + continue + + label = key + if hasattr(entity, "label"): + label = entity.label or label + + enum_items_list.append({key: label}) valid_keys.add(key) + + if others_entity is not None: + print(others_entity) + get_child_label_func = getattr( + others_entity, "get_child_label", None + ) + for key, child_entity in others_entity.items(): + label = key + if callable(get_child_label_func): + label = get_child_label_func(child_entity) or label + + enum_items_list.append({key: label}) + valid_keys.add(key) + return enum_items_list, valid_keys def set_override_state(self, *args, **kwargs): From 9d4451a3d40bb507a257ff2fd8e38826966a3f82 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 26 Aug 2021 15:18:28 +0200 Subject: [PATCH 04/18] removed debug print --- openpype/settings/entities/enum_entity.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/settings/entities/enum_entity.py b/openpype/settings/entities/enum_entity.py index c35a8008831..c5330a2f04f 100644 --- a/openpype/settings/entities/enum_entity.py +++ b/openpype/settings/entities/enum_entity.py @@ -539,7 +539,6 @@ def _get_enum_values(self): valid_keys.add(key) if others_entity is not None: - print(others_entity) get_child_label_func = getattr( others_entity, "get_child_label", None ) From c0d36a27984ef232c6d66bd37f55db7ef3156e0c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 26 Aug 2021 18:44:39 +0200 Subject: [PATCH 05/18] hide not used widgets --- openpype/tools/workfiles/app.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openpype/tools/workfiles/app.py b/openpype/tools/workfiles/app.py index 42f0e422aea..66e204e89c1 100644 --- a/openpype/tools/workfiles/app.py +++ b/openpype/tools/workfiles/app.py @@ -126,10 +126,14 @@ def __init__(self, parent, root, anatomy, template_key, session=None): # for "{version". if "{version" in self.template: inputs_layout.addRow("Version:", version_widget) + else: + version_widget.setVisible(False) # Add subversion only if template containt `{comment}` if "{comment}" in self.template: inputs_layout.addRow("Subversion:", subversion_input) + else: + subversion_input.setVisible(False) inputs_layout.addRow("Extension:", ext_combo) inputs_layout.addRow("Preview:", preview_label) From 43d718d0ee3401b446cc91ce2192e0ed5d4959b2 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 26 Aug 2021 18:44:52 +0200 Subject: [PATCH 06/18] reimplemented task model --- openpype/tools/workfiles/model.py | 145 +++++++++++++++++++++++++++++- 1 file changed, 144 insertions(+), 1 deletion(-) diff --git a/openpype/tools/workfiles/model.py b/openpype/tools/workfiles/model.py index 368988fd4ef..92fbf76b956 100644 --- a/openpype/tools/workfiles/model.py +++ b/openpype/tools/workfiles/model.py @@ -1,7 +1,7 @@ import os import logging -from Qt import QtCore +from Qt import QtCore, QtGui from avalon import style from avalon.vendor import qtawesome @@ -9,6 +9,10 @@ log = logging.getLogger(__name__) +TASK_NAME_ROLE = QtCore.Qt.UserRole + 1 +TASK_TYPE_ROLE = QtCore.Qt.UserRole + 2 +TASK_ORDER_ROLE = QtCore.Qt.UserRole + 3 + class FilesModel(TreeModel): """Model listing files with specified extensions in a root folder""" @@ -151,3 +155,142 @@ def headerData(self, section, orientation, role): return "Date modified" return super(FilesModel, self).headerData(section, orientation, role) + + +class TasksProxyModel(QtCore.QSortFilterProxyModel): + def lessThan(self, x_index, y_index): + x_order = x_index.data(TASK_ORDER_ROLE) + y_order = y_index.data(TASK_ORDER_ROLE) + if x_order is not None and y_order is not None: + if x_order < y_order: + return True + if x_order > y_order: + return False + + elif x_order is None and y_order is not None: + return True + + elif y_order is None and x_order is not None: + return False + + x_name = x_index.data(QtCore.Qt.DisplayRole) + y_name = y_index.data(QtCore.Qt.DisplayRole) + if x_name == y_name: + return True + + if x_name == tuple(sorted((x_name, y_name)))[0]: + return False + return True + + +class TasksModel(QtGui.QStandardItemModel): + """A model listing the tasks combined for a list of assets""" + def __init__(self, dbcon, parent=None): + super(TasksModel, self).__init__(parent=parent) + self.dbcon = dbcon + self._default_icon = qtawesome.icon( + "fa.male", + color=style.colors.default + ) + self._no_tasks_icon = qtawesome.icon( + "fa.exclamation-circle", + color=style.colors.mid + ) + self._cached_icons = {} + self._project_task_types = {} + + self._refresh_task_types() + + def _refresh_task_types(self): + # Get the project configured icons from database + project = self.dbcon.find_one( + {"type": "project"}, + {"config.tasks"} + ) + tasks = project["config"].get("tasks") or {} + self._project_task_types = tasks + + def _try_get_awesome_icon(self, icon_name): + icon = None + if icon_name: + try: + icon = qtawesome.icon( + "fa.{}".format(icon_name), + color=style.colors.default + ) + + except Exception: + pass + return icon + + def headerData(self, section, orientation, role): + # Show nice labels in the header + if ( + role == QtCore.Qt.DisplayRole + and orientation == QtCore.Qt.Horizontal + ): + if section == 0: + return "Tasks" + + return super(TasksModel, self).headerData(section, orientation, role) + + def _get_icon(self, task_icon, task_type_icon): + if task_icon in self._cached_icons: + return self._cached_icons[task_icon] + + icon = self._try_get_awesome_icon(task_icon) + if icon is not None: + self._cached_icons[task_icon] = icon + return icon + + if task_type_icon in self._cached_icons: + icon = self._cached_icons[task_type_icon] + self._cached_icons[task_icon] = icon + return icon + + icon = self._try_get_awesome_icon(task_type_icon) + if icon is None: + icon = self._default_icon + + self._cached_icons[task_icon] = icon + self._cached_icons[task_type_icon] = icon + + return icon + + def set_asset(self, asset_doc): + """Set assets to track by their database id + + Arguments: + asset_doc (dict): Asset document from MongoDB. + """ + self.clear() + + if not asset_doc: + return + + asset_tasks = asset_doc.get("data", {}).get("tasks") or {} + items = [] + for task_name, task_info in asset_tasks.items(): + task_icon = task_info.get("icon") + task_type = task_info.get("type") + task_order = task_info.get("order") + task_type_info = self._project_task_types.get(task_type) or {} + task_type_icon = task_type_info.get("icon") + icon = self._get_icon(task_icon, task_type_icon) + + label = "{} ({})".format(task_name, task_type or "type N/A") + item = QtGui.QStandardItem(label) + item.setData(task_name, TASK_NAME_ROLE) + item.setData(task_type, TASK_TYPE_ROLE) + item.setData(task_order, TASK_ORDER_ROLE) + item.setData(icon, QtCore.Qt.DecorationRole) + item.setFlags(QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable) + items.append(item) + + if not items: + item = QtGui.QStandardItem("No task") + item.setData(self._no_tasks_icon, QtCore.Qt.DecorationRole) + item.setFlags(QtCore.Qt.NoItemFlags) + items.append(item) + + self.invisibleRootItem().appendRows(items) From a4f8521e49c3cd3d24ee403ee90e4a9bfad07dd2 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 26 Aug 2021 18:45:26 +0200 Subject: [PATCH 07/18] implemented functions to retrieve template key for workfile --- openpype/lib/avalon_context.py | 64 ++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/openpype/lib/avalon_context.py b/openpype/lib/avalon_context.py index c4217cc6d5b..b363027ec24 100644 --- a/openpype/lib/avalon_context.py +++ b/openpype/lib/avalon_context.py @@ -344,6 +344,70 @@ def get_latest_version(asset_name, subset_name, dbcon=None, project_name=None): return version_doc +def get_workfile_template_key_from_context( + project_name, asset_name, task_name, host_name, + dbcon=None, project_settings=None +): + if not dbcon: + from avalon.api import AvalonMongoDB + + dbcon = AvalonMongoDB() + + dbcon.Session["AVALON_PROJECT"] = project_name + asset_doc = dbcon.find_one( + { + "type": "asset", + "name": asset_name + }, + { + "data.tasks": 1 + } + ) + asset_tasks = asset_doc.get("data", {}).get("tasks") or {} + task_info = asset_tasks.get(task_name) or {} + task_type = task_info.get("type") + + return get_workfile_template_key( + project_name, task_type, host_name, project_settings + ) + + +def get_workfile_template_key( + project_name, task_type, host_name, project_settings=None +): + default = "work" + if not task_type or not host_name: + return default + + if not project_settings: + project_settings = get_project_settings(project_name) + + try: + profiles = ( + project_settings + ["global"] + ["tools"] + ["Workfiles"] + ["workfile_template_profiles"] + ) + except Exception: + profiles = [] + + if not profiles: + return default + + from .profiles_filtering import filter_profiles + + profile_filter = { + "task_types": task_type, + "hosts": host_name + } + profile = filter_profiles(profiles, profile_filter) + if profile: + return profile["workfile_template"] or default + return default + + def get_workdir_data(project_doc, asset_doc, task_name, host_name): """Prepare data for workdir template filling from entered information. From 461c33321861628737e6da76b9915e0e6738851f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 26 Aug 2021 19:08:06 +0200 Subject: [PATCH 08/18] changed arguments and filled docstrings to new functions --- openpype/lib/avalon_context.py | 65 +++++++++++++++++++++++++++++++--- 1 file changed, 61 insertions(+), 4 deletions(-) diff --git a/openpype/lib/avalon_context.py b/openpype/lib/avalon_context.py index b363027ec24..449dde51c43 100644 --- a/openpype/lib/avalon_context.py +++ b/openpype/lib/avalon_context.py @@ -345,15 +345,47 @@ def get_latest_version(asset_name, subset_name, dbcon=None, project_name=None): def get_workfile_template_key_from_context( - project_name, asset_name, task_name, host_name, + asset_name, task_name, host_name, project_name=None, dbcon=None, project_settings=None ): + """Helper function to get template key for workfile template. + + Do the same as `get_workfile_template_key` but returns value for "session + context". + + It is required to pass one of 'dbcon' with already set project name or + 'project_name' arguments. + + Args: + asset_name(str): Name of asset document. + task_name(str): Task name for which is template key retrieved. + Must be available on asset document under `data.tasks`. + host_name(str): Name of host implementation for which is workfile + used. + project_name(str): Project name where asset and task is. Not required + when 'dbcon' is passed. + dbcon(AvalonMongoDB): Connection to mongo with already set project + under `AVALON_PROJECT`. Not required when 'project_name' is passed. + project_settings(dict): Project settings for passed 'project_name'. + Not required at all but makes function faster. + Raises: + ValueError: When both 'dbcon' and 'project_name' were not + passed. + """ if not dbcon: + if not project_name: + raise ValueError(( + "`get_workfile_template_key_from_context` requires to pass" + " one of 'dbcon' or 'project_name' arguments." + )) from avalon.api import AvalonMongoDB dbcon = AvalonMongoDB() + dbcon.Session["AVALON_PROJECT"] = project_name + + elif not project_name: + project_name = dbcon.Session["AVALON_PROJECT"] - dbcon.Session["AVALON_PROJECT"] = project_name asset_doc = dbcon.find_one( { "type": "asset", @@ -368,18 +400,43 @@ def get_workfile_template_key_from_context( task_type = task_info.get("type") return get_workfile_template_key( - project_name, task_type, host_name, project_settings + task_type, host_name, project_name, project_settings ) def get_workfile_template_key( - project_name, task_type, host_name, project_settings=None + task_type, host_name, project_name=None, project_settings=None ): + """Workfile template key which should be used to get workfile template. + + Function is using profiles from project settings to return right template + for passet task type and host name. + + One of 'project_name' or 'project_settings' must be passed it is preffered + to pass settings if are already available. + + Args: + task_type(str): Name of task type. + host_name(str): Name of host implementation (e.g. "maya", "nuke", ...) + project_name(str): Name of project in which context should look for + settings. Not required if `project_settings` are passed. + project_settings(dict): Prepare project settings for project name. + Not needed if `project_name` is passed. + + Raises: + ValueError: When both 'project_name' and 'project_settings' were not + passed. + """ default = "work" if not task_type or not host_name: return default if not project_settings: + if not project_name: + raise ValueError(( + "`get_workfile_template_key` requires to pass" + " one of 'project_name' or 'project_settings' arguments." + )) project_settings = get_project_settings(project_name) try: From 96b1309ec927909f360b6e22f653ac85b5537bec Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 26 Aug 2021 19:10:24 +0200 Subject: [PATCH 09/18] use new functions in already existing workdir functions --- openpype/lib/__init__.py | 4 ++++ openpype/lib/avalon_context.py | 25 +++++++++++++++++++------ 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/openpype/lib/__init__.py b/openpype/lib/__init__.py index 9bcd0f7587f..3d392dc745f 100644 --- a/openpype/lib/__init__.py +++ b/openpype/lib/__init__.py @@ -71,6 +71,8 @@ get_linked_assets, get_latest_version, + get_workfile_template_key, + get_workfile_template_key_from_context, get_workdir_data, get_workdir, get_workdir_with_workdir_data, @@ -189,6 +191,8 @@ "get_linked_assets", "get_latest_version", + "get_workfile_template_key", + "get_workfile_template_key_from_context", "get_workdir_data", "get_workdir", "get_workdir_with_workdir_data", diff --git a/openpype/lib/avalon_context.py b/openpype/lib/avalon_context.py index 449dde51c43..497348af337 100644 --- a/openpype/lib/avalon_context.py +++ b/openpype/lib/avalon_context.py @@ -494,7 +494,8 @@ def get_workdir_data(project_doc, asset_doc, task_name, host_name): def get_workdir_with_workdir_data( - workdir_data, anatomy=None, project_name=None, template_key=None + workdir_data, anatomy=None, project_name=None, + template_key=None, dbcon=None ): """Fill workdir path from entered data and project's anatomy. @@ -508,8 +509,10 @@ def get_workdir_with_workdir_data( `project_name` is entered. project_name (str): Project's name. Optional if `anatomy` is entered otherwise Anatomy object is created with using the project name. - template_key (str): Key of work templates in anatomy templates. By - default is seto to `"work"`. + template_key (str): Key of work templates in anatomy templates. If not + passed `get_workfile_template_key_from_context` is used to get it. + dbcon(AvalonMongoDB): Mongo connection. Required only if 'template_key' + and 'project_name' are not passed. Returns: TemplateResult: Workdir path. @@ -527,7 +530,13 @@ def get_workdir_with_workdir_data( anatomy = Anatomy(project_name) if not template_key: - template_key = "work" + template_key = get_workfile_template_key_from_context( + workdir_data["asset"], + workdir_data["task"], + workdir_data["app"], + project_name=workdir_data["project"]["name"], + dbcon=dbcon + ) anatomy_filled = anatomy.format(workdir_data) # Output is TemplateResult object which contain usefull data @@ -568,7 +577,9 @@ def get_workdir( project_doc, asset_doc, task_name, host_name ) # Output is TemplateResult object which contain usefull data - return get_workdir_with_workdir_data(workdir_data, anatomy, template_key) + return get_workdir_with_workdir_data( + workdir_data, anatomy, template_key=template_key + ) @with_avalon @@ -637,7 +648,9 @@ def create_workfile_doc(asset_doc, task_name, filename, workdir, dbcon=None): # Prepare anatomy anatomy = Anatomy(project_doc["name"]) # Get workdir path (result is anatomy.TemplateResult) - template_workdir = get_workdir_with_workdir_data(workdir_data, anatomy) + template_workdir = get_workdir_with_workdir_data( + workdir_data, anatomy, dbcon=dbcon + ) template_workdir_path = str(template_workdir).replace("\\", "/") # Replace slashses in workdir path where workfile is located From 960c5a2279e155cdbfdd8c600d7d86a6fde48d3d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 26 Aug 2021 19:10:55 +0200 Subject: [PATCH 10/18] template key is used when launching application --- openpype/lib/applications.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/openpype/lib/applications.py b/openpype/lib/applications.py index 71ab2eac61b..fbf991a32ea 100644 --- a/openpype/lib/applications.py +++ b/openpype/lib/applications.py @@ -28,7 +28,8 @@ from .local_settings import get_openpype_username from .avalon_context import ( get_workdir_data, - get_workdir_with_workdir_data + get_workdir_with_workdir_data, + get_workfile_template_key_from_context ) from .python_module_tools import ( @@ -1236,8 +1237,18 @@ def prepare_context_environments(data): anatomy = data["anatomy"] + template_key = get_workfile_template_key_from_context( + asset_doc["name"], + task_name, + app.host_name, + project_name=project_name, + dbcon=data["dbcon"] + ) + try: - workdir = get_workdir_with_workdir_data(workdir_data, anatomy) + workdir = get_workdir_with_workdir_data( + workdir_data, anatomy, template_key=template_key + ) except Exception as exc: raise ApplicationLaunchFailed( From eb0ec073b1243b1c1b392cf43a1c2ce830954549 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 26 Aug 2021 19:11:19 +0200 Subject: [PATCH 11/18] reduced project query time by projection of required (and used) keys --- openpype/tools/workfiles/app.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/openpype/tools/workfiles/app.py b/openpype/tools/workfiles/app.py index 66e204e89c1..ca202ae2cae 100644 --- a/openpype/tools/workfiles/app.py +++ b/openpype/tools/workfiles/app.py @@ -55,9 +55,13 @@ def __init__(self, parent, root, anatomy, template_key, session=None): # Set work file data for template formatting asset_name = session["AVALON_ASSET"] - project_doc = io.find_one({ - "type": "project" - }) + project_doc = io.find_one( + {"type": "project"}, + { + "name": True, + "data.code": True + } + ) self.data = { "project": { "name": project_doc["name"], From 7e1ac056b6044a60ba38924e8d6254aa210da4aa Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 26 Aug 2021 19:13:13 +0200 Subject: [PATCH 12/18] reorganized attributes and used new task models --- openpype/tools/workfiles/app.py | 76 ++++++++++++++++----------------- 1 file changed, 37 insertions(+), 39 deletions(-) diff --git a/openpype/tools/workfiles/app.py b/openpype/tools/workfiles/app.py index ca202ae2cae..2fa2d19b358 100644 --- a/openpype/tools/workfiles/app.py +++ b/openpype/tools/workfiles/app.py @@ -12,10 +12,15 @@ from avalon.tools import lib as tools_lib from avalon.tools.widgets import AssetWidget -from avalon.tools.models import TasksModel from avalon.tools.delegates import PrettyTimeDelegate -from .model import FilesModel +from .model import ( + TASK_NAME_ROLE, + TASK_TYPE_ROLE, + FilesModel, + TasksModel, + TasksProxyModel +) from .view import FilesView from openpype.lib import ( @@ -313,32 +318,30 @@ class TasksWidget(QtWidgets.QWidget): task_changed = QtCore.Signal() - def __init__(self, parent=None): + def __init__(self, dbcon=None, parent=None): super(TasksWidget, self).__init__(parent) - self.setContentsMargins(0, 0, 0, 0) - view = QtWidgets.QTreeView() - view.setIndentation(0) - model = TasksModel(io) - view.setModel(model) + tasks_view = QtWidgets.QTreeView(self) + tasks_view.setIndentation(0) + tasks_view.setSortingEnabled(True) + if dbcon is None: + dbcon = io + + tasks_model = TasksModel(dbcon) + tasks_proxy = TasksProxyModel() + tasks_proxy.setSourceModel(tasks_model) + tasks_view.setModel(tasks_proxy) layout = QtWidgets.QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) - layout.addWidget(view) - - # Hide the default tasks "count" as we don't need that data here. - view.setColumnHidden(1, True) - - selection = view.selectionModel() - selection.currentChanged.connect(self.task_changed) + layout.addWidget(tasks_view) - self.models = { - "tasks": model - } + selection_model = tasks_view.selectionModel() + selection_model.currentChanged.connect(self.task_changed) - self.widgets = { - "view": view, - } + self._tasks_model = tasks_model + self._tasks_proxy = tasks_proxy + self._tasks_view = tasks_view self._last_selected_task = None @@ -354,7 +357,7 @@ def set_asset(self, asset): if current: self._last_selected_task = current - self.models["tasks"].set_assets(asset_docs=[asset]) + self._tasks_model.set_asset(asset_doc) if self._last_selected_task: self.select_task(self._last_selected_task) @@ -374,21 +377,20 @@ def select_task(self, task): """ # Clear selection - view = self.widgets["view"] - model = view.model() - selection_model = view.selectionModel() + selection_model = self._tasks_view.selectionModel() selection_model.clearSelection() # Select the task mode = selection_model.Select | selection_model.Rows - for row in range(model.rowCount(QtCore.QModelIndex())): - index = model.index(row, 0, QtCore.QModelIndex()) + for row in range(self._tasks_model.rowCount()): + index = self._tasks_model.index(row, 0) name = index.data(QtCore.Qt.DisplayRole) - if name == task: + if name == task_name: selection_model.select(index, mode) # Set the currently active index - view.setCurrentIndex(index) + self._tasks_view.setCurrentIndex(index) + break def get_current_task(self): """Return name of task at current index (selected) @@ -397,16 +399,12 @@ def get_current_task(self): str: Name of the current task. """ - view = self.widgets["view"] - index = view.currentIndex() - index = index.sibling(index.row(), 0) # ensure column zero for name - - selection = view.selectionModel() - if selection.isSelected(index): - # Ignore when the current task is not selected as the "No task" - # placeholder might be the current index even though it's - # disallowed to be selected. So we only return if it is selected. - return index.data(QtCore.Qt.DisplayRole) + index = self._tasks_view.currentIndex() + selection_model = self._tasks_view.selectionModel() + if index.isValid() and selection_model.isSelected(index): + return index.data(TASK_NAME_ROLE) + return None + class FilesWidget(QtWidgets.QWidget): From 9449df703f5f8d53d63b46c17f7838d8e148040f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 26 Aug 2021 19:18:58 +0200 Subject: [PATCH 13/18] added task type as part of task widget context --- openpype/tools/workfiles/app.py | 56 +++++++++++++++++++-------------- 1 file changed, 32 insertions(+), 24 deletions(-) diff --git a/openpype/tools/workfiles/app.py b/openpype/tools/workfiles/app.py index 2fa2d19b358..11d6257b06b 100644 --- a/openpype/tools/workfiles/app.py +++ b/openpype/tools/workfiles/app.py @@ -345,15 +345,15 @@ def __init__(self, dbcon=None, parent=None): self._last_selected_task = None - def set_asset(self, asset): - if asset is None: - # Asset deselected + def set_asset(self, asset_doc): + # Asset deselected + if asset_doc is None: return # Try and preserve the last selected task and reselect it # after switching assets. If there's no currently selected # asset keep whatever the "last selected" was prior to it. - current = self.get_current_task() + current = self.get_current_task_name() if current: self._last_selected_task = current @@ -365,7 +365,7 @@ def set_asset(self, asset): # Force a task changed emit. self.task_changed.emit() - def select_task(self, task): + def select_task(self, task_name): """Select a task by name. If the task does not exist in the current model then selection is only @@ -384,7 +384,7 @@ def select_task(self, task): mode = selection_model.Select | selection_model.Rows for row in range(self._tasks_model.rowCount()): index = self._tasks_model.index(row, 0) - name = index.data(QtCore.Qt.DisplayRole) + name = index.data(TASK_NAME_ROLE) if name == task_name: selection_model.select(index, mode) @@ -392,7 +392,7 @@ def select_task(self, task): self._tasks_view.setCurrentIndex(index) break - def get_current_task(self): + def get_current_task_name(self): """Return name of task at current index (selected) Returns: @@ -405,6 +405,12 @@ def get_current_task(self): return index.data(TASK_NAME_ROLE) return None + def get_current_task_type(self): + index = self._tasks_view.currentIndex() + selection_model = self._tasks_view.selectionModel() + if index.isValid() and selection_model.isSelected(index): + return index.data(TASK_TYPE_ROLE) + return None class FilesWidget(QtWidgets.QWidget): @@ -417,7 +423,8 @@ def __init__(self, parent=None): # Setup self._asset = None - self._task = None + self._task_name = None + self._task_type = None # Pype's anatomy object for current project self.anatomy = Anatomy(io.Session["AVALON_PROJECT"]) @@ -512,14 +519,15 @@ def __init__(self, parent=None): self.btn_browse = btn_browse self.btn_save = btn_save - def set_asset_task(self, asset, task): + def set_asset_task(self, asset, task_name, task_type): self._asset = asset - self._task = task + self._task_name = task_name + self._task_type = task_type # Define a custom session so we can query the work root # for a "Work area" that is not our current Session. # This way we can browse it even before we enter it. - if self._asset and self._task: + if self._asset and self._task_name and self._task_type: session = self._get_session() self.root = self.host.work_root(session) self.files_model.set_root(self.root) @@ -542,7 +550,7 @@ def _get_session(self): changes = pipeline.compute_session_changes( session, asset=self._asset, - task=self._task + task=self._task_name, ) session.update(changes) @@ -555,14 +563,14 @@ def _enter_session(self): changes = pipeline.compute_session_changes( session, asset=self._asset, - task=self._task + task=self._task_name, ) if not changes: # Return early if we're already in the right Session context # to avoid any unwanted Task Changed callbacks to be triggered. return - api.update_current_task(asset=self._asset, task=self._task) + api.update_current_task(asset=self._asset, task=self._task_name) def open_file(self, filepath): host = self.host @@ -706,7 +714,7 @@ def on_save_as_pressed(self): self._enter_session() # Make sure we are in the right session self.host.save_file(file_path) - self.set_asset_task(self._asset, self._task) + self.set_asset_task(self._asset, self._task_name, self._task_type) pipeline.emit("after.workfile.save", [file_path]) @@ -733,7 +741,7 @@ def initialize_work_directory(self): changes = pipeline.compute_session_changes( session, asset=self._asset, - task=self._task + task=self._task_name, ) session.update(changes) @@ -756,7 +764,7 @@ def initialize_work_directory(self): # Force a full to the asset as opposed to just self.refresh() so # that it will actually check again whether the Work directory exists - self.set_asset_task(self._asset, self._task) + self.set_asset_task(self._asset, self._task_name, self._task_type) def refresh(self): """Refresh listed files for current selection in the interface""" @@ -1005,7 +1013,7 @@ def on_file_select(self, filepath): if asset_docs: asset_doc = asset_docs[0] - task_name = self.tasks_widget.get_current_task() + task_name = self.tasks_widget.get_current_task_name() workfile_doc = None if asset_doc and task_name and filepath: @@ -1032,7 +1040,7 @@ def on_side_panel_save(self): def _get_current_workfile_doc(self, filepath=None): if filepath is None: filepath = self.files_widget._get_selected_filepath() - task_name = self.tasks_widget.get_current_task() + task_name = self.tasks_widget.get_current_task_name() asset_docs = self.assets_widget.get_selected_assets() if not task_name or not asset_docs or not filepath: return @@ -1052,7 +1060,7 @@ def _create_workfile_doc(self, filepath, force=False): workdir, filename = os.path.split(filepath) asset_docs = self.assets_widget.get_selected_assets() asset_doc = asset_docs[0] - task_name = self.tasks_widget.get_current_task() + task_name = self.tasks_widget.get_current_task_name() create_workfile_doc(asset_doc, task_name, filename, workdir, io) def set_context(self, context): @@ -1071,7 +1079,6 @@ def set_context(self, context): # Select the asset self.assets_widget.select_assets([asset], expand=True) - # Force a refresh on Tasks? self.tasks_widget.set_asset(asset_document) if "task" in context: @@ -1101,12 +1108,13 @@ def _on_task_changed(self): asset = self.assets_widget.get_selected_assets() or None if asset is not None: asset = asset[0] - task = self.tasks_widget.get_current_task() + task_name = self.tasks_widget.get_current_task_name() + task_type = self.tasks_widget.get_current_task_type() self.tasks_widget.setEnabled(bool(asset)) - self.files_widget.setEnabled(all([bool(task), bool(asset)])) - self.files_widget.set_asset_task(asset, task) + self.files_widget.setEnabled(all([bool(task_name), bool(asset)])) + self.files_widget.set_asset_task(asset, task_name, task_type) self.files_widget.refresh() From 2bb5059b85d574432f017d9eb65028984ffcbd76 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 26 Aug 2021 19:19:30 +0200 Subject: [PATCH 14/18] use template key functions from openpype lib --- openpype/tools/workfiles/app.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/openpype/tools/workfiles/app.py b/openpype/tools/workfiles/app.py index 11d6257b06b..3a5272c4b98 100644 --- a/openpype/tools/workfiles/app.py +++ b/openpype/tools/workfiles/app.py @@ -28,7 +28,8 @@ get_workdir, get_workfile_doc, create_workfile_doc, - save_workfile_data_to_doc + save_workfile_data_to_doc, + get_workfile_template_key ) log = logging.getLogger(__name__) @@ -547,10 +548,16 @@ def _get_session(self): """Return a modified session for the current asset and task""" session = api.Session.copy() + self.template_key = get_workfile_template_key( + self._task_type, + session["AVALON_APP"], + project_name=session["AVALON_PROJECT"] + ) changes = pipeline.compute_session_changes( session, asset=self._asset, task=self._task_name, + template_key=self.template_key ) session.update(changes) @@ -564,6 +571,7 @@ def _enter_session(self): session, asset=self._asset, task=self._task_name, + template_key=self.template_key ) if not changes: # Return early if we're already in the right Session context @@ -742,6 +750,7 @@ def initialize_work_directory(self): session, asset=self._asset, task=self._task_name, + template_key=self.template_key ) session.update(changes) From cb6b27f62e94bdfdd3e6b59debb2649452c01724 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 26 Aug 2021 19:19:39 +0200 Subject: [PATCH 15/18] minor fixes --- openpype/tools/workfiles/app.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/tools/workfiles/app.py b/openpype/tools/workfiles/app.py index 3a5272c4b98..e559eb61f39 100644 --- a/openpype/tools/workfiles/app.py +++ b/openpype/tools/workfiles/app.py @@ -628,7 +628,7 @@ def save_changes_prompt(self): result = messagebox.exec_() if result == messagebox.Yes: return True - elif result == messagebox.No: + if result == messagebox.No: return False return None @@ -950,7 +950,7 @@ def __init__(self, parent=None): assets_widget = AssetWidget(io, parent=home_body_widget) assets_widget.set_current_asset_btn_visibility(True) - tasks_widget = TasksWidget(home_body_widget) + tasks_widget = TasksWidget(io, home_body_widget) files_widget = FilesWidget(home_body_widget) side_panel = SidePanelWidget(home_body_widget) From fd30b9290487e866c567875c935be8539f191ec5 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 26 Aug 2021 19:31:20 +0200 Subject: [PATCH 16/18] added new anatomy-templates-enum to readme --- openpype/settings/entities/schemas/README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/openpype/settings/entities/schemas/README.md b/openpype/settings/entities/schemas/README.md index 2034d4e4635..05605f8ce11 100644 --- a/openpype/settings/entities/schemas/README.md +++ b/openpype/settings/entities/schemas/README.md @@ -380,6 +380,20 @@ How output of the schema could look like on save: } ``` +### anatomy-templates-enum +- enumeration of all available anatomy template keys +- have only single selection mode +- it is possible to define default value `default` + - `"work"` is used if default value is not specified +``` +{ + "key": "host", + "label": "Host name", + "type": "anatomy-templates-enum", + "default": "publish" +} +``` + ### hosts-enum - enumeration of available hosts - multiselection can be allowed with setting key `"multiselection"` to `True` (Default: `False`) From 1c571b62febbc70340b39b5c4ba7c3f23ed44d7c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 27 Aug 2021 14:41:24 +0200 Subject: [PATCH 17/18] pass template key --- openpype/tools/workfiles/app.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openpype/tools/workfiles/app.py b/openpype/tools/workfiles/app.py index e559eb61f39..b542e6e7189 100644 --- a/openpype/tools/workfiles/app.py +++ b/openpype/tools/workfiles/app.py @@ -578,7 +578,11 @@ def _enter_session(self): # to avoid any unwanted Task Changed callbacks to be triggered. return - api.update_current_task(asset=self._asset, task=self._task_name) + api.update_current_task( + asset=self._asset, + task=self._task_name, + template_key=self.template_key + ) def open_file(self, filepath): host = self.host From f36f504faacb7a86690b90bccc45f82ce9395b14 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 31 Aug 2021 19:18:45 +0200 Subject: [PATCH 18/18] updated avalon-core --- repos/avalon-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/repos/avalon-core b/repos/avalon-core index 52e24a9993e..f48fce09c09 160000 --- a/repos/avalon-core +++ b/repos/avalon-core @@ -1 +1 @@ -Subproject commit 52e24a9993e5223b0a719786e77a4b87e936e556 +Subproject commit f48fce09c0986c1fd7f6731de33907be46b436c5