From c98aafea8ef5f18bd31bdd53c13c52a299395b9b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 28 Jun 2021 17:35:34 +0200 Subject: [PATCH 01/53] base of dict contitional --- .../settings/entities/dict_conditional.py | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 openpype/settings/entities/dict_conditional.py diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py new file mode 100644 index 0000000000..01da57a190 --- /dev/null +++ b/openpype/settings/entities/dict_conditional.py @@ -0,0 +1,33 @@ +import copy +import collections + +from .lib import ( + WRAPPER_TYPES, + OverrideState, + NOT_SET +) +from openpype.settings.constants import ( + METADATA_KEYS, + M_OVERRIDEN_KEY, + KEY_REGEX +) +from . import ( + BaseItemEntity, + ItemEntity, + BoolEntity, + GUIEntity +) +from .exceptions import ( + SchemaDuplicatedKeys, + EntitySchemaError, + InvalidKeySymbols +) + + +class DictConditionalEntity(ItemEntity): + schema_types = ["dict-conditional"] + _default_label_wrap = { + "use_label_wrap": False, + "collapsible": False, + "collapsed": True + } From 6c63bc048fe2e12f82581a14797dc8776d65f46e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 28 Jun 2021 17:35:50 +0200 Subject: [PATCH 02/53] added example schema for reference --- .../settings/entities/dict_conditional.py | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index 01da57a190..8e7a7b79c9 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -24,6 +24,52 @@ from .exceptions import ( ) +example_schema = { + "type": "dict-conditional", + "key": "KEY", + "label": "LABEL", + "enum_key": "type", + "enum_label": "label", + "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": "list", + "object_type": "text" + } + ] + }, + { + "key": "separator", + "label": "Separator" + } + ] +} + + class DictConditionalEntity(ItemEntity): schema_types = ["dict-conditional"] _default_label_wrap = { From 1063f8210ab0ec1c8e7c41493408935023f88cda Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 28 Jun 2021 17:36:38 +0200 Subject: [PATCH 03/53] implemented `_item_initalization` similar to 'dict' entity --- .../settings/entities/dict_conditional.py | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index 8e7a7b79c9..989d69a290 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -77,3 +77,33 @@ class DictConditionalEntity(ItemEntity): "collapsible": False, "collapsed": True } + + def _item_initalization(self): + self._default_metadata = NOT_SET + self._studio_override_metadata = NOT_SET + self._project_override_metadata = NOT_SET + + self._ignore_child_changes = False + + # `current_metadata` are still when schema is loaded + # - only metadata stored with dict item are gorup overrides in + # M_OVERRIDEN_KEY + self._current_metadata = {} + self._metadata_are_modified = False + + # Children are stored by key as keys are immutable and are defined by + # schema + self.valid_value_types = (dict, ) + self.children = collections.defaultdict(list) + self.non_gui_children = collections.defaultdict(dict) + self.gui_layout = collections.defaultdict(list) + + if self.is_dynamic_item: + self.require_key = False + + self.enum_key = self.schema_data.get("enum_key") + self.enum_label = self.schema_data.get("enum_label") + self.enum_children = self.schema_data.get("enum_children") + + self.enum_entity = None + self.current_enum = None From 82f1817ec0ca4fca3cd6efca01b28e28c76b1715 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 28 Jun 2021 17:39:32 +0200 Subject: [PATCH 04/53] implemented _add_children method --- .../settings/entities/dict_conditional.py | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index 989d69a290..da6df6170d 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -107,3 +107,61 @@ class DictConditionalEntity(ItemEntity): self.enum_entity = None self.current_enum = None + + self._add_children() + + def _add_children(self): + """Add children from schema data and repare enum items. + + Each enum item must have defined it's children. None are shared across + all enum items. + + Nice to have: Have ability to have shared keys across all enum items. + + All children are stored by their enum item. + """ + # Skip and wait for validation + if not self.enum_children or not self.enum_key: + return + + enum_items = [] + valid_enum_items = [] + for item in self.enum_children: + if isinstance(item, dict) and "key" in item: + valid_enum_items.append(item) + + first_key = None + for item in valid_enum_items: + item_key = item["key"] + if first_key is None: + first_key = item_key + item_label = item.get("label") or item_key + enum_items.append({item_key: item_label}) + + if not enum_items: + return + + self.current_enum = first_key + + enum_key = self.enum_key or "invalid" + enum_schema = { + "type": "enum", + "multiselection": False, + "enum_items": enum_items, + "key": enum_key, + "label": self.enum_label or enum_key + } + enum_entity = self.create_schema_object(enum_schema, self) + self.enum_entity = enum_entity + + for item in valid_enum_items: + item_key = item["key"] + children = item.get("children") or [] + for children_schema in children: + child_obj = self.create_schema_object(children_schema, self) + self.children[item_key].append(child_obj) + self.gui_layout[item_key].append(child_obj) + if isinstance(child_obj, GUIEntity): + continue + + self.non_gui_children[item_key][child_obj.key] = child_obj From 694f6b58cb0467aa7ddd496a60a61ef220db31b1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 28 Jun 2021 17:39:54 +0200 Subject: [PATCH 05/53] added schema validations of conditional dictionary --- .../settings/entities/dict_conditional.py | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index da6df6170d..20ee80337d 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -110,6 +110,67 @@ class DictConditionalEntity(ItemEntity): self._add_children() + def schema_validations(self): + """Validation of schema data.""" + if self.enum_key is None: + raise EntitySchemaError(self, "Key 'enum_key' is not set.") + + if not isinstance(self.enum_children, list): + raise EntitySchemaError( + self, "Key 'enum_children' must be a list. Got: {}".format( + str(type(self.enum_children)) + ) + ) + + if not self.enum_children: + raise EntitySchemaError(self, ( + "Key 'enum_children' have empty value. Entity can't work" + " without children definitions." + )) + + children_def_keys = [] + for children_def in self.enum_children: + if not isinstance(children_def, dict): + raise EntitySchemaError(( + "Children definition under key 'enum_children' must" + " be a dictionary." + )) + + if "key" not in children_def: + raise EntitySchemaError(( + "Children definition under key 'enum_children' miss" + " 'key' definition." + )) + # We don't validate regex of these keys because they will be stored + # as value at the end. + key = children_def["key"] + if key in children_def_keys: + # TODO this hould probably be different exception? + raise SchemaDuplicatedKeys(self, key) + children_def_keys.append(key) + + for children in self.children.values(): + children_keys = set() + children_keys.add(self.enum_key) + for child_entity in children: + if not isinstance(child_entity, BaseItemEntity): + continue + elif child_entity.key not in children_keys: + children_keys.add(child_entity.key) + else: + raise SchemaDuplicatedKeys(self, child_entity.key) + + for children_by_key in self.non_gui_children.values(): + for key in children_by_key.keys(): + if not KEY_REGEX.match(key): + raise InvalidKeySymbols(self.path, key) + + super(DictConditionalEntity, self).schema_validations() + # Trigger schema validation on children entities + for children in self.children.values(): + for child_obj in children: + child_obj.schema_validations() + def _add_children(self): """Add children from schema data and repare enum items. From ff701860f704c186fe969d76042747bd227bb331 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 28 Jun 2021 17:48:09 +0200 Subject: [PATCH 06/53] implemented `get_child_path` --- .../settings/entities/dict_conditional.py | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index 20ee80337d..79b5624505 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -226,3 +226,24 @@ class DictConditionalEntity(ItemEntity): continue self.non_gui_children[item_key][child_obj.key] = child_obj + + def get_child_path(self, child_obj): + """Get hierarchical path of child entity. + + Child must be entity's direct children. This must be possible to get + for any children even if not from current enum value. + """ + if child_obj is self.enum_entity: + return "/".join([self.path, self.enum_key]) + + result_key = None + for children in self.non_gui_children.values(): + for key, _child_obj in children.items(): + if _child_obj is child_obj: + result_key = key + break + + if result_key is None: + raise ValueError("Didn't found child {}".format(child_obj)) + + return "/".join([self.path, result_key]) From 218158ddc4961e24788c332fb74104678452ed4d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 28 Jun 2021 17:51:56 +0200 Subject: [PATCH 07/53] implemented base dictionary methods --- .../settings/entities/dict_conditional.py | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index 79b5624505..71e727e53f 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -78,6 +78,52 @@ class DictConditionalEntity(ItemEntity): "collapsed": True } + def __getitem__(self, key): + """Return entity inder key.""" + return self.non_gui_children[self.current_enum][key] + + def __setitem__(self, key, value): + """Set value of item under key.""" + child_obj = self.non_gui_children[self.current_enum][key] + child_obj.set(value) + + def __iter__(self): + """Iter through keys.""" + for key in self.keys(): + yield key + + def __contains__(self, key): + """Check if key is available.""" + return key in self.non_gui_children[self.current_enum] + + def get(self, key, default=None): + """Safe entity getter by key.""" + return self.non_gui_children[self.current_enum].get(key, default) + + def keys(self): + """Entity's keys.""" + keys = list(self.non_gui_children[self.current_enum].keys()) + keys.insert(0, [self.current_enum]) + return keys + + def values(self): + """Children entities.""" + values = [ + self.enum_entity + ] + for child_entiy in self.non_gui_children[self.current_enum].values(): + values.append(child_entiy) + return values + + def items(self): + """Children entities paired with their key (key, value).""" + items = [ + (self.enum_key, self.enum_entity) + ] + for key, value in self.non_gui_children[self.current_enum].items(): + items.append((key, value)) + return items + def _item_initalization(self): self._default_metadata = NOT_SET self._studio_override_metadata = NOT_SET From 9ccc667c85471437c5acace23f2279b13e97380a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 28 Jun 2021 17:53:45 +0200 Subject: [PATCH 08/53] implemented idea of set value --- openpype/settings/entities/dict_conditional.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index 71e727e53f..a933dfd586 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -124,6 +124,16 @@ class DictConditionalEntity(ItemEntity): items.append((key, value)) return items + def set(self, value): + """Set value.""" + new_value = self.convert_to_valid_type(value) + # First change value of enum key if available + if self.enum_key in new_value: + self.enum_entity.set(new_value.pop(self.enum_key)) + + for _key, _value in new_value.items(): + self.non_gui_children[self.current_enum][_key].set(_value) + def _item_initalization(self): self._default_metadata = NOT_SET self._studio_override_metadata = NOT_SET From 469dcc09fa7051313039b81e7ff4a6ebcdf840aa Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 28 Jun 2021 17:55:34 +0200 Subject: [PATCH 09/53] implemented change callbacks --- .../settings/entities/dict_conditional.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index a933dfd586..9e6b5b6f36 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -227,6 +227,25 @@ class DictConditionalEntity(ItemEntity): for child_obj in children: child_obj.schema_validations() + def on_change(self): + """Update metadata on change and pass change to parent.""" + self._update_current_metadata() + + for callback in self.on_change_callbacks: + callback() + self.parent.on_child_change(self) + + def on_child_change(self, child_obj): + """Trigger on change callback if child changes are not ignored.""" + if self._ignore_child_changes: + return + + if ( + child_obj is self.enum_entity + or child_obj in self.children[self.current_enum] + ): + self.on_change() + def _add_children(self): """Add children from schema data and repare enum items. From be1f0f77a05351bd7c3a154ad35e787a392701d3 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 28 Jun 2021 17:58:55 +0200 Subject: [PATCH 10/53] implemented set_override_state --- openpype/settings/entities/dict_conditional.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index 9e6b5b6f36..8bf9b87218 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -322,3 +322,20 @@ class DictConditionalEntity(ItemEntity): raise ValueError("Didn't found child {}".format(child_obj)) return "/".join([self.path, result_key]) + + def set_override_state(self, state): + # Trigger override state change of root if is not same + if self.root_item.override_state is not state: + self.root_item.set_override_state(state) + return + + # Change has/had override states + self._override_state = state + + self.enum_entity.set_override_state(state) + + for children_by_key in self.non_gui_children.values(): + for child_obj in children_by_key.values(): + child_obj.set_override_state(state) + + self._update_current_metadata() From 92d0c9f37b3e116700ff25a3422acfb3436aefe6 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 28 Jun 2021 18:02:05 +0200 Subject: [PATCH 11/53] implemented `value` and `settings_value` --- .../settings/entities/dict_conditional.py | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index 8bf9b87218..d2bab1ed15 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -339,3 +339,53 @@ class DictConditionalEntity(ItemEntity): child_obj.set_override_state(state) self._update_current_metadata() + + @property + def value(self): + output = { + self.current_enum: self.enum_entity.value + } + for key, child_obj in self.non_gui_children[self.current_enum].items(): + output[key] = child_obj.value + return output + + def settings_value(self): + if self._override_state is OverrideState.NOT_DEFINED: + return NOT_SET + + if self._override_state is OverrideState.DEFAULTS: + output = { + self.current_enum: self.enum_entity.settings_value() + } + non_gui_children = self.non_gui_children[self.current_enum] + for key, child_obj in non_gui_children.items(): + child_value = child_obj.settings_value() + if not child_obj.is_file and not child_obj.file_item: + for _key, _value in child_value.items(): + new_key = "/".join([key, _key]) + output[new_key] = _value + else: + output[key] = child_value + return output + + if self.is_group: + if self._override_state is OverrideState.STUDIO: + if not self.has_studio_override: + return NOT_SET + elif self._override_state is OverrideState.PROJECT: + if not self.has_project_override: + return NOT_SET + + output = { + self.current_enum: self.enum_entity.settings_value() + } + for key, child_obj in self.non_gui_children[self.current_enum].items(): + value = child_obj.settings_value() + if value is not NOT_SET: + output[key] = value + + if not output: + return NOT_SET + + output.update(self._current_metadata) + return output From 04207c689fe584c8c2393ad95893f67d6e5dc4b1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 28 Jun 2021 18:02:23 +0200 Subject: [PATCH 12/53] implemented modification and override properties --- .../settings/entities/dict_conditional.py | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index d2bab1ed15..f82cc02e3e 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -349,6 +349,52 @@ class DictConditionalEntity(ItemEntity): output[key] = child_obj.value return output + @property + def has_unsaved_changes(self): + if self._metadata_are_modified: + return True + + return self._child_has_unsaved_changes + + @property + def _child_has_unsaved_changes(self): + if self.enum_entity.has_unsaved_changes: + return True + for child_obj in self.non_gui_children[self.current_enum].values(): + if child_obj.has_unsaved_changes: + return True + return False + + @property + def has_studio_override(self): + return self._child_has_studio_override + + @property + def _child_has_studio_override(self): + if self._override_state >= OverrideState.STUDIO: + if self.enum_entity.has_studio_override: + return True + + for child_obj in self.non_gui_children[self.current_enum].values(): + if child_obj.has_studio_override: + return True + return False + + @property + def has_project_override(self): + return self._child_has_project_override + + @property + def _child_has_project_override(self): + if self._override_state >= OverrideState.PROJECT: + if self.enum_entity.has_project_override: + return True + + for child_obj in self.non_gui_children[self.current_enum].values(): + if child_obj.has_project_override: + return True + return False + def settings_value(self): if self._override_state is OverrideState.NOT_DEFINED: return NOT_SET From 58ade824c5a49f0f59fcd6ad0d46c24ded5bd7c1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 28 Jun 2021 18:02:45 +0200 Subject: [PATCH 13/53] implemented update current metadata --- .../settings/entities/dict_conditional.py | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index f82cc02e3e..df7699a90e 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -323,6 +323,45 @@ class DictConditionalEntity(ItemEntity): return "/".join([self.path, result_key]) + def _update_current_metadata(self): + current_metadata = {} + for key, child_obj in self.non_gui_children[self.current_enum].items(): + if self._override_state is OverrideState.DEFAULTS: + break + + if not child_obj.is_group: + continue + + if ( + self._override_state is OverrideState.STUDIO + and not child_obj.has_studio_override + ): + continue + + if ( + self._override_state is OverrideState.PROJECT + and not child_obj.has_project_override + ): + continue + + if M_OVERRIDEN_KEY not in current_metadata: + current_metadata[M_OVERRIDEN_KEY] = [] + current_metadata[M_OVERRIDEN_KEY].append(key) + + # Define if current metadata are avaialble for current override state + metadata = NOT_SET + if self._override_state is OverrideState.STUDIO: + metadata = self._studio_override_metadata + + elif self._override_state is OverrideState.PROJECT: + metadata = self._project_override_metadata + + if metadata is NOT_SET: + metadata = {} + + self._metadata_are_modified = current_metadata != metadata + self._current_metadata = current_metadata + def set_override_state(self, state): # Trigger override state change of root if is not same if self.root_item.override_state is not state: From 1f9ba64a45bbe6b2300436b341b10e985597ca63 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 28 Jun 2021 18:02:55 +0200 Subject: [PATCH 14/53] implemented prepare value --- .../settings/entities/dict_conditional.py | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index df7699a90e..8172550075 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -474,3 +474,37 @@ class DictConditionalEntity(ItemEntity): output.update(self._current_metadata) return output + + def _prepare_value(self, value): + if value is NOT_SET or self.enum_key not in value: + return NOT_SET, NOT_SET + + enum_value = value.get(self.enum_key) + if enum_value not in self.non_gui_children: + return NOT_SET, NOT_SET + + # Create copy of value before poping values + value = copy.deepcopy(value) + metadata = {} + for key in METADATA_KEYS: + if key in value: + metadata[key] = value.pop(key) + + enum_value = value.get(self.enum_key) + + old_metadata = metadata.get(M_OVERRIDEN_KEY) + if old_metadata: + old_metadata_set = set(old_metadata) + new_metadata = [] + non_gui_children = self.non_gui_children[enum_value] + for key in non_gui_children.keys(): + if key in old_metadata: + new_metadata.append(key) + old_metadata_set.remove(key) + + for key in old_metadata_set: + new_metadata.append(key) + metadata[M_OVERRIDEN_KEY] = new_metadata + + return value, metadata + From f47ec0df6cfe1137e78afa14b27c54b8e4f88e49 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 28 Jun 2021 18:15:06 +0200 Subject: [PATCH 15/53] implemented update methods --- .../settings/entities/dict_conditional.py | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index 8172550075..20697506fd 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -508,3 +508,112 @@ class DictConditionalEntity(ItemEntity): return value, metadata + def update_default_value(self, value): + """Update default values. + + Not an api method, should be called by parent. + """ + value = self._check_update_value(value, "default") + self.has_default_value = value is not NOT_SET + # TODO add value validation + value, metadata = self._prepare_value(value) + self._default_metadata = metadata + + if value is NOT_SET: + self.enum_entity.update_default_value(value) + for children_by_key in self.non_gui_children.values(): + for child_obj in children_by_key: + child_obj.update_default_value(value) + return + + value_keys = set(value.keys()) + enum_value = value[self.enum_key] + expected_keys = set(self.non_gui_children[enum_value]) + expected_keys.add(self.enum_key) + unknown_keys = value_keys - expected_keys + if unknown_keys: + self.log.warning( + "{} Unknown keys in default values: {}".format( + self.path, + ", ".join("\"{}\"".format(key) for key in unknown_keys) + ) + ) + + self.enum_entity.update_default_value(enum_value) + for children_by_key in self.non_gui_children.items(): + for key, child_obj in children_by_key.items(): + child_value = value.get(key, NOT_SET) + child_obj.update_default_value(child_value) + + def update_studio_value(self, value): + """Update studio override values. + + Not an api method, should be called by parent. + """ + value = self._check_update_value(value, "studio override") + value, metadata = self._prepare_value(value) + self._studio_override_metadata = metadata + self.had_studio_override = metadata is not NOT_SET + + if value is NOT_SET: + self.enum_entity.update_default_value(value) + for children_by_key in self.non_gui_children.values(): + for child_obj in children_by_key: + child_obj.update_default_value(value) + return + + value_keys = set(value.keys()) + enum_value = value[self.enum_key] + expected_keys = set(self.non_gui_children[enum_value]) + expected_keys.add(self.enum_key) + unknown_keys = value_keys - expected_keys + if unknown_keys: + self.log.warning( + "{} Unknown keys in studio overrides: {}".format( + self.path, + ", ".join("\"{}\"".format(key) for key in unknown_keys) + ) + ) + + self.enum_entity.update_studio_value(enum_value) + for children_by_key in self.non_gui_children.items(): + for key, child_obj in children_by_key.items(): + child_value = value.get(key, NOT_SET) + child_obj.update_studio_value(child_value) + + def update_project_value(self, value): + """Update project override values. + + Not an api method, should be called by parent. + """ + value = self._check_update_value(value, "project override") + value, metadata = self._prepare_value(value) + self._project_override_metadata = metadata + self.had_project_override = metadata is not NOT_SET + + if value is NOT_SET: + self.enum_entity.update_default_value(value) + for children_by_key in self.non_gui_children.values(): + for child_obj in children_by_key: + child_obj.update_default_value(value) + return + + value_keys = set(value.keys()) + enum_value = value[self.enum_key] + expected_keys = set(self.non_gui_children[enum_value]) + expected_keys.add(self.enum_key) + unknown_keys = value_keys - expected_keys + if unknown_keys: + self.log.warning( + "{} Unknown keys in project overrides: {}".format( + self.path, + ", ".join("\"{}\"".format(key) for key in unknown_keys) + ) + ) + + self.enum_entity.update_project_value(enum_value) + for children_by_key in self.non_gui_children.items(): + for key, child_obj in children_by_key.items(): + child_value = value.get(key, NOT_SET) + child_obj.update_project_value(child_value) + From baf706627af1cfc6a4724897d3ddfda054bcac37 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 28 Jun 2021 18:15:28 +0200 Subject: [PATCH 16/53] implemented actions for conditional dictionary --- .../settings/entities/dict_conditional.py | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index 20697506fd..5549ce13f2 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -617,3 +617,64 @@ class DictConditionalEntity(ItemEntity): child_value = value.get(key, NOT_SET) child_obj.update_project_value(child_value) + def _discard_changes(self, on_change_trigger): + self._ignore_child_changes = True + + self.enum_entity.discard_changes(on_change_trigger) + for children_by_key in self.non_gui_children.values(): + for child_obj in children_by_key.values(): + child_obj.discard_changes(on_change_trigger) + + self._ignore_child_changes = False + + def _add_to_studio_default(self, on_change_trigger): + self._ignore_child_changes = True + + self.enum_entity.add_to_studio_default(on_change_trigger) + for children_by_key in self.non_gui_children.values(): + for child_obj in children_by_key.values(): + child_obj.add_to_studio_default(on_change_trigger) + + self._ignore_child_changes = False + + self._update_current_metadata() + + self.parent.on_child_change(self) + + def _remove_from_studio_default(self, on_change_trigger): + self._ignore_child_changes = True + + self.enum_entity.remove_from_studio_default(on_change_trigger) + for children_by_key in self.non_gui_children.values(): + for child_obj in children_by_key.values(): + child_obj.remove_from_studio_default(on_change_trigger) + + self._ignore_child_changes = False + + def _add_to_project_override(self, on_change_trigger): + self._ignore_child_changes = True + + self.enum_entity.add_to_project_override(on_change_trigger) + for children_by_key in self.non_gui_children.values(): + for child_obj in children_by_key.values(): + child_obj.add_to_project_override(on_change_trigger) + + self._ignore_child_changes = False + + self._update_current_metadata() + + self.parent.on_child_change(self) + + def _remove_from_project_override(self, on_change_trigger): + if self._override_state is not OverrideState.PROJECT: + return + + self._ignore_child_changes = True + + self.enum_entity.remove_from_project_override(on_change_trigger) + for children_by_key in self.non_gui_children.values(): + for child_obj in children_by_key.values(): + child_obj.remove_from_project_override(on_change_trigger) + + self._ignore_child_changes = False + From f70b19305cd69f4d9755324b97dfc6702970b290 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 28 Jun 2021 18:16:07 +0200 Subject: [PATCH 17/53] implemented reset_callbacks method --- openpype/settings/entities/dict_conditional.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index 5549ce13f2..df852587f6 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -678,3 +678,9 @@ class DictConditionalEntity(ItemEntity): self._ignore_child_changes = False + def reset_callbacks(self): + """Reset registered callbacks on entity and children.""" + super(DictConditionalEntity, self).reset_callbacks() + for children in self.children.values(): + for child_entity in children: + child_entity.reset_callbacks() From 1048a1268cde84d2d0236c78d6eacc7d5f2a25d1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 28 Jun 2021 18:18:02 +0200 Subject: [PATCH 18/53] import DictConditionalEntity in entities init --- openpype/settings/entities/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/settings/entities/__init__.py b/openpype/settings/entities/__init__.py index 94eb819f2b..c0eef15e69 100644 --- a/openpype/settings/entities/__init__.py +++ b/openpype/settings/entities/__init__.py @@ -111,6 +111,7 @@ from .enum_entity import ( from .list_entity import ListEntity from .dict_immutable_keys_entity import DictImmutableKeysEntity from .dict_mutable_keys_entity import DictMutableKeysEntity +from .dict_conditional import DictConditionalEntity from .anatomy_entities import AnatomyEntity @@ -166,5 +167,7 @@ __all__ = ( "DictMutableKeysEntity", + "DictConditionalEntity", + "AnatomyEntity" ) From e8f7f1418e6a11c510825f7337388b2909b77214 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 28 Jun 2021 18:32:35 +0200 Subject: [PATCH 19/53] fix example schema --- openpype/settings/entities/dict_conditional.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index df852587f6..e5803b7606 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -57,6 +57,8 @@ example_schema = { "label": "Menu", "children": [ { + "key": "children", + "label": "Children", "type": "list", "object_type": "text" } From 85a3dd1ea6d6742df6eb23d4a8609db3a892c8d4 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 28 Jun 2021 18:32:54 +0200 Subject: [PATCH 20/53] fix dict vs. list approach --- openpype/settings/entities/dict_conditional.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index e5803b7606..8fc22348a7 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -524,7 +524,7 @@ class DictConditionalEntity(ItemEntity): if value is NOT_SET: self.enum_entity.update_default_value(value) for children_by_key in self.non_gui_children.values(): - for child_obj in children_by_key: + for child_obj in children_by_key.values(): child_obj.update_default_value(value) return @@ -560,7 +560,7 @@ class DictConditionalEntity(ItemEntity): if value is NOT_SET: self.enum_entity.update_default_value(value) for children_by_key in self.non_gui_children.values(): - for child_obj in children_by_key: + for child_obj in children_by_key.values(): child_obj.update_default_value(value) return @@ -596,7 +596,7 @@ class DictConditionalEntity(ItemEntity): if value is NOT_SET: self.enum_entity.update_default_value(value) for children_by_key in self.non_gui_children.values(): - for child_obj in children_by_key: + for child_obj in children_by_key.values(): child_obj.update_default_value(value) return From 16ac770359d5d9d78e9db11fde41649838e922e1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 28 Jun 2021 19:57:42 +0200 Subject: [PATCH 21/53] created copy of DictConditionalWidget as base for DictConditionalWidget --- .../tools/settings/settings/categories.py | 5 + .../settings/settings/dict_conditional.py | 253 ++++++++++++++++++ 2 files changed, 258 insertions(+) create mode 100644 openpype/tools/settings/settings/dict_conditional.py diff --git a/openpype/tools/settings/settings/categories.py b/openpype/tools/settings/settings/categories.py index 34ab4c464a..0dfafce186 100644 --- a/openpype/tools/settings/settings/categories.py +++ b/openpype/tools/settings/settings/categories.py @@ -11,6 +11,7 @@ from openpype.settings.entities import ( GUIEntity, DictImmutableKeysEntity, DictMutableKeysEntity, + DictConditionalEntity, ListEntity, PathEntity, ListStrictEntity, @@ -35,6 +36,7 @@ from .base import GUIWidget from .list_item_widget import ListWidget from .list_strict_widget import ListStrictWidget from .dict_mutable_widget import DictMutableKeysWidget +from .dict_conditional import DictConditionalWidget from .item_widgets import ( BoolWidget, DictImmutableKeysWidget, @@ -100,6 +102,9 @@ class SettingsCategoryWidget(QtWidgets.QWidget): if isinstance(entity, GUIEntity): return GUIWidget(*args) + elif isinstance(entity, DictConditionalEntity): + return DictConditionalWidget(*args) + elif isinstance(entity, DictImmutableKeysEntity): return DictImmutableKeysWidget(*args) diff --git a/openpype/tools/settings/settings/dict_conditional.py b/openpype/tools/settings/settings/dict_conditional.py new file mode 100644 index 0000000000..e7e0a31401 --- /dev/null +++ b/openpype/tools/settings/settings/dict_conditional.py @@ -0,0 +1,253 @@ +import collections +from Qt import QtWidgets, QtCore, QtGui + +from .widgets import ( + ExpandingWidget, + GridLabelWidget +) +from .wrapper_widgets import ( + WrapperWidget, + CollapsibleWrapper, + FormWrapper +) +from .base import BaseWidget +from openpype.tools.settings import CHILD_OFFSET + + +class DictConditionalWidget(BaseWidget): + def create_ui(self): + self.input_fields = [] + self.checkbox_child = None + + self.label_widget = None + self.body_widget = None + self.content_widget = None + self.content_layout = None + + label = None + if self.entity.is_dynamic_item: + self._ui_as_dynamic_item() + + elif self.entity.use_label_wrap: + self._ui_label_wrap() + self.checkbox_child = self.entity.non_gui_children.get( + self.entity.checkbox_key + ) + + else: + self._ui_item_base() + label = self.entity.label + + self._parent_widget_by_entity_id = {} + self._added_wrapper_ids = set() + self._prepare_entity_layouts( + self.entity.gui_layout, self.content_widget + ) + + for child_obj in self.entity.children: + self.input_fields.append( + self.create_ui_for_entity( + self.category_widget, child_obj, self + ) + ) + + if self.entity.use_label_wrap and self.content_layout.count() == 0: + self.body_widget.hide_toolbox(True) + + self.entity_widget.add_widget_to_layout(self, label) + + def _prepare_entity_layouts(self, children, widget): + for child in children: + if not isinstance(child, dict): + if child is not self.checkbox_child: + self._parent_widget_by_entity_id[child.id] = widget + continue + + if child["type"] == "collapsible-wrap": + wrapper = CollapsibleWrapper(child, widget) + + elif child["type"] == "form": + wrapper = FormWrapper(child, widget) + + else: + raise KeyError( + "Unknown Wrapper type \"{}\"".format(child["type"]) + ) + + self._parent_widget_by_entity_id[wrapper.id] = widget + + self._prepare_entity_layouts(child["children"], wrapper) + + def _ui_item_base(self): + self.setObjectName("DictInvisible") + + self.content_widget = self + self.content_layout = QtWidgets.QGridLayout(self) + self.content_layout.setContentsMargins(0, 0, 0, 0) + self.content_layout.setSpacing(5) + + def _ui_as_dynamic_item(self): + content_widget = QtWidgets.QWidget(self) + content_widget.setObjectName("DictAsWidgetBody") + + show_borders = str(int(self.entity.show_borders)) + content_widget.setProperty("show_borders", show_borders) + + label_widget = QtWidgets.QLabel(self.entity.label) + + content_layout = QtWidgets.QGridLayout(content_widget) + content_layout.setContentsMargins(5, 5, 5, 5) + + main_layout = QtWidgets.QHBoxLayout(self) + main_layout.setContentsMargins(0, 0, 0, 0) + main_layout.setSpacing(5) + main_layout.addWidget(content_widget) + + self.label_widget = label_widget + self.content_widget = content_widget + self.content_layout = content_layout + + def _ui_label_wrap(self): + content_widget = QtWidgets.QWidget(self) + content_widget.setObjectName("ContentWidget") + + if self.entity.highlight_content: + content_state = "hightlighted" + bottom_margin = 5 + else: + content_state = "" + bottom_margin = 0 + content_widget.setProperty("content_state", content_state) + content_layout_margins = (CHILD_OFFSET, 5, 0, bottom_margin) + + body_widget = ExpandingWidget(self.entity.label, self) + label_widget = body_widget.label_widget + body_widget.set_content_widget(content_widget) + + content_layout = QtWidgets.QGridLayout(content_widget) + content_layout.setContentsMargins(*content_layout_margins) + + main_layout = QtWidgets.QHBoxLayout(self) + main_layout.setContentsMargins(0, 0, 0, 0) + main_layout.setSpacing(0) + main_layout.addWidget(body_widget) + + self.label_widget = label_widget + self.body_widget = body_widget + self.content_widget = content_widget + self.content_layout = content_layout + + if len(self.input_fields) == 1 and self.checkbox_child: + body_widget.hide_toolbox(hide_content=True) + + elif self.entity.collapsible: + if not self.entity.collapsed: + body_widget.toggle_content() + else: + body_widget.hide_toolbox(hide_content=False) + + def add_widget_to_layout(self, widget, label=None): + if self.checkbox_child and widget.entity is self.checkbox_child: + self.body_widget.add_widget_before_label(widget) + return + + if not widget.entity: + map_id = widget.id + else: + map_id = widget.entity.id + + wrapper = self._parent_widget_by_entity_id[map_id] + if wrapper is not self.content_widget: + wrapper.add_widget_to_layout(widget, label) + if wrapper.id not in self._added_wrapper_ids: + self.add_widget_to_layout(wrapper) + self._added_wrapper_ids.add(wrapper.id) + return + + row = self.content_layout.rowCount() + if not label or isinstance(widget, WrapperWidget): + self.content_layout.addWidget(widget, row, 0, 1, 2) + else: + label_widget = GridLabelWidget(label, widget) + label_widget.input_field = widget + widget.label_widget = label_widget + self.content_layout.addWidget(label_widget, row, 0, 1, 1) + self.content_layout.addWidget(widget, row, 1, 1, 1) + + def set_entity_value(self): + for input_field in self.input_fields: + input_field.set_entity_value() + + def hierarchical_style_update(self): + self.update_style() + for input_field in self.input_fields: + input_field.hierarchical_style_update() + + def update_style(self): + if not self.body_widget and not self.label_widget: + return + + if self.entity.group_item: + group_item = self.entity.group_item + has_unsaved_changes = group_item.has_unsaved_changes + has_project_override = group_item.has_project_override + has_studio_override = group_item.has_studio_override + else: + has_unsaved_changes = self.entity.has_unsaved_changes + has_project_override = self.entity.has_project_override + has_studio_override = self.entity.has_studio_override + + style_state = self.get_style_state( + self.is_invalid, + has_unsaved_changes, + has_project_override, + has_studio_override + ) + if self._style_state == style_state: + return + + self._style_state = style_state + + if self.body_widget: + if style_state: + child_style_state = "child-{}".format(style_state) + else: + child_style_state = "" + + self.body_widget.side_line_widget.setProperty( + "state", child_style_state + ) + self.body_widget.side_line_widget.style().polish( + self.body_widget.side_line_widget + ) + + # There is nothing to care if there is no label + if not self.label_widget: + return + + # Don't change label if is not group or under group item + if not self.entity.is_group and not self.entity.group_item: + return + + self.label_widget.setProperty("state", style_state) + self.label_widget.style().polish(self.label_widget) + + def _on_entity_change(self): + pass + + @property + def is_invalid(self): + return self._is_invalid or self._child_invalid + + @property + def _child_invalid(self): + for input_field in self.input_fields: + if input_field.is_invalid: + return True + return False + + def get_invalid(self): + invalid = [] + for input_field in self.input_fields: + invalid.extend(input_field.get_invalid()) + return invalid From c3614dbfce046c692968399a9e7433613bff6655 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 28 Jun 2021 19:58:29 +0200 Subject: [PATCH 22/53] removed checkbox checks as checkbox is not available for conditional dict --- .../tools/settings/settings/dict_conditional.py | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/openpype/tools/settings/settings/dict_conditional.py b/openpype/tools/settings/settings/dict_conditional.py index e7e0a31401..2024bd3258 100644 --- a/openpype/tools/settings/settings/dict_conditional.py +++ b/openpype/tools/settings/settings/dict_conditional.py @@ -17,7 +17,6 @@ from openpype.tools.settings import CHILD_OFFSET class DictConditionalWidget(BaseWidget): def create_ui(self): self.input_fields = [] - self.checkbox_child = None self.label_widget = None self.body_widget = None @@ -30,9 +29,6 @@ class DictConditionalWidget(BaseWidget): elif self.entity.use_label_wrap: self._ui_label_wrap() - self.checkbox_child = self.entity.non_gui_children.get( - self.entity.checkbox_key - ) else: self._ui_item_base() @@ -59,8 +55,7 @@ class DictConditionalWidget(BaseWidget): def _prepare_entity_layouts(self, children, widget): for child in children: if not isinstance(child, dict): - if child is not self.checkbox_child: - self._parent_widget_by_entity_id[child.id] = widget + parent_widget_by_entity_id[child.id] = widget continue if child["type"] == "collapsible-wrap": @@ -137,20 +132,13 @@ class DictConditionalWidget(BaseWidget): self.content_widget = content_widget self.content_layout = content_layout - if len(self.input_fields) == 1 and self.checkbox_child: - body_widget.hide_toolbox(hide_content=True) - - elif self.entity.collapsible: + if self.entity.collapsible: if not self.entity.collapsed: body_widget.toggle_content() else: body_widget.hide_toolbox(hide_content=False) def add_widget_to_layout(self, widget, label=None): - if self.checkbox_child and widget.entity is self.checkbox_child: - self.body_widget.add_widget_before_label(widget) - return - if not widget.entity: map_id = widget.id else: From 1b1ce1f2a5602b07714f19e8aeb0404ba072f7f7 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 28 Jun 2021 19:59:07 +0200 Subject: [PATCH 23/53] modified _prepare_entity_layouts to be able to store result into passed dictionary --- openpype/tools/settings/settings/dict_conditional.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/openpype/tools/settings/settings/dict_conditional.py b/openpype/tools/settings/settings/dict_conditional.py index 2024bd3258..aeb2b7d86c 100644 --- a/openpype/tools/settings/settings/dict_conditional.py +++ b/openpype/tools/settings/settings/dict_conditional.py @@ -52,8 +52,10 @@ class DictConditionalWidget(BaseWidget): self.entity_widget.add_widget_to_layout(self, label) - def _prepare_entity_layouts(self, children, widget): - for child in children: + def _prepare_entity_layouts( + self, gui_layout, widget, parent_widget_by_entity_id + ): + for child in gui_layout: if not isinstance(child, dict): parent_widget_by_entity_id[child.id] = widget continue @@ -69,9 +71,11 @@ class DictConditionalWidget(BaseWidget): "Unknown Wrapper type \"{}\"".format(child["type"]) ) - self._parent_widget_by_entity_id[wrapper.id] = widget + parent_widget_by_entity_id[wrapper.id] = widget - self._prepare_entity_layouts(child["children"], wrapper) + self._prepare_entity_layouts( + child["children"], wrapper, parent_widget_by_entity_id + ) def _ui_item_base(self): self.setObjectName("DictInvisible") From 9131982be41add0330eaef9f0812e056eb194c13 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 28 Jun 2021 19:59:37 +0200 Subject: [PATCH 24/53] added few required attributes --- openpype/tools/settings/settings/dict_conditional.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openpype/tools/settings/settings/dict_conditional.py b/openpype/tools/settings/settings/dict_conditional.py index aeb2b7d86c..47c1d7d4c9 100644 --- a/openpype/tools/settings/settings/dict_conditional.py +++ b/openpype/tools/settings/settings/dict_conditional.py @@ -18,6 +18,9 @@ class DictConditionalWidget(BaseWidget): def create_ui(self): self.input_fields = [] + self._content_by_enum_value = {} + self._last_enum_value = None + self.label_widget = None self.body_widget = None self.content_widget = None @@ -35,6 +38,7 @@ class DictConditionalWidget(BaseWidget): label = self.entity.label self._parent_widget_by_entity_id = {} + self._enum_key_by_wrapper_id = {} self._added_wrapper_ids = set() self._prepare_entity_layouts( self.entity.gui_layout, self.content_widget From 84f725b36447aca06ed676a2df3b44ede503dc8d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 28 Jun 2021 20:02:02 +0200 Subject: [PATCH 25/53] modified how preparation of layout works --- .../settings/settings/dict_conditional.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/openpype/tools/settings/settings/dict_conditional.py b/openpype/tools/settings/settings/dict_conditional.py index 47c1d7d4c9..2287e52595 100644 --- a/openpype/tools/settings/settings/dict_conditional.py +++ b/openpype/tools/settings/settings/dict_conditional.py @@ -40,9 +40,22 @@ class DictConditionalWidget(BaseWidget): self._parent_widget_by_entity_id = {} self._enum_key_by_wrapper_id = {} self._added_wrapper_ids = set() - self._prepare_entity_layouts( - self.entity.gui_layout, self.content_widget - ) + + # Add enum entity to layout mapping + enum_entity = self.entity.enum_entity + self._parent_widget_by_entity_id[enum_entity.id] = self.content_widget + + # Add rest of entities to wrapper mappings + for enum_key, children in self.entity.gui_layout.items(): + parent_widget_by_entity_id = {} + self._prepare_entity_layouts( + children, + self.content_widget, + parent_widget_by_entity_id + ) + for item_id in parent_widget_by_entity_id.keys(): + self._enum_key_by_wrapper_id[item_id] = enum_key + self._parent_widget_by_entity_id.update(parent_widget_by_entity_id) for child_obj in self.entity.children: self.input_fields.append( From 2ebd5daac3be15bd3cc3feb68f863191f653df89 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 28 Jun 2021 20:02:56 +0200 Subject: [PATCH 26/53] store content of each enum key to different widget --- .../tools/settings/settings/dict_conditional.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/openpype/tools/settings/settings/dict_conditional.py b/openpype/tools/settings/settings/dict_conditional.py index 2287e52595..c2e59e0fbe 100644 --- a/openpype/tools/settings/settings/dict_conditional.py +++ b/openpype/tools/settings/settings/dict_conditional.py @@ -48,9 +48,22 @@ class DictConditionalWidget(BaseWidget): # Add rest of entities to wrapper mappings for enum_key, children in self.entity.gui_layout.items(): parent_widget_by_entity_id = {} + + content_widget = QtWidgets.QWidget(self.content_widget) + content_layout = QtWidgets.QGridLayout(content_widget) + content_layout.setColumnStretch(0, 0) + content_layout.setColumnStretch(1, 1) + content_layout.setContentsMargins(0, 0, 0, 0) + content_layout.setSpacing(5) + + self._content_by_enum_value[enum_key] = { + "widget": content_widget, + "layout": content_layout + } + self._prepare_entity_layouts( children, - self.content_widget, + content_widget, parent_widget_by_entity_id ) for item_id in parent_widget_by_entity_id.keys(): From cc72287ddbccb400edea8f25d6ac1850f2359609 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 28 Jun 2021 20:03:26 +0200 Subject: [PATCH 27/53] modified how entity widgets are created and when --- .../settings/settings/dict_conditional.py | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/openpype/tools/settings/settings/dict_conditional.py b/openpype/tools/settings/settings/dict_conditional.py index c2e59e0fbe..3798cffe38 100644 --- a/openpype/tools/settings/settings/dict_conditional.py +++ b/openpype/tools/settings/settings/dict_conditional.py @@ -70,12 +70,23 @@ class DictConditionalWidget(BaseWidget): self._enum_key_by_wrapper_id[item_id] = enum_key self._parent_widget_by_entity_id.update(parent_widget_by_entity_id) - for child_obj in self.entity.children: - self.input_fields.append( - self.create_ui_for_entity( - self.category_widget, child_obj, self + enum_input_field = self.create_ui_for_entity( + self.category_widget, self.entity.enum_entity, self + ) + self.enum_input_field = enum_input_field + self.input_fields.append(enum_input_field) + + for item_key, children in self.entity.children.items(): + content_widget = self._content_by_enum_value[item_key]["widget"] + row = self.content_layout.rowCount() + self.content_layout.addWidget(content_widget, row, 0, 1, 2) + + for child_obj in children: + self.input_fields.append( + self.create_ui_for_entity( + self.category_widget, child_obj, self + ) ) - ) if self.entity.use_label_wrap and self.content_layout.count() == 0: self.body_widget.hide_toolbox(True) From c8a5de88bd5918a302132e0e37fa1bc362a41b7b Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 28 Jun 2021 20:03:52 +0200 Subject: [PATCH 28/53] define content widget based on map_id and entity id --- .../tools/settings/settings/dict_conditional.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/openpype/tools/settings/settings/dict_conditional.py b/openpype/tools/settings/settings/dict_conditional.py index 3798cffe38..013fefb74f 100644 --- a/openpype/tools/settings/settings/dict_conditional.py +++ b/openpype/tools/settings/settings/dict_conditional.py @@ -189,23 +189,30 @@ class DictConditionalWidget(BaseWidget): else: map_id = widget.entity.id + content_widget = self.content_widget + content_layout = self.content_layout + if map_id != self.entity.enum_entity.id: + enum_value = self._enum_key_by_wrapper_id[map_id] + content_widget = self._content_by_enum_value[enum_value]["widget"] + content_layout = self._content_by_enum_value[enum_value]["layout"] + wrapper = self._parent_widget_by_entity_id[map_id] - if wrapper is not self.content_widget: + if wrapper is not content_widget: wrapper.add_widget_to_layout(widget, label) if wrapper.id not in self._added_wrapper_ids: self.add_widget_to_layout(wrapper) self._added_wrapper_ids.add(wrapper.id) return - row = self.content_layout.rowCount() + row = content_layout.rowCount() if not label or isinstance(widget, WrapperWidget): - self.content_layout.addWidget(widget, row, 0, 1, 2) + content_layout.addWidget(widget, row, 0, 1, 2) else: label_widget = GridLabelWidget(label, widget) label_widget.input_field = widget widget.label_widget = label_widget - self.content_layout.addWidget(label_widget, row, 0, 1, 1) - self.content_layout.addWidget(widget, row, 1, 1, 1) + content_layout.addWidget(label_widget, row, 0, 1, 1) + content_layout.addWidget(widget, row, 1, 1, 1) def set_entity_value(self): for input_field in self.input_fields: From 313a78a3918cf279c0feceac64480b47afeb927e Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 28 Jun 2021 20:04:11 +0200 Subject: [PATCH 29/53] trigger change of visibility on change of enum --- openpype/tools/settings/settings/dict_conditional.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/openpype/tools/settings/settings/dict_conditional.py b/openpype/tools/settings/settings/dict_conditional.py index 013fefb74f..5442af14b4 100644 --- a/openpype/tools/settings/settings/dict_conditional.py +++ b/openpype/tools/settings/settings/dict_conditional.py @@ -273,7 +273,14 @@ class DictConditionalWidget(BaseWidget): self.label_widget.style().polish(self.label_widget) def _on_entity_change(self): - pass + enum_value = self.enum_input_field.entity.value + if enum_value == self._last_enum_value: + return + + self._last_enum_value = enum_value + for item_key, content in self._content_by_enum_value.items(): + widget = content["widget"] + widget.setVisible(item_key == enum_value) @property def is_invalid(self): From b344e0c27ad71350c887f2d90c3a2c5fe7ecb031 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 28 Jun 2021 20:04:32 +0200 Subject: [PATCH 30/53] set_entity_value triggers on entity change --- openpype/tools/settings/settings/dict_conditional.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/tools/settings/settings/dict_conditional.py b/openpype/tools/settings/settings/dict_conditional.py index 5442af14b4..05dfa47e60 100644 --- a/openpype/tools/settings/settings/dict_conditional.py +++ b/openpype/tools/settings/settings/dict_conditional.py @@ -218,6 +218,8 @@ class DictConditionalWidget(BaseWidget): for input_field in self.input_fields: input_field.set_entity_value() + self._on_entity_change() + def hierarchical_style_update(self): self.update_style() for input_field in self.input_fields: From c9ee4e5f713f20dec0d4f684a596147b71f58850 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Mon, 28 Jun 2021 20:04:45 +0200 Subject: [PATCH 31/53] added column stretch to grid layout --- openpype/tools/settings/settings/dict_conditional.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/tools/settings/settings/dict_conditional.py b/openpype/tools/settings/settings/dict_conditional.py index 05dfa47e60..84288f7b5b 100644 --- a/openpype/tools/settings/settings/dict_conditional.py +++ b/openpype/tools/settings/settings/dict_conditional.py @@ -41,6 +41,9 @@ class DictConditionalWidget(BaseWidget): self._enum_key_by_wrapper_id = {} self._added_wrapper_ids = set() + self.content_layout.setColumnStretch(0, 0) + self.content_layout.setColumnStretch(1, 1) + # Add enum entity to layout mapping enum_entity = self.entity.enum_entity self._parent_widget_by_entity_id[enum_entity.id] = self.content_widget From e1129bdbad5a167e4428bd3d298e1e4e5012fdd9 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 29 Jun 2021 10:25:45 +0200 Subject: [PATCH 32/53] fix keys method --- openpype/settings/entities/dict_conditional.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index 8fc22348a7..f72d1c8b82 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -105,7 +105,7 @@ class DictConditionalEntity(ItemEntity): def keys(self): """Entity's keys.""" keys = list(self.non_gui_children[self.current_enum].keys()) - keys.insert(0, [self.current_enum]) + keys.insert(0, [self.enum_key]) return keys def values(self): From 4bff4a8138506f1fdbe247e54d4fb739bb111bff Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 29 Jun 2021 10:29:07 +0200 Subject: [PATCH 33/53] force to be a group --- openpype/settings/entities/dict_conditional.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index f72d1c8b82..2e8cd6affe 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -149,6 +149,13 @@ class DictConditionalEntity(ItemEntity): self._current_metadata = {} self._metadata_are_modified = False + if ( + self.group_item is None + and not self.is_dynamic_item + and not self.is_in_dynamic_item + ): + self.is_group = True + # Children are stored by key as keys are immutable and are defined by # schema self.valid_value_types = (dict, ) From 40f87f2f20c5fee45f2be2e903aaf9814f9acf43 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 29 Jun 2021 10:30:11 +0200 Subject: [PATCH 34/53] few minor fixes of entity --- .../settings/entities/dict_conditional.py | 40 +++++++++++-------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index 2e8cd6affe..d3aad60df6 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -391,7 +391,7 @@ class DictConditionalEntity(ItemEntity): @property def value(self): output = { - self.current_enum: self.enum_entity.value + self.enum_key: self.enum_entity.value } for key, child_obj in self.non_gui_children[self.current_enum].items(): output[key] = child_obj.value @@ -408,6 +408,7 @@ class DictConditionalEntity(ItemEntity): def _child_has_unsaved_changes(self): if self.enum_entity.has_unsaved_changes: return True + for child_obj in self.non_gui_children[self.current_enum].values(): if child_obj.has_unsaved_changes: return True @@ -448,11 +449,14 @@ class DictConditionalEntity(ItemEntity): return NOT_SET if self._override_state is OverrideState.DEFAULTS: - output = { - self.current_enum: self.enum_entity.settings_value() - } - non_gui_children = self.non_gui_children[self.current_enum] - for key, child_obj in non_gui_children.items(): + children_items = [ + (self.enum_key, self.enum_entity) + ] + for item in self.non_gui_children[self.current_enum].items(): + children_items.append(item) + + output = {} + for key, child_obj in children_items: child_value = child_obj.settings_value() if not child_obj.is_file and not child_obj.file_item: for _key, _value in child_value.items(): @@ -470,10 +474,14 @@ class DictConditionalEntity(ItemEntity): if not self.has_project_override: return NOT_SET - output = { - self.current_enum: self.enum_entity.settings_value() - } - for key, child_obj in self.non_gui_children[self.current_enum].items(): + output = {} + children_items = [ + (self.enum_key, self.enum_entity) + ] + for item in self.non_gui_children[self.current_enum].items(): + children_items.append(item) + + for key, child_obj in children_items: value = child_obj.settings_value() if value is not NOT_SET: output[key] = value @@ -537,7 +545,7 @@ class DictConditionalEntity(ItemEntity): value_keys = set(value.keys()) enum_value = value[self.enum_key] - expected_keys = set(self.non_gui_children[enum_value]) + expected_keys = set(self.non_gui_children[enum_value].keys()) expected_keys.add(self.enum_key) unknown_keys = value_keys - expected_keys if unknown_keys: @@ -549,7 +557,7 @@ class DictConditionalEntity(ItemEntity): ) self.enum_entity.update_default_value(enum_value) - for children_by_key in self.non_gui_children.items(): + for children_by_key in self.non_gui_children.values(): for key, child_obj in children_by_key.items(): child_value = value.get(key, NOT_SET) child_obj.update_default_value(child_value) @@ -565,10 +573,10 @@ class DictConditionalEntity(ItemEntity): self.had_studio_override = metadata is not NOT_SET if value is NOT_SET: - self.enum_entity.update_default_value(value) + self.enum_entity.update_studio_value(value) for children_by_key in self.non_gui_children.values(): for child_obj in children_by_key.values(): - child_obj.update_default_value(value) + child_obj.update_studio_value(value) return value_keys = set(value.keys()) @@ -601,10 +609,10 @@ class DictConditionalEntity(ItemEntity): self.had_project_override = metadata is not NOT_SET if value is NOT_SET: - self.enum_entity.update_default_value(value) + self.enum_entity.update_project_value(value) for children_by_key in self.non_gui_children.values(): for child_obj in children_by_key.values(): - child_obj.update_default_value(value) + child_obj.update_project_value(value) return value_keys = set(value.keys()) From 3d59ba17d54600247593a7c05c00a0576e124dc1 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 29 Jun 2021 10:37:04 +0200 Subject: [PATCH 35/53] current_enum is dynamic property --- openpype/settings/entities/dict_conditional.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index d3aad60df6..6802af5806 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -171,10 +171,15 @@ class DictConditionalEntity(ItemEntity): self.enum_children = self.schema_data.get("enum_children") self.enum_entity = None - self.current_enum = None self._add_children() + @property + def current_enum(self): + if self.enum_entity is None: + return None + return self.enum_entity.value + def schema_validations(self): """Validation of schema data.""" if self.enum_key is None: @@ -269,25 +274,20 @@ class DictConditionalEntity(ItemEntity): if not self.enum_children or not self.enum_key: return - enum_items = [] valid_enum_items = [] for item in self.enum_children: if isinstance(item, dict) and "key" in item: valid_enum_items.append(item) - first_key = None + enum_items = [] for item in valid_enum_items: item_key = item["key"] - if first_key is None: - first_key = item_key item_label = item.get("label") or item_key enum_items.append({item_key: item_label}) if not enum_items: return - self.current_enum = first_key - enum_key = self.enum_key or "invalid" enum_schema = { "type": "enum", @@ -296,6 +296,7 @@ class DictConditionalEntity(ItemEntity): "key": enum_key, "label": self.enum_label or enum_key } + enum_entity = self.create_schema_object(enum_schema, self) self.enum_entity = enum_entity From f3ae791f5c0afc283a5a17490bb70bf362e4a1e4 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 29 Jun 2021 10:56:13 +0200 Subject: [PATCH 36/53] make sure all keys are available in all variables --- openpype/settings/entities/dict_conditional.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index 6802af5806..956180c3da 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -159,9 +159,9 @@ class DictConditionalEntity(ItemEntity): # Children are stored by key as keys are immutable and are defined by # schema self.valid_value_types = (dict, ) - self.children = collections.defaultdict(list) - self.non_gui_children = collections.defaultdict(dict) - self.gui_layout = collections.defaultdict(list) + self.children = {} + self.non_gui_children = {} + self.gui_layout = {} if self.is_dynamic_item: self.require_key = False @@ -302,6 +302,11 @@ class DictConditionalEntity(ItemEntity): for item in valid_enum_items: item_key = item["key"] + # Make sure all keys have set value in there variables + self.non_gui_children[item_key] = {} + self.children[item_key] = [] + self.gui_layout[item_key] = [] + children = item.get("children") or [] for children_schema in children: child_obj = self.create_schema_object(children_schema, self) From 0e0e527741392bad6a67de9f3d2736521a149326 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 29 Jun 2021 12:44:50 +0200 Subject: [PATCH 37/53] items vs. values fix --- openpype/settings/entities/dict_conditional.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index 956180c3da..858c2ca4e8 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -599,7 +599,7 @@ class DictConditionalEntity(ItemEntity): ) self.enum_entity.update_studio_value(enum_value) - for children_by_key in self.non_gui_children.items(): + for children_by_key in self.non_gui_children.values(): for key, child_obj in children_by_key.items(): child_value = value.get(key, NOT_SET) child_obj.update_studio_value(child_value) @@ -635,7 +635,7 @@ class DictConditionalEntity(ItemEntity): ) self.enum_entity.update_project_value(enum_value) - for children_by_key in self.non_gui_children.items(): + for children_by_key in self.non_gui_children.values(): for key, child_obj in children_by_key.items(): child_value = value.get(key, NOT_SET) child_obj.update_project_value(child_value) From d58c8f1a112a0daaf2e3227794923d0262ee344f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 29 Jun 2021 12:46:10 +0200 Subject: [PATCH 38/53] added argument ignore_missing_defaults to set_override_state method --- openpype/settings/entities/base_entity.py | 9 ++++++++- openpype/settings/entities/dict_conditional.py | 2 +- .../entities/dict_immutable_keys_entity.py | 4 ++-- .../entities/dict_mutable_keys_entity.py | 14 ++++++++++---- openpype/settings/entities/input_entities.py | 12 +++++++++--- openpype/settings/entities/item_entities.py | 18 ++++++++++++------ openpype/settings/entities/list_entity.py | 16 ++++++++++++---- openpype/settings/entities/root_entities.py | 7 +++++-- 8 files changed, 59 insertions(+), 23 deletions(-) diff --git a/openpype/settings/entities/base_entity.py b/openpype/settings/entities/base_entity.py index 0e29a35e1f..e1cd5134e7 100644 --- a/openpype/settings/entities/base_entity.py +++ b/openpype/settings/entities/base_entity.py @@ -280,7 +280,7 @@ class BaseItemEntity(BaseEntity): ) @abstractmethod - def set_override_state(self, state): + def set_override_state(self, state, ignore_missing_defaults): """Set override state and trigger it on children. Method discard all changes in hierarchy and use values, metadata @@ -290,8 +290,15 @@ class BaseItemEntity(BaseEntity): Should start on root entity and when triggered then must be called on all entities in hierarchy. + Argument `ignore_missing_defaults` should be used when entity has + children that are not saved or used all the time but override statu + must be changed and children must have any default value. + Args: state (OverrideState): State to which should be data changed. + ignore_missing_defaults (bool): Ignore missing default values. + Entity won't raise `DefaultsNotDefined` and + `StudioDefaultsNotDefined`. """ pass diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index 858c2ca4e8..98aa10dacb 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -377,7 +377,7 @@ class DictConditionalEntity(ItemEntity): self._metadata_are_modified = current_metadata != metadata self._current_metadata = current_metadata - def set_override_state(self, state): + def set_override_state(self, state, ignore_missing_defaults): # Trigger override state change of root if is not same if self.root_item.override_state is not state: self.root_item.set_override_state(state) diff --git a/openpype/settings/entities/dict_immutable_keys_entity.py b/openpype/settings/entities/dict_immutable_keys_entity.py index c965dc3b5a..2802290e68 100644 --- a/openpype/settings/entities/dict_immutable_keys_entity.py +++ b/openpype/settings/entities/dict_immutable_keys_entity.py @@ -258,7 +258,7 @@ class DictImmutableKeysEntity(ItemEntity): self._metadata_are_modified = current_metadata != metadata self._current_metadata = current_metadata - def set_override_state(self, state): + def set_override_state(self, state, ignore_missing_defaults): # Trigger override state change of root if is not same if self.root_item.override_state is not state: self.root_item.set_override_state(state) @@ -268,7 +268,7 @@ class DictImmutableKeysEntity(ItemEntity): self._override_state = state for child_obj in self.non_gui_children.values(): - child_obj.set_override_state(state) + child_obj.set_override_state(state, ignore_missing_defaults) self._update_current_metadata() diff --git a/openpype/settings/entities/dict_mutable_keys_entity.py b/openpype/settings/entities/dict_mutable_keys_entity.py index 3c2645e3e5..a5734e36b8 100644 --- a/openpype/settings/entities/dict_mutable_keys_entity.py +++ b/openpype/settings/entities/dict_mutable_keys_entity.py @@ -320,7 +320,7 @@ class DictMutableKeysEntity(EndpointEntity): def _metadata_for_current_state(self): return self._get_metadata_for_state(self._override_state) - def set_override_state(self, state): + def set_override_state(self, state, ignore_missing_defaults): # Trigger override state change of root if is not same if self.root_item.override_state is not state: self.root_item.set_override_state(state) @@ -331,11 +331,17 @@ class DictMutableKeysEntity(EndpointEntity): # Ignore if is dynamic item and use default in that case if not self.is_dynamic_item and not self.is_in_dynamic_item: if state > OverrideState.DEFAULTS: - if not self.has_default_value: + if ( + not self.has_default_value + and not ignore_missing_defaults + ): raise DefaultsNotDefined(self) elif state > OverrideState.STUDIO: - if not self.had_studio_override: + if ( + not self.had_studio_override + and not ignore_missing_defaults + ): raise StudioDefaultsNotDefined(self) if state is OverrideState.STUDIO: @@ -426,7 +432,7 @@ class DictMutableKeysEntity(EndpointEntity): if label: children_label_by_id[child_entity.id] = label - child_entity.set_override_state(state) + child_entity.set_override_state(state, ignore_missing_defaults) self.children_label_by_id = children_label_by_id diff --git a/openpype/settings/entities/input_entities.py b/openpype/settings/entities/input_entities.py index 295333eb60..9b41a26bdb 100644 --- a/openpype/settings/entities/input_entities.py +++ b/openpype/settings/entities/input_entities.py @@ -217,7 +217,7 @@ class InputEntity(EndpointEntity): return True return False - def set_override_state(self, state): + def set_override_state(self, state, ignore_missing_defaults): # Trigger override state change of root if is not same if self.root_item.override_state is not state: self.root_item.set_override_state(state) @@ -227,11 +227,17 @@ class InputEntity(EndpointEntity): # Ignore if is dynamic item and use default in that case if not self.is_dynamic_item and not self.is_in_dynamic_item: if state > OverrideState.DEFAULTS: - if not self.has_default_value: + if ( + not self.has_default_value + and not ignore_missing_defaults + ): raise DefaultsNotDefined(self) elif state > OverrideState.STUDIO: - if not self.had_studio_override: + if ( + not self.had_studio_override + and not ignore_missing_defaults + ): raise StudioDefaultsNotDefined(self) if state is OverrideState.STUDIO: diff --git a/openpype/settings/entities/item_entities.py b/openpype/settings/entities/item_entities.py index 48336080b6..c52eab988f 100644 --- a/openpype/settings/entities/item_entities.py +++ b/openpype/settings/entities/item_entities.py @@ -150,14 +150,14 @@ class PathEntity(ItemEntity): def value(self): return self.child_obj.value - def set_override_state(self, state): + def set_override_state(self, state, ignore_missing_defaults): # Trigger override state change of root if is not same if self.root_item.override_state is not state: self.root_item.set_override_state(state) return self._override_state = state - self.child_obj.set_override_state(state) + self.child_obj.set_override_state(state, ignore_missing_defaults) def update_default_value(self, value): self.child_obj.update_default_value(value) @@ -344,7 +344,7 @@ class ListStrictEntity(ItemEntity): return True return False - def set_override_state(self, state): + def set_override_state(self, state, ignore_missing_defaults): # Trigger override state change of root if is not same if self.root_item.override_state is not state: self.root_item.set_override_state(state) @@ -354,15 +354,21 @@ class ListStrictEntity(ItemEntity): # Ignore if is dynamic item and use default in that case if not self.is_dynamic_item and not self.is_in_dynamic_item: if state > OverrideState.DEFAULTS: - if not self.has_default_value: + if ( + not self.has_default_value + and not ignore_missing_defaults + ): raise DefaultsNotDefined(self) elif state > OverrideState.STUDIO: - if not self.had_studio_override: + if ( + not self.had_studio_override + and not ignore_missing_defaults + ): raise StudioDefaultsNotDefined(self) for child_entity in self.children: - child_entity.set_override_state(state) + child_entity.set_override_state(state, ignore_missing_defaults) self.initial_value = self.settings_value() diff --git a/openpype/settings/entities/list_entity.py b/openpype/settings/entities/list_entity.py index 4b3f7a2659..2225523792 100644 --- a/openpype/settings/entities/list_entity.py +++ b/openpype/settings/entities/list_entity.py @@ -205,7 +205,7 @@ class ListEntity(EndpointEntity): self._has_project_override = True self.on_change() - def set_override_state(self, state): + def set_override_state(self, state, ignore_missing_defaults): # Trigger override state change of root if is not same if self.root_item.override_state is not state: self.root_item.set_override_state(state) @@ -219,11 +219,17 @@ class ListEntity(EndpointEntity): # Ignore if is dynamic item and use default in that case if not self.is_dynamic_item and not self.is_in_dynamic_item: if state > OverrideState.DEFAULTS: - if not self.has_default_value: + if ( + not self.has_default_value + and not ignore_missing_defaults + ): raise DefaultsNotDefined(self) elif state > OverrideState.STUDIO: - if not self.had_studio_override: + if ( + not self.had_studio_override + and not ignore_missing_defaults + ): raise StudioDefaultsNotDefined(self) value = NOT_SET @@ -257,7 +263,9 @@ class ListEntity(EndpointEntity): child_obj.update_studio_value(item) for child_obj in self.children: - child_obj.set_override_state(self._override_state) + child_obj.set_override_state( + self._override_state, ignore_missing_defaults + ) self.initial_value = self.settings_value() diff --git a/openpype/settings/entities/root_entities.py b/openpype/settings/entities/root_entities.py index 1833535a07..b758e30cbe 100644 --- a/openpype/settings/entities/root_entities.py +++ b/openpype/settings/entities/root_entities.py @@ -218,7 +218,7 @@ class RootEntity(BaseItemEntity): schema_data, *args, **kwargs ) - def set_override_state(self, state): + def set_override_state(self, state, ignore_missing_defaults=None): """Set override state and trigger it on children. Method will discard all changes in hierarchy and use values, metadata @@ -227,9 +227,12 @@ class RootEntity(BaseItemEntity): Args: state (OverrideState): State to which should be data changed. """ + if not ignore_missing_defaults: + ignore_missing_defaults = False + self._override_state = state for child_obj in self.non_gui_children.values(): - child_obj.set_override_state(state) + child_obj.set_override_state(state, ignore_missing_defaults) def on_change(self): """Trigger callbacks on change.""" From 0ed3a2ee701f26fc9cdebe19ceefb225848ae38c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 29 Jun 2021 12:47:12 +0200 Subject: [PATCH 39/53] Use ignore missing defaults in conditional dictionary children that are not using current enum value --- openpype/settings/entities/dict_conditional.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index 98aa10dacb..112ef8bddc 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -386,11 +386,17 @@ class DictConditionalEntity(ItemEntity): # Change has/had override states self._override_state = state - self.enum_entity.set_override_state(state) + self.enum_entity.set_override_state(state, ignore_missing_defaults) + + for child_obj in self.non_gui_children[self.current_enum].values(): + child_obj.set_override_state(state, ignore_missing_defaults) + + for item_key, children_by_key in self.non_gui_children.items(): + if item_key == self.current_enum: + continue - for children_by_key in self.non_gui_children.values(): for child_obj in children_by_key.values(): - child_obj.set_override_state(state) + child_obj.set_override_state(state, True) self._update_current_metadata() From c1d6db4356e7258970dbffb9152a82f408bab025 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 29 Jun 2021 13:04:52 +0200 Subject: [PATCH 40/53] added few comments and docstring --- .../settings/entities/dict_conditional.py | 35 +++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index 112ef8bddc..6e28cbd591 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -73,6 +73,19 @@ example_schema = { class DictConditionalEntity(ItemEntity): + """Entity represents dictionay with only one persistent key definition. + + The persistent key is enumerator which define rest of children under + dictionary. There is not possibility of shared children. + + Entity's keys can't be removed or added. But they may change based on + the persistent key. If you're change value manually (key by key) make sure + you'll change value of the persistent key as first. It is recommended to + use `set` method which handle this for you. + + It is possible to use entity similar way as `dict` object. Returned values + are not real settings values but entities representing the value. + """ schema_types = ["dict-conditional"] _default_label_wrap = { "use_label_wrap": False, @@ -149,6 +162,7 @@ class DictConditionalEntity(ItemEntity): self._current_metadata = {} self._metadata_are_modified = False + # Entity must be group or in group if ( self.group_item is None and not self.is_dynamic_item @@ -176,15 +190,21 @@ class DictConditionalEntity(ItemEntity): @property def current_enum(self): + """Current value of enum entity. + + This value define what children are used. + """ if self.enum_entity is None: return None return self.enum_entity.value def schema_validations(self): """Validation of schema data.""" + # Enum key must be defined if self.enum_key is None: raise EntitySchemaError(self, "Key 'enum_key' is not set.") + # Validate type of enum children if not isinstance(self.enum_children, list): raise EntitySchemaError( self, "Key 'enum_children' must be a list. Got: {}".format( @@ -192,6 +212,7 @@ class DictConditionalEntity(ItemEntity): ) ) + # Without defined enum children entity has nothing to do if not self.enum_children: raise EntitySchemaError(self, ( "Key 'enum_children' have empty value. Entity can't work" @@ -219,6 +240,7 @@ class DictConditionalEntity(ItemEntity): raise SchemaDuplicatedKeys(self, key) children_def_keys.append(key) + # Validate key duplications per each enum item for children in self.children.values(): children_keys = set() children_keys.add(self.enum_key) @@ -230,6 +252,7 @@ class DictConditionalEntity(ItemEntity): else: raise SchemaDuplicatedKeys(self, child_entity.key) + # Validate all remaining keys with key regex for children_by_key in self.non_gui_children.values(): for key in children_by_key.keys(): if not KEY_REGEX.match(key): @@ -270,7 +293,8 @@ class DictConditionalEntity(ItemEntity): All children are stored by their enum item. """ - # Skip and wait for validation + # Skip if are not defined + # - schema validations should raise and exception if not self.enum_children or not self.enum_key: return @@ -288,6 +312,7 @@ class DictConditionalEntity(ItemEntity): if not enum_items: return + # Create Enum child first enum_key = self.enum_key or "invalid" enum_schema = { "type": "enum", @@ -300,9 +325,11 @@ class DictConditionalEntity(ItemEntity): enum_entity = self.create_schema_object(enum_schema, self) self.enum_entity = enum_entity + # Create children per each enum item for item in valid_enum_items: item_key = item["key"] - # Make sure all keys have set value in there variables + # Make sure all keys have set value in these variables + # - key 'children' is optional self.non_gui_children[item_key] = {} self.children[item_key] = [] self.gui_layout[item_key] = [] @@ -386,11 +413,15 @@ class DictConditionalEntity(ItemEntity): # Change has/had override states self._override_state = state + # Set override state on enum entity first self.enum_entity.set_override_state(state, ignore_missing_defaults) + # Set override state on other entities under current enum value for child_obj in self.non_gui_children[self.current_enum].values(): child_obj.set_override_state(state, ignore_missing_defaults) + # Set override state on other enum children + # - these must not raise exception about missing defaults for item_key, children_by_key in self.non_gui_children.items(): if item_key == self.current_enum: continue From 3b217c57a2c8b2069b4cf3e4f5a03acbe66e90ff Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 29 Jun 2021 13:05:02 +0200 Subject: [PATCH 41/53] added enum key validation --- openpype/settings/entities/dict_conditional.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index 6e28cbd591..96e6c518f3 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -252,6 +252,10 @@ class DictConditionalEntity(ItemEntity): else: raise SchemaDuplicatedKeys(self, child_entity.key) + # Enum key must match key regex + if not KEY_REGEX.match(self.enum_key): + raise InvalidKeySymbols(self.path, self.enum_key) + # Validate all remaining keys with key regex for children_by_key in self.non_gui_children.values(): for key in children_by_key.keys(): From 605a0454b2c69ce3e76a4df85eced6e0364ae47a Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 29 Jun 2021 13:05:20 +0200 Subject: [PATCH 42/53] include enum_key in builtin methods --- openpype/settings/entities/dict_conditional.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index 96e6c518f3..9ba24cf0de 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -95,11 +95,16 @@ class DictConditionalEntity(ItemEntity): def __getitem__(self, key): """Return entity inder key.""" + if key == self.enum_key: + return self.enum_entity return self.non_gui_children[self.current_enum][key] def __setitem__(self, key, value): """Set value of item under key.""" - child_obj = self.non_gui_children[self.current_enum][key] + if key == self.enum_key: + child_obj = self.enum_entity + else: + child_obj = self.non_gui_children[self.current_enum][key] child_obj.set(value) def __iter__(self): @@ -109,10 +114,14 @@ class DictConditionalEntity(ItemEntity): def __contains__(self, key): """Check if key is available.""" + if key == self.enum_key: + return True return key in self.non_gui_children[self.current_enum] def get(self, key, default=None): """Safe entity getter by key.""" + if key == self.enum_key: + return self.enum_entity return self.non_gui_children[self.current_enum].get(key, default) def keys(self): From 082e453d138055a86c25e8e2ad09060f28ff5d11 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 29 Jun 2021 14:24:08 +0200 Subject: [PATCH 43/53] fix variable name usage --- openpype/tools/settings/settings/item_widgets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/settings/settings/item_widgets.py b/openpype/tools/settings/settings/item_widgets.py index b23372e9ac..82afbb0a13 100644 --- a/openpype/tools/settings/settings/item_widgets.py +++ b/openpype/tools/settings/settings/item_widgets.py @@ -145,7 +145,7 @@ class DictImmutableKeysWidget(BaseWidget): self.content_widget = content_widget self.content_layout = content_layout - if len(self.input_fields) == 1 and self.checkbox_widget: + if len(self.input_fields) == 1 and self.checkbox_child: body_widget.hide_toolbox(hide_content=True) elif self.entity.collapsible: From 905db947bbf3ae5d53aade3c16f1b2de08c63def Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 29 Jun 2021 14:40:49 +0200 Subject: [PATCH 44/53] added dict-conditional to readme --- openpype/settings/entities/schemas/README.md | 97 ++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/openpype/settings/entities/schemas/README.md b/openpype/settings/entities/schemas/README.md index bbd53fa46b..3c360b892f 100644 --- a/openpype/settings/entities/schemas/README.md +++ b/openpype/settings/entities/schemas/README.md @@ -181,6 +181,103 @@ } ``` +## dict-conditional +- is similar to `dict` but has only one child entity that will be always available +- the one entity is enumerator of possible values and based on value of the entity are defined and used other children entities +- each value of enumerator have defined children that will be used + - there is no way how to have shared entities across multiple enum items +- value from enumerator is also stored next to other values + - to define the key under which will be enum value stored use `enum_key` + - `enum_key` must match key regex and any enum item can't have children with same key + - `enum_label` is label of the entity for UI purposes +- enum items are define with `enum_children` + - it's a list where each item represents enum item + - all items in `enum_children` must have at least `key` key which represents value stored under `enum_key` + - items can define `label` for UI purposes + - most important part is that item can define `children` key where are definitions of it's children (`children` value works the same way as in `dict`) +- entity must have defined `"label"` if is not used as widget +- is set as group if any parent is not group +- if `"label"` is entetered there which will be shown in GUI + - item with label can be collapsible + - that can be set with key `"collapsible"` as `True`/`False` (Default: `True`) + - with key `"collapsed"` as `True`/`False` can be set that is collapsed when GUI is opened (Default: `False`) + - it is possible to add darker background with `"highlight_content"` (Default: `False`) + - darker background has limits of maximum applies after 3-4 nested highlighted items there is not difference in the color + - output is dictionary `{the "key": children values}` +``` +# Example +{ + "type": "dict-conditional", + "key": "my_key", + "label": "My Key", + "enum_key": "type", + "enum_label": "label", + "enum_children": [ + # Each item must be a dictionary with 'key' + { + "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": [ + { + "key": "children", + "label": "Children", + "type": "list", + "object_type": "text" + } + ] + }, + { + # Separator does not have children as "separator" value is enough + "key": "separator", + "label": "Separator" + } + ] +} +``` + +How output of the schema could look like on save: +``` +{ + "type": "separator" +} + +{ + "type": "action", + "key": "action_1", + "label": "Action 1", + "command": "run command -arg" +} + +{ + "type": "menu", + "children": [ + "child_1", + "child_2" + ] +} +``` + ## Inputs for setting any kind of value (`Pure` inputs) - all these input must have defined `"key"` under which will be stored and `"label"` which will be shown next to input - unless they are used in different types of inputs (later) "as widgets" in that case `"key"` and `"label"` are not required as there is not place where to set them From df37c2e1fffbeea1baf66c5061ac2699ed9d5a7c Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 29 Jun 2021 14:50:07 +0200 Subject: [PATCH 45/53] removed example schema --- .../settings/entities/dict_conditional.py | 48 ------------------- 1 file changed, 48 deletions(-) diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index 9ba24cf0de..0e6540e606 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -24,54 +24,6 @@ from .exceptions import ( ) -example_schema = { - "type": "dict-conditional", - "key": "KEY", - "label": "LABEL", - "enum_key": "type", - "enum_label": "label", - "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": [ - { - "key": "children", - "label": "Children", - "type": "list", - "object_type": "text" - } - ] - }, - { - "key": "separator", - "label": "Separator" - } - ] -} - - class DictConditionalEntity(ItemEntity): """Entity represents dictionay with only one persistent key definition. From 672ee1d98db2a7adf341a63e635834ede87d139d Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 29 Jun 2021 16:57:01 +0200 Subject: [PATCH 46/53] store ignore_missing_defaults and reuse it on callbacks --- openpype/settings/entities/base_entity.py | 1 + openpype/settings/entities/dict_conditional.py | 1 + .../entities/dict_immutable_keys_entity.py | 1 + .../entities/dict_mutable_keys_entity.py | 18 ++++++++++++++---- openpype/settings/entities/input_entities.py | 1 + openpype/settings/entities/item_entities.py | 2 ++ openpype/settings/entities/list_entity.py | 18 ++++++++++++++---- 7 files changed, 34 insertions(+), 8 deletions(-) diff --git a/openpype/settings/entities/base_entity.py b/openpype/settings/entities/base_entity.py index e1cd5134e7..6c2f382403 100644 --- a/openpype/settings/entities/base_entity.py +++ b/openpype/settings/entities/base_entity.py @@ -136,6 +136,7 @@ class BaseItemEntity(BaseEntity): # Override state defines which values are used, saved and how. # TODO convert to private attribute self._override_state = OverrideState.NOT_DEFINED + self._ignore_missing_defaults = None # These attributes may change values during existence of an object # Default value, studio override values and project override values diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index 0e6540e606..33cedd7b54 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -377,6 +377,7 @@ class DictConditionalEntity(ItemEntity): # Change has/had override states self._override_state = state + self._ignore_missing_defaults = ignore_missing_defaults # Set override state on enum entity first self.enum_entity.set_override_state(state, ignore_missing_defaults) diff --git a/openpype/settings/entities/dict_immutable_keys_entity.py b/openpype/settings/entities/dict_immutable_keys_entity.py index 2802290e68..bde5304787 100644 --- a/openpype/settings/entities/dict_immutable_keys_entity.py +++ b/openpype/settings/entities/dict_immutable_keys_entity.py @@ -266,6 +266,7 @@ class DictImmutableKeysEntity(ItemEntity): # Change has/had override states self._override_state = state + self._ignore_missing_defaults = ignore_missing_defaults for child_obj in self.non_gui_children.values(): child_obj.set_override_state(state, ignore_missing_defaults) diff --git a/openpype/settings/entities/dict_mutable_keys_entity.py b/openpype/settings/entities/dict_mutable_keys_entity.py index a5734e36b8..c3df935269 100644 --- a/openpype/settings/entities/dict_mutable_keys_entity.py +++ b/openpype/settings/entities/dict_mutable_keys_entity.py @@ -154,7 +154,9 @@ class DictMutableKeysEntity(EndpointEntity): def add_key(self, key): new_child = self._add_key(key) - new_child.set_override_state(self._override_state) + new_child.set_override_state( + self._override_state, self._ignore_missing_defaults + ) self.on_change() return new_child @@ -328,6 +330,8 @@ class DictMutableKeysEntity(EndpointEntity): # TODO change metadata self._override_state = state + self._ignore_missing_defaults = ignore_missing_defaults + # Ignore if is dynamic item and use default in that case if not self.is_dynamic_item and not self.is_in_dynamic_item: if state > OverrideState.DEFAULTS: @@ -616,7 +620,9 @@ class DictMutableKeysEntity(EndpointEntity): if not self._can_discard_changes: return - self.set_override_state(self._override_state) + self.set_override_state( + self._override_state, self._ignore_missing_defaults + ) on_change_trigger.append(self.on_change) def _add_to_studio_default(self, _on_change_trigger): @@ -651,7 +657,9 @@ class DictMutableKeysEntity(EndpointEntity): if label: children_label_by_id[child_entity.id] = label - child_entity.set_override_state(self._override_state) + child_entity.set_override_state( + self._override_state, self._ignore_missing_defaults + ) self.children_label_by_id = children_label_by_id @@ -700,7 +708,9 @@ class DictMutableKeysEntity(EndpointEntity): if label: children_label_by_id[child_entity.id] = label - child_entity.set_override_state(self._override_state) + child_entity.set_override_state( + self._override_state, self._ignore_missing_defaults + ) self.children_label_by_id = children_label_by_id diff --git a/openpype/settings/entities/input_entities.py b/openpype/settings/entities/input_entities.py index 9b41a26bdb..2abb7a2253 100644 --- a/openpype/settings/entities/input_entities.py +++ b/openpype/settings/entities/input_entities.py @@ -224,6 +224,7 @@ class InputEntity(EndpointEntity): return self._override_state = state + self._ignore_missing_defaults = ignore_missing_defaults # Ignore if is dynamic item and use default in that case if not self.is_dynamic_item and not self.is_in_dynamic_item: if state > OverrideState.DEFAULTS: diff --git a/openpype/settings/entities/item_entities.py b/openpype/settings/entities/item_entities.py index c52eab988f..7e84f8c801 100644 --- a/openpype/settings/entities/item_entities.py +++ b/openpype/settings/entities/item_entities.py @@ -157,6 +157,7 @@ class PathEntity(ItemEntity): return self._override_state = state + self._ignore_missing_defaults = ignore_missing_defaults self.child_obj.set_override_state(state, ignore_missing_defaults) def update_default_value(self, value): @@ -351,6 +352,7 @@ class ListStrictEntity(ItemEntity): return self._override_state = state + self._ignore_missing_defaults = ignore_missing_defaults # Ignore if is dynamic item and use default in that case if not self.is_dynamic_item and not self.is_in_dynamic_item: if state > OverrideState.DEFAULTS: diff --git a/openpype/settings/entities/list_entity.py b/openpype/settings/entities/list_entity.py index 2225523792..64bbad28a7 100644 --- a/openpype/settings/entities/list_entity.py +++ b/openpype/settings/entities/list_entity.py @@ -102,7 +102,9 @@ class ListEntity(EndpointEntity): def add_new_item(self, idx=None, trigger_change=True): child_obj = self._add_new_item(idx) - child_obj.set_override_state(self._override_state) + child_obj.set_override_state( + self._override_state, self._ignore_missing_defaults + ) if trigger_change: self.on_child_change(child_obj) @@ -212,6 +214,7 @@ class ListEntity(EndpointEntity): return self._override_state = state + self._ignore_missing_defaults = ignore_missing_defaults while self.children: self.children.pop(0) @@ -403,7 +406,9 @@ class ListEntity(EndpointEntity): if self.had_studio_override: child_obj.update_studio_value(item) - child_obj.set_override_state(self._override_state) + child_obj.set_override_state( + self._override_state, self._ignore_missing_defaults + ) if self._override_state >= OverrideState.PROJECT: self._has_project_override = self.had_project_override @@ -435,7 +440,9 @@ class ListEntity(EndpointEntity): for item in value: child_obj = self._add_new_item() child_obj.update_default_value(item) - child_obj.set_override_state(self._override_state) + child_obj.set_override_state( + self._override_state, self._ignore_missing_defaults + ) self._ignore_child_changes = False @@ -468,7 +475,10 @@ class ListEntity(EndpointEntity): child_obj.update_default_value(item) if self._has_studio_override: child_obj.update_studio_value(item) - child_obj.set_override_state(self._override_state) + child_obj.set_override_state( + self._override_state, + self._ignore_missing_defaults + ) self._ignore_child_changes = False From 477b4ecfcc8d421fd4334c87666d61767e049371 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Tue, 29 Jun 2021 17:19:13 +0200 Subject: [PATCH 47/53] removed unused imports --- openpype/settings/entities/dict_conditional.py | 3 --- openpype/tools/settings/settings/dict_conditional.py | 3 +-- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index 33cedd7b54..c115cac18a 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -1,8 +1,6 @@ import copy -import collections from .lib import ( - WRAPPER_TYPES, OverrideState, NOT_SET ) @@ -14,7 +12,6 @@ from openpype.settings.constants import ( from . import ( BaseItemEntity, ItemEntity, - BoolEntity, GUIEntity ) from .exceptions import ( diff --git a/openpype/tools/settings/settings/dict_conditional.py b/openpype/tools/settings/settings/dict_conditional.py index 84288f7b5b..da2f53497e 100644 --- a/openpype/tools/settings/settings/dict_conditional.py +++ b/openpype/tools/settings/settings/dict_conditional.py @@ -1,5 +1,4 @@ -import collections -from Qt import QtWidgets, QtCore, QtGui +from Qt import QtWidgets from .widgets import ( ExpandingWidget, From ca7d91af622636b71adb4da42694b79ab8377409 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 30 Jun 2021 13:53:15 +0200 Subject: [PATCH 48/53] fix typos --- openpype/settings/entities/lib.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/settings/entities/lib.py b/openpype/settings/entities/lib.py index 42a08232b9..faaacd4230 100644 --- a/openpype/settings/entities/lib.py +++ b/openpype/settings/entities/lib.py @@ -147,7 +147,7 @@ class SchemasHub: crashed_item = self._crashed_on_load[schema_name] raise KeyError( "Unable to parse schema file \"{}\". {}".format( - crashed_item["filpath"], crashed_item["message"] + crashed_item["filepath"], crashed_item["message"] ) ) @@ -177,7 +177,7 @@ class SchemasHub: crashed_item = self._crashed_on_load[template_name] raise KeyError( "Unable to parse templace file \"{}\". {}".format( - crashed_item["filpath"], crashed_item["message"] + crashed_item["filepath"], crashed_item["message"] ) ) @@ -345,7 +345,7 @@ class SchemasHub: " One of them crashed on load \"{}\" {}" ).format( filename, - crashed_item["filpath"], + crashed_item["filepath"], crashed_item["message"] )) From 898157c95be9205d77934a03d5c02a7b658e3978 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 7 Jul 2021 19:28:12 +0200 Subject: [PATCH 49/53] fixed typo --- openpype/settings/entities/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/settings/entities/lib.py b/openpype/settings/entities/lib.py index faaacd4230..e58281644a 100644 --- a/openpype/settings/entities/lib.py +++ b/openpype/settings/entities/lib.py @@ -176,7 +176,7 @@ class SchemasHub: elif template_name in self._crashed_on_load: crashed_item = self._crashed_on_load[template_name] raise KeyError( - "Unable to parse templace file \"{}\". {}".format( + "Unable to parse template file \"{}\". {}".format( crashed_item["filepath"], crashed_item["message"] ) ) From ec01e148e56cc17b3b50dfe811e5cf27d4be07ef Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 7 Jul 2021 19:36:32 +0200 Subject: [PATCH 50/53] added missing attributes --- openpype/settings/entities/dict_conditional.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index c115cac18a..641986491c 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -144,6 +144,11 @@ class DictConditionalEntity(ItemEntity): self.enum_entity = None + self.highlight_content = self.schema_data.get( + "highlight_content", False + ) + self.show_borders = self.schema_data.get("show_borders", True) + self._add_children() @property From 1cb8d0f5e8b64f9d6990deebb103ddbf920eb987 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 7 Jul 2021 19:37:43 +0200 Subject: [PATCH 51/53] added example of conditional dictionary --- .../schemas/system_schema/example_schema.json | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/openpype/settings/entities/schemas/system_schema/example_schema.json b/openpype/settings/entities/schemas/system_schema/example_schema.json index a4ed56df32..c3287d7452 100644 --- a/openpype/settings/entities/schemas/system_schema/example_schema.json +++ b/openpype/settings/entities/schemas/system_schema/example_schema.json @@ -9,6 +9,54 @@ "label": "Color input", "type": "color" }, + { + "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": [ + { + "key": "children", + "label": "Children", + "type": "list", + "object_type": "text" + } + ] + }, + { + "key": "separator", + "label": "Separator" + } + ] + }, { "type": "dict", "key": "schema_template_exaples", From 54eb42f16ac024723270a3468fe72d03274b5b19 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 8 Jul 2021 14:43:00 +0200 Subject: [PATCH 52/53] fix ignoring of missing defaults --- openpype/settings/entities/dict_conditional.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index 641986491c..1ffc7ab450 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -384,16 +384,9 @@ class DictConditionalEntity(ItemEntity): # Set override state on enum entity first self.enum_entity.set_override_state(state, ignore_missing_defaults) - # Set override state on other entities under current enum value - for child_obj in self.non_gui_children[self.current_enum].values(): - child_obj.set_override_state(state, ignore_missing_defaults) - # Set override state on other enum children # - these must not raise exception about missing defaults for item_key, children_by_key in self.non_gui_children.items(): - if item_key == self.current_enum: - continue - for child_obj in children_by_key.values(): child_obj.set_override_state(state, True) From 696c72c34cc555a7af365c4f194a7f510b1151a7 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 8 Jul 2021 14:44:34 +0200 Subject: [PATCH 53/53] remove unusued variable --- openpype/settings/entities/dict_conditional.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/settings/entities/dict_conditional.py b/openpype/settings/entities/dict_conditional.py index 1ffc7ab450..96065b670e 100644 --- a/openpype/settings/entities/dict_conditional.py +++ b/openpype/settings/entities/dict_conditional.py @@ -386,7 +386,7 @@ class DictConditionalEntity(ItemEntity): # Set override state on other enum children # - these must not raise exception about missing defaults - for item_key, children_by_key in self.non_gui_children.items(): + for children_by_key in self.non_gui_children.values(): for child_obj in children_by_key.values(): child_obj.set_override_state(state, True)