ayon-core/openpype/settings/entities/item_entities.py

457 lines
14 KiB
Python

from .lib import (
NOT_SET,
STRING_TYPE,
OverrideState
)
from .exceptions import (
DefaultsNotDefined,
StudioDefaultsNotDefined,
EntitySchemaError
)
from .base_entity import ItemEntity
class PathEntity(ItemEntity):
schema_types = ["path"]
platforms = ("windows", "darwin", "linux")
platform_labels_mapping = {
"windows": "Windows",
"darwin": "MacOS",
"linux": "Linux"
}
path_item_type_error = "Got invalid path value type {}. Expected: {}"
attribute_error_msg = (
"'PathEntity' has no attribute '{}' if is not set as multiplatform"
)
def __setitem__(self, *args, **kwargs):
return self.child_obj.__setitem__(*args, **kwargs)
def __getitem__(self, *args, **kwargs):
return self.child_obj.__getitem__(*args, **kwargs)
def __iter__(self):
return self.child_obj.__iter__()
def keys(self):
if not self.multiplatform:
raise AttributeError(self.attribute_error_msg.format("keys"))
return self.child_obj.keys()
def values(self):
if not self.multiplatform:
raise AttributeError(self.attribute_error_msg.format("values"))
return self.child_obj.values()
def items(self):
if not self.multiplatform:
raise AttributeError(self.attribute_error_msg.format("items"))
return self.child_obj.items()
def _item_initalization(self):
if self.group_item is None and not self.is_group:
self.is_group = True
self.multiplatform = self.schema_data.get("multiplatform", False)
self.multipath = self.schema_data.get("multipath", False)
# Create child object
if not self.multiplatform and not self.multipath:
valid_value_types = (STRING_TYPE, )
item_schema = {
"type": "path-input",
"key": self.key
}
elif not self.multiplatform:
valid_value_types = (list, )
item_schema = {
"type": "list",
"key": self.key,
"object_type": "path-input"
}
else:
valid_value_types = (dict, )
item_schema = {
"type": "dict",
"key": self.key,
"show_borders": False,
"children": []
}
for platform_key in self.platforms:
platform_label = self.platform_labels_mapping[platform_key]
child_item = {
"key": platform_key,
"label": platform_label
}
if self.multipath:
child_item["type"] = "list"
child_item["object_type"] = "path-input"
else:
child_item["type"] = "path-input"
item_schema["children"].append(child_item)
self.valid_value_types = valid_value_types
self.child_obj = self.create_schema_object(item_schema, self)
def get_child_path(self, _child_obj):
return self.path
def set(self, value):
self.child_obj.set(value)
def settings_value(self):
if self._override_state is OverrideState.NOT_DEFINED:
return NOT_SET
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
return self.child_obj.settings_value()
def on_change(self):
for callback in self.on_change_callbacks:
callback()
self.parent.on_child_change(self)
def on_child_change(self, _child_obj):
self.on_change()
@property
def has_unsaved_changes(self):
return self.child_obj.has_unsaved_changes
@property
def has_studio_override(self):
return self.child_obj.has_studio_override
@property
def has_project_override(self):
return self.child_obj.has_project_override
@property
def value(self):
return self.child_obj.value
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
self._override_state = state
self.child_obj.set_override_state(state)
def update_default_value(self, value):
self.child_obj.update_default_value(value)
def update_project_value(self, value):
self.child_obj.update_project_value(value)
def update_studio_value(self, value):
self.child_obj.update_studio_value(value)
def _discard_changes(self, *args, **kwargs):
self.child_obj.discard_changes(*args, **kwargs)
def _add_to_studio_default(self, *args, **kwargs):
self.child_obj.add_to_studio_default(*args, **kwargs)
def _remove_from_studio_default(self, *args, **kwargs):
self.child_obj.remove_from_studio_default(*args, **kwargs)
def _add_to_project_override(self, *args, **kwargs):
self.child_obj.add_to_project_override(*args, **kwargs)
def _remove_from_project_override(self, *args, **kwargs):
self.child_obj.remove_from_project_override(*args, **kwargs)
def reset_callbacks(self):
super(PathEntity, self).reset_callbacks()
self.child_obj.reset_callbacks()
class ListStrictEntity(ItemEntity):
schema_types = ["list-strict"]
def _item_initalization(self):
self.valid_value_types = (list, )
self.require_key = True
self.initial_value = None
self._ignore_child_changes = False
# Child items
self.object_types = self.schema_data["object_types"]
self.children = []
for children_schema in self.object_types:
child_obj = self.create_schema_object(children_schema, self, True)
self.children.append(child_obj)
# GUI attribute
self.is_horizontal = self.schema_data.get("horizontal", True)
if self.group_item is None and not self.is_group:
self.is_group = True
def schema_validations(self):
# List entity must have file parent.
if not self.file_item and not self.is_file:
raise EntitySchemaError(
self, "Missing file entity in hierarchy."
)
super(ListStrictEntity, self).schema_validations()
def get_child_path(self, child_obj):
result_idx = None
for idx, _child_obj in enumerate(self.children):
if _child_obj is child_obj:
result_idx = idx
break
if result_idx is None:
raise ValueError("Didn't found child {}".format(child_obj))
return "/".join([self.path, str(result_idx)])
@property
def value(self):
output = []
for child_obj in self.children:
output.append(child_obj.value)
return output
def set(self, value):
new_value = self.convert_to_valid_type(value)
for idx, item in enumerate(new_value):
self.children[idx].set(item)
def settings_value(self):
if self._override_state is OverrideState.NOT_DEFINED:
return NOT_SET
if (
self.is_group
and self._override_state is not OverrideState.DEFAULTS
):
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 = []
for child_obj in self.children:
output.append(child_obj.settings_value())
return output
def on_change(self):
for callback in self.on_change_callbacks:
callback()
self.parent.on_child_change(self)
def on_child_change(self, _child_obj):
if self._ignore_child_changes:
return
if self._override_state is OverrideState.STUDIO:
self._has_studio_override = self._child_has_studio_override
elif self._override_state is OverrideState.PROJECT:
self._has_project_override = self._child_has_project_override
self.on_change()
@property
def has_unsaved_changes(self):
if self._override_state is OverrideState.NOT_DEFINED:
return False
if self._override_state is OverrideState.DEFAULTS:
if not self.has_default_value:
return True
elif self._override_state is OverrideState.STUDIO:
if self.had_studio_override != self._has_studio_override:
return True
if not self._has_studio_override and not self.has_default_value:
return True
elif self._override_state is OverrideState.PROJECT:
if self.had_project_override != self._has_project_override:
return True
if (
not self._has_project_override
and not self._has_studio_override
and not self.has_default_value
):
return True
if self._child_has_unsaved_changes:
return True
if self.settings_value() != self.initial_value:
return True
return False
@property
def has_studio_override(self):
return self._has_studio_override or self._child_has_studio_override
@property
def has_project_override(self):
return self._has_project_override or self._child_has_project_override
@property
def _child_has_unsaved_changes(self):
for child_obj in self.children:
if child_obj.has_unsaved_changes:
return True
return False
@property
def _child_has_studio_override(self):
for child_obj in self.children:
if child_obj.has_studio_override:
return True
return False
@property
def _child_has_project_override(self):
for child_obj in self.children:
if child_obj.has_project_override:
return True
return False
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
self._override_state = state
# 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:
raise DefaultsNotDefined(self)
elif state > OverrideState.STUDIO:
if not self.had_studio_override:
raise StudioDefaultsNotDefined(self)
for child_entity in self.children:
child_entity.set_override_state(state)
self.initial_value = self.settings_value()
def _discard_changes(self, on_change_trigger):
for child_obj in self.children:
child_obj.discard_changes(on_change_trigger)
def _add_to_studio_default(self, _on_change_trigger):
self._has_studio_override = True
self.on_change()
def _remove_from_studio_default(self, on_change_trigger):
self._ignore_child_changes = True
for child_obj in self.children:
child_obj.remove_from_studio_default(on_change_trigger)
self._ignore_child_changes = False
self._has_studio_override = False
def _add_to_project_override(self, _on_change_trigger):
self._has_project_override = True
self.on_change()
def _remove_from_project_override(self, on_change_trigger):
self._ignore_child_changes = True
for child_obj in self.children:
child_obj.remove_from_project_override(on_change_trigger)
self._ignore_child_changes = False
self._has_project_override = False
def _check_update_value(self, value, value_type):
value = super(ListStrictEntity, self)._check_update_value(
value, value_type
)
if value is NOT_SET:
return value
child_len = len(self.children)
value_len = len(value)
if value_len == child_len:
return value
self.log.warning(
(
"{} Amount of strict list items in {} values is"
" not same as expected. Expected {} items. Got {} items. {}"
).format(
self.path, value_type,
child_len, value_len, str(value)
)
)
if value_len < child_len:
# Fill missing values with NOT_SET
for _ in range(child_len - value_len):
value.append(NOT_SET)
else:
# Pop values that are overloaded
for _ in range(value_len - child_len):
value.pop(child_len)
return value
def update_default_value(self, value):
value = self._check_update_value(value, "default")
self.has_default_value = value is not NOT_SET
if value is NOT_SET:
for child_obj in self.children:
child_obj.update_default_value(value)
else:
for idx, item_value in enumerate(value):
self.children[idx].update_default_value(item_value)
def update_studio_value(self, value):
value = self._check_update_value(value, "studio override")
if value is NOT_SET:
for child_obj in self.children:
child_obj.update_studio_value(value)
else:
for idx, item_value in enumerate(value):
self.children[idx].update_studio_value(item_value)
def update_project_value(self, value):
value = self._check_update_value(value, "project override")
if value is NOT_SET:
for child_obj in self.children:
child_obj.update_project_value(value)
else:
for idx, item_value in enumerate(value):
self.children[idx].update_project_value(item_value)
def reset_callbacks(self):
super(ListStrictEntity, self).reset_callbacks()
for child_obj in self.children:
child_obj.reset_callbacks()