diff --git a/openpype/hosts/hiero/plugins/load/load_clip.py b/openpype/hosts/hiero/plugins/load/load_clip.py index 9e12fa360e..b905dd4431 100644 --- a/openpype/hosts/hiero/plugins/load/load_clip.py +++ b/openpype/hosts/hiero/plugins/load/load_clip.py @@ -54,6 +54,10 @@ class LoadClip(phiero.SequenceLoader): object_name = self.clip_name_template.format( **context["representation"]["context"]) + # set colorspace + if colorspace: + track_item.source().setSourceMediaColourTransform(colorspace) + # add additional metadata from the version to imprint Avalon knob add_keys = [ "frameStart", "frameEnd", "source", "author", @@ -109,9 +113,14 @@ 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 + if colorspace: + clip.setSourceMediaColourTransform(colorspace) # add additional metadata from the version to imprint Avalon knob add_keys = [ @@ -160,6 +169,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 +182,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) 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: diff --git a/openpype/hosts/nuke/plugins/load/load_mov.py b/openpype/hosts/nuke/plugins/load/load_mov.py index d84c3d4c71..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(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: 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 diff --git a/openpype/lib/applications.py b/openpype/lib/applications.py index ada194f15f..fe964d3bab 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(env_values, 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/openpype/settings/entities/lib.py b/openpype/settings/entities/lib.py index e58281644a..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, @@ -111,6 +112,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() @@ -126,6 +131,60 @@ 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 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 + + @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. diff --git a/openpype/settings/entities/list_entity.py b/openpype/settings/entities/list_entity.py index 64bbad28a7..b07441251a 100644 --- a/openpype/settings/entities/list_entity.py +++ b/openpype/settings/entities/list_entity.py @@ -141,7 +141,20 @@ 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 + + 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: + 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._obj_template_name = obj_template_name if self.group_item is None: self.is_group = True @@ -150,6 +163,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: @@ -167,18 +186,36 @@ 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 - if 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) + 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 for idx, _child_obj in enumerate(self.children): diff --git a/openpype/settings/entities/schemas/README.md b/openpype/settings/entities/schemas/README.md index 399c4ac1d9..42a8973f43 100644 --- a/openpype/settings/entities/schemas/README.md +++ b/openpype/settings/entities/schemas/README.md @@ -417,6 +417,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 ``` @@ -442,6 +444,65 @@ 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`). + +'template_object_example.json' : +[ + { + "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 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 8ec97064a1..f633d5cb1a 100644 --- a/openpype/settings/entities/schemas/system_schema/example_schema.json +++ b/openpype/settings/entities/schemas/system_schema/example_schema.json @@ -82,6 +82,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", 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" 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 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)