From 08e8f6016193002979af8ed090d02634ca1c1db5 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 12 Jul 2021 15:04:28 +0200 Subject: [PATCH 01/21] list entity can use templates or schemas --- openpype/settings/entities/list_entity.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/openpype/settings/entities/list_entity.py b/openpype/settings/entities/list_entity.py index 64bbad28a7..ce200862f6 100644 --- a/openpype/settings/entities/list_entity.py +++ b/openpype/settings/entities/list_entity.py @@ -141,7 +141,21 @@ class ListEntity(EndpointEntity): item_schema = self.schema_data["object_type"] if not isinstance(item_schema, dict): item_schema = {"type": item_schema} - self.item_schema = item_schema + + schema_template_used = False + _item_schemas = self.schema_hub.resolve_schema_data(item_schema) + if len(_item_schemas) == 1: + self.item_schema = _item_schemas[0] + if self.item_schema != item_schema: + schema_template_used = True + if "label" in self.item_schema: + self.item_schema.pop("label") + self.item_schema["use_label_wrap"] = False + else: + self.item_schema = _item_schemas + + # Store if was used template or schema + self._schema_template_used = schema_template_used if self.group_item is None: self.is_group = True From ff7ccfecba187237465151d1fbd6f5024973ef93 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 12 Jul 2021 15:07:35 +0200 Subject: [PATCH 02/21] validate children on schema validations only if was not used from schema --- openpype/settings/entities/list_entity.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openpype/settings/entities/list_entity.py b/openpype/settings/entities/list_entity.py index ce200862f6..4a2b5968d9 100644 --- a/openpype/settings/entities/list_entity.py +++ b/openpype/settings/entities/list_entity.py @@ -187,7 +187,11 @@ class ListEntity(EndpointEntity): child_validated = True break - if not child_validated: + # Do not validate if was used schema or template + # - that is validated on first created children + # - it is because template or schema can use itself inside children + # TODO Do validations maybe store to `schema_hub` what is validated + if not self._schema_template_used and not child_validated: idx = 0 tmp_child = self._add_new_item(idx) tmp_child.schema_validations() From 8b789df5ec7beaf19b8d473c1012949ff294fc84 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 12 Jul 2021 15:07:58 +0200 Subject: [PATCH 03/21] validate if item_schema is list --- openpype/settings/entities/list_entity.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/openpype/settings/entities/list_entity.py b/openpype/settings/entities/list_entity.py index 4a2b5968d9..b12e6d8f5c 100644 --- a/openpype/settings/entities/list_entity.py +++ b/openpype/settings/entities/list_entity.py @@ -164,6 +164,12 @@ class ListEntity(EndpointEntity): self.initial_value = [] def schema_validations(self): + if isinstance(self.item_schema, list): + reason = ( + "`ListWidget` has multiple items as object type." + ) + raise EntitySchemaError(self, reason) + super(ListEntity, self).schema_validations() if self.is_dynamic_item and self.use_label_wrap: From 8f6e8b19885e836a8adf3892659d282909f1cbf0 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 12 Jul 2021 15:08:09 +0200 Subject: [PATCH 04/21] handle child validations --- openpype/settings/entities/list_entity.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/openpype/settings/entities/list_entity.py b/openpype/settings/entities/list_entity.py index b12e6d8f5c..e89c7cadec 100644 --- a/openpype/settings/entities/list_entity.py +++ b/openpype/settings/entities/list_entity.py @@ -94,6 +94,12 @@ class ListEntity(EndpointEntity): def _add_new_item(self, idx=None): child_obj = self.create_schema_object(self.item_schema, self, True) + + # Validate child if was not validated yet + if not self._child_validated: + child_obj.schema_validations() + self._child_validated = True + if idx is None: self.children.append(child_obj) else: @@ -156,6 +162,8 @@ class ListEntity(EndpointEntity): # Store if was used template or schema self._schema_template_used = schema_template_used + # Store if child was validated + self._child_validated = False if self.group_item is None: self.is_group = True @@ -202,6 +210,9 @@ class ListEntity(EndpointEntity): tmp_child = self._add_new_item(idx) tmp_child.schema_validations() self.children.pop(idx) + child_validated = True + + self._child_validated = child_validated def get_child_path(self, child_obj): result_idx = None From 0356e60faf83c48db881cf94ea21e6fa8a482b99 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 12 Jul 2021 17:31:34 +0200 Subject: [PATCH 05/21] get template name from item --- openpype/settings/entities/lib.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/openpype/settings/entities/lib.py b/openpype/settings/entities/lib.py index e58281644a..dee80c09aa 100644 --- a/openpype/settings/entities/lib.py +++ b/openpype/settings/entities/lib.py @@ -126,6 +126,22 @@ class SchemasHub: def gui_types(self): return self._gui_types + def get_template_name(self, item_def, default=None): + """Get template name from passed item definition. + + Args: + item_def(dict): Definition of item with "type". + default(object): Default return value. + """ + output = default + if not item_def or not isinstance(item_def, dict): + return output + + item_type = item_def.get("type") + if item_type in ("template", "schema_template"): + output = item_def["name"] + return output + def get_schema(self, schema_name): """Get schema definition data by it's name. From 41218b61ec79a5ad7dc68a42f636a44562c4cef5 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 12 Jul 2021 17:32:13 +0200 Subject: [PATCH 06/21] added validation methods and variables --- openpype/settings/entities/lib.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/openpype/settings/entities/lib.py b/openpype/settings/entities/lib.py index dee80c09aa..1c4a51b7c9 100644 --- a/openpype/settings/entities/lib.py +++ b/openpype/settings/entities/lib.py @@ -111,6 +111,10 @@ class SchemasHub: self._loaded_templates = {} self._loaded_schemas = {} + # Store validating and validated dynamic template or schemas + self._validating_dynamic = set() + self._validated_dynamic = set() + # It doesn't make sence to reload types on each reset as they can't be # changed self._load_types() @@ -142,6 +146,27 @@ class SchemasHub: output = item_def["name"] return output + def is_dynamic_template_validating(self, template_name): + """Is template validating using different entity. + + Returns: + bool: Is template validating. + """ + if template_name in self._validating_dynamic: + return True + return False + + def is_dynamic_template_validated(self, template_name): + """Is template already validated. + + Returns: + bool: Is template validated. + """ + + if template_name in self._validated_dynamic: + return True + return False + def get_schema(self, schema_name): """Get schema definition data by it's name. From 027cb48a13e718dfcdb0c25327ddba4fbed677d7 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 12 Jul 2021 17:36:54 +0200 Subject: [PATCH 07/21] added context manager method for using validation of dynamic template --- openpype/settings/entities/lib.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/openpype/settings/entities/lib.py b/openpype/settings/entities/lib.py index 1c4a51b7c9..01f61d8bdf 100644 --- a/openpype/settings/entities/lib.py +++ b/openpype/settings/entities/lib.py @@ -3,6 +3,7 @@ import re import json import copy import inspect +import contextlib from .exceptions import ( SchemaTemplateMissingKeys, @@ -167,6 +168,23 @@ class SchemasHub: return True return False + @contextlib.contextmanager + def validating_dynamic(self, template_name): + """Template name is validating and validated. + + Context manager that cares about storing template name validations of + template. + + This is to avoid infinite loop of dynamic children validation. + """ + self._validating_dynamic.add(template_name) + try: + yield + self._validated_dynamic.add(template_name) + + finally: + self._validating_dynamic.remove(template_name) + def get_schema(self, schema_name): """Get schema definition data by it's name. From 148e1a9564c421fabcd31349aac1a146db2c70e6 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 12 Jul 2021 17:37:15 +0200 Subject: [PATCH 08/21] better children validation of dynamic templates --- openpype/settings/entities/list_entity.py | 42 ++++++++++++----------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/openpype/settings/entities/list_entity.py b/openpype/settings/entities/list_entity.py index e89c7cadec..b07441251a 100644 --- a/openpype/settings/entities/list_entity.py +++ b/openpype/settings/entities/list_entity.py @@ -94,12 +94,6 @@ class ListEntity(EndpointEntity): def _add_new_item(self, idx=None): child_obj = self.create_schema_object(self.item_schema, self, True) - - # Validate child if was not validated yet - if not self._child_validated: - child_obj.schema_validations() - self._child_validated = True - if idx is None: self.children.append(child_obj) else: @@ -148,12 +142,11 @@ class ListEntity(EndpointEntity): if not isinstance(item_schema, dict): item_schema = {"type": item_schema} - schema_template_used = False + obj_template_name = self.schema_hub.get_template_name(item_schema) _item_schemas = self.schema_hub.resolve_schema_data(item_schema) if len(_item_schemas) == 1: self.item_schema = _item_schemas[0] if self.item_schema != item_schema: - schema_template_used = True if "label" in self.item_schema: self.item_schema.pop("label") self.item_schema["use_label_wrap"] = False @@ -161,9 +154,7 @@ class ListEntity(EndpointEntity): self.item_schema = _item_schemas # Store if was used template or schema - self._schema_template_used = schema_template_used - # Store if child was validated - self._child_validated = False + self._obj_template_name = obj_template_name if self.group_item is None: self.is_group = True @@ -195,24 +186,35 @@ class ListEntity(EndpointEntity): raise EntitySchemaError(self, reason) # Validate object type schema - child_validated = False + validate_children = True for child_entity in self.children: child_entity.schema_validations() - child_validated = True + validate_children = False break - # Do not validate if was used schema or template - # - that is validated on first created children - # - it is because template or schema can use itself inside children - # TODO Do validations maybe store to `schema_hub` what is validated - if not self._schema_template_used and not child_validated: + if validate_children and self._obj_template_name: + _validated = self.schema_hub.is_dynamic_template_validated( + self._obj_template_name + ) + _validating = self.schema_hub.is_dynamic_template_validating( + self._obj_template_name + ) + validate_children = not _validated and not _validating + + if not validate_children: + return + + def _validate(): idx = 0 tmp_child = self._add_new_item(idx) tmp_child.schema_validations() self.children.pop(idx) - child_validated = True - self._child_validated = child_validated + if self._obj_template_name: + with self.schema_hub.validating_dynamic(self._obj_template_name): + _validate() + else: + _validate() def get_child_path(self, child_obj): result_idx = None From 858e46d0f63e3e2e2575935f9e4a673d6932b7af Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 15 Jul 2021 13:53:58 +0200 Subject: [PATCH 09/21] added description and example --- openpype/settings/entities/schemas/README.md | 59 ++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/openpype/settings/entities/schemas/README.md b/openpype/settings/entities/schemas/README.md index 3c360b892f..e098198c2c 100644 --- a/openpype/settings/entities/schemas/README.md +++ b/openpype/settings/entities/schemas/README.md @@ -404,6 +404,8 @@ How output of the schema could look like on save: - there are 2 possible ways how to set the type: 1.) dictionary with item modifiers (`number` input has `minimum`, `maximum` and `decimals`) in that case item type must be set as value of `"type"` (example below) 2.) item type name as string without modifiers (e.g. `text`) + 3.) enhancement of 1.) there is also support of `template` type but be carefull about endless loop of templates + - goal of using `template` is to easily change same item definitions in multiple lists 1.) with item modifiers ``` @@ -429,6 +431,63 @@ How output of the schema could look like on save: } ``` +3.) with template definition +``` +# Schema of list item where template is used +{ + "type": "list", + "key": "menu_items", + "label": "Menu Items", + "object_type": { + "type": "template", + "name": "template_object_example" + } +} + +# WARNING: +# In this example the template use itself inside which will work in `list` +# but may cause an issue in other entity types (e.g. `dict`). +[ + { + "type": "dict-conditional", + "use_label_wrap": true, + "collapsible": true, + "key": "menu_items", + "label": "Menu items", + "enum_key": "type", + "enum_label": "Type", + "enum_children": [ + { + "key": "action", + "label": "Action", + "children": [ + { + "type": "text", + "key": "key", + "label": "Key" + } + ] + }, + { + "key": "menu", + "label": "Menu", + "children": [ + { + "key": "children", + "label": "Children", + "type": "list", + "object_type": { + "type": "template", + "name": "template_object_example" + } + } + ] + } + ] + } +] +``` + ### dict-modifiable - one of dictionary inputs, this is only used as value input - items in this input can be removed and added same way as in `list` input From e97d14634535bed8aa2daaa4a3078dc6bf252427 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 27 Jul 2021 14:50:38 +0200 Subject: [PATCH 10/21] added more specific name of template filename --- openpype/settings/entities/schemas/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/settings/entities/schemas/README.md b/openpype/settings/entities/schemas/README.md index 02e3e0a83c..8760187038 100644 --- a/openpype/settings/entities/schemas/README.md +++ b/openpype/settings/entities/schemas/README.md @@ -452,6 +452,8 @@ How output of the schema could look like on save: # WARNING: # In this example the template use itself inside which will work in `list` # but may cause an issue in other entity types (e.g. `dict`). + +'template_object_example.json' : [ { "type": "dict-conditional", From 12063d2b5ff38f7d3d900f8d41d8c839be80120d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 29 Jul 2021 19:16:40 +0200 Subject: [PATCH 11/21] added example of using template as object type in list --- .../example_infinite_hierarchy.json | 58 +++++++++++++++++++ .../schemas/system_schema/example_schema.json | 11 ++++ 2 files changed, 69 insertions(+) create mode 100644 openpype/settings/entities/schemas/system_schema/example_infinite_hierarchy.json diff --git a/openpype/settings/entities/schemas/system_schema/example_infinite_hierarchy.json b/openpype/settings/entities/schemas/system_schema/example_infinite_hierarchy.json new file mode 100644 index 0000000000..a2660e9bf2 --- /dev/null +++ b/openpype/settings/entities/schemas/system_schema/example_infinite_hierarchy.json @@ -0,0 +1,58 @@ +[ + { + "type": "dict-conditional", + "use_label_wrap": true, + "collapsible": true, + "key": "menu_items", + "label": "Menu items", + "enum_key": "type", + "enum_label": "Type", + "enum_children": [ + { + "key": "action", + "label": "Action", + "children": [ + { + "type": "text", + "key": "key", + "label": "Key" + }, + { + "type": "text", + "key": "label", + "label": "Label" + }, + { + "type": "text", + "key": "command", + "label": "Comand" + } + ] + }, + { + "key": "menu", + "label": "Menu", + "children": [ + { + "type": "text", + "key": "label", + "label": "Label" + }, + { + "key": "children", + "label": "Children", + "type": "list", + "object_type": { + "type": "template", + "name": "example_infinite_hierarchy" + } + } + ] + }, + { + "key": "separator", + "label": "Separator" + } + ] + } +] diff --git a/openpype/settings/entities/schemas/system_schema/example_schema.json b/openpype/settings/entities/schemas/system_schema/example_schema.json index c3287d7452..71a15ca721 100644 --- a/openpype/settings/entities/schemas/system_schema/example_schema.json +++ b/openpype/settings/entities/schemas/system_schema/example_schema.json @@ -57,6 +57,17 @@ } ] }, + { + "type": "list", + "use_label_wrap": true, + "collapsible": true, + "key": "infinite_hierarchy", + "label": "Infinite list template hierarchy", + "object_type": { + "type": "template", + "name": "example_infinite_hierarchy" + } + }, { "type": "dict", "key": "schema_template_exaples", From 0e2bf5f522b4668cbd0dc43cc54efb3c97737696 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 3 Aug 2021 17:49:21 +0200 Subject: [PATCH 12/21] compute environments after merge envs --- openpype/lib/applications.py | 8 ++++++-- start.py | 6 +++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/openpype/lib/applications.py b/openpype/lib/applications.py index ada194f15f..9f5a092afc 100644 --- a/openpype/lib/applications.py +++ b/openpype/lib/applications.py @@ -1138,7 +1138,8 @@ def prepare_host_environments(data, implementation_envs=True): # Merge dictionaries env_values = _merge_env(tool_env, env_values) - loaded_env = _merge_env(acre.compute(env_values), data["env"]) + merged_env = _merge_env(computed_env, data["env"]) + loaded_env = acre.compute(merged_env, cleanup=False) final_env = None # Add host specific environments @@ -1189,7 +1190,10 @@ def apply_project_environments_value(project_name, env, project_settings=None): env_value = project_settings["global"]["project_environments"] if env_value: - env.update(_merge_env(acre.parse(env_value), env)) + env.update(acre.compute( + _merge_env(acre.parse(env_value), env), + cleanup=False + )) return env diff --git a/start.py b/start.py index 419a956835..6473a926d0 100644 --- a/start.py +++ b/start.py @@ -221,10 +221,14 @@ def set_openpype_global_environments() -> None: all_env = get_environments() general_env = all_env["global"] - env = acre.merge( + merged_env = acre.merge( acre.parse(general_env), dict(os.environ) ) + env = acre.compute( + merged_env, + cleanup=False + ) os.environ.clear() os.environ.update(env) From 6c0283f9ab9e70daf3fda03f2359b4dec11bf090 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 3 Aug 2021 17:54:56 +0200 Subject: [PATCH 13/21] fix variable --- openpype/lib/applications.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/lib/applications.py b/openpype/lib/applications.py index 9f5a092afc..fe964d3bab 100644 --- a/openpype/lib/applications.py +++ b/openpype/lib/applications.py @@ -1138,7 +1138,7 @@ def prepare_host_environments(data, implementation_envs=True): # Merge dictionaries env_values = _merge_env(tool_env, env_values) - merged_env = _merge_env(computed_env, data["env"]) + merged_env = _merge_env(env_values, data["env"]) loaded_env = acre.compute(merged_env, cleanup=False) final_env = None From b19b38a925e80e2d9d4fdf2bdb63ee52e7e02a15 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 4 Aug 2021 13:55:27 +0200 Subject: [PATCH 14/21] updated acre commit --- poetry.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index aad1898983..e011b781c9 100644 --- a/poetry.lock +++ b/poetry.lock @@ -11,7 +11,7 @@ develop = false type = "git" url = "https://github.com/pypeclub/acre.git" reference = "master" -resolved_reference = "5a812c6dcfd3aada87adb49be98c548c894d6566" +resolved_reference = "55a7c331e6dc5f81639af50ca4a8cc9d73e9273d" [[package]] name = "aiohttp" From 9fa69dd43c51f1c41bf1c8a259ec7812cdd9c331 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 5 Aug 2021 12:16:32 +0200 Subject: [PATCH 15/21] Hiero: loaded clip was not set colorspace from version data --- openpype/hosts/hiero/plugins/load/load_clip.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/hiero/plugins/load/load_clip.py b/openpype/hosts/hiero/plugins/load/load_clip.py index 9e12fa360e..fa666207c5 100644 --- a/openpype/hosts/hiero/plugins/load/load_clip.py +++ b/openpype/hosts/hiero/plugins/load/load_clip.py @@ -54,6 +54,9 @@ class LoadClip(phiero.SequenceLoader): object_name = self.clip_name_template.format( **context["representation"]["context"]) + # set colorspace + track_item.source().setSourceMediaColourTransform(colorspace) + # add additional metadata from the version to imprint Avalon knob add_keys = [ "frameStart", "frameEnd", "source", "author", @@ -109,9 +112,13 @@ class LoadClip(phiero.SequenceLoader): colorspace = version_data.get("colorspace", None) object_name = "{}_{}".format(name, namespace) file = api.get_representation_path(representation).replace("\\", "/") + clip = track_item.source() # reconnect media to new path - track_item.source().reconnectMedia(file) + clip.reconnectMedia(file) + + # set colorspace + clip.setSourceMediaColourTransform(colorspace) # add additional metadata from the version to imprint Avalon knob add_keys = [ @@ -160,6 +167,7 @@ class LoadClip(phiero.SequenceLoader): @classmethod def set_item_color(cls, track_item, version): + clip = track_item.source() # define version name version_name = version.get("name", None) # get all versions in list @@ -172,6 +180,6 @@ class LoadClip(phiero.SequenceLoader): # set clip colour if version_name == max_version: - track_item.source().binItem().setColor(cls.clip_color_last) + clip.binItem().setColor(cls.clip_color_last) else: - track_item.source().binItem().setColor(cls.clip_color) + clip.binItem().setColor(cls.clip_color) From 5b7aa3717ff915c25f850e301c5bff2fa4c5eef3 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Fri, 6 Aug 2021 12:04:35 +0200 Subject: [PATCH 16/21] row order changes are propagated right way with right buttons --- .../settings/settings/list_item_widget.py | 186 ++++++++++-------- 1 file changed, 107 insertions(+), 79 deletions(-) diff --git a/openpype/tools/settings/settings/list_item_widget.py b/openpype/tools/settings/settings/list_item_widget.py index 82ca541132..c9df5caf01 100644 --- a/openpype/tools/settings/settings/list_item_widget.py +++ b/openpype/tools/settings/settings/list_item_widget.py @@ -117,6 +117,9 @@ class ListItem(QtWidgets.QWidget): self.spacer_widget = spacer_widget + self._row = -1 + self._is_last = False + @property def category_widget(self): return self.entity_widget.category_widget @@ -136,28 +139,40 @@ class ListItem(QtWidgets.QWidget): def add_widget_to_layout(self, widget, label=None): self.content_layout.addWidget(widget, 1) + def set_row(self, row, is_last): + if row == self._row and is_last == self._is_last: + return + + trigger_order_changed = ( + row != self._row + or is_last != self._is_last + ) + self._row = row + self._is_last = is_last + + if trigger_order_changed: + self.order_changed() + + @property def row(self): - return self.entity_widget.input_fields.index(self) + return self._row def parent_rows_count(self): return len(self.entity_widget.input_fields) def _on_add_clicked(self): - self.entity_widget.add_new_item(row=self.row() + 1) + self.entity_widget.add_new_item(row=self.row + 1) def _on_remove_clicked(self): self.entity_widget.remove_row(self) def _on_up_clicked(self): - row = self.row() - self.entity_widget.swap_rows(row - 1, row) + self.entity_widget.swap_rows(self.row - 1, self.row) def _on_down_clicked(self): - row = self.row() - self.entity_widget.swap_rows(row, row + 1) + self.entity_widget.swap_rows(self.row, self.row + 1) def order_changed(self): - row = self.row() parent_row_count = self.parent_rows_count() if parent_row_count == 1: self.up_btn.setVisible(False) @@ -168,11 +183,11 @@ class ListItem(QtWidgets.QWidget): self.up_btn.setVisible(True) self.down_btn.setVisible(True) - if row == 0: + if self.row == 0: self.up_btn.setEnabled(False) self.down_btn.setEnabled(True) - elif row == parent_row_count - 1: + elif self.row == parent_row_count - 1: self.up_btn.setEnabled(True) self.down_btn.setEnabled(False) @@ -191,6 +206,7 @@ class ListWidget(InputWidget): def create_ui(self): self._child_style_state = "" self.input_fields = [] + self._input_fields_by_entity_id = {} main_layout = QtWidgets.QHBoxLayout(self) main_layout.setContentsMargins(0, 0, 0, 0) @@ -243,8 +259,7 @@ class ListWidget(InputWidget): self.entity_widget.add_widget_to_layout(self, entity_label) def set_entity_value(self): - for input_field in tuple(self.input_fields): - self.remove_row(input_field) + self.remove_all_rows() for entity in self.entity.children: self.add_row(entity) @@ -262,39 +277,60 @@ class ListWidget(InputWidget): def _on_entity_change(self): # TODO do less inefficient - input_field_last_idx = len(self.input_fields) - 1 - child_len = len(self.entity) + childen_order = [] + new_children = [] for idx, child_entity in enumerate(self.entity): - if idx > input_field_last_idx: - self.add_row(child_entity, idx) - input_field_last_idx += 1 + input_field = self._input_fields_by_entity_id.get(child_entity.id) + if input_field is not None: + childen_order.append(input_field) + else: + new_children.append((idx, child_entity)) + + order_changed = False + for idx, input_field in enumerate(childen_order): + current_field = self.input_fields[idx] + if current_field is input_field: continue + order_changed = True + old_idx = self.input_fields.index(input_field) + self.input_fields[old_idx], self.input_fields[idx] = ( + current_field, input_field + ) + self.content_layout.insertWidget(idx + 1, input_field) - if self.input_fields[idx].entity is child_entity: - continue + kept_len = len(childen_order) + fields_len = len(self.input_fields) + if fields_len > kept_len: + order_changed = True + for row in reversed(range(kept_len, fields_len)): + self.remove_row(row=row) - input_field_idx = None - for _input_field_idx, input_field in enumerate(self.input_fields): - if input_field.entity is child_entity: - input_field_idx = _input_field_idx - break + for idx, child_entity in new_children: + order_changed = False + self.add_row(child_entity, idx) - if input_field_idx is None: - self.add_row(child_entity, idx) - input_field_last_idx += 1 - continue + if not order_changed: + return - input_field = self.input_fields.pop(input_field_idx) - self.input_fields.insert(idx, input_field) - self.content_layout.insertWidget(idx, input_field) + self._on_order_change() - new_input_field_len = len(self.input_fields) - if child_len != new_input_field_len: - for _idx in range(child_len, new_input_field_len): - # Remove row at the same index - self.remove_row(self.input_fields[child_len]) + input_field_len = self.count() + self.empty_row.setVisible(input_field_len == 0) - self.empty_row.setVisible(self.count() == 0) + def _on_order_change(self): + last_idx = self.count() - 1 + previous_input = None + for idx, input_field in enumerate(self.input_fields): + input_field.set_row(idx, idx == last_idx) + next_input = input_field.input_field.focusProxy() + if previous_input is not None: + self.setTabOrder(previous_input, next_input) + else: + self.setTabOrder(self, next_input) + previous_input = next_input + + if previous_input is not None: + self.setTabOrder(previous_input, self) def count(self): return len(self.input_fields) @@ -307,32 +343,20 @@ class ListWidget(InputWidget): def add_new_item(self, row=None): new_entity = self.entity.add_new_item(row) - for input_field in self.input_fields: - if input_field.entity is new_entity: - input_field.input_field.setFocus(True) - break + input_field = self._input_fields_by_entity_id.get(new_entity.id) + if input_field is not None: + input_field.input_field.setFocus(True) return new_entity def add_row(self, child_entity, row=None): # Create new item item_widget = ListItem(child_entity, self) - - previous_field = None - next_field = None + self._input_fields_by_entity_id[child_entity.id] = item_widget if row is None: - if self.input_fields: - previous_field = self.input_fields[-1] self.content_layout.addWidget(item_widget) self.input_fields.append(item_widget) else: - if row > 0: - previous_field = self.input_fields[row - 1] - - max_index = self.count() - if row < max_index: - next_field = self.input_fields[row] - self.content_layout.insertWidget(row + 1, item_widget) self.input_fields.insert(row, item_widget) @@ -342,49 +366,53 @@ class ListWidget(InputWidget): # added as widget here which won't because is not in input_fields item_widget.input_field.set_entity_value() - if previous_field: - previous_field.order_changed() + self._on_order_change() - if next_field: - next_field.order_changed() - - item_widget.order_changed() - - previous_input = None - for input_field in self.input_fields: - if previous_input is not None: - self.setTabOrder( - previous_input, input_field.input_field.focusProxy() - ) - previous_input = input_field.input_field.focusProxy() + input_field_len = self.count() + self.empty_row.setVisible(input_field_len == 0) self.updateGeometry() - def remove_row(self, item_widget): - row = self.input_fields.index(item_widget) - previous_field = None - next_field = None - if row > 0: - previous_field = self.input_fields[row - 1] + def remove_all_rows(self): + self._input_fields_by_entity_id = {} + while self.input_fields: + item_widget = self.input_fields.pop(0) + self.content_layout.removeWidget(item_widget) + item_widget.setParent(None) + item_widget.deleteLater() - if row != len(self.input_fields) - 1: - next_field = self.input_fields[row + 1] + self.empty_row.setVisible(True) + + self.updateGeometry() + + def remove_row(self, item_widget=None, row=None): + if item_widget is None: + item_widget = self.input_fields[row] + elif row is None: + row = self.input_fields.index(item_widget) self.content_layout.removeWidget(item_widget) self.input_fields.pop(row) + self._input_fields_by_entity_id.pop(item_widget.entity.id) item_widget.setParent(None) item_widget.deleteLater() if item_widget.entity in self.entity: self.entity.remove(item_widget.entity) - if previous_field: - previous_field.order_changed() + rows = self.count() + any_item = rows == 0 + if any_item: + start_row = 0 + if row > 0: + start_row = row - 1 - if next_field: - next_field.order_changed() + last_row = rows - 1 + _enum = enumerate(self.input_fields[start_row:rows]) + for idx, _item_widget in _enum: + _item_widget.set_row(idx, idx == last_row) - self.empty_row.setVisible(self.count() == 0) + self.empty_row.setVisible(any_item) self.updateGeometry() From 9e8e8ec656de4442a83a8753a9e8a43610bc3af5 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 6 Aug 2021 18:13:25 +0200 Subject: [PATCH 17/21] hiero: fix colorspace attribute distribution --- openpype/hosts/hiero/plugins/load/load_clip.py | 6 ++++-- .../hiero/plugins/publish/precollect_instances.py | 14 +++++++------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/openpype/hosts/hiero/plugins/load/load_clip.py b/openpype/hosts/hiero/plugins/load/load_clip.py index fa666207c5..b905dd4431 100644 --- a/openpype/hosts/hiero/plugins/load/load_clip.py +++ b/openpype/hosts/hiero/plugins/load/load_clip.py @@ -55,7 +55,8 @@ class LoadClip(phiero.SequenceLoader): **context["representation"]["context"]) # set colorspace - track_item.source().setSourceMediaColourTransform(colorspace) + if colorspace: + track_item.source().setSourceMediaColourTransform(colorspace) # add additional metadata from the version to imprint Avalon knob add_keys = [ @@ -118,7 +119,8 @@ class LoadClip(phiero.SequenceLoader): clip.reconnectMedia(file) # set colorspace - clip.setSourceMediaColourTransform(colorspace) + if colorspace: + clip.setSourceMediaColourTransform(colorspace) # add additional metadata from the version to imprint Avalon knob add_keys = [ diff --git a/openpype/hosts/hiero/plugins/publish/precollect_instances.py b/openpype/hosts/hiero/plugins/publish/precollect_instances.py index 4984849aa7..9b529edf88 100644 --- a/openpype/hosts/hiero/plugins/publish/precollect_instances.py +++ b/openpype/hosts/hiero/plugins/publish/precollect_instances.py @@ -120,6 +120,13 @@ class PrecollectInstances(pyblish.api.ContextPlugin): # create instance instance = context.create_instance(**data) + # add colorspace data + instance.data.update({ + "versionData": { + "colorspace": track_item.sourceMediaColourTransform(), + } + }) + # create shot instance for shot attributes create/update self.create_shot_instance(context, **data) @@ -133,13 +140,6 @@ class PrecollectInstances(pyblish.api.ContextPlugin): # create audio subset instance self.create_audio_instance(context, **data) - # add colorspace data - instance.data.update({ - "versionData": { - "colorspace": track_item.sourceMediaColourTransform(), - } - }) - # add audioReview attribute to plate instance data # if reviewTrack is on if tag_data.get("reviewTrack") is not None: From 7b71e1237a13e49b2da86ba7c664dd038244a458 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 9 Aug 2021 12:22:01 +0200 Subject: [PATCH 18/21] submodules: avalon-core update --- repos/avalon-core | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/repos/avalon-core b/repos/avalon-core index cfd4191e36..e5c8a15fde 160000 --- a/repos/avalon-core +++ b/repos/avalon-core @@ -1 +1 @@ -Subproject commit cfd4191e364b47de7364096f45d9d9d9a901692a +Subproject commit e5c8a15fde77708c924eab3018bda255f17b5390 From a4f9ee1496f34f94d5e476b6614ecb772784fd93 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 9 Aug 2021 15:24:56 +0200 Subject: [PATCH 19/21] Fix - texture validators for workfiles triggers only for textures workfiles It was triggering for all workfiles --- .../standalonepublisher/plugins/publish/collect_texture.py | 1 + .../plugins/publish/validate_texture_batch.py | 2 +- .../plugins/publish/validate_texture_name.py | 2 +- .../plugins/publish/validate_texture_workfiles.py | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/standalonepublisher/plugins/publish/collect_texture.py b/openpype/hosts/standalonepublisher/plugins/publish/collect_texture.py index d70a0a75b8..596a8ccfd2 100644 --- a/openpype/hosts/standalonepublisher/plugins/publish/collect_texture.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/collect_texture.py @@ -270,6 +270,7 @@ class CollectTextures(pyblish.api.ContextPlugin): # store origin if family == 'workfile': families = self.workfile_families + families.append("texture_batch_workfile") new_instance.data["source"] = "standalone publisher" else: diff --git a/openpype/hosts/standalonepublisher/plugins/publish/validate_texture_batch.py b/openpype/hosts/standalonepublisher/plugins/publish/validate_texture_batch.py index af200b59e0..d592a4a059 100644 --- a/openpype/hosts/standalonepublisher/plugins/publish/validate_texture_batch.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/validate_texture_batch.py @@ -8,7 +8,7 @@ class ValidateTextureBatch(pyblish.api.InstancePlugin): label = "Validate Texture Presence" hosts = ["standalonepublisher"] order = openpype.api.ValidateContentsOrder - families = ["workfile"] + families = ["texture_batch_workfile"] optional = False def process(self, instance): diff --git a/openpype/hosts/standalonepublisher/plugins/publish/validate_texture_name.py b/openpype/hosts/standalonepublisher/plugins/publish/validate_texture_name.py index 92f930c3fc..f210be3631 100644 --- a/openpype/hosts/standalonepublisher/plugins/publish/validate_texture_name.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/validate_texture_name.py @@ -8,7 +8,7 @@ class ValidateTextureBatchNaming(pyblish.api.InstancePlugin): label = "Validate Texture Batch Naming" hosts = ["standalonepublisher"] order = openpype.api.ValidateContentsOrder - families = ["workfile", "textures"] + families = ["texture_batch_workfile", "textures"] optional = False def process(self, instance): diff --git a/openpype/hosts/standalonepublisher/plugins/publish/validate_texture_workfiles.py b/openpype/hosts/standalonepublisher/plugins/publish/validate_texture_workfiles.py index aa3aad71db..25bb5aea4a 100644 --- a/openpype/hosts/standalonepublisher/plugins/publish/validate_texture_workfiles.py +++ b/openpype/hosts/standalonepublisher/plugins/publish/validate_texture_workfiles.py @@ -11,7 +11,7 @@ class ValidateTextureBatchWorkfiles(pyblish.api.InstancePlugin): label = "Validate Texture Workfile Has Resources" hosts = ["standalonepublisher"] order = openpype.api.ValidateContentsOrder - families = ["workfile"] + families = ["texture_batch_workfile"] optional = True # from presets From c386fc340dcfe02f5a0d6c440b9ab8040052b51f Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 10 Aug 2021 12:15:31 +0200 Subject: [PATCH 20/21] Nuke: update video file crassing --- openpype/hosts/nuke/plugins/load/load_mov.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/nuke/plugins/load/load_mov.py b/openpype/hosts/nuke/plugins/load/load_mov.py index d84c3d4c71..95f20b305f 100644 --- a/openpype/hosts/nuke/plugins/load/load_mov.py +++ b/openpype/hosts/nuke/plugins/load/load_mov.py @@ -259,7 +259,7 @@ class LoadMov(api.Loader): read_node["last"].setValue(last) read_node['frame_mode'].setValue("start at") - if int(self.first_frame) == int(read_node['frame'].value()): + if int(float(self.first_frame)) == int(float(read_node['frame'].value())): # start at workfile start read_node['frame'].setValue(str(self.first_frame)) else: From 3be9fa8cf170184c9ed0003a024d503fb77956bc Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 10 Aug 2021 12:20:06 +0200 Subject: [PATCH 21/21] hound: pep8 --- openpype/hosts/nuke/plugins/load/load_mov.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/nuke/plugins/load/load_mov.py b/openpype/hosts/nuke/plugins/load/load_mov.py index 95f20b305f..f7523d0a6e 100644 --- a/openpype/hosts/nuke/plugins/load/load_mov.py +++ b/openpype/hosts/nuke/plugins/load/load_mov.py @@ -259,7 +259,8 @@ class LoadMov(api.Loader): read_node["last"].setValue(last) read_node['frame_mode'].setValue("start at") - if int(float(self.first_frame)) == int(float(read_node['frame'].value())): + if int(float(self.first_frame)) == int( + float(read_node['frame'].value())): # start at workfile start read_node['frame'].setValue(str(self.first_frame)) else: