From 8b2edb368bf38c7465a648e868bf331767efb4f4 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 16 Dec 2021 12:23:36 +0100 Subject: [PATCH 01/14] added 2022 variant to aftereffects settings schemas --- .../system_schema/host_settings/schema_aftereffects.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openpype/settings/entities/schemas/system_schema/host_settings/schema_aftereffects.json b/openpype/settings/entities/schemas/system_schema/host_settings/schema_aftereffects.json index 6c36a9bb8a..334c9aa235 100644 --- a/openpype/settings/entities/schemas/system_schema/host_settings/schema_aftereffects.json +++ b/openpype/settings/entities/schemas/system_schema/host_settings/schema_aftereffects.json @@ -36,6 +36,11 @@ "app_variant_label": "2021", "app_variant": "2021", "variant_skip_paths": ["use_python_2"] + }, + { + "app_variant_label": "2022", + "app_variant": "2022", + "variant_skip_paths": ["use_python_2"] } ] } From 8f622281cf7300ea3bc92912ea133cbcfbef51e2 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 16 Dec 2021 13:45:31 +0100 Subject: [PATCH 02/14] change override state on calling pop --- openpype/settings/entities/dict_mutable_keys_entity.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/openpype/settings/entities/dict_mutable_keys_entity.py b/openpype/settings/entities/dict_mutable_keys_entity.py index cff346e9ea..be78018ebb 100644 --- a/openpype/settings/entities/dict_mutable_keys_entity.py +++ b/openpype/settings/entities/dict_mutable_keys_entity.py @@ -60,6 +60,12 @@ class DictMutableKeysEntity(EndpointEntity): def pop(self, key, *args, **kwargs): if key in self.required_keys: raise RequiredKeyModified(self.path, key) + + if self._override_state is OverrideState.STUDIO: + self._has_studio_override = True + elif self._override_state is OverrideState.PROJECT: + self._has_project_override = True + result = self.children_by_key.pop(key, *args, **kwargs) self.on_change() return result From 16ae2913863602b003c548eb7e126c2e741a3ac8 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 16 Dec 2021 15:46:45 +0100 Subject: [PATCH 03/14] adde abstract method to be able know if entity has children by a path key --- openpype/settings/entities/base_entity.py | 5 +++++ .../settings/entities/dict_conditional.py | 3 +++ .../entities/dict_immutable_keys_entity.py | 3 +++ .../entities/dict_mutable_keys_entity.py | 3 +++ openpype/settings/entities/input_entities.py | 3 +++ openpype/settings/entities/item_entities.py | 21 +++++++++++++++++++ openpype/settings/entities/list_entity.py | 16 ++++++++++++++ openpype/settings/entities/root_entities.py | 3 +++ 8 files changed, 57 insertions(+) diff --git a/openpype/settings/entities/base_entity.py b/openpype/settings/entities/base_entity.py index 341968bd75..cbc042d29d 100644 --- a/openpype/settings/entities/base_entity.py +++ b/openpype/settings/entities/base_entity.py @@ -235,6 +235,11 @@ class BaseItemEntity(BaseEntity): """Return system settings entity.""" pass + @abstractmethod + def has_child_with_key(self, key): + """Entity contains key as children.""" + pass + def schema_validations(self): """Validate schema of entity and it's hierachy. diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index 5f1c172f31..92512a6668 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -107,6 +107,9 @@ class DictConditionalEntity(ItemEntity): for _key, _value in new_value.items(): self.non_gui_children[self.current_enum][_key].set(_value) + def has_child_with_key(self, key): + return key in self.keys() + def _item_initialization(self): self._default_metadata = NOT_SET self._studio_override_metadata = NOT_SET diff --git a/openpype/settings/entities/dict_immutable_keys_entity.py b/openpype/settings/entities/dict_immutable_keys_entity.py index 6131fa2ac7..c477a0eb0f 100644 --- a/openpype/settings/entities/dict_immutable_keys_entity.py +++ b/openpype/settings/entities/dict_immutable_keys_entity.py @@ -205,6 +205,9 @@ class DictImmutableKeysEntity(ItemEntity): ) self.show_borders = self.schema_data.get("show_borders", True) + def has_child_with_key(self, key): + return key in self.non_gui_children + def collect_static_entities_by_path(self): output = {} if self.is_dynamic_item or self.is_in_dynamic_item: diff --git a/openpype/settings/entities/dict_mutable_keys_entity.py b/openpype/settings/entities/dict_mutable_keys_entity.py index cff346e9ea..97af9e5c81 100644 --- a/openpype/settings/entities/dict_mutable_keys_entity.py +++ b/openpype/settings/entities/dict_mutable_keys_entity.py @@ -191,6 +191,9 @@ class DictMutableKeysEntity(EndpointEntity): child_entity = self.children_by_key[key] self.set_child_label(child_entity, label) + def has_child_with_key(self, key): + return key in self.children_by_key + def _item_initialization(self): self._default_metadata = {} self._studio_override_metadata = {} diff --git a/openpype/settings/entities/input_entities.py b/openpype/settings/entities/input_entities.py index a0598d405e..16893747a6 100644 --- a/openpype/settings/entities/input_entities.py +++ b/openpype/settings/entities/input_entities.py @@ -118,6 +118,9 @@ class InputEntity(EndpointEntity): return self.value == other.value return self.value == other + def has_child_with_key(self, key): + return False + def get_child_path(self, child_obj): raise TypeError("{} can't have children".format( self.__class__.__name__ diff --git a/openpype/settings/entities/item_entities.py b/openpype/settings/entities/item_entities.py index ff0a982900..9c6f428b97 100644 --- a/openpype/settings/entities/item_entities.py +++ b/openpype/settings/entities/item_entities.py @@ -1,3 +1,7 @@ +import re + +import six + from .lib import ( NOT_SET, STRING_TYPE, @@ -48,6 +52,9 @@ class PathEntity(ItemEntity): raise AttributeError(self.attribute_error_msg.format("items")) return self.child_obj.items() + def has_child_with_key(self, key): + return self.child_obj.has_child_with_key(key) + def _item_initialization(self): if self.group_item is None and not self.is_group: self.is_group = True @@ -197,6 +204,7 @@ class PathEntity(ItemEntity): class ListStrictEntity(ItemEntity): schema_types = ["list-strict"] + _key_regex = re.compile(r"[0-9]+") def __getitem__(self, idx): if not isinstance(idx, int): @@ -216,6 +224,19 @@ class ListStrictEntity(ItemEntity): return self.children[idx] return default + def has_child_with_key(self, key): + if ( + key + and isinstance(key, six.string_types) + and self._key_regex.match(key) + ): + key = int(key) + + if not isinstance(key, int): + return False + + return 0 <= key < len(self.children) + def _item_initialization(self): self.valid_value_types = (list, ) self.require_key = True diff --git a/openpype/settings/entities/list_entity.py b/openpype/settings/entities/list_entity.py index 5d89a81351..0268c208bb 100644 --- a/openpype/settings/entities/list_entity.py +++ b/openpype/settings/entities/list_entity.py @@ -1,4 +1,6 @@ import copy +import six +import re from . import ( BaseEntity, EndpointEntity @@ -21,6 +23,7 @@ class ListEntity(EndpointEntity): "collapsible": True, "collapsed": False } + _key_regex = re.compile(r"[0-9]+") def __iter__(self): for item in self.children: @@ -144,6 +147,19 @@ class ListEntity(EndpointEntity): ) self.on_change() + def has_child_with_key(self, key): + if ( + key + and isinstance(key, six.string_types) + and self._key_regex.match(key) + ): + key = int(key) + + if not isinstance(key, int): + return False + + return 0 <= key < len(self.children) + def _convert_to_valid_type(self, value): if isinstance(value, (set, tuple)): return list(value) diff --git a/openpype/settings/entities/root_entities.py b/openpype/settings/entities/root_entities.py index b8baed8a93..687784a359 100644 --- a/openpype/settings/entities/root_entities.py +++ b/openpype/settings/entities/root_entities.py @@ -127,6 +127,9 @@ class RootEntity(BaseItemEntity): for _key, _value in new_value.items(): self.non_gui_children[_key].set(_value) + def has_child_with_key(self, key): + return key in self.non_gui_children + def keys(self): return self.non_gui_children.keys() From 06fed885ad5e9a5626baa50dd981856ea98eb183 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 16 Dec 2021 15:47:29 +0100 Subject: [PATCH 04/14] breadcrumbs are not brute focing access to child entities by accessing with __getitem__ but checking if 'has_child_with_key' --- .../settings/settings/breadcrumbs_widget.py | 31 +++++++++++++++---- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/openpype/tools/settings/settings/breadcrumbs_widget.py b/openpype/tools/settings/settings/breadcrumbs_widget.py index d25cbdc8cb..7524bc61f0 100644 --- a/openpype/tools/settings/settings/breadcrumbs_widget.py +++ b/openpype/tools/settings/settings/breadcrumbs_widget.py @@ -71,17 +71,35 @@ class SettingsBreadcrumbs(BreadcrumbsModel): return True return False + def get_valid_path(self, path): + if not path: + return "" + + path_items = path.split("/") + new_path_items = [] + entity = self.entity + for item in path_items: + if not entity.has_child_with_key(item): + break + + new_path_items.append(item) + entity = entity[item] + + return "/".join(new_path_items) + def is_valid_path(self, path): if not path: return True path_items = path.split("/") - try: - entity = self.entity - for item in path_items: - entity = entity[item] - except Exception: - return False + + entity = self.entity + for item in path_items: + if not entity.has_child_with_key(item): + return False + + entity = entity[item] + return True @@ -436,6 +454,7 @@ class BreadcrumbsAddressBar(QtWidgets.QFrame): self.change_path(path) def change_path(self, path): + path = self._model.get_valid_path(path) if self._model and not self._model.is_valid_path(path): self._show_address_field() else: From d392885b35de566faaecab0e8e0a1a3a029eeac2 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 16 Dec 2021 15:58:16 +0100 Subject: [PATCH 05/14] OP-2038 - introduced settings for invalid characters to use in ValidateNaming plugin --- .../plugins/publish/validate_naming.py | 44 ++++++++++++++----- .../defaults/project_settings/photoshop.json | 14 +++--- .../schema_project_photoshop.json | 42 +++++++++++++----- 3 files changed, 75 insertions(+), 25 deletions(-) diff --git a/openpype/hosts/photoshop/plugins/publish/validate_naming.py b/openpype/hosts/photoshop/plugins/publish/validate_naming.py index 0fd6794313..077f7cf132 100644 --- a/openpype/hosts/photoshop/plugins/publish/validate_naming.py +++ b/openpype/hosts/photoshop/plugins/publish/validate_naming.py @@ -1,3 +1,5 @@ +import re + import pyblish.api import openpype.api from avalon import photoshop @@ -19,20 +21,32 @@ class ValidateNamingRepair(pyblish.api.Action): and result["instance"] not in failed): failed.append(result["instance"]) + invalid_chars, replace_char = plugin.get_replace_chars() + # Apply pyblish.logic to get the instances for the plug-in instances = pyblish.api.instances_by_plugin(failed, plugin) stub = photoshop.stub() for instance in instances: self.log.info("validate_naming instance {}".format(instance)) - name = instance.data["name"].replace(" ", "_") - name = name.replace(instance.data["family"], '') - instance[0].Name = name - data = stub.read(instance[0]) - data["subset"] = "image" + name - stub.imprint(instance[0], data) + metadata = stub.read(instance[0]) + self.log.info("metadata instance {}".format(metadata)) + layer_name = None + if metadata.get("uuid"): + layer_data = stub.get_layer(metadata["uuid"]) + self.log.info("layer_data {}".format(layer_data)) + if layer_data: + layer_name = re.sub(invalid_chars, + replace_char, + layer_name) - name = stub.PUBLISH_ICON + name - stub.rename_layer(instance.data["uuid"], name) + stub.rename_layer(instance.data["uuid"], layer_name) + + subset_name = re.sub(invalid_chars, replace_char, + instance.data["name"]) + + instance[0].Name = layer_name or subset_name + metadata["subset"] = subset_name + stub.imprint(instance[0], metadata) return True @@ -49,12 +63,22 @@ class ValidateNaming(pyblish.api.InstancePlugin): families = ["image"] actions = [ValidateNamingRepair] + # configured by Settings + invalid_chars = '' + replace_char = '' + def process(self, instance): help_msg = ' Use Repair action (A) in Pyblish to fix it.' msg = "Name \"{}\" is not allowed.{}".format(instance.data["name"], help_msg) - assert " " not in instance.data["name"], msg + assert not re.search(self.invalid_chars, instance.data["name"]), msg msg = "Subset \"{}\" is not allowed.{}".format(instance.data["subset"], help_msg) - assert " " not in instance.data["subset"], msg + assert not re.search(self.invalid_chars, instance.data["subset"]), msg + + + @classmethod + def get_replace_chars(cls): + """Pass values configured in Settings for Repair.""" + return cls.invalid_chars, cls.replace_char \ No newline at end of file diff --git a/openpype/settings/defaults/project_settings/photoshop.json b/openpype/settings/defaults/project_settings/photoshop.json index 0c24c943ec..db9bf87268 100644 --- a/openpype/settings/defaults/project_settings/photoshop.json +++ b/openpype/settings/defaults/project_settings/photoshop.json @@ -7,11 +7,6 @@ } }, "publish": { - "ValidateContainers": { - "enabled": true, - "optional": true, - "active": true - }, "CollectRemoteInstances": { "color_code_mapping": [ { @@ -22,6 +17,15 @@ } ] }, + "ValidateContainers": { + "enabled": true, + "optional": true, + "active": true + }, + "ValidateNaming": { + "invalid_chars": "[ \\\\/+\\*\\?\\(\\)\\[\\]\\{\\}:,]", + "replace_char": "_" + }, "ExtractImage": { "formats": [ "png", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json b/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json index ca388de60c..51ea5b3fe7 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json @@ -33,16 +33,6 @@ "key": "publish", "label": "Publish plugins", "children": [ - { - "type": "schema_template", - "name": "template_publish_plugin", - "template_data": [ - { - "key": "ValidateContainers", - "label": "ValidateContainers" - } - ] - }, { "type": "dict", "collapsible": true, @@ -108,6 +98,38 @@ } ] }, + { + "type": "schema_template", + "name": "template_publish_plugin", + "template_data": [ + { + "key": "ValidateContainers", + "label": "ValidateContainers" + } + ] + }, + { + "type": "dict", + "collapsible": true, + "key": "ValidateNaming", + "label": "Validate naming of subsets and layers", + "children": [ + { + "type": "label", + "label": "Subset cannot contain invalid characters or extract to file would fail" + }, + { + "type": "text", + "key": "invalid_chars", + "label": "Regex pattern of invalid characters" + }, + { + "type": "text", + "key": "replace_char", + "label": "Replacement character" + } + ] + }, { "type": "dict", "collapsible": true, From 63b7b56547cd395fa5336a7a8abe36ea9888edb2 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 16 Dec 2021 17:12:12 +0100 Subject: [PATCH 06/14] OP-2038 - fix use correct value --- openpype/hosts/photoshop/plugins/publish/validate_naming.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/photoshop/plugins/publish/validate_naming.py b/openpype/hosts/photoshop/plugins/publish/validate_naming.py index 077f7cf132..7c9ad1e923 100644 --- a/openpype/hosts/photoshop/plugins/publish/validate_naming.py +++ b/openpype/hosts/photoshop/plugins/publish/validate_naming.py @@ -22,6 +22,7 @@ class ValidateNamingRepair(pyblish.api.Action): failed.append(result["instance"]) invalid_chars, replace_char = plugin.get_replace_chars() + self.log.info("{} --- {}".format(invalid_chars, replace_char)) # Apply pyblish.logic to get the instances for the plug-in instances = pyblish.api.instances_by_plugin(failed, plugin) @@ -37,7 +38,7 @@ class ValidateNamingRepair(pyblish.api.Action): if layer_data: layer_name = re.sub(invalid_chars, replace_char, - layer_name) + layer_data.name) stub.rename_layer(instance.data["uuid"], layer_name) From 27dc4552eb2adb3dbdbab72d2b06558cec94fbb2 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 16 Dec 2021 17:14:34 +0100 Subject: [PATCH 07/14] OP-2038 - Hound --- openpype/hosts/photoshop/plugins/publish/validate_naming.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/hosts/photoshop/plugins/publish/validate_naming.py b/openpype/hosts/photoshop/plugins/publish/validate_naming.py index 7c9ad1e923..1635096f4b 100644 --- a/openpype/hosts/photoshop/plugins/publish/validate_naming.py +++ b/openpype/hosts/photoshop/plugins/publish/validate_naming.py @@ -78,8 +78,7 @@ class ValidateNaming(pyblish.api.InstancePlugin): help_msg) assert not re.search(self.invalid_chars, instance.data["subset"]), msg - @classmethod def get_replace_chars(cls): """Pass values configured in Settings for Repair.""" - return cls.invalid_chars, cls.replace_char \ No newline at end of file + return cls.invalid_chars, cls.replace_char From f1b21fedf17aa95aeea56c80f20579a8d517c9c8 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 16 Dec 2021 17:21:36 +0100 Subject: [PATCH 08/14] moved job queue module one hierarchy higher from default_modules --- openpype/modules/{default_modules => }/job_queue/__init__.py | 0 .../{default_modules => }/job_queue/job_server/__init__.py | 0 .../{default_modules => }/job_queue/job_server/job_queue_route.py | 0 .../modules/{default_modules => }/job_queue/job_server/jobs.py | 0 .../modules/{default_modules => }/job_queue/job_server/server.py | 0 .../modules/{default_modules => }/job_queue/job_server/utils.py | 0 .../modules/{default_modules => }/job_queue/job_server/workers.py | 0 .../job_queue/job_server/workers_rpc_route.py | 0 .../{default_modules => }/job_queue/job_workers/__init__.py | 0 .../{default_modules => }/job_queue/job_workers/base_worker.py | 0 openpype/modules/{default_modules => }/job_queue/module.py | 0 11 files changed, 0 insertions(+), 0 deletions(-) rename openpype/modules/{default_modules => }/job_queue/__init__.py (100%) rename openpype/modules/{default_modules => }/job_queue/job_server/__init__.py (100%) rename openpype/modules/{default_modules => }/job_queue/job_server/job_queue_route.py (100%) rename openpype/modules/{default_modules => }/job_queue/job_server/jobs.py (100%) rename openpype/modules/{default_modules => }/job_queue/job_server/server.py (100%) rename openpype/modules/{default_modules => }/job_queue/job_server/utils.py (100%) rename openpype/modules/{default_modules => }/job_queue/job_server/workers.py (100%) rename openpype/modules/{default_modules => }/job_queue/job_server/workers_rpc_route.py (100%) rename openpype/modules/{default_modules => }/job_queue/job_workers/__init__.py (100%) rename openpype/modules/{default_modules => }/job_queue/job_workers/base_worker.py (100%) rename openpype/modules/{default_modules => }/job_queue/module.py (100%) diff --git a/openpype/modules/default_modules/job_queue/__init__.py b/openpype/modules/job_queue/__init__.py similarity index 100% rename from openpype/modules/default_modules/job_queue/__init__.py rename to openpype/modules/job_queue/__init__.py diff --git a/openpype/modules/default_modules/job_queue/job_server/__init__.py b/openpype/modules/job_queue/job_server/__init__.py similarity index 100% rename from openpype/modules/default_modules/job_queue/job_server/__init__.py rename to openpype/modules/job_queue/job_server/__init__.py diff --git a/openpype/modules/default_modules/job_queue/job_server/job_queue_route.py b/openpype/modules/job_queue/job_server/job_queue_route.py similarity index 100% rename from openpype/modules/default_modules/job_queue/job_server/job_queue_route.py rename to openpype/modules/job_queue/job_server/job_queue_route.py diff --git a/openpype/modules/default_modules/job_queue/job_server/jobs.py b/openpype/modules/job_queue/job_server/jobs.py similarity index 100% rename from openpype/modules/default_modules/job_queue/job_server/jobs.py rename to openpype/modules/job_queue/job_server/jobs.py diff --git a/openpype/modules/default_modules/job_queue/job_server/server.py b/openpype/modules/job_queue/job_server/server.py similarity index 100% rename from openpype/modules/default_modules/job_queue/job_server/server.py rename to openpype/modules/job_queue/job_server/server.py diff --git a/openpype/modules/default_modules/job_queue/job_server/utils.py b/openpype/modules/job_queue/job_server/utils.py similarity index 100% rename from openpype/modules/default_modules/job_queue/job_server/utils.py rename to openpype/modules/job_queue/job_server/utils.py diff --git a/openpype/modules/default_modules/job_queue/job_server/workers.py b/openpype/modules/job_queue/job_server/workers.py similarity index 100% rename from openpype/modules/default_modules/job_queue/job_server/workers.py rename to openpype/modules/job_queue/job_server/workers.py diff --git a/openpype/modules/default_modules/job_queue/job_server/workers_rpc_route.py b/openpype/modules/job_queue/job_server/workers_rpc_route.py similarity index 100% rename from openpype/modules/default_modules/job_queue/job_server/workers_rpc_route.py rename to openpype/modules/job_queue/job_server/workers_rpc_route.py diff --git a/openpype/modules/default_modules/job_queue/job_workers/__init__.py b/openpype/modules/job_queue/job_workers/__init__.py similarity index 100% rename from openpype/modules/default_modules/job_queue/job_workers/__init__.py rename to openpype/modules/job_queue/job_workers/__init__.py diff --git a/openpype/modules/default_modules/job_queue/job_workers/base_worker.py b/openpype/modules/job_queue/job_workers/base_worker.py similarity index 100% rename from openpype/modules/default_modules/job_queue/job_workers/base_worker.py rename to openpype/modules/job_queue/job_workers/base_worker.py diff --git a/openpype/modules/default_modules/job_queue/module.py b/openpype/modules/job_queue/module.py similarity index 100% rename from openpype/modules/default_modules/job_queue/module.py rename to openpype/modules/job_queue/module.py From fd7ed342337aa4272e499a38ee2dbde58491b626 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 16 Dec 2021 17:21:49 +0100 Subject: [PATCH 09/14] added 'job_queue' module to default modules list --- openpype/modules/base.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/modules/base.py b/openpype/modules/base.py index a1df3cfd14..b5c491a1c0 100644 --- a/openpype/modules/base.py +++ b/openpype/modules/base.py @@ -41,6 +41,7 @@ DEFAULT_OPENPYPE_MODULES = ( "project_manager_action", "settings_action", "standalonepublish_action", + "job_queue", ) From 449487031c37b8e881d30d8e642b4f4f0ed5a71d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 17 Dec 2021 11:37:54 +0100 Subject: [PATCH 10/14] type label can contain links that can lead to settings or open standard urls --- openpype/tools/settings/settings/base.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/openpype/tools/settings/settings/base.py b/openpype/tools/settings/settings/base.py index b10c958880..8a420c2447 100644 --- a/openpype/tools/settings/settings/base.py +++ b/openpype/tools/settings/settings/base.py @@ -554,7 +554,9 @@ class GUIWidget(BaseWidget): def _create_label_ui(self): label = self.entity["label"] label_widget = QtWidgets.QLabel(label, self) + label_widget.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction) label_widget.setObjectName("SettingsLabel") + label_widget.linkActivated.connect(self._on_link_activate) layout = QtWidgets.QHBoxLayout(self) layout.setContentsMargins(0, 5, 0, 5) @@ -570,6 +572,14 @@ class GUIWidget(BaseWidget): layout.setContentsMargins(5, 5, 5, 5) layout.addWidget(splitter_item) + def _on_link_activate(self, url): + if not url.startswith("settings://"): + QtGui.QDesktopServices.openUrl(url) + return + + path = url.replace("settings://", "") + self.category_widget.go_to_fullpath(path) + def set_entity_value(self): pass From c647649de0ee578217b1afde4141fac2789ef145 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 17 Dec 2021 11:38:18 +0100 Subject: [PATCH 11/14] category widget can trigger change of full path --- openpype/tools/settings/settings/categories.py | 1 + openpype/tools/settings/settings/window.py | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/openpype/tools/settings/settings/categories.py b/openpype/tools/settings/settings/categories.py index 029619849e..724399c443 100644 --- a/openpype/tools/settings/settings/categories.py +++ b/openpype/tools/settings/settings/categories.py @@ -82,6 +82,7 @@ class SettingsCategoryWidget(QtWidgets.QWidget): state_changed = QtCore.Signal() saved = QtCore.Signal(QtWidgets.QWidget) restart_required_trigger = QtCore.Signal() + full_path_requested = QtCore.Signal(str, str) def __init__(self, user_role, parent=None): super(SettingsCategoryWidget, self).__init__(parent) diff --git a/openpype/tools/settings/settings/window.py b/openpype/tools/settings/settings/window.py index fd0cd1d7cd..c376e5e91e 100644 --- a/openpype/tools/settings/settings/window.py +++ b/openpype/tools/settings/settings/window.py @@ -63,7 +63,9 @@ class MainWidget(QtWidgets.QWidget): tab_widget.restart_required_trigger.connect( self._on_restart_required ) + tab_widget.full_path_requested.connect(self._on_full_path_request) + self._header_tab_widget = header_tab_widget self.tab_widgets = tab_widgets def _on_tab_save(self, source_widget): @@ -90,6 +92,14 @@ class MainWidget(QtWidgets.QWidget): if app: app.processEvents() + def _on_full_path_request(self, category, path): + for tab_widget in self.tab_widgets: + if tab_widget.contain_category_key(category): + idx = self._header_tab_widget.indexOf(tab_widget) + self._header_tab_widget.setCurrentIndex(idx) + tab_widget.set_category_path(category, path) + break + def showEvent(self, event): super(MainWidget, self).showEvent(event) if self._reset_on_show: From a5082df4634f35010ed3194b8fc3147ef36aab5a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 17 Dec 2021 11:41:19 +0100 Subject: [PATCH 12/14] added new methods for category widget that can change full path (lead to different category) --- .../tools/settings/settings/categories.py | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/openpype/tools/settings/settings/categories.py b/openpype/tools/settings/settings/categories.py index 724399c443..d7650b8663 100644 --- a/openpype/tools/settings/settings/categories.py +++ b/openpype/tools/settings/settings/categories.py @@ -269,6 +269,37 @@ class SettingsCategoryWidget(QtWidgets.QWidget): # Scroll to widget self.scroll_widget.ensureWidgetVisible(widget) + def go_to_fullpath(self, full_path): + """Full path of settings entity which can lead to different category. + + Args: + full_path (str): Full path to settings entity. It is expected that + path starts with category name ("system_setting" etc.). + """ + if not full_path: + return + items = full_path.split("/") + category = items[0] + path = "" + if len(items) > 1: + path = "/".join(items[1:]) + self.full_path_requested.emit(category, path) + + def contain_category_key(self, category): + """Parent widget ask if category of full path lead to this widget. + + Args: + category (str): The category name. + + Returns: + bool: Passed category lead to this widget. + """ + return False + + def set_category_path(self, category, path): + """Change path of widget based on category full path.""" + pass + def set_path(self, path): self.breadcrumbs_widget.set_path(path) From 61dff2c51cb0b6253affa27604c81af9cabac7ee Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 17 Dec 2021 11:41:38 +0100 Subject: [PATCH 13/14] override required methods for both current categories --- .../tools/settings/settings/categories.py | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/openpype/tools/settings/settings/categories.py b/openpype/tools/settings/settings/categories.py index d7650b8663..b046085975 100644 --- a/openpype/tools/settings/settings/categories.py +++ b/openpype/tools/settings/settings/categories.py @@ -587,6 +587,14 @@ class SettingsCategoryWidget(QtWidgets.QWidget): class SystemWidget(SettingsCategoryWidget): + def contain_category_key(self, category): + if category == "system_settings": + return True + return False + + def set_category_path(self, category, path): + self.breadcrumbs_widget.change_path(path) + def _create_root_entity(self): self.entity = SystemSettings(set_studio_state=False) self.entity.on_change_callbacks.append(self._on_entity_change) @@ -623,6 +631,21 @@ class SystemWidget(SettingsCategoryWidget): class ProjectWidget(SettingsCategoryWidget): + def contain_category_key(self, category): + if category in ("project_settings", "project_anatomy"): + return True + return False + + def set_category_path(self, category, path): + if path: + path_items = path.split("/") + if path_items[0] not in ("project_settings", "project_anatomy"): + path = "/".join([category, path]) + else: + path = category + + self.breadcrumbs_widget.change_path(path) + def initialize_attributes(self): self.project_name = None From b79b5369fb3b297016b89242dc69b059f15139e7 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 17 Dec 2021 12:03:53 +0100 Subject: [PATCH 14/14] Merge current avalon-core main --- repos/avalon-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/repos/avalon-core b/repos/avalon-core index 85c656fcf9..4f10fb1255 160000 --- a/repos/avalon-core +++ b/repos/avalon-core @@ -1 +1 @@ -Subproject commit 85c656fcf9beb06ab92d3d6ce47f6472cf88df54 +Subproject commit 4f10fb1255beb156f23afa1bb8362dfc53d0c6f8