Merge pull request #1028 from pypeclub/feature/project_settings_to_entities

Project settings to entities
This commit is contained in:
Milan Kolar 2021-02-18 16:48:10 +01:00 committed by GitHub
commit c236cea8b8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
77 changed files with 2112 additions and 5988 deletions

View file

@ -5,7 +5,8 @@ from .settings import (
get_anatomy_settings,
get_environments,
SystemSettings
SystemSettings,
ProjectSettings
)
from .lib import (
PypeLogger,

View file

@ -5,7 +5,10 @@ from .lib import (
get_anatomy_settings,
get_environments
)
from .entities import SystemSettings
from .entities import (
SystemSettings,
ProjectSettings
)
__all__ = (
@ -15,5 +18,6 @@ __all__ = (
"get_anatomy_settings",
"get_environments",
"SystemSettings"
"SystemSettings",
"ProjectSettings"
)

View file

@ -1,39 +0,0 @@
{
"object_types": ["Milestone", "Task", "Folder", "Asset Build", "Shot", "Library", "Sequence"],
"version_workflow": ["Pending Review", "Client Review", "On Farm", "Reviewed", "Render Complete", "Approved", "CBB", "Delivered", "Render Failed", "data"],
"task_workflow": ["Not Ready", "Ready", "Change Requested", "In progress", "Pending Review", "On Farm", "Waiting", "Render Complete", "Complete", "CBB", "On Hold", "Render Failed", "Omitted"],
"overrides": [{
"task_types": ["Animation"],
"statuses": ["Not Ready", "Ready", "Change Requested", "Blocking", "Animating", "blocking review", "anim review", "Complete", "CBB", "On Hold", "Omitted"]
}, {
"task_types": ["Lighting"],
"statuses": ["Not Ready", "Ready", "Change Requested", "In progress", "To render", "On Farm", "Render Complete", "Complete", "CBB", "On Hold", "Render Failed", "Omitted"]
}],
"task_type_schema": ["Layout", "Animation", "Modeling", "Previz", "Lookdev", "FX", "Lighting", "Compositing", "Rigging", "Texture", "Matte-paint", "Roto-paint", "Art", "Match-moving", "Production", "Build", "Setdress", "Edit", "R&D", "Boards"],
"schemas": [{
"object_type": "Shot",
"statuses": ["Omitted", "Normal", "Complete"],
"task_types": []
}, {
"object_type": "Asset Build",
"statuses": ["Omitted", "Normal", "Complete"],
"task_types": ["Setups", "Sets", "Characters", "Props", "Locations", "Assembly", "R&D", "Elements"]
}, {
"object_type": "Milestone",
"statuses": ["Normal", "Complete"],
"task_types": ["Generic"]
}],
"task_templates": [{
"name": "Character",
"task_types": ["Art", "Modeling", "Lookdev", "Rigging"]
}, {
"name": "Element",
"task_types": ["Modeling", "Lookdev"]
}, {
"name": "Prop",
"task_types": ["Modeling", "Lookdev", "Rigging"]
}, {
"name": "Location",
"task_types": ["Layout", "Setdress"]
}]
}

View file

@ -10,18 +10,6 @@
}
},
"create": {
"__dynamic_keys_labels__": {
"create_workfile": "Workfile",
"create_model": "Model",
"create_rig": "Rig",
"create_pointcache": "Pointcache",
"create_plate": "Plate",
"create_camera": "Camera",
"create_editorial": "Editorial",
"create_image": "Image",
"create_matchmove": "Matchmove",
"": ""
},
"create_workfile": {
"name": "workfile",
"label": "Workfile",
@ -121,6 +109,17 @@
"Mocap"
],
"help": "Script exported from matchmoving application"
},
"__dynamic_keys_labels__": {
"create_workfile": "Workfile",
"create_model": "Model",
"create_rig": "Rig",
"create_pointcache": "Pointcache",
"create_plate": "Plate",
"create_camera": "Camera",
"create_editorial": "Editorial",
"create_image": "Image",
"create_matchmove": "Matchmove"
}
}
}

View file

@ -55,6 +55,7 @@ print(system_settings["general"]["studio_name"].value)
from .exceptions import (
DefaultsNotDefined,
StudioDefaultsNotDefined,
InvalidValueType,
SchemaMissingFileInfo,
SchemeGroupHierarchyBug,
@ -73,7 +74,10 @@ from .base_entity import (
ItemEntity
)
from .root_entities import SystemSettings
from .root_entities import (
SystemSettings,
ProjectSettings
)
from .item_entities import (
PathEntity,
@ -96,9 +100,16 @@ from .list_entity import ListEntity
from .dict_immutable_keys_entity import DictImmutableKeysEntity
from .dict_mutable_keys_entity import DictMutableKeysEntity
from .anatomy_entities import (
AnatomyEntity,
AnatomyRootsEntity,
AnatomyTemplatesEntity
)
__all__ = (
"DefaultsNotDefined",
"StudioDefaultsNotDefined",
"InvalidValueType",
"SchemaMissingFileInfo",
"SchemeGroupHierarchyBug",
@ -115,6 +126,7 @@ __all__ = (
"ItemEntity",
"SystemSettings",
"ProjectSettings",
"PathEntity",
"ListStrictEntity",
@ -133,5 +145,9 @@ __all__ = (
"DictImmutableKeysEntity",
"DictMutableKeysEntity"
"DictMutableKeysEntity",
"AnatomyEntity",
"AnatomyRootsEntity",
"AnatomyTemplatesEntity"
)

View file

@ -0,0 +1,102 @@
from .dict_immutable_keys_entity import DictImmutableKeysEntity
from .dict_mutable_keys_entity import DictMutableKeysEntity
class AnatomyEntity(DictImmutableKeysEntity):
schema_types = ["anatomy"]
def _item_initalization(self):
self._roots_entity = None
self._templates_entity = None
super(AnatomyEntity, self)._item_initalization()
@property
def roots_entity(self):
if self._roots_entity is None:
_roots_entity = None
for child_entity in self.non_gui_children.values():
if isinstance(child_entity, AnatomyRootsEntity):
_roots_entity = child_entity
break
if _roots_entity is None:
raise KeyError(
"AnatomyEntity does not contain AnatomyRootsEntity"
)
self._roots_entity = _roots_entity
return self._roots_entity
@property
def templates_entity(self):
if self._templates_entity is None:
_templates_entity = None
for child_entity in self.non_gui_children.values():
if isinstance(child_entity, AnatomyTemplatesEntity):
_templates_entity = child_entity
break
if _templates_entity is None:
raise KeyError(
"AnatomyEntity does not contain AnatomyRootsEntity"
)
self._templates_entity = _templates_entity
return self._templates_entity
class AnatomyRootsEntity(DictMutableKeysEntity):
schema_types = ["anatomy_roots"]
def schema_validations(self):
if not isinstance(self.parent, AnatomyEntity):
raise TypeError("Parent of {} is not AnatomyEntity object".format(
self.__class__.__name__
))
super(AnatomyRootsEntity, self).schema_validations()
@property
def has_studio_override(self):
output = super(AnatomyRootsEntity, self).has_studio_override
if not output:
output = self.parent.templates_entity._child_has_studio_override
return output
@property
def has_project_override(self):
output = super(AnatomyRootsEntity, self).has_project_override
if not output:
output = self.parent.templates_entity._child_has_project_override
return output
class AnatomyTemplatesEntity(DictImmutableKeysEntity):
schema_types = ["anatomy_templates"]
def schema_validations(self):
if not isinstance(self.parent, AnatomyEntity):
raise TypeError("Parent of {} is not AnatomyEntity object".format(
self.__class__.__name__
))
super(AnatomyTemplatesEntity, self).schema_validations()
@property
def has_studio_override(self):
output = super(AnatomyTemplatesEntity, self).has_studio_override
if not output:
output = (
self.parent.roots_entity._has_studio_override
or self.parent.roots_entity._child_has_studio_override
)
return output
@property
def has_project_override(self):
output = super(AnatomyTemplatesEntity, self).has_project_override
if not output:
output = (
self.parent.roots_entity._has_project_override
or self.parent.roots_entity._child_has_project_override
)
return output

View file

@ -57,7 +57,7 @@ class GUIEntity(BaseEntity):
"""Entity without any specific logic that should be handled only in GUI."""
gui_type = True
schema_types = ["divider", "splitter", "label"]
schema_types = ["separator", "splitter", "label"]
def __getitem__(self, key):
return self.schema_data[key]
@ -247,6 +247,14 @@ class BaseItemEntity(BaseEntity):
"{}: Item has set label but is used as dynamic item."
).format(self.path))
# Dynamic items or items in dynamic item must not have set `is_group`
if self.is_group and (self.is_dynamic_item or self.is_in_dynamic_item):
raise ValueError(
"{} Dynamic entity has set `is_group` to true.".format(
self.path
)
)
@abstractmethod
def set_override_state(self, state):
"""Set override state and trigger it on children.
@ -409,6 +417,94 @@ class BaseItemEntity(BaseEntity):
"""Value of entity without metadata."""
pass
@property
def can_discard_changes(self):
"""Result defines if `discard_changes` will be processed.
Also can be used as validation before the method is called.
"""
return self.has_unsaved_changes
@property
def can_add_to_studio_default(self):
"""Result defines if `add_to_studio_default` will be processed.
Also can be used as validation before the method is called.
"""
if self._override_state is not OverrideState.STUDIO:
return False
if self.is_dynamic_item or self.is_in_dynamic_item:
return False
# Skip if entity is under group
if self.group_item:
return False
# Skip if is group and any children is already marked with studio
# overrides
if self.is_group and self.has_studio_override:
return False
return True
@property
def can_remove_from_studio_default(self):
"""Result defines if `remove_from_studio_default` can be triggered.
This can be also used as validation before the method is called.
"""
if self._override_state is not OverrideState.STUDIO:
return False
if self.is_dynamic_item or self.is_in_dynamic_item:
return False
if not self.has_studio_override:
return False
return True
@property
def can_add_to_project_override(self):
"""Result defines if `add_to_project_override` can be triggered.
Also can be used as validation before the method is called.
"""
if self.is_dynamic_item or self.is_in_dynamic_item:
return False
# Show only when project overrides are set
if self._override_state is not OverrideState.PROJECT:
return False
# Do not show on items under group item
if self.group_item:
return False
# Skip if already is marked to save project overrides
if self.is_group and self.has_project_override:
return False
return True
@property
def can_remove_from_project_override(self):
"""Result defines if `remove_from_project_override` can be triggered.
This can be also used as validation before the method is called.
"""
if self.is_dynamic_item or self.is_in_dynamic_item:
return False
if self._override_state is not OverrideState.PROJECT:
return False
# Dynamic items can't have these actions
if self.is_dynamic_item or self.is_in_dynamic_item:
return False
if not self.has_project_override:
return False
return True
def discard_changes(self, on_change_trigger=None):
"""Discard changes on entity and it's children.
@ -433,6 +529,9 @@ class BaseItemEntity(BaseEntity):
"""
initialized = False
if on_change_trigger is None:
if not self.can_discard_changes:
return
initialized = True
on_change_trigger = []
@ -447,8 +546,23 @@ class BaseItemEntity(BaseEntity):
"""Entity's implementation to discard all changes made by user."""
pass
def add_to_studio_default(self, on_change_trigger=None):
initialized = False
if on_change_trigger is None:
if not self.can_add_to_studio_default:
return
initialized = True
on_change_trigger = []
self._add_to_studio_default(on_change_trigger)
if initialized:
for callback in on_change_trigger:
callback()
@abstractmethod
def add_to_studio_default(self):
def _add_to_studio_default(self, on_change_trigger):
"""Item's implementation to set current values as studio's overrides.
Mark item and it's children as they have studio overrides.
@ -470,11 +584,11 @@ class BaseItemEntity(BaseEntity):
on_change_trigger (list): Callbacks of `on_change` should be stored
to trigger them afterwards.
"""
if self._override_state is not OverrideState.STUDIO:
return
initialized = False
if on_change_trigger is None:
if not self.can_remove_from_studio_default:
return
initialized = True
on_change_trigger = []
@ -493,6 +607,29 @@ class BaseItemEntity(BaseEntity):
"""
pass
def add_to_project_override(self, on_change_trigger=None):
initialized = False
if on_change_trigger is None:
if not self.can_add_to_project_override:
return
initialized = True
on_change_trigger = []
self._add_to_project_override(on_change_trigger)
if initialized:
for callback in on_change_trigger:
callback()
@abstractmethod
def _add_to_project_override(self, on_change_trigger):
"""Item's implementation to set values as overriden for project.
Mark item and all it's children to be stored as project overrides.
"""
pass
def remove_from_project_override(self, on_change_trigger=None):
"""Remove project overrides from entity and it's children.
@ -513,6 +650,8 @@ class BaseItemEntity(BaseEntity):
initialized = False
if on_change_trigger is None:
if not self.can_remove_from_project_override:
return
initialized = True
on_change_trigger = []
@ -522,14 +661,6 @@ class BaseItemEntity(BaseEntity):
for callback in on_change_trigger:
callback()
@abstractmethod
def add_to_project_override(self):
"""Item's implementation to set values as overriden for project.
Mark item and all it's children to be stored as project overrides.
"""
pass
@abstractmethod
def _remove_from_project_override(self, on_change_trigger):
"""Item's implementation to remove project overrides.
@ -630,7 +761,7 @@ class ItemEntity(BaseItemEntity):
pass
@abstractmethod
def update_studio_values(self, parent_values):
def update_studio_value(self, parent_values):
"""Fill studio override values on startup or on refresh.
Set studio value if is not set to NOT_SET, in that case studio
@ -643,7 +774,7 @@ class ItemEntity(BaseItemEntity):
pass
@abstractmethod
def update_project_values(self, parent_values):
def update_project_value(self, parent_values):
"""Fill project override values on startup, refresh or project change.
Set project value if is not set to NOT_SET, in that case project

View file

@ -169,7 +169,7 @@ class DictImmutableKeysEntity(ItemEntity):
"highlight_content", False
)
self.show_borders = self.schema_data.get("show_borders", True)
self.collapsible = self.schema_data.get("collapsable", True)
self.collapsible = self.schema_data.get("collapsible", True)
self.collapsed = self.schema_data.get("collapsed", True)
# Not yet implemented
@ -192,22 +192,6 @@ class DictImmutableKeysEntity(ItemEntity):
return "/".join([self.path, result_key])
def _update_current_metadata(self):
# Define if current metadata are avaialble for current override state
metadata = NOT_SET
if self._override_state is OverrideState.DEFAULTS:
metadata = {}
if self._override_state is OverrideState.PROJECT:
# metadata are NOT_SET if project overrides do not override this
# item
metadata = self._project_override_metadata
if (
self._override_state >= OverrideState.STUDIO
and metadata is NOT_SET
):
metadata = self._studio_override_metadata
current_metadata = {}
for key, child_obj in self.non_gui_children.items():
if self._override_state is OverrideState.DEFAULTS:
@ -232,10 +216,18 @@ class DictImmutableKeysEntity(ItemEntity):
current_metadata[M_OVERRIDEN_KEY] = []
current_metadata[M_OVERRIDEN_KEY].append(key)
if metadata is NOT_SET and not current_metadata:
self._metadata_are_modified = False
else:
self._metadata_are_modified = current_metadata != metadata
# 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):
@ -246,24 +238,6 @@ class DictImmutableKeysEntity(ItemEntity):
# Change has/had override states
self._override_state = state
if state is OverrideState.NOT_DEFINED:
pass
elif state is OverrideState.DEFAULTS:
self.has_default_value = self._default_value is not NOT_SET
elif state is OverrideState.STUDIO:
self.had_studio_override = (
self._studio_override_metadata is not NOT_SET
)
self._has_studio_override = self.had_studio_override
elif state is OverrideState.PROJECT:
self._has_studio_override = self.had_studio_override
self.had_project_override = (
self._project_override_metadata is not NOT_SET
)
self._has_project_override = self.had_project_override
for child_obj in self.non_gui_children.values():
child_obj.set_override_state(state)
@ -282,18 +256,6 @@ class DictImmutableKeysEntity(ItemEntity):
if self._metadata_are_modified:
return True
if (
self._override_state is OverrideState.PROJECT
and self._has_project_override != self.had_project_override
):
return True
elif (
self._override_state is OverrideState.STUDIO
and self._has_studio_override != self.had_studio_override
):
return True
return self._child_has_unsaved_changes
@property
@ -305,7 +267,7 @@ class DictImmutableKeysEntity(ItemEntity):
@property
def has_studio_override(self):
return self._has_studio_override or self._child_has_studio_override
return self._child_has_studio_override
@property
def _child_has_studio_override(self):
@ -317,7 +279,7 @@ class DictImmutableKeysEntity(ItemEntity):
@property
def has_project_override(self):
return self._has_project_override or self._child_has_project_override
return self._child_has_project_override
@property
def _child_has_project_override(self):
@ -345,10 +307,10 @@ class DictImmutableKeysEntity(ItemEntity):
if self.is_group:
if self._override_state is OverrideState.STUDIO:
if not self._has_studio_override:
if not self.has_studio_override:
return NOT_SET
elif self._override_state is OverrideState.PROJECT:
if not self._has_project_override:
if not self.has_project_override:
return NOT_SET
output = {}
@ -396,7 +358,8 @@ class DictImmutableKeysEntity(ItemEntity):
unknown_keys = value_keys - expected_keys
if unknown_keys:
self.log.warning(
"Unknown keys in default values: {}".format(
"{} Unknown keys in default values: {}".format(
self.path,
", ".join("\"{}\"".format(key) for key in unknown_keys)
)
)
@ -405,7 +368,7 @@ class DictImmutableKeysEntity(ItemEntity):
child_value = value.get(key, NOT_SET)
child_obj.update_default_value(child_value)
def update_studio_values(self, value):
def update_studio_value(self, value):
"""Update studio override values.
Not an api method, should be called by parent.
@ -413,10 +376,11 @@ class DictImmutableKeysEntity(ItemEntity):
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:
for child_obj in self.non_gui_children.values():
child_obj.update_studio_values(value)
child_obj.update_studio_value(value)
return
value_keys = set(value.keys())
@ -424,15 +388,16 @@ class DictImmutableKeysEntity(ItemEntity):
unknown_keys = value_keys - expected_keys
if unknown_keys:
self.log.warning(
"Unknown keys in studio overrides: {}".format(
"{} Unknown keys in studio overrides: {}".format(
self.path,
", ".join("\"{}\"".format(key) for key in unknown_keys)
)
)
for key, child_obj in self.non_gui_children.items():
child_value = value.get(key, NOT_SET)
child_obj.update_studio_values(child_value)
child_obj.update_studio_value(child_value)
def update_project_values(self, value):
def update_project_value(self, value):
"""Update project override values.
Not an api method, should be called by parent.
@ -440,10 +405,11 @@ class DictImmutableKeysEntity(ItemEntity):
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:
for child_obj in self.non_gui_children.values():
child_obj.update_project_values(value)
child_obj.update_project_value(value)
return
value_keys = set(value.keys())
@ -451,14 +417,15 @@ class DictImmutableKeysEntity(ItemEntity):
unknown_keys = value_keys - expected_keys
if unknown_keys:
self.log.warning(
"Unknown keys in project overrides: {}".format(
"{} Unknown keys in project overrides: {}".format(
self.path,
", ".join("\"{}\"".format(key) for key in unknown_keys)
)
)
for key, child_obj in self.non_gui_children.items():
child_value = value.get(key, NOT_SET)
child_obj.update_project_values(child_value)
child_obj.update_project_value(child_value)
def _discard_changes(self, on_change_trigger):
self._ignore_child_changes = True
@ -468,13 +435,10 @@ class DictImmutableKeysEntity(ItemEntity):
self._ignore_child_changes = False
def add_to_studio_default(self):
if self._override_state is not OverrideState.STUDIO:
return
def _add_to_studio_default(self, on_change_trigger):
self._ignore_child_changes = True
for child_obj in self.non_gui_children.values():
child_obj.add_to_studio_default()
child_obj.add_to_studio_default(on_change_trigger)
self._ignore_child_changes = False
self.parent.on_child_change(self)
@ -483,25 +447,21 @@ class DictImmutableKeysEntity(ItemEntity):
for child_obj in self.non_gui_children.values():
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):
if self._override_state is not OverrideState.PROJECT:
return
def _add_to_project_override(self, _on_change_trigger):
self._ignore_child_changes = True
for child_obj in self.non_gui_children.values():
child_obj.add_to_project_override()
child_obj.add_to_project_override(_on_change_trigger)
self._ignore_child_changes = False
self.parent.on_child_change(self)
def _remove_from_project_override(self):
def _remove_from_project_override(self, on_change_trigger):
if self._override_state is not OverrideState.PROJECT:
return
self._ignore_child_changes = True
for child_obj in self.non_gui_children.values():
child_obj.remove_from_project_override()
child_obj.remove_from_project_override(on_change_trigger)
self._ignore_child_changes = False
def reset_callbacks(self):

View file

@ -5,7 +5,10 @@ from .lib import (
OverrideState
)
from . import EndpointEntity
from .exceptions import DefaultsNotDefined
from .exceptions import (
DefaultsNotDefined,
StudioDefaultsNotDefined
)
from pype.settings.constants import (
METADATA_KEYS,
M_DYNAMIC_KEY_LABEL,
@ -14,7 +17,17 @@ from pype.settings.constants import (
class DictMutableKeysEntity(EndpointEntity):
""""""
"""Dictionary entity that has mutable keys.
Keys of entity's children can be modified, removed or added. Children have
defined entity type so it is not possible to have 2 different entity types
as children.
TODOs:
- cleanup children on pop
- remove child's reference to parent
- clear callbacks
"""
schema_types = ["dict-modifiable"]
_miss_arg = object()
@ -22,7 +35,7 @@ class DictMutableKeysEntity(EndpointEntity):
return self.children_by_key[key]
def __setitem__(self, key, value):
self.set_value_for_key(key, value)
self.set_key_value(key, value)
def __iter__(self):
for key in self.keys():
@ -58,14 +71,14 @@ class DictMutableKeysEntity(EndpointEntity):
prev_keys = set(self.keys())
for _key, _value in value.items():
self.set_value_for_key(_key, _value)
self.set_key_value(_key, _value)
if _key in prev_keys:
prev_keys.remove(_key)
for key in prev_keys:
self.pop(key)
def set_value_for_key(self, key, value):
def set_key_value(self, key, value):
# TODO Check for value type if is Settings entity?
child_obj = self.children_by_key.get(key)
if not child_obj:
@ -77,6 +90,13 @@ class DictMutableKeysEntity(EndpointEntity):
if new_key == old_key:
return
self.children_by_key[new_key] = self.children_by_key.pop(old_key)
self._on_key_label_change()
def _on_key_label_change(self):
if self._override_state is OverrideState.STUDIO:
self._has_studio_override = True
elif self._override_state is OverrideState.PROJECT:
self._has_project_override = True
self.on_change()
def _add_key(self, key):
@ -114,6 +134,22 @@ class DictMutableKeysEntity(EndpointEntity):
return key
return None
# Label methods
def get_child_label(self, child_entity):
return self.children_label_by_id.get(child_entity.id)
def set_child_label(self, child_entity, label):
self.children_label_by_id[child_entity.id] = label
self._on_key_label_change()
def get_key_label(self, key):
child_entity = self.children_by_key[key]
return self.get_child_label(child_entity)
def set_key_label(self, key, label):
child_entity = self.children_by_key[key]
self.set_child_label(child_entity, label)
def _item_initalization(self):
self._default_metadata = {}
self._studio_override_metadata = {}
@ -133,12 +169,12 @@ class DictMutableKeysEntity(EndpointEntity):
self.schema_data.get("value_is_env_group") or False
)
self.required_keys = self.schema_data.get("required_keys") or []
self.collapsible_key = self.schema_data.get("collapsable_key") or False
self.collapsible_key = self.schema_data.get("collapsible_key") or False
# GUI attributes
self.hightlight_content = (
self.schema_data.get("highlight_content") or False
)
self.collapsible = self.schema_data.get("collapsable", True)
self.collapsible = self.schema_data.get("collapsible", True)
self.collapsed = self.schema_data.get("collapsed", True)
object_type = self.schema_data["object_type"]
@ -192,9 +228,10 @@ class DictMutableKeysEntity(EndpointEntity):
return
if self._override_state is OverrideState.STUDIO:
self._has_studio_override = self._child_has_studio_override
self._has_studio_override = True
elif self._override_state is OverrideState.PROJECT:
self._has_project_override = self._child_has_project_override
self._has_project_override = True
self.on_change()
def _metadata_for_current_state(self):
@ -220,10 +257,15 @@ class DictMutableKeysEntity(EndpointEntity):
# TODO change metadata
self._override_state = state
if not self.has_default_value and state > OverrideState.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:
raise DefaultsNotDefined(self)
# 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)
if state is OverrideState.STUDIO:
self._has_studio_override = self.had_studio_override
@ -232,20 +274,25 @@ class DictMutableKeysEntity(EndpointEntity):
self._has_project_override = self.had_project_override
self._has_studio_override = self.had_studio_override
using_overrides = True
using_project_overrides = False
using_studio_overrides = False
if (
state is OverrideState.PROJECT
and self.had_project_override
):
using_project_overrides = True
value = self._project_override_value
metadata = self._project_override_metadata
elif self.had_studio_override:
elif (
state >= OverrideState.STUDIO
and self.had_studio_override
):
using_studio_overrides = True
value = self._studio_override_value
metadata = self._studio_override_metadata
else:
using_overrides = False
value = self._default_value
metadata = self._default_metadata
@ -256,24 +303,23 @@ class DictMutableKeysEntity(EndpointEntity):
# Simulate `clear` method without triggering value change
for key in tuple(self.children_by_key.keys()):
child_obj = self.children_by_key.pop(key)
self.children_by_key.pop(key)
# Create new children
children_label_by_id = {}
metadata_labels = metadata.get(M_DYNAMIC_KEY_LABEL) or {}
for _key, _value in new_value.items():
child_obj = self._add_key(_key)
child_obj.update_default_value(_value)
if using_overrides:
if state is OverrideState.STUDIO:
child_obj.update_studio_values(_value)
else:
child_obj.update_project_values(_value)
child_entity = self._add_key(_key)
child_entity.update_default_value(_value)
if using_project_overrides:
child_entity.update_project_value(_value)
elif using_studio_overrides:
child_entity.update_studio_value(_value)
label = metadata_labels.get(_key)
if label:
children_label_by_id[child_obj.id] = label
child_obj.set_override_state(state)
children_label_by_id[child_entity.id] = label
child_entity.set_override_state(state)
self.children_label_by_id = children_label_by_id
@ -301,8 +347,9 @@ class DictMutableKeysEntity(EndpointEntity):
children_key_by_id = self.children_key_by_id()
label_metadata = {}
for child_id, label in self.children_label_by_id.items():
key = children_key_by_id[child_id]
label_metadata[key] = label
key = children_key_by_id.get(child_id)
if key:
label_metadata[key] = label
output[M_DYNAMIC_KEY_LABEL] = label_metadata
@ -368,6 +415,8 @@ class DictMutableKeysEntity(EndpointEntity):
output = {}
for key, child_entity in self.children_by_key.items():
child_value = child_entity.settings_value()
# TODO child should have setter of env group key se child can
# know what env group represents.
if self.value_is_env_group:
if key not in child_value[M_ENVIRONMENT_KEY]:
_metadata = child_value[M_ENVIRONMENT_KEY]
@ -393,14 +442,14 @@ class DictMutableKeysEntity(EndpointEntity):
self._default_value = value
self._default_metadata = metadata
def update_studio_values(self, value):
def update_studio_value(self, value):
value = self._check_update_value(value, "studio override")
value, metadata = self._prepare_value(value)
self._studio_override_value = value
self._studio_override_metadata = metadata
self.had_studio_override = value is not NOT_SET
def update_project_values(self, value):
def update_project_value(self, value):
value = self._check_update_value(value, "project override")
value, metadata = self._prepare_value(value)
self._project_override_value = value
@ -411,9 +460,7 @@ class DictMutableKeysEntity(EndpointEntity):
self.set_override_state(self._override_state)
on_change_trigger.append(self.on_change)
def add_to_studio_default(self):
if self._override_state is not OverrideState.STUDIO:
return
def _add_to_studio_default(self, _on_change_trigger):
self._has_studio_override = True
self.on_change()
@ -431,7 +478,7 @@ class DictMutableKeysEntity(EndpointEntity):
# Create new children
for _key, _value in new_value.items():
child_obj = self.add_key(_key)
child_obj = self._add_key(_key)
child_obj.update_default_value(_value)
child_obj.set_override_state(self._override_state)
@ -441,9 +488,7 @@ class DictMutableKeysEntity(EndpointEntity):
on_change_trigger.append(self.on_change)
def add_to_project_override(self):
if self._override_state is not OverrideState.PROJECT:
return
def _add_to_project_override(self, _on_change_trigger):
self._has_project_override = True
self.on_change()
@ -454,10 +499,8 @@ class DictMutableKeysEntity(EndpointEntity):
if not self.has_project_override:
return
using_overrides = False
if self._has_studio_override:
value = self._studio_override_value
using_overrides = True
elif self.has_default_value:
value = self._default_value
else:
@ -473,9 +516,9 @@ class DictMutableKeysEntity(EndpointEntity):
# Create new children
for _key, _value in new_value.items():
child_obj = self.add_key(_key)
child_obj = self._add_key(_key)
child_obj.update_default_value(_value)
if using_overrides:
if self._has_studio_override:
child_obj.update_studio_value(_value)
child_obj.set_override_state(self._override_state)

View file

@ -4,6 +4,14 @@ class DefaultsNotDefined(Exception):
super(DefaultsNotDefined, self).__init__(msg)
class StudioDefaultsNotDefined(Exception):
def __init__(self, obj):
msg = "Studio default values for object are not set. {}".format(
obj.path
)
super(StudioDefaultsNotDefined, self).__init__(msg)
class InvalidValueType(Exception):
msg_template = "{}"

View file

@ -1,12 +1,16 @@
import copy
from abc import abstractmethod
from .item_entities import ItemEntity
from .base_entity import ItemEntity
from .lib import (
NOT_SET,
OverrideState
)
from .exceptions import DefaultsNotDefined
from .exceptions import (
DefaultsNotDefined,
StudioDefaultsNotDefined
)
from pype.settings.constants import (
METADATA_KEYS,
M_ENVIRONMENT_KEY
@ -24,7 +28,10 @@ class EndpointEntity(ItemEntity):
def __init__(self, *args, **kwargs):
super(EndpointEntity, self).__init__(*args, **kwargs)
if not self.group_item and not self.is_group:
if (
not (self.group_item or self.is_group)
and not (self.is_dynamic_item or self.is_in_dynamic_item)
):
self.is_group = True
def schema_validations(self):
@ -37,12 +44,6 @@ class EndpointEntity(ItemEntity):
)
)
# Input entity must have file parent.
if not self.file_item:
raise ValueError(
"{}: Missing parent file entity.".format(self.path)
)
super(EndpointEntity, self).schema_validations()
@abstractmethod
@ -55,10 +56,10 @@ class EndpointEntity(ItemEntity):
if self.is_group:
if self._override_state is OverrideState.STUDIO:
if not self._has_studio_override:
if not self.has_studio_override:
return NOT_SET
elif self._override_state is OverrideState.PROJECT:
if not self._has_project_override:
if not self.has_project_override:
return NOT_SET
return self._settings_value()
@ -72,12 +73,12 @@ class EndpointEntity(ItemEntity):
self._default_value = value
self.has_default_value = value is not NOT_SET
def update_studio_values(self, value):
def update_studio_value(self, value):
value = self._check_update_value(value, "studio override")
self._studio_override_value = value
self.had_studio_override = bool(value is not NOT_SET)
def update_project_values(self, value):
def update_project_value(self, value):
value = self._check_update_value(value, "project override")
self._project_override_value = value
self.had_project_override = bool(value is not NOT_SET)
@ -100,6 +101,15 @@ class InputEntity(EndpointEntity):
self.__class__.__name__
))
def schema_validations(self):
# Input entity must have file parent.
if not self.file_item:
raise ValueError(
"{}: Missing parent file entity.".format(self.path)
)
super(InputEntity, self).schema_validations()
@property
def value(self):
"""Entity's value without metadata."""
@ -201,10 +211,15 @@ class InputEntity(EndpointEntity):
return
self._override_state = state
if not self.has_default_value and state > OverrideState.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:
raise DefaultsNotDefined(self)
# 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)
if state is OverrideState.STUDIO:
self._has_studio_override = (
@ -237,9 +252,6 @@ class InputEntity(EndpointEntity):
self._current_value = copy.deepcopy(value)
def _discard_changes(self, on_change_trigger=None):
if self._override_state is OverrideState.NOT_DEFINED:
return
self._value_is_modified = False
if self._override_state >= OverrideState.PROJECT:
self._has_project_override = self.had_project_override
@ -270,9 +282,7 @@ class InputEntity(EndpointEntity):
raise NotImplementedError("BUG: Unexcpected part of code.")
def add_to_studio_default(self):
if self._override_state is not OverrideState.STUDIO:
return
def _add_to_studio_default(self, _on_change_trigger):
self._has_studio_override = True
self.on_change()
@ -287,9 +297,7 @@ class InputEntity(EndpointEntity):
on_change_trigger.append(self.on_change)
def add_to_project_override(self):
if self._override_state is not OverrideState.PROJECT:
return
def _add_to_project_override(self, _on_change_trigger):
self._has_project_override = True
self.on_change()
@ -510,14 +518,14 @@ class RawJsonEntity(InputEntity):
self._default_value = value
self.default_metadata = metadata
def update_studio_values(self, value):
def update_studio_value(self, value):
value = self._check_update_value(value, "studio override")
self.had_studio_override = value is not NOT_SET
value, metadata = self._prepare_value(value)
self._studio_override_value = value
self.studio_override_metadata = metadata
def update_project_values(self, value):
def update_project_value(self, value):
value = self._check_update_value(value, "project override")
self.had_project_override = value is not NOT_SET
value, metadata = self._prepare_value(value)

View file

@ -2,7 +2,10 @@ from .lib import (
NOT_SET,
OverrideState
)
from .exceptions import DefaultsNotDefined
from .exceptions import (
DefaultsNotDefined,
StudioDefaultsNotDefined
)
from .base_entity import ItemEntity
@ -156,26 +159,26 @@ class PathEntity(ItemEntity):
def update_default_value(self, value):
self.child_obj.update_default_value(value)
def update_project_values(self, value):
self.child_obj.update_project_values(value)
def update_project_value(self, value):
self.child_obj.update_project_value(value)
def update_studio_values(self, value):
self.child_obj.update_studio_values(value)
def update_studio_value(self, value):
self.child_obj.update_studio_value(value)
def _discard_changes(self, *args):
self.child_obj.discard_changes(*args)
def _discard_changes(self, *args, **kwargs):
self.child_obj.discard_changes(*args, **kwargs)
def add_to_studio_default(self):
self.child_obj.add_to_studio_default()
def _add_to_studio_default(self, *args, **kwargs):
self.child_obj.add_to_studio_default(*args, **kwargs)
def _remove_from_studio_default(self, *args):
self.child_obj.remove_from_studio_default(*args)
def _remove_from_studio_default(self, *args, **kwargs):
self.child_obj.remove_from_studio_default(*args, **kwargs)
def add_to_project_override(self):
self.child_obj.add_to_project_override()
def _add_to_project_override(self, *args, **kwargs):
self.child_obj.add_to_project_override(*args, **kwargs)
def _remove_from_project_override(self, *args):
self.child_obj.remove_from_project_override(*args)
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()
@ -206,6 +209,15 @@ class ListStrictEntity(ItemEntity):
if not self.group_item 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 ValueError(
"{}: Missing file entity in hierarchy.".format(self.path)
)
super(ListStrictEntity, self).schema_validations()
def get_child_path(self, child_obj):
result_idx = None
for idx, _child_obj in enumerate(self.children):
@ -231,6 +243,20 @@ class ListStrictEntity(ItemEntity):
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())
@ -246,12 +272,13 @@ class ListStrictEntity(ItemEntity):
return
if self._override_state is OverrideState.STUDIO:
self._has_studio_override = self.child_has_studio_override
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._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
@ -285,6 +312,14 @@ class ListStrictEntity(ItemEntity):
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:
@ -313,13 +348,18 @@ class ListStrictEntity(ItemEntity):
return
self._override_state = state
if not self.has_default_value and state > OverrideState.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:
raise DefaultsNotDefined(self)
# 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)
for child_obj in self.children:
child_obj.set_override_state(state)
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()
@ -327,9 +367,7 @@ class ListStrictEntity(ItemEntity):
for child_obj in self.children:
child_obj.discard_changes(on_change_trigger)
def add_to_studio_default(self):
if self._override_state is not OverrideState.STUDIO:
return
def _add_to_studio_default(self, _on_change_trigger):
self._has_studio_override = True
self.on_change()
@ -343,14 +381,11 @@ class ListStrictEntity(ItemEntity):
self._has_studio_override = False
def add_to_project_override(self):
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):
if self._override_state is not OverrideState.PROJECT:
return
self._ignore_child_changes = True
for child_obj in self.children:
@ -403,25 +438,25 @@ class ListStrictEntity(ItemEntity):
for idx, item_value in enumerate(value):
self.children[idx].update_default_value(item_value)
def update_studio_values(self, 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_values(value)
child_obj.update_studio_value(value)
else:
for idx, item_value in enumerate(value):
self.children[idx].update_studio_values(item_value)
self.children[idx].update_studio_value(item_value)
def update_project_values(self, 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_values(value)
child_obj.update_project_value(value)
else:
for idx, item_value in enumerate(value):
self.children[idx].update_project_values(item_value)
self.children[idx].update_project_value(item_value)
def reset_callbacks(self):
super(ListStrictEntity, self).reset_callbacks()

View file

@ -7,7 +7,10 @@ from .lib import (
NOT_SET,
OverrideState
)
from .exceptions import DefaultsNotDefined
from .exceptions import (
DefaultsNotDefined,
StudioDefaultsNotDefined
)
class ListEntity(EndpointEntity):
@ -36,6 +39,19 @@ class ListEntity(EndpointEntity):
return True
return False
def index(self, item):
if isinstance(item, BaseEntity):
for idx, child_entity in enumerate(self.children):
if child_entity.id == item.id:
return idx
else:
for idx, _item in enumerate(self.value):
if item == _item:
return idx
raise ValueError(
"{} is not in {}".format(item, self.__class__.__name__)
)
def append(self, item):
child_obj = self._add_new_item()
child_obj.set_override_state(self._override_state)
@ -89,6 +105,22 @@ class ListEntity(EndpointEntity):
self.on_change()
return child_obj
def swap_items(self, item_1, item_2):
index_1 = self.index(item_1)
index_2 = self.index(item_2)
self.swap_indexes(index_1, index_2)
def swap_indexes(self, index_1, index_2):
children_len = len(self.children)
if index_1 > children_len or index_2 > children_len:
raise IndexError(
"{} index out of range".format(self.__class__.__name__)
)
self.children[index_1], self.children[index_2] = (
self.children[index_2], self.children[index_1]
)
self.on_change()
def _item_initalization(self):
self.valid_value_types = (list, )
self.children = []
@ -153,11 +185,10 @@ class ListEntity(EndpointEntity):
if self._ignore_child_changes:
return
# TODO is this enough?
if self._override_state is OverrideState.STUDIO:
self._has_studio_override = self._child_has_studio_override
self._has_studio_override = True
elif self._override_state is OverrideState.PROJECT:
self._has_project_override = self._child_has_project_override
self._has_project_override = True
self.on_change()
def set_override_state(self, state):
@ -171,10 +202,15 @@ class ListEntity(EndpointEntity):
while self.children:
self.children.pop(0)
if not self.has_default_value and state > OverrideState.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:
raise DefaultsNotDefined(self)
# 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)
value = NOT_SET
if self._override_state is OverrideState.PROJECT:
@ -198,13 +234,13 @@ class ListEntity(EndpointEntity):
child_obj.update_default_value(item)
if self._override_state is OverrideState.PROJECT:
if self.had_project_override:
child_obj.update_project_values(item)
child_obj.update_project_value(item)
elif self.had_studio_override:
child_obj.update_studio_values(item)
child_obj.update_studio_value(item)
elif self._override_state is OverrideState.STUDIO:
if self.had_studio_override:
child_obj.update_studio_values(item)
child_obj.update_studio_value(item)
for child_obj in self.children:
child_obj.set_override_state(self._override_state)
@ -337,13 +373,13 @@ class ListEntity(EndpointEntity):
child_obj.update_default_value(item)
if self._override_state is OverrideState.PROJECT:
if self.had_project_override:
child_obj.update_project_values(item)
child_obj.update_project_value(item)
elif self.had_studio_override:
child_obj.update_studio_values(item)
child_obj.update_studio_value(item)
elif self._override_state is OverrideState.STUDIO:
if self.had_studio_override:
child_obj.update_studio_values(item)
child_obj.update_studio_value(item)
child_obj.set_override_state(self._override_state)
@ -357,9 +393,7 @@ class ListEntity(EndpointEntity):
on_change_trigger.append(self.on_change)
def add_to_studio_default(self):
if self._override_state is not OverrideState.STUDIO:
return
def _add_to_studio_default(self, _on_change_trigger):
self._has_studio_override = True
self.on_change()
@ -387,7 +421,7 @@ class ListEntity(EndpointEntity):
on_change_trigger.append(self.on_change)
def add_to_project_override(self):
def _add_to_project_override(self, _on_change_trigger):
self._has_project_override = True
self.on_change()
@ -407,11 +441,14 @@ class ListEntity(EndpointEntity):
self._ignore_child_changes = True
while self.children:
self.children.pop(0)
for item in value:
child_obj = self._add_new_item()
child_obj.update_default_value(item)
if self._has_studio_override:
child_obj.update_studio_values(item)
child_obj.update_studio_value(item)
child_obj.set_override_state(self._override_state)
self._ignore_child_changes = False

View file

@ -12,7 +12,11 @@ from .lib import (
OverrideState,
gui_schema
)
from pype.settings.constants import SYSTEM_SETTINGS_KEY
from pype.settings.constants import (
SYSTEM_SETTINGS_KEY,
PROJECT_SETTINGS_KEY,
PROJECT_ANATOMY_KEY
)
from pype.settings.lib import (
DEFAULTS_DIR,
@ -20,15 +24,18 @@ from pype.settings.lib import (
get_default_settings,
get_studio_system_settings_overrides,
save_studio_settings,
get_studio_project_settings_overrides,
get_studio_project_anatomy_overrides,
get_project_settings_overrides,
get_project_anatomy_overrides,
save_project_settings,
save_project_anatomy,
find_environments,
apply_overrides
)
# TODO implement
# ProjectSettings
# AnatomySettings
class RootEntity(BaseItemEntity):
@ -132,8 +139,16 @@ class RootEntity(BaseItemEntity):
self._add_children(self.schema_data)
for children in self.children:
children.schema_validations()
self.schema_validations()
def schema_validations(self):
for child_entity in self.children:
if child_entity.is_group:
raise ValueError((
"Root entity \"{}\" has child with `is_group`"
" attribute set to True but root can't save overrides."
).format(self.__class__.__name__))
child_entity.schema_validations()
def create_schema_object(self, schema_data, *args, **kwargs):
"""Create entity by entered schema data.
@ -166,10 +181,11 @@ class RootEntity(BaseItemEntity):
if not issubclass(item, entities.BaseEntity):
continue
# Skip class that is abstract by design
if item in known_abstract_classes:
continue
if inspect.isabstract(item):
# Skip class that is abstract by design
if item in known_abstract_classes:
continue
# Create an object to get crash and get traceback
item()
@ -302,25 +318,25 @@ class RootEntity(BaseItemEntity):
for child_obj in self.non_gui_children.values():
child_obj.discard_changes(on_change_trigger)
def add_to_studio_default(self):
def _add_to_studio_default(self, *args, **kwargs):
"""Implementation of abstract method only trigger children callback."""
for child_obj in self.non_gui_children.values():
child_obj.add_to_studio_default()
child_obj.add_to_studio_default(*args, **kwargs)
def _remove_from_studio_default(self, on_change_trigger):
"""Implementation of abstract method only trigger children callback."""
for child_obj in self.non_gui_children.values():
child_obj.remove_from_studio_default(on_change_trigger)
def add_to_project_override(self):
def _add_to_project_override(self, *args, **kwargs):
"""Implementation of abstract method only trigger children callback."""
for child_obj in self.non_gui_children.values():
child_obj.add_to_project_override()
child_obj.add_to_project_override(*args, **kwargs)
def _remove_from_project_override(self):
def _remove_from_project_override(self, on_change_trigger):
"""Implementation of abstract method only trigger children callback."""
for child_obj in self.non_gui_children.values():
child_obj.remove_from_project_override()
child_obj.remove_from_project_override(on_change_trigger)
def save(self):
"""Save values for current override state.
@ -450,7 +466,7 @@ class SystemSettings(RootEntity):
studio_overrides = get_studio_system_settings_overrides()
for key, child_obj in self.non_gui_children.items():
value = studio_overrides.get(key, NOT_SET)
child_obj.update_studio_values(value)
child_obj.update_studio_value(value)
def reset(self, new_state=None):
"""Discard changes and reset entit's values.
@ -478,7 +494,7 @@ class SystemSettings(RootEntity):
Implementation of abstract method.
"""
return os.path.join(DEFAULTS_DIR, SYSTEM_SETTINGS_KEY)
return DEFAULTS_DIR
def _save_studio_values(self):
settings_value = self.settings_value()
@ -521,3 +537,147 @@ class SystemSettings(RootEntity):
overrides.
"""
raise ValueError("System settings can't save project overrides.")
class ProjectSettings(RootEntity):
"""Root entity for project settings.
Allows to modify project settings via entity system and loaded schemas.
Args:
project_name (str): Project name which overrides will be loaded.
Use `None` to modify studio defaults.
change_state (bool): Set values on initialization. By
default is set to True.
reset (bool): Reset values on initialization. By default is set to
True.
schema_data (dict): Pass schema data to entity. This is for development
and debugging purposes.
"""
def __init__(
self,
project_name=None,
change_state=True,
reset=True,
schema_data=None
):
self._project_name = project_name
if schema_data is None:
# Load system schemas
schema_data = gui_schema("projects_schema", "schema_main")
super(ProjectSettings, self).__init__(schema_data, reset)
if change_state:
if self.project_name is None:
self.set_studio_state()
else:
self.set_project_state()
@property
def project_name(self):
return self._project_name
@project_name.setter
def project_name(self, project_name):
self.change_project(project_name)
def change_project(self, project_name):
if project_name == self._project_name:
return
self._project_name = project_name
if project_name is None:
self.set_studio_state()
return
project_override_value = {
PROJECT_SETTINGS_KEY: get_project_settings_overrides(project_name),
PROJECT_ANATOMY_KEY: get_project_anatomy_overrides(project_name)
}
for key, child_obj in self.non_gui_children.items():
value = project_override_value.get(key, NOT_SET)
child_obj.update_project_value(value)
self.set_project_state()
def _reset_values(self):
default_values = {
PROJECT_SETTINGS_KEY: get_default_settings()[PROJECT_SETTINGS_KEY],
PROJECT_ANATOMY_KEY: get_default_settings()[PROJECT_ANATOMY_KEY]
}
for key, child_obj in self.non_gui_children.items():
value = default_values.get(key, NOT_SET)
child_obj.update_default_value(value)
studio_overrides = {
PROJECT_SETTINGS_KEY: get_studio_project_settings_overrides(),
PROJECT_ANATOMY_KEY: get_studio_project_anatomy_overrides()
}
for key, child_obj in self.non_gui_children.items():
value = studio_overrides.get(key, NOT_SET)
child_obj.update_studio_value(value)
if not self.project_name:
return
project_name = self.project_name
project_override_value = {
PROJECT_SETTINGS_KEY: get_project_settings_overrides(project_name),
PROJECT_ANATOMY_KEY: get_project_anatomy_overrides(project_name)
}
for key, child_obj in self.non_gui_children.items():
value = project_override_value.get(key, NOT_SET)
child_obj.update_project_value(value)
def reset(self, new_state=None):
"""Discard changes and reset entit's values.
Reload default values and studio override values and update entities.
Args:
new_state (OverrideState): It is possible to change override state
during reset. Current state is used if not defined.
"""
if new_state is None:
new_state = self._override_state
if new_state is OverrideState.NOT_DEFINED:
new_state = OverrideState.DEFAULTS
self._reset_values()
self.set_override_state(new_state)
def defaults_dir(self):
"""Path to defaults directory.
Implementation of abstract method.
"""
return DEFAULTS_DIR
def _save_studio_values(self):
settings_value = self.settings_value()
self._validate_values_to_save(settings_value)
self.log.debug("Saving project settings: {}".format(
json.dumps(settings_value, indent=4)
))
project_settings = settings_value.get(PROJECT_SETTINGS_KEY) or {}
project_anatomy = settings_value.get(PROJECT_ANATOMY_KEY) or {}
save_project_settings(self.project_name, project_settings)
save_project_anatomy(self.project_name, project_anatomy)
def _validate_defaults_to_save(self, value):
"""Valiations of default values before save."""
pass
def _validate_values_to_save(self, value):
pass
def _save_project_values(self):
"""Project overrides are saved same ways as studio overrides."""
self._save_studio_values()

View file

@ -0,0 +1,490 @@
# Creating GUI schemas
## Basic rules
- configurations does not define GUI, but GUI defines configurations!
- output is always json (yaml is not needed for anatomy templates anymore)
- GUI schema has multiple input types, all inputs are represented by a dictionary
- each input may have "input modifiers" (keys in dictionary) that are required or optional
- only required modifier for all input items is key `"type"` which says what type of item it is
- there are special keys across all inputs
- `"is_file"` - this key is for storing pype defaults in `pype` repo
- reasons of existence: developing new schemas does not require to create defaults manually
- key is validated, must be once in hierarchy else it won't be possible to store pype defaults
- `"is_group"` - define that all values under key in hierarchy will be overriden if any value is modified, this information is also stored to overrides
- this keys is not allowed for all inputs as they may have not reason for that
- key is validated, can be only once in hierarchy but is not required
- currently there are `system configurations` and `project configurations`
## Inner schema
- GUI schemas are huge json files, to be able to split whole configuration into multiple schema there's type `schema`
- system configuration schemas are stored in `~/tools/settings/settings/gui_schemas/system_schema/` and project configurations in `~/tools/settings/settings/gui_schemas/projects_schema/`
- each schema name is filename of json file except extension (without ".json")
- if content is dictionary content will be used as `schema` else will be used as `schema_template`
### schema
- can have only key `"children"` which is list of strings, each string should represent another schema (order matters) string represebts name of the schema
- will just paste schemas from other schema file in order of "children" list
```
{
"type": "schema",
"name": "my_schema_name"
}
```
### schema_template
- allows to define schema "templates" to not duplicate same content multiple times
```javascript
// EXAMPLE json file content (filename: example_template.json)
[
{
"__default_values__": {
"multipath_executables": true
}
}, {
"type": "raw-json",
"label": "{host_label} Environments",
"key": "{host_name}_environments",
"env_group_key": "{host_name}"
}, {
"type": "path-widget",
"key": "{host_name}_executables",
"label": "{host_label} - Full paths to executables",
"multiplatform": "{multipath_executables}",
"multipath": true
}
]
```
```javascript
// EXAMPLE usage of the template in schema
{
"type": "dict",
"key": "schema_template_examples",
"label": "Schema template examples",
"children": [
{
"type": "schema_template",
// filename of template (example_template.json)
"name": "example_template",
"template_data": {
"host_label": "Maya 2019",
"host_name": "maya_2019",
"multipath_executables": false
}
}, {
"type": "schema_template",
"name": "example_template",
"template_data": {
"host_label": "Maya 2020",
"host_name": "maya_2020"
}
}
]
}
```
- item in schema mush contain `"type"` and `"name"` keys but it is also expected that `"template_data"` will be entered too
- all items in the list, except `__default_values__`, will replace `schema_template` item in schema
- template may contain another template or schema
- it is expected that schema template will have unfilled fields as in example
- unfilled fields are allowed only in values of schema dictionary
```javascript
{
...
// Allowed
"key": "{to_fill}"
...
// Not allowed
"{to_fill}": "value"
...
}
```
- Unfilled fields can be also used for non string values, in that case value must contain only one key and value for fill must contain right type.
```javascript
{
...
// Allowed
"multiplatform": "{executable_multiplatform}"
...
// Not allowed
"multiplatform": "{executable_multiplatform}_enhanced_string"
...
}
```
- It is possible to define default values for unfilled fields to do so one of items in list must be dictionary with key `"__default_values__"` and value as dictionary with default key: values (as in example above).
## Basic Dictionary inputs
- these inputs wraps another inputs into {key: value} relation
## dict
- this is another dictionary input wrapping more inputs but visually makes them different
- item may be used as widget (in `list` or `dict-modifiable`)
- in that case the only key modifier is `children` which is list of it's keys
- USAGE: e.g. List of dictionaries where each dictionary have same structure.
- item may be with or without `"label"` if is not used as widget
- required keys are `"key"` under which will be stored
- without label it is just wrap item holding `"key"`
- can't have `"is_group"` key set to True as it breaks visual override showing
- 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
{
"key": "applications",
"type": "dict",
"label": "Applications",
"collapsible": true,
"highlight_content": true,
"is_group": true,
"is_file": true,
"children": [
...ITEMS...
]
}
# Without label
{
"type": "dict",
"key": "global",
"children": [
...ITEMS...
]
}
# When used as widget
{
"type": "list",
"key": "profiles",
"label": "Profiles",
"object_type": {
"type": "dict",
"children": [
{
"key": "families",
"label": "Families",
"type": "list",
"object_type": "text"
}, {
"key": "hosts",
"label": "Hosts",
"type": "list",
"object_type": "text"
}
...
]
}
}
```
## 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
### boolean
- simple checkbox, nothing more to set
```
{
"type": "boolean",
"key": "my_boolean_key",
"label": "Do you want to use Pype?"
}
```
### number
- number input, can be used for both integer and float
- key `"decimal"` defines how many decimal places will be used, 0 is for integer input (Default: `0`)
- key `"minimum"` as minimum allowed number to enter (Default: `-99999`)
- key `"maxium"` as maximum allowed number to enter (Default: `99999`)
```
{
"type": "number",
"key": "fps",
"label": "Frame rate (FPS)"
"decimal": 2,
"minimum": 1,
"maximum": 300000
}
```
### text
- simple text input
- key `"multiline"` allows to enter multiple lines of text (Default: `False`)
- key `"placeholder"` allows to show text inside input when is empty (Default: `None`)
```
{
"type": "text",
"key": "deadline_pool",
"label": "Deadline pool"
}
```
### path-input
- enhanced text input
- does not allow to enter backslash, is auto-converted to forward slash
- may be added another validations, like do not allow end path with slash
- this input is implemented to add additional features to text input
- this is meant to be used in proxy input `path-widget`
- DO NOT USE this input in schema please
### raw-json
- a little bit enhanced text input for raw json
- has validations of json format
- empty value is invalid value, always must be at least `{}` of `[]`
```
{
"type": "raw-json",
"key": "profiles",
"label": "Extract Review profiles"
}
```
### enum
- returns value of single on multiple items from predefined values
- multiselection can be allowed with setting key `"multiselection"` to `True` (Default: `False`)
- values are defined under value of key `"enum_items"` as list
- each item in list is simple dictionary where value is label and key is value which will be stored
- should be possible to enter single dictionary if order of items doesn't matter
```
{
"key": "tags",
"label": "Tags",
"type": "enum",
"multiselection": true,
"enum_items": [
{"burnin": "Add burnins"},
{"ftrackreview": "Add to Ftrack"},
{"delete": "Delete output"},
{"slate-frame": "Add slate frame"},
{"no-hnadles": "Skip handle frames"}
]
}
```
## Inputs for setting value using Pure inputs
- these inputs also have required `"key"`
- attribute `"label"` is required in few conditions
- when item is marked `as_group` or when `use_label_wrap`
- they use Pure inputs "as widgets"
### list
- output is list
- items can be added and removed
- items in list must be the same type
- to wrap item in collapsible widget with label on top set `use_label_wrap` to `True`
- when this is used `collapsible` and `collapsed` can be set (same as `dict` item does)
- type of items is defined with key `"object_type"`
- there are 2 possible ways how to set the type:
1.) dictionary with item modifiers (`number` input has `minimum`, `maximum` and `decimals`) in that case item type must be set as value of `"type"` (example below)
2.) item type name as string without modifiers (e.g. `text`)
1.) with item modifiers
```
{
"type": "list",
"key": "exclude_ports",
"label": "Exclude ports",
"object_type": {
"type": "number", # number item type
"minimum": 1, # minimum modifier
"maximum": 65535 # maximum modifier
}
}
```
2.) without modifiers
```
{
"type": "list",
"key": "exclude_ports",
"label": "Exclude ports",
"object_type": "text"
}
```
### dict-modifiable
- one of dictionary inputs, this is only used as value input
- items in this input can be removed and added same way as in `list` input
- value items in dictionary must be the same type
- type of items is defined with key `"object_type"`
- required keys may be defined under `"required_keys"`
- required keys must be defined as a list (e.g. `["key_1"]`) and are moved to the top
- these keys can't be removed or edited (it is possible to edit label if item is collapsible)
- there are 2 possible ways how to set the type:
1.) dictionary with item modifiers (`number` input has `minimum`, `maximum` and `decimals`) in that case item type must be set as value of `"type"` (example below)
2.) item type name as string without modifiers (e.g. `text`)
- this input 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`)
1.) with item modifiers
```
{
"type": "dict-modifiable",
"object_type": {
"type": "number",
"minimum": 0,
"maximum": 300
},
"is_group": true,
"key": "templates_mapping",
"label": "Muster - Templates mapping",
"is_file": true
}
```
2.) without modifiers
```
{
"type": "dict-modifiable",
"object_type": "text",
"is_group": true,
"key": "templates_mapping",
"label": "Muster - Templates mapping",
"is_file": true
}
```
### path-widget
- input for paths, use `path-input` internally
- has 2 input modifiers `"multiplatform"` and `"multipath"`
- `"multiplatform"` - adds `"windows"`, `"linux"` and `"darwin"` path inputs result is dictionary
- `"multipath"` - it is possible to enter multiple paths
- if both are enabled result is dictionary with lists
```
{
"type": "path-widget",
"key": "ffmpeg_path",
"label": "FFmpeg path",
"multiplatform": true,
"multipath": true
}
```
### list-strict
- input for strict number of items in list
- each child item can be different type with different possible modifiers
- it is possible to display them in horizontal or vertical layout
- key `"horizontal"` as `True`/`False` (Default: `True`)
- each child may have defined `"label"` which is shown next to input
- label does not reflect modifications or overrides (TODO)
- children item are defined under key `"object_types"` which is list of dictionaries
- key `"children"` is not used because is used for hierarchy validations in schema
- USAGE: For colors, transformations, etc. Custom number and different modifiers
give ability to define if color is HUE or RGB, 0-255, 0-1, 0-100 etc.
```
{
"type": "list-strict",
"key": "color",
"label": "Color",
"object_types": [
{
"label": "Red",
"type": "number",
"minimum": 0,
"maximum": 255,
"decimal": 0
}, {
"label": "Green",
"type": "number",
"minimum": 0,
"maximum": 255,
"decimal": 0
}, {
"label": "Blue",
"type": "number",
"minimum": 0,
"maximum": 255,
"decimal": 0
}, {
"label": "Alpha",
"type": "number",
"minimum": 0,
"maximum": 1,
"decimal": 6
}
]
}
```
## Noninteractive widgets
- have nothing to do with data
### label
- add label with note or explanations
- it is possible to use html tags inside the label
```
{
"type": "label",
"label": "<span style=\"color:#FF0000\";>RED LABEL:</span> Normal label"
}
```
### splitter
- visual splitter of items (more divider than splitter)
```
{
"type": "splitter"
}
```
## Proxy wrappers
- should wraps multiple inputs only visually
- these does not have `"key"` key and do not allow to have `"is_file"` or `"is_group"` modifiers enabled
- can't be used as widget (first item in e.g. `list`, `dict-modifiable`, etc.)
### form
- wraps inputs into form look layout
- should be used only for Pure inputs
```
{
"type": "dict-form",
"children": [
{
"type": "text",
"key": "deadline_department",
"label": "Deadline apartment"
}, {
"type": "number",
"key": "deadline_priority",
"label": "Deadline priority"
}, {
...
}
]
}
```
### collapsible-wrap
- wraps inputs into collapsible widget
- looks like `dict` but does not hold `"key"`
- should be used only for Pure inputs
```
{
"type": "collapsible-wrap",
"label": "Collapsible example"
"children": [
{
"type": "text",
"key": "_example_input_collapsible",
"label": "Example input in collapsible wrapper"
}, {
...
}
]
}

View file

@ -5,12 +5,19 @@
{
"type": "anatomy",
"key": "project_anatomy",
"label": "Anatomy",
"children": [
{
"type": "anatomy_roots",
"key": "roots",
"label": "Roots",
"is_file": true
"type": "anatomy_roots",
"is_file": true,
"is_group": true,
"expandable": false,
"object_type": {
"type": "path-widget",
"multiplatform": true
}
},
{
"type": "schema",

View file

@ -1,32 +1,32 @@
{
"type": "dict",
"collapsable": true,
"collapsible": true,
"key": "plugins",
"label": "Plugins",
"children": [
{
"type": "dict",
"collapsable": true,
"collapsible": true,
"key": "standalonepublisher",
"label": "Standalone Publisher",
"children": [
{
"type": "dict",
"collapsable": true,
"collapsible": true,
"key": "publish",
"label": "Publish plugins",
"is_file": true,
"children": [
{
"type": "dict",
"collapsable": true,
"collapsible": true,
"key": "ExtractThumbnailSP",
"label": "ExtractThumbnailSP",
"is_group": true,
"children": [
{
"type": "dict",
"collapsable": false,
"collapsible": false,
"key": "ffmpeg_args",
"label": "ffmpeg_args",
"children": [

View file

@ -1,19 +1,19 @@
{
"type": "dict",
"collapsable": true,
"collapsible": true,
"key": "celaction",
"label": "CelAction",
"is_file": true,
"children": [
{
"type": "dict",
"collapsable": true,
"collapsible": true,
"key": "publish",
"label": "Publish plugins",
"children": [
{
"type": "dict",
"collapsable": true,
"collapsible": true,
"checkbox_key": "enabled",
"key": "ExtractCelactionDeadline",
"label": "ExtractCelactionDeadline",

View file

@ -2,8 +2,7 @@
"type": "dict",
"key": "ftrack",
"label": "Ftrack",
"collapsable": true,
"checkbox_key": "enabled",
"collapsible": true,
"is_file": true,
"children": [
{
@ -162,6 +161,7 @@
"type": "dict",
"key": "status_update",
"label": "Update status on task action",
"is_group": true,
"checkbox_key": "enabled",
"children": [
{
@ -240,6 +240,7 @@
"type": "dict",
"key": "status_task_to_version",
"label": "Sync status from Task to Version",
"is_group": true,
"checkbox_key": "enabled",
"children": [
{
@ -272,6 +273,7 @@
"type": "dict",
"key": "status_version_to_task",
"label": "Sync status from Version to Task",
"is_group": true,
"checkbox_key": "enabled",
"children": [
{
@ -327,6 +329,7 @@
{
"type": "dict",
"key": "next_task_update",
"is_group": true,
"label": "Update status on next task",
"checkbox_key": "enabled",
"children": [
@ -386,6 +389,7 @@
{
"type": "dict",
"key": "application_launch_statuses",
"is_group": true,
"label": "Application - Status change on launch",
"checkbox_key": "enabled",
"children": [
@ -605,14 +609,14 @@
},
{
"type": "dict",
"collapsable": true,
"collapsible": true,
"key": "publish",
"label": "Publish plugins",
"is_file": true,
"children": [
{
"type": "dict",
"collapsable": true,
"collapsible": true,
"checkbox_key": "enabled",
"key": "IntegrateFtrackNote",
"label": "IntegrateFtrackNote",
@ -639,7 +643,7 @@
{
"type": "dict",
"collapsable": true,
"collapsible": true,
"checkbox_key": "enabled",
"key": "ValidateFtrackAttributes",
"label": "ValidateFtrackAttributes",

View file

@ -0,0 +1,26 @@
{
"type": "dict",
"collapsible": true,
"key": "global",
"label": "Global",
"is_file": true,
"children": [
{
"type": "schema",
"name": "schema_global_publish"
},
{
"type": "schema",
"name": "schema_global_tools"
},
{
"type": "raw-json",
"label": "Project Folder Structure",
"key": "project_folder_structure"
},
{
"type": "schema",
"name": "schema_project_syncserver"
}
]
}

View file

@ -1,20 +1,20 @@
{
"type": "dict",
"collapsable": true,
"collapsible": true,
"key": "harmony",
"label": "Harmony",
"is_file": true,
"children": [
{
"type": "dict",
"collapsable": true,
"collapsible": true,
"key": "publish",
"label": "Publish plugins",
"children": []
},
{
"type": "dict",
"collapsable": true,
"collapsible": true,
"key": "general",
"label": "General",
"children": [

View file

@ -1,19 +1,19 @@
{
"type": "dict",
"collapsable": true,
"collapsible": true,
"key": "hiero",
"label": "Hiero",
"is_file": true,
"children": [
{
"type": "dict",
"collapsable": true,
"collapsible": true,
"key": "create",
"label": "Create plugins",
"children": [
{
"type": "dict",
"collapsable": true,
"collapsible": true,
"key": "CreateShotClip",
"label": "Create Shot Clip",
"is_group": true,
@ -21,7 +21,7 @@
{
"type": "collapsible-wrap",
"label": "Shot Hierarchy And Rename Settings",
"collapsable": false,
"collapsible": false,
"children": [
{
"type": "text",
@ -53,7 +53,7 @@
{
"type": "collapsible-wrap",
"label": "Shot Template Keywords",
"collapsable": false,
"collapsible": false,
"children": [
{
"type": "text",
@ -85,7 +85,7 @@
{
"type": "collapsible-wrap",
"label": "Vertical Synchronization Of Attributes",
"collapsable": false,
"collapsible": false,
"children": [
{
"type": "boolean",
@ -97,7 +97,7 @@
{
"type": "collapsible-wrap",
"label": "Shot Attributes",
"collapsable": false,
"collapsible": false,
"children": [
{
"type": "number",
@ -122,13 +122,13 @@
},
{
"type": "dict",
"collapsable": true,
"collapsible": true,
"key": "publish",
"label": "Publish plugins",
"children": [
{
"type": "dict",
"collapsable": true,
"collapsible": true,
"checkbox_key": "enabled",
"key": "CollectInstanceVersion",
"label": "Collect Instance Version",
@ -143,7 +143,7 @@
},
{
"type": "dict",
"collapsable": true,
"collapsible": true,
"checkbox_key": "enabled",
"key": "ExtractReviewCutUpVideo",
"label": "Extract Review Cut Up Video",

View file

@ -1,6 +1,6 @@
{
"type": "dict",
"collapsable": true,
"collapsible": true,
"key": "maya",
"label": "Maya",
"is_file": true,

View file

@ -1,19 +1,19 @@
{
"type": "dict",
"collapsable": true,
"collapsible": true,
"key": "nuke",
"label": "Nuke",
"is_file": true,
"children": [
{
"type": "dict",
"collapsable": true,
"collapsible": true,
"key": "create",
"label": "Create plugins",
"children": [
{
"type": "dict",
"collapsable": false,
"collapsible": false,
"key": "CreateWriteRender",
"label": "CreateWriteRender",
"is_group": true,
@ -27,7 +27,7 @@
},
{
"type": "dict",
"collapsable": false,
"collapsible": false,
"key": "CreateWritePrerender",
"label": "CreateWritePrerender",
"is_group": true,
@ -43,13 +43,13 @@
},
{
"type": "dict",
"collapsable": true,
"collapsible": true,
"key": "publish",
"label": "Publish plugins",
"children": [
{
"type": "dict",
"collapsable": true,
"collapsible": true,
"key": "PreCollectNukeInstances",
"label": "PreCollectNukeInstances",
"is_group": true,
@ -63,7 +63,7 @@
},
{
"type": "dict",
"collapsable": true,
"collapsible": true,
"checkbox_key": "enabled",
"key": "ExtractThumbnail",
"label": "ExtractThumbnail",
@ -83,7 +83,7 @@
},
{
"type": "dict",
"collapsable": true,
"collapsible": true,
"checkbox_key": "enabled",
"key": "ValidateKnobs",
"label": "ValidateKnobs",
@ -103,7 +103,7 @@
},
{
"type": "dict",
"collapsable": true,
"collapsible": true,
"checkbox_key": "enabled",
"key": "ExtractReviewDataLut",
"label": "ExtractReviewDataLut",
@ -118,7 +118,7 @@
},
{
"type": "dict",
"collapsable": true,
"collapsible": true,
"checkbox_key": "enabled",
"key": "ExtractReviewDataMov",
"label": "ExtractReviewDataMov",
@ -138,7 +138,7 @@
},
{
"type": "dict",
"collapsable": true,
"collapsible": true,
"key": "ExtractSlateFrame",
"label": "ExtractSlateFrame",
"is_group": true,
@ -152,7 +152,7 @@
},
{
"type": "dict",
"collapsable": true,
"collapsible": true,
"key": "NukeSubmitDeadline",
"label": "NukeSubmitDeadline",
"is_group": true,

View file

@ -1,19 +1,19 @@
{
"type": "dict",
"collapsable": true,
"collapsible": true,
"key": "resolve",
"label": "DaVinci Resolve",
"is_file": true,
"children": [
{
"type": "dict",
"collapsable": true,
"collapsible": true,
"key": "create",
"label": "Creator plugins",
"children": [
{
"type": "dict",
"collapsable": true,
"collapsible": true,
"key": "CreateShotClip",
"label": "Create Shot Clip",
"is_group": true,
@ -21,7 +21,7 @@
{
"type": "collapsible-wrap",
"label": "Shot Hierarchy And Rename Settings",
"collapsable": false,
"collapsible": false,
"children": [
{
"type": "text",
@ -53,7 +53,7 @@
{
"type": "collapsible-wrap",
"label": "Shot Template Keywords",
"collapsable": false,
"collapsible": false,
"children": [
{
"type": "text",
@ -85,7 +85,7 @@
{
"type": "collapsible-wrap",
"label": "Vertical Synchronization Of Attributes",
"collapsable": false,
"collapsible": false,
"children": [
{
"type": "boolean",
@ -97,7 +97,7 @@
{
"type": "collapsible-wrap",
"label": "Shot Attributes",
"collapsable": false,
"collapsible": false,
"children": [
{
"type": "number",

View file

@ -1,27 +1,27 @@
{
"type": "dict",
"collapsable": true,
"collapsible": true,
"key": "standalonepublisher",
"label": "Standalone Publisher",
"is_file": true,
"children": [
{
"type": "dict",
"collapsable": true,
"collapsible": true,
"key": "publish",
"label": "Publish plugins",
"is_file": true,
"children": [
{
"type": "dict",
"collapsable": true,
"collapsible": true,
"key": "ExtractThumbnailSP",
"label": "ExtractThumbnailSP",
"is_group": true,
"children": [
{
"type": "dict",
"collapsable": false,
"collapsible": false,
"key": "ffmpeg_args",
"label": "ffmpeg_args",
"children": [
@ -45,10 +45,10 @@
},
{
"type": "dict-modifiable",
"collapsable": true,
"collapsible": true,
"key": "create",
"label": "Creator plugins",
"collapsable_key": true,
"collapsible_key": true,
"is_file": true,
"object_type": {
"type": "dict",

View file

@ -2,7 +2,7 @@
"type": "dict",
"key": "sync_server",
"label": "Sync Server (currently unused)",
"collapsable": true,
"collapsible": true,
"checkbox_key": "enabled",
"is_file": true,
"children": [
@ -15,7 +15,7 @@
"type": "dict",
"key": "config",
"label": "Config",
"collapsable": true,
"collapsible": true,
"children": [
{
@ -46,10 +46,10 @@
]
}, {
"type": "dict-modifiable",
"collapsable": true,
"collapsible": true,
"key": "sites",
"label": "Sites",
"collapsable_key": false,
"collapsible_key": false,
"is_file": true,
"object_type":
{

View file

@ -1,13 +1,13 @@
{
"type": "dict",
"collapsable": true,
"collapsible": true,
"key": "unreal",
"label": "Unreal Engine",
"is_file": true,
"children": [
{
"type": "dict",
"collapsable": true,
"collapsible": true,
"key": "project_setup",
"label": "Project Setup",
"children": [

View file

@ -1,9 +1,10 @@
{
"type": "dict",
"collapsable": true,
"collapsible": true,
"key": "attributes",
"label": "Attributes",
"is_file": true,
"is_group": true,
"children": [
{
"type": "number",

View file

@ -3,6 +3,7 @@
"key": "imageio",
"label": "Color Management and Output Formats",
"is_file": true,
"is_group": true,
"children": [
{
"key": "hiero",
@ -13,7 +14,7 @@
"key": "workfile",
"type": "dict",
"label": "Workfile",
"collapsable": false,
"collapsible": false,
"children": [
{
"type": "form",
@ -87,7 +88,7 @@
"key": "regexInputs",
"type": "dict",
"label": "Colorspace on Inputs by regex detection",
"collapsable": true,
"collapsible": true,
"children": [
{
"type": "list",
@ -121,8 +122,7 @@
"key": "workfile",
"type": "dict",
"label": "Workfile",
"collapsable": false,
"is_group": true,
"collapsible": false,
"children": [
{
"type": "form",
@ -155,13 +155,13 @@
"spi-anim": "spi-anim"
},
{
"aces_1.0.3": "aces_0.1.1"
"aces_0.1.1": "aces_0.1.1"
},
{
"aces_1.0.3": "aces_0.7.1"
"aces_0.7.1": "aces_0.7.1"
},
{
"aces_1.0.3": "aces_1.0.1"
"aces_1.0.1": "aces_1.0.1"
},
{
"aces_1.0.3": "aces_1.0.3"
@ -219,8 +219,7 @@
"key": "nodes",
"type": "dict",
"label": "Nodes",
"collapsable": true,
"is_group": true,
"collapsible": true,
"children": [
{
"key": "requiredNodes",
@ -324,7 +323,7 @@
"key": "regexInputs",
"type": "dict",
"label": "Colorspace on Inputs by regex detection",
"collapsable": true,
"collapsible": true,
"children": [
{
"type": "list",

View file

@ -1,10 +1,11 @@
{
"type": "dict",
"collapsable": true,
"type": "anatomy_templates",
"key": "templates",
"label": "Templates",
"collapsable_key": true,
"collapsible": true,
"collapsible_key": true,
"is_file": true,
"is_group": true,
"children": [
{
"type": "number",

View file

@ -1,12 +1,12 @@
{
"type": "dict",
"collapsable": true,
"collapsible": true,
"key": "publish",
"label": "Publish plugins",
"children": [
{
"type": "dict",
"collapsable": true,
"collapsible": true,
"checkbox_key": "enabled",
"key": "IntegrateMasterVersion",
"label": "IntegrateMasterVersion",
@ -21,7 +21,7 @@
},
{
"type": "dict",
"collapsable": true,
"collapsible": true,
"checkbox_key": "enabled",
"key": "ExtractJpegEXR",
"label": "ExtractJpegEXR",
@ -54,7 +54,7 @@
},
{
"type": "dict",
"collapsable": true,
"collapsible": true,
"key": "ExtractReview",
"label": "ExtractReview",
"checkbox_key": "enabled",
@ -188,7 +188,7 @@
},
{
"type": "dict",
"collapsable": true,
"collapsible": true,
"key": "ExtractBurnin",
"label": "ExtractBurnin",
"checkbox_key": "enabled",
@ -201,7 +201,7 @@
},
{
"type": "dict",
"collapsable": true,
"collapsible": true,
"key": "options",
"label": "Burnin formating options",
"children": [
@ -268,7 +268,7 @@
"label": "Burnins",
"type": "dict-modifiable",
"highlight_content": true,
"collapsable": false,
"collapsible": false,
"object_type": {
"type": "dict",
"children": [
@ -312,7 +312,7 @@
},
{
"type": "dict",
"collapsable": true,
"collapsible": true,
"key": "IntegrateAssetNew",
"label": "IntegrateAssetNew",
"is_group": true,
@ -326,7 +326,7 @@
},
{
"type": "dict",
"collapsable": true,
"collapsible": true,
"key": "ProcessSubmittedJobOnFarm",
"label": "ProcessSubmittedJobOnFarm",
"checkbox_key": "enabled",

View file

@ -1,18 +1,18 @@
{
"type": "dict",
"collapsable": true,
"collapsible": true,
"key": "tools",
"label": "Tools",
"children": [
{
"type": "dict",
"collapsable": true,
"collapsible": true,
"key": "creator",
"label": "Creator",
"children": [
{
"type": "dict-modifiable",
"collapsable": false,
"collapsible": false,
"key": "families_smart_select",
"label": "Families smart select",
"object_type": {
@ -24,7 +24,7 @@
},
{
"type": "dict",
"collapsable": true,
"collapsible": true,
"key": "Workfiles",
"label": "Workfiles",
"children": [
@ -62,7 +62,7 @@
},
{
"type": "dict-modifiable",
"collapsable": true,
"collapsible": true,
"key": "sw_folders",
"label": "Extra task folders",
"is_group": true,

View file

@ -4,7 +4,7 @@
"children": [
{
"type": "dict",
"collapsable": true,
"collapsible": true,
"key": "capture",
"label": "Maya Playblast settings",
"is_file": true,
@ -311,7 +311,7 @@
},
{
"type": "dict",
"collapsable": true,
"collapsible": true,
"key": "Viewport Options",
"label": "Viewport Options",
"children": [
@ -527,7 +527,7 @@
},
{
"type": "dict",
"collapsable": true,
"collapsible": true,
"key": "Camera Options",
"label": "Camera Options",
"children": [

View file

@ -1,12 +1,12 @@
{
"type": "dict",
"collapsable": true,
"collapsible": true,
"key": "load",
"label": "Loader plugins",
"children": [
{
"type": "dict",
"collapsable": true,
"collapsible": true,
"key": "colors",
"label": "Loaded Subsets Outliner Colors",
"children": [

View file

@ -1,6 +1,6 @@
{
"type": "dict",
"collapsable": true,
"collapsible": true,
"key": "publish",
"label": "Publish plugins",
"children": [
@ -10,7 +10,7 @@
},
{
"type": "dict",
"collapsable": true,
"collapsible": true,
"key": "CollectMayaRender",
"label": "Collect Render Layers",
"children": [
@ -30,7 +30,7 @@
},
{
"type": "dict",
"collapsable": true,
"collapsible": true,
"key": "ValidateCameraAttributes",
"label": "Validate Camera Attributes",
"checkbox_key": "enabled",
@ -49,7 +49,7 @@
},
{
"type": "dict",
"collapsable": true,
"collapsible": true,
"key": "ValidateModelName",
"label": "Validate Model Name",
"checkbox_key": "enabled",
@ -79,7 +79,7 @@
},
{
"type": "dict",
"collapsable": true,
"collapsible": true,
"key": "ValidateAssemblyName",
"label": "Validate Assembly Name",
"checkbox_key": "enabled",
@ -93,7 +93,7 @@
},
{
"type": "dict",
"collapsable": true,
"collapsible": true,
"key": "ValidateShaderName",
"label": "ValidateShaderName",
"checkbox_key": "enabled",
@ -116,7 +116,7 @@
},
{
"type": "dict",
"collapsable": true,
"collapsible": true,
"key": "ValidateMeshHasOverlappingUVs",
"label": "ValidateMeshHasOverlappingUVs",
"checkbox_key": "enabled",
@ -130,7 +130,7 @@
},
{
"type": "dict",
"collapsable": true,
"collapsible": true,
"key": "ValidateAttributes",
"label": "ValidateAttributes",
"checkbox_key": "enabled",
@ -156,7 +156,7 @@
},
{
"type": "dict",
"collapsable": true,
"collapsible": true,
"key": "ExtractCameraAlembic",
"label": "Extract camera to Alembic",
"checkbox_key": "enabled",
@ -184,7 +184,7 @@
},
{
"type": "dict",
"collapsable": true,
"collapsible": true,
"key": "MayaSubmitDeadline",
"label": "Submit maya job to deadline",
"checkbox_key": "enabled",

View file

@ -1,6 +1,6 @@
{
"type": "dict-modifiable",
"collapsable": true,
"collapsible": true,
"key": "filters",
"label": "Publish GUI Filters",
"object_type": {

View file

@ -1,6 +1,6 @@
{
"type": "dict",
"collapsable": true,
"collapsible": true,
"key": "workfile_build",
"label": "Workfile Build Settings",
"children": [

View file

@ -326,10 +326,10 @@
"multipath": true
},
{
"key": "collapsable",
"key": "collapsible",
"type": "dict",
"label": "collapsable dictionary",
"collapsable": true,
"label": "collapsible dictionary",
"collapsible": true,
"is_group": true,
"children": [
{
@ -340,10 +340,10 @@
]
},
{
"key": "collapsable_expanded",
"key": "collapsible_expanded",
"type": "dict",
"label": "collapsable dictionary, expanded on creation",
"collapsable": true,
"label": "collapsible dictionary, expanded on creation",
"collapsible": true,
"collapsed": false,
"is_group": true,
"children": [
@ -355,10 +355,10 @@
]
},
{
"key": "not_collapsable",
"key": "not_collapsible",
"type": "dict",
"label": "Not collapsable",
"collapsable": false,
"label": "Not collapsible",
"collapsible": false,
"is_group": true,
"children": [
{

View file

@ -2,7 +2,7 @@
"type": "dict",
"key": "aftereffects",
"label": "Adobe AfterEffects",
"collapsable": true,
"collapsible": true,
"checkbox_key": "enabled",
"children": [
{

View file

@ -2,7 +2,7 @@
"type": "dict",
"key": "blender",
"label": "Blender",
"collapsable": true,
"collapsible": true,
"checkbox_key": "enabled",
"children": [
{

View file

@ -2,7 +2,7 @@
"type": "dict",
"key": "celaction",
"label": "CelAction2D",
"collapsable": true,
"collapsible": true,
"checkbox_key": "enabled",
"children": [
{

View file

@ -2,7 +2,7 @@
"type": "dict",
"key": "djvview",
"label": "DJV View",
"collapsable": true,
"collapsible": true,
"checkbox_key": "enabled",
"children": [
{

View file

@ -2,7 +2,7 @@
"type": "dict",
"key": "fusion",
"label": "Blackmagic Fusion",
"collapsable": true,
"collapsible": true,
"checkbox_key": "enabled",
"children": [
{

View file

@ -2,7 +2,7 @@
"type": "dict",
"key": "harmony",
"label": "Toon Boom Harmony",
"collapsable": true,
"collapsible": true,
"checkbox_key": "enabled",
"children": [
{

View file

@ -2,7 +2,7 @@
"type": "dict",
"key": "houdini",
"label": "SideFX Houdini",
"collapsable": true,
"collapsible": true,
"checkbox_key": "enabled",
"children": [
{

View file

@ -2,7 +2,7 @@
"type": "dict",
"key": "maya",
"label": "Autodesk Maya",
"collapsable": true,
"collapsible": true,
"checkbox_key": "enabled",
"children": [
{

View file

@ -2,7 +2,7 @@
"type": "dict",
"key": "mayabatch",
"label": "Autodesk Maya Batch",
"collapsable": true,
"collapsible": true,
"checkbox_key": "enabled",
"children": [
{

View file

@ -2,7 +2,7 @@
"type": "dict",
"key": "photoshop",
"label": "Adobe Photoshop",
"collapsable": true,
"collapsible": true,
"checkbox_key": "enabled",
"children": [
{

View file

@ -2,7 +2,7 @@
"type": "dict",
"key": "resolve",
"label": "Blackmagic DaVinci Resolve",
"collapsable": true,
"collapsible": true,
"checkbox_key": "enabled",
"children": [
{

View file

@ -2,7 +2,7 @@
"type": "dict",
"key": "shell",
"label": "Shell",
"collapsable": true,
"collapsible": true,
"checkbox_key": "enabled",
"children": [
{

View file

@ -2,7 +2,7 @@
"type": "dict",
"key": "tvpaint",
"label": "TVPaint",
"collapsable": true,
"collapsible": true,
"checkbox_key": "enabled",
"children": [
{

View file

@ -2,7 +2,7 @@
"type": "dict",
"key": "unreal",
"label": "Unreal Editor",
"collapsable": true,
"collapsible": true,
"checkbox_key": "enabled",
"children": [
{

View file

@ -9,7 +9,7 @@
"type": "dict",
"key": "{host_name}_{host_version}",
"label": "{host_version}",
"collapsable": true,
"collapsible": true,
"checkbox_key": "enabled",
"children": [
{

View file

@ -3,7 +3,7 @@
"type": "dict",
"key": "{nuke_type}",
"label": "Foundry {nuke_label}",
"collapsable": true,
"collapsible": true,
"checkbox_key": "enabled",
"children": [
{

View file

@ -2,7 +2,7 @@
"type": "dict",
"key": "ftrack",
"label": "Ftrack",
"collapsable": true,
"collapsible": true,
"checkbox_key": "enabled",
"children": [
{
@ -58,7 +58,7 @@
"key": "intent",
"type": "dict",
"label": "Intent",
"collapsable_key": true,
"collapsible_key": true,
"is_group": true,
"children": [
{

View file

@ -2,7 +2,7 @@
"key": "applications",
"type": "dict",
"label": "Applications",
"collapsable": true,
"collapsible": true,
"is_file": true,
"children": [
{

View file

@ -2,7 +2,7 @@
"key": "general",
"type": "dict",
"label": "General",
"collapsable": true,
"collapsible": true,
"is_file": true,
"children": [
{

View file

@ -2,14 +2,14 @@
"key": "modules",
"type": "dict",
"label": "Modules",
"collapsable": true,
"collapsible": true,
"is_file": true,
"children": [
{
"type": "dict",
"key": "avalon",
"label": "Avalon",
"collapsable": true,
"collapsible": true,
"children": [
{
"type": "text",
@ -45,7 +45,7 @@
"type": "dict",
"key": "rest_api",
"label": "Rest Api",
"collapsable": true,
"collapsible": true,
"children": [
{
"type": "number",
@ -70,7 +70,7 @@
"type": "dict",
"key": "timers_manager",
"label": "Timers Manager",
"collapsable": true,
"collapsible": true,
"checkbox_key": "enabled",
"children": [
{
@ -96,7 +96,7 @@
"type": "dict",
"key": "clockify",
"label": "Clockify",
"collapsable": true,
"collapsible": true,
"checkbox_key": "enabled",
"children": [
{
@ -114,7 +114,7 @@
"type": "dict",
"key": "sync_server",
"label": "Sync Server",
"collapsable": true,
"collapsible": true,
"checkbox_key": "enabled",
"children": [{
"type": "boolean",
@ -125,7 +125,7 @@
"type": "dict",
"key": "deadline",
"label": "Deadline",
"collapsable": true,
"collapsible": true,
"checkbox_key": "enabled",
"children": [
{
@ -144,7 +144,7 @@
"type": "dict",
"key": "muster",
"label": "Muster",
"collapsable": true,
"collapsible": true,
"checkbox_key": "enabled",
"children": [
{
@ -175,7 +175,7 @@
"type": "dict",
"key": "log_viewer",
"label": "Logging",
"collapsable": true,
"collapsible": true,
"checkbox_key": "enabled",
"children": [
{
@ -189,7 +189,7 @@
"type": "dict",
"key": "user",
"label": "User setting",
"collapsable": true,
"collapsible": true,
"checkbox_key": "enabled",
"children": [
{
@ -203,7 +203,7 @@
"type": "dict",
"key": "standalonepublish_tool",
"label": "Standalone Publish",
"collapsable": true,
"collapsible": true,
"checkbox_key": "enabled",
"children": [
{
@ -217,7 +217,7 @@
"type": "dict",
"key": "idle_manager",
"label": "Idle Manager",
"collapsable": true,
"collapsible": true,
"checkbox_key": "enabled",
"children": [
{

View file

@ -2,7 +2,7 @@
"key": "tools",
"type": "dict",
"label": "Tools",
"collapsable": true,
"collapsible": true,
"is_file": true,
"children": [
{

View file

@ -2,7 +2,7 @@
"type": "dict",
"key": "mtoa",
"label": "Autodesk Arnold",
"collapsable": true,
"collapsible": true,
"checkbox_key": "enabled",
"children": [
{

View file

@ -2,7 +2,7 @@
"type": "dict",
"key": "vray",
"label": "Chaos Group Vray",
"collapsable": true,
"collapsible": true,
"checkbox_key": "enabled",
"children": [
{

View file

@ -2,7 +2,7 @@
"type": "dict",
"key": "yeti",
"label": "Pergrine Labs Yeti",
"collapsable": true,
"collapsible": true,
"checkbox_key": "enabled",
"children": [
{

View file

@ -126,8 +126,8 @@
- without label it is just wrap item holding `"key"`
- can't have `"is_group"` key set to True as it breaks visual override showing
- if `"label"` is entetered there which will be shown in GUI
- item with label can be collapsable
- that can be set with key `"collapsable"` as `True`/`False` (Default: `True`)
- 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
@ -138,7 +138,7 @@
"key": "applications",
"type": "dict",
"label": "Applications",
"collapsable": true,
"collapsible": true,
"highlight_content": true,
"is_group": true,
"is_file": true,
@ -320,8 +320,8 @@
- there are 2 possible ways how to set the type:
1.) dictionary with item modifiers (`number` input has `minimum`, `maximum` and `decimals`) in that case item type must be set as value of `"type"` (example below)
2.) item type name as string without modifiers (e.g. `text`)
- this input can be collapsable
- that can be set with key `"collapsable"` as `True`/`False` (Default: `True`)
- this input 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`)
1.) with item modifiers

View file

@ -1,33 +0,0 @@
{
"type": "dict",
"collapsable": true,
"key": "global",
"label": "Global",
"is_file": true,
"children": [
{
"type": "schema",
"name": "schema_global_publish"
},
{
"type": "schema",
"name": "schema_global_tools"
},
{
"type": "collapsible-wrap",
"label": "Project Folder Structure",
"children": [
{
"type": "raw-json",
"key": "project_folder_structure"
}
]
},
{
"type": "schema",
"name": "schema_project_syncserver"
}
]
}

View file

@ -1,11 +1,8 @@
from .window import MainWidget
from .categories import ProjectListWidget
from . import item_types
from . import anatomy_types
from .widgets import ProjectListWidget
__all__ = [
"MainWidget",
"ProjectListWidget",
"item_types",
"anatomy_types"
"ProjectListWidget"
]

View file

@ -1,574 +0,0 @@
from Qt import QtWidgets, QtCore
from .widgets import ExpandingWidget
from .item_types import (
SettingObject,
ModifiableDict,
PathWidget,
RawJsonWidget,
DictWidget
)
from .lib import NOT_SET, TypeToKlass, CHILD_OFFSET, METADATA_KEY
class AnatomyWidget(QtWidgets.QWidget, SettingObject):
value_changed = QtCore.Signal(object)
template_keys = (
"project[name]",
"project[code]",
"asset",
"task",
"subset",
"family",
"version",
"ext",
"representation"
)
default_exmaple_data = {
"project": {
"name": "ProjectPype",
"code": "pp",
},
"asset": "sq01sh0010",
"task": "compositing",
"subset": "renderMain",
"family": "render",
"version": 1,
"ext": ".png",
"representation": "png"
}
def __init__(self, schema_data, parent, as_widget=False):
if as_widget:
raise TypeError(
"`AnatomyWidget` does not allow to be used as widget."
)
super(AnatomyWidget, self).__init__(parent)
self.setObjectName("AnatomyWidget")
self.initial_attributes(schema_data, parent, as_widget)
self.input_fields = []
self.key = schema_data["key"]
def create_ui(self, label_widget=None):
children_data = self.schema_data["children"]
for schema_data in children_data:
item = TypeToKlass.types[schema_data["type"]](schema_data, self)
item.create_ui()
self.input_fields.append(item)
self.setAttribute(QtCore.Qt.WA_StyledBackground)
body_widget = ExpandingWidget("Anatomy", self)
layout = QtWidgets.QVBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(0)
layout.addWidget(body_widget)
content_widget = QtWidgets.QWidget(body_widget)
content_layout = QtWidgets.QVBoxLayout(content_widget)
content_layout.setContentsMargins(CHILD_OFFSET, 5, 0, 0)
content_layout.setSpacing(5)
for input_field in self.input_fields:
content_layout.addWidget(input_field)
input_field.value_changed.connect(self._on_value_change)
body_widget.set_content_widget(content_widget)
self.body_widget = body_widget
self.label_widget = body_widget.label_widget
def update_default_values(self, parent_values):
self._state = None
self._child_state = None
if isinstance(parent_values, dict):
value = parent_values.get(self.key, NOT_SET)
else:
value = NOT_SET
for input_field in self.input_fields:
input_field.update_default_values(value)
def update_studio_values(self, parent_values):
self._state = None
self._child_state = None
if isinstance(parent_values, dict):
value = parent_values.get(self.key, NOT_SET)
else:
value = NOT_SET
for input_field in self.input_fields:
input_field.update_studio_values(value)
def apply_overrides(self, parent_values):
# Make sure this is set to False
self._state = None
self._child_state = None
value = NOT_SET
if parent_values is not NOT_SET:
value = parent_values.get(self.key, value)
for input_field in self.input_fields:
input_field.apply_overrides(value)
def set_value(self, value):
raise TypeError("AnatomyWidget does not allow to use `set_value`")
def _on_value_change(self, item=None):
if self.ignore_value_changes:
return
self.hierarchical_style_update()
self.value_changed.emit(self)
def update_style(self, is_overriden=None):
child_has_studio_override = self.child_has_studio_override
child_modified = self.child_modified
child_invalid = self.child_invalid
child_state = self.style_state(
child_has_studio_override,
child_invalid,
self.child_overriden,
child_modified
)
if child_state:
child_state = "child-{}".format(child_state)
if child_state != self._child_state:
self.body_widget.side_line_widget.setProperty("state", child_state)
self.body_widget.side_line_widget.style().polish(
self.body_widget.side_line_widget
)
self._child_state = child_state
def hierarchical_style_update(self):
for input_field in self.input_fields:
input_field.hierarchical_style_update()
self.update_style()
@property
def child_has_studio_override(self):
for input_field in self.input_fields:
if input_field.child_has_studio_override:
return True
return False
@property
def child_modified(self):
for input_field in self.input_fields:
if input_field.child_modified:
return True
return False
@property
def child_overriden(self):
for input_field in self.input_fields:
if input_field.child_overriden:
return True
return False
@property
def child_invalid(self):
for input_field in self.input_fields:
if input_field.child_invalid:
return True
return False
def set_as_overriden(self):
for input_field in self.input_fields:
input_field.child_invalid.set_as_overriden()
def remove_overrides(self):
for input_field in self.input_fields:
input_field.remove_overrides()
def reset_to_pype_default(self):
for input_field in self.input_fields:
input_field.reset_to_pype_default()
def set_studio_default(self):
for input_field in self.input_fields:
input_field.set_studio_default()
def discard_changes(self):
for input_field in self.input_fields:
input_field.discard_changes()
def overrides(self):
if self.child_overriden:
return self.config_value(), True
return NOT_SET, False
def item_value(self):
output = {}
for input_field in self.input_fields:
output.update(input_field.config_value())
return output
def studio_overrides(self):
has_overrides = False
for input_field in self.input_fields:
if input_field.child_has_studio_override:
has_overrides = True
break
if not has_overrides:
return NOT_SET, False
groups = []
for input_field in self.input_fields:
groups.append(input_field.key)
value = self.config_value()
if METADATA_KEY not in value[self.key]:
value[self.key][METADATA_KEY] = {}
value[self.key][METADATA_KEY]["groups"] = groups
return value, True
def config_value(self):
return {self.key: self.item_value()}
class RootsWidget(QtWidgets.QWidget, SettingObject):
value_changed = QtCore.Signal(object)
def __init__(self, input_data, parent):
super(RootsWidget, self).__init__(parent)
self.setObjectName("RootsWidget")
input_data["is_group"] = True
self.initial_attributes(input_data, parent, False)
self.key = input_data["key"]
self.was_multiroot = NOT_SET
def create_ui(self, _label_widget=None):
body_widget = ExpandingWidget("Roots", self)
content_widget = QtWidgets.QWidget(body_widget)
multiroot_data = {
"key": self.key,
"expandable": False,
"object_type": {
"type": "path-widget",
"multiplatform": True
}
}
roots_widget = ModifiableDict(
multiroot_data, self,
as_widget=True, parent_widget=content_widget
)
roots_widget.create_ui()
content_layout = QtWidgets.QVBoxLayout(content_widget)
content_layout.setContentsMargins(0, 0, 0, 0)
content_layout.addWidget(roots_widget)
body_widget.set_content_widget(content_widget)
self.label_widget = body_widget.label_widget
main_layout = QtWidgets.QVBoxLayout(self)
main_layout.setContentsMargins(0, 0, 0, 0)
main_layout.addWidget(body_widget)
self.body_widget = body_widget
self.roots_widget = roots_widget
roots_widget.value_changed.connect(self._on_value_change)
def update_default_values(self, parent_values):
self._state = None
self._is_modified = False
if isinstance(parent_values, dict):
value = parent_values.get(self.key, NOT_SET)
else:
value = NOT_SET
self._has_studio_override = False
self._had_studio_override = False
is_multiroot = True
# Backward compatibility: Allow to switch to multiroot
if isinstance(value, dict):
is_multiroot = False
for _value in value.values():
if isinstance(_value, dict):
is_multiroot = True
break
if not is_multiroot:
value = {"": value}
self.roots_widget.update_default_values(value)
def update_studio_values(self, parent_values):
self._state = None
self._is_modified = False
if isinstance(parent_values, dict):
value = parent_values.get(self.key, NOT_SET)
else:
value = NOT_SET
self._has_studio_override = value is not NOT_SET
self._had_studio_override = value is not NOT_SET
self.roots_widget.update_studio_values(value)
def apply_overrides(self, parent_values):
# Make sure this is set to False
self._state = None
self._is_modified = False
value = NOT_SET
if parent_values is not NOT_SET:
value = parent_values.get(self.key, value)
self._is_overriden = value is not NOT_SET
self._was_overriden = bool(self._is_overriden)
self.roots_widget.apply_overrides(value)
def hierarchical_style_update(self):
self.roots_widget.hierarchical_style_update()
self.update_style()
def update_style(self):
state = self.style_state(
self.has_studio_override,
self.child_invalid,
self.is_overriden,
self.is_modified
)
if self._state == state:
return
if state:
child_state = "child-{}".format(state)
else:
child_state = ""
self.body_widget.side_line_widget.setProperty("state", child_state)
self.body_widget.side_line_widget.style().polish(
self.body_widget.side_line_widget
)
self.label_widget.setProperty("state", state)
self.label_widget.style().polish(self.label_widget)
self._state = state
def _on_value_change(self, item=None):
if self.ignore_value_changes:
return
if self.is_group and self.is_overidable:
self._is_overriden = True
self._is_modified = bool(self.child_modified)
self.update_style()
self.value_changed.emit(self)
@property
def child_has_studio_override(self):
return self.roots_widget.has_studio_override
@property
def child_modified(self):
return self.roots_widget.child_modified
@property
def child_overriden(self):
return (
self.roots_widget.is_overriden
or self.roots_widget.child_overriden
)
@property
def child_invalid(self):
return self.roots_widget.child_invalid
def remove_overrides(self):
self._is_overriden = False
self._is_modified = False
self.roots_widget.remove_overrides()
def reset_to_pype_default(self):
self.roots_widget.reset_to_pype_default()
self._has_studio_override = False
def set_studio_default(self):
self.roots_widget.reset_to_pype_default()
self._has_studio_override = True
def discard_changes(self):
self._is_overriden = self._was_overriden
self._is_modified = False
self.roots_widget.discard_changes()
self._is_modified = self.child_modified
self._has_studio_override = self._had_studio_override
def set_as_overriden(self):
self._is_overriden = True
self.roots_widget.set_as_overriden()
def item_value(self):
return self.roots_widget.item_value()
def config_value(self):
return {self.key: self.item_value()}
class TemplatesWidget(QtWidgets.QWidget, SettingObject):
value_changed = QtCore.Signal(object)
def __init__(self, input_data, parent):
super(TemplatesWidget, self).__init__(parent)
input_data["is_group"] = True
self.initial_attributes(input_data, parent, False)
self.key = input_data["key"]
def create_ui(self, label_widget=None):
body_widget = ExpandingWidget("Templates", self)
content_widget = QtWidgets.QWidget(body_widget)
body_widget.set_content_widget(content_widget)
content_layout = QtWidgets.QVBoxLayout(content_widget)
template_input_data = {
"key": self.key
}
self.body_widget = body_widget
self.label_widget = body_widget.label_widget
self.value_input = RawJsonWidget(template_input_data, self)
self.value_input.create_ui(label_widget=self.label_widget)
content_layout.addWidget(self.value_input)
layout = QtWidgets.QVBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(0)
layout.addWidget(body_widget)
self.value_input.value_changed.connect(self._on_value_change)
def _on_value_change(self, item):
self.update_style()
self.value_changed.emit(self)
def update_default_values(self, values):
self._state = None
self.value_input.update_default_values(values)
def update_studio_values(self, values):
self._state = None
self.value_input.update_studio_values(values)
def apply_overrides(self, parent_values):
self._state = None
self.value_input.apply_overrides(parent_values)
def hierarchical_style_update(self):
self.value_input.hierarchical_style_update()
self.update_style()
def update_style(self):
state = self.style_state(
self.has_studio_override,
self.child_invalid,
self.child_overriden,
self.child_modified
)
if self._state == state:
return
if state:
child_state = "child-{}".format(state)
else:
child_state = ""
self.body_widget.side_line_widget.setProperty("state", child_state)
self.body_widget.side_line_widget.style().polish(
self.body_widget.side_line_widget
)
self.label_widget.setProperty("state", state)
self.label_widget.style().polish(self.label_widget)
self._state = state
@property
def is_modified(self):
return self.value_input.is_modified
@property
def is_overriden(self):
return self._is_overriden
@property
def has_studio_override(self):
return self.value_input._has_studio_override
@property
def child_has_studio_override(self):
return self.value_input.child_has_studio_override
@property
def child_modified(self):
return self.value_input.child_modified
@property
def child_overriden(self):
return self.value_input.child_overriden
@property
def child_invalid(self):
return self.value_input.child_invalid
def remove_overrides(self):
self.value_input.remove_overrides()
def reset_to_pype_default(self):
self.value_input.reset_to_pype_default()
def set_studio_default(self):
self.value_input.set_studio_default()
def discard_changes(self):
self.value_input.discard_changes()
def set_as_overriden(self):
self.value_input.set_as_overriden()
def overrides(self):
if not self.child_overriden:
return NOT_SET, False
return self.config_value(), True
def item_value(self):
return self.value_input.item_value()
def config_value(self):
return self.value_input.config_value()
TypeToKlass.types["anatomy"] = AnatomyWidget
TypeToKlass.types["anatomy_roots"] = RootsWidget
TypeToKlass.types["anatomy_templates"] = TemplatesWidget

View file

@ -4,15 +4,12 @@ from Qt import QtWidgets, QtGui, QtCore
class BaseWidget(QtWidgets.QWidget):
allow_actions = True
def __init__(self, entity, entity_widget):
def __init__(self, category_widget, entity, entity_widget):
self.category_widget = category_widget
self.entity = entity
self.entity_widget = entity_widget
self.trigger_hierarchical_style_update = (
self.entity_widget.trigger_hierarchical_style_update
)
self.ignore_input_changes = entity_widget.ignore_input_changes
self.create_ui_for_entity = entity_widget.create_ui_for_entity
self._is_invalid = False
self._style_state = None
@ -24,6 +21,12 @@ class BaseWidget(QtWidgets.QWidget):
self.label_widget = None
self.create_ui()
def trigger_hierarchical_style_update(self):
self.category_widget.hierarchical_style_update()
def create_ui_for_entity(self, *args, **kwargs):
return self.category_widget.create_ui_for_entity(*args, **kwargs)
@property
def is_invalid(self):
return self._is_invalid
@ -59,14 +62,14 @@ class BaseWidget(QtWidgets.QWidget):
def _on_entity_change(self):
"""Not yet used."""
print("{}: Wraning missing `_on_entity_change` implementation".format(
print("{}: Warning missing `_on_entity_change` implementation".format(
self.__class__.__name__
))
def _discard_changes_action(self, menu, actions_mapping):
# TODO use better condition as unsaved changes may be caused due to
# changes in schema.
if not self.entity.has_unsaved_changes:
if not self.entity.can_discard_changes:
return
def discard_changes():
@ -78,74 +81,46 @@ class BaseWidget(QtWidgets.QWidget):
actions_mapping[action] = discard_changes
menu.addAction(action)
def _set_project_override_action(self, menu, actions_mapping):
# Show only when project overrides are set
if not self.entity.root_item.is_in_project_state():
return
# Do not show on items under group item
if self.entity.group_item:
return
# Skip if already is marked to save project overrides
if self.entity.is_group and self.entity.has_studio_override:
return
action = QtWidgets.QAction("Add to project project override")
actions_mapping[action] = self.entity.add_to_project_override
menu.addAction(action)
def _remove_from_studio_default_action(self, menu, actions_mapping):
if not self.entity.root_item.is_in_studio_state():
return
if self.entity.has_studio_override:
def remove_from_studio_default():
self.ignore_input_changes.set_ignore(True)
self.entity.remove_from_studio_default()
self.ignore_input_changes.set_ignore(False)
action = QtWidgets.QAction("Remove from studio default")
actions_mapping[action] = remove_from_studio_default
menu.addAction(action)
def _add_to_studio_default(self, menu, actions_mapping):
"""Set values as studio overrides."""
# Skip if not in studio overrides
if not self.entity.root_item.is_in_studio_state():
return
# Skip if entity is under group
if self.entity.group_item:
return
# Skip if is group and any children is already marked with studio
# overrides
if self.entity.is_group and self.entity.has_studio_override:
if not self.entity.can_add_to_studio_default:
return
action = QtWidgets.QAction("Add to studio default")
actions_mapping[action] = self.entity.add_to_studio_default
menu.addAction(action)
def _remove_project_override_action(self, menu, actions_mapping):
# Dynamic items can't have these actions
if self.entity.is_dynamic_item or self.entity.is_in_dynamic_item:
def _remove_from_studio_default_action(self, menu, actions_mapping):
if not self.entity.can_remove_from_studio_default:
return
if self.entity.is_group:
if not self.entity.has_project_override:
return
def remove_from_studio_default():
self.ignore_input_changes.set_ignore(True)
self.entity.remove_from_studio_default()
self.ignore_input_changes.set_ignore(False)
action = QtWidgets.QAction("Remove from studio default")
actions_mapping[action] = remove_from_studio_default
menu.addAction(action)
elif self.entity.group_item:
if not self.entity.group_item.has_project_override:
return
elif not self.entity.has_project_override:
def _add_to_project_override_action(self, menu, actions_mapping):
if not self.entity.can_add_to_project_override:
return
# TODO better label
action = QtWidgets.QAction("Add to project project override")
actions_mapping[action] = self.entity.add_to_project_override
menu.addAction(action)
def _remove_from_project_override_action(self, menu, actions_mapping):
if not self.entity.can_remove_from_project_override:
return
def remove_from_project_override():
self.ignore_input_changes.set_ignore(True)
self.entity.remove_from_project_override()
self.ignore_input_changes.set_ignore(False)
action = QtWidgets.QAction("Remove from project override")
actions_mapping[action] = self.entity.remove_from_project_override
actions_mapping[action] = remove_from_project_override
menu.addAction(action)
def show_actions_menu(self, event=None):
@ -164,8 +139,8 @@ class BaseWidget(QtWidgets.QWidget):
self._discard_changes_action(menu, actions_mapping)
self._add_to_studio_default(menu, actions_mapping)
self._remove_from_studio_default_action(menu, actions_mapping)
self._set_project_override_action(menu, actions_mapping)
self._remove_project_override_action(menu, actions_mapping)
self._add_to_project_override_action(menu, actions_mapping)
self._remove_from_project_override_action(menu, actions_mapping)
if not actions_mapping:
action = QtWidgets.QAction("< No action >")
@ -265,3 +240,28 @@ class GUIWidget(BaseWidget):
def get_invalid(self):
return []
class MockUpWidget(BaseWidget):
allow_actions = False
child_invalid = False
def create_ui(self):
self.setObjectName("LabelWidget")
label = "Mockup widget for entity {}".format(self.entity.path)
label_widget = QtWidgets.QLabel(label, self)
layout = QtWidgets.QHBoxLayout(self)
layout.setContentsMargins(0, 5, 0, 5)
layout.addWidget(label_widget)
self.entity_widget.add_widget_to_layout(self)
def set_entity_value(self):
return
def hierarchical_style_update(self):
pass
def get_invalid(self):
return []

View file

@ -1,13 +1,10 @@
import os
import json
from enum import Enum
from Qt import QtWidgets, QtCore, QtGui
from pype.settings.constants import (
PROJECT_SETTINGS_KEY,
PROJECT_ANATOMY_KEY
)
from pype.settings.entities import (
SystemSettings,
ProjectSettings,
GUIEntity,
DictImmutableKeysEntity,
@ -23,31 +20,18 @@ from pype.settings.entities import (
PathInput,
RawJsonEntity,
DefaultsNotDefined
DefaultsNotDefined,
StudioDefaultsNotDefined
)
from pype.settings.lib import (
DEFAULTS_DIR,
from pype.settings.lib import get_system_settings
from .widgets import ProjectListWidget
reset_default_settings,
get_default_settings,
get_studio_project_settings_overrides,
get_studio_project_anatomy_overrides,
get_project_settings_overrides,
get_project_anatomy_overrides,
save_project_settings,
save_project_anatomy,
get_system_settings
)
from .widgets import UnsavedChangesDialog
from . import lib
from .base import GUIWidget
from .list_item_widget import ListWidget
from .list_strict_widget import ListStrictWidget
from .dict_mutable_widget import DictMutableKeysWidget
from .item_widgets import (
BoolWidget,
@ -59,10 +43,7 @@ from .item_widgets import (
PathWidget,
PathInputWidget
)
from avalon.mongodb import (
AvalonMongoConnection,
AvalonMongoDB
)
from avalon.vendor import qtawesome
@ -71,10 +52,23 @@ class CategoryState(Enum):
Working = object()
class SettingsCategoryWidget(QtWidgets.QWidget):
schema_category = None
initial_schema_name = None
class IgnoreInputChangesObj:
def __init__(self, top_widget):
self._ignore_changes = False
self.top_widget = top_widget
def __bool__(self):
return self._ignore_changes
def set_ignore(self, ignore_changes=True):
if self._ignore_changes == ignore_changes:
return
self._ignore_changes = ignore_changes
if not ignore_changes:
self.top_widget.hierarchical_style_update()
class SettingsCategoryWidget(QtWidgets.QWidget):
state_changed = QtCore.Signal()
saved = QtCore.Signal(QtWidgets.QWidget)
@ -83,48 +77,58 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
self.user_role = user_role
self.entity = None
self._state = CategoryState.Idle
self._hide_studio_overrides = False
self.ignore_input_changes = IgnoreInputChangesObj(self)
self.keys = []
self.input_fields = []
self.initialize_attributes()
self.create_ui()
@staticmethod
def create_ui_for_entity(entity, entity_widget):
def create_ui_for_entity(category_widget, entity, entity_widget):
args = (category_widget, entity, entity_widget)
if isinstance(entity, GUIEntity):
return GUIWidget(entity, entity_widget)
return GUIWidget(*args)
elif isinstance(entity, DictImmutableKeysEntity):
return DictImmutableKeysWidget(entity, entity_widget)
return DictImmutableKeysWidget(*args)
elif isinstance(entity, BoolEntity):
return BoolWidget(entity, entity_widget)
return BoolWidget(*args)
elif isinstance(entity, TextEntity):
return TextWidget(entity, entity_widget)
return TextWidget(*args)
elif isinstance(entity, NumberEntity):
return NumberWidget(entity, entity_widget)
return NumberWidget(*args)
elif isinstance(entity, RawJsonEntity):
return RawJsonWidget(entity, entity_widget)
return RawJsonWidget(*args)
elif isinstance(entity, EnumEntity):
return EnumeratorWidget(entity, entity_widget)
return EnumeratorWidget(*args)
elif isinstance(entity, PathEntity):
return PathWidget(entity, entity_widget)
return PathWidget(*args)
elif isinstance(entity, PathInput):
return PathInputWidget(entity, entity_widget)
return PathInputWidget(*args)
elif isinstance(entity, ListEntity):
return ListWidget(entity, entity_widget)
return ListWidget(*args)
elif isinstance(entity, DictMutableKeysEntity):
return DictMutableKeysWidget(entity, entity_widget)
return DictMutableKeysWidget(*args)
elif isinstance(entity, ListStrictEntity):
pass
return ListStrictWidget(*args)
label = "<{}>: {} ({})".format(
entity.__class__.__name__, entity.path, entity.value
@ -152,357 +156,8 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
app.processEvents()
def initialize_attributes(self):
self._hide_studio_overrides = False
self._ignore_value_changes = False
self.keys = []
self.input_fields = []
self.schema = None
self.main_schema_key = None
# Required attributes for items
self.is_overidable = False
self._has_studio_override = False
self._is_overriden = False
self._as_widget = False
self._is_group = False
self._any_parent_as_widget = False
self._any_parent_is_group = False
self.has_studio_override = self._has_studio_override
self.is_overriden = self._is_overriden
self.as_widget = self._as_widget
self.is_group = self._as_widget
self.any_parent_as_widget = self._any_parent_as_widget
self.any_parent_is_group = self._any_parent_is_group
def create_ui(self):
scroll_widget = QtWidgets.QScrollArea(self)
scroll_widget.setObjectName("GroupWidget")
content_widget = QtWidgets.QWidget(scroll_widget)
content_layout = QtWidgets.QVBoxLayout(content_widget)
content_layout.setContentsMargins(3, 3, 3, 3)
content_layout.setSpacing(0)
content_layout.setAlignment(QtCore.Qt.AlignTop)
scroll_widget.setWidgetResizable(True)
scroll_widget.setWidget(content_widget)
configurations_widget = QtWidgets.QWidget(self)
footer_widget = QtWidgets.QWidget(configurations_widget)
footer_layout = QtWidgets.QHBoxLayout(footer_widget)
if self.user_role == "developer":
self._add_developer_ui(footer_layout)
save_btn = QtWidgets.QPushButton("Save")
spacer_widget = QtWidgets.QWidget()
footer_layout.addWidget(spacer_widget, 1)
footer_layout.addWidget(save_btn, 0)
configurations_layout = QtWidgets.QVBoxLayout(configurations_widget)
configurations_layout.setContentsMargins(0, 0, 0, 0)
configurations_layout.setSpacing(0)
configurations_layout.addWidget(scroll_widget, 1)
configurations_layout.addWidget(footer_widget, 0)
main_layout = QtWidgets.QHBoxLayout(self)
main_layout.setContentsMargins(0, 0, 0, 0)
main_layout.setSpacing(0)
main_layout.addWidget(configurations_widget, 1)
save_btn.clicked.connect(self._save)
self.scroll_widget = scroll_widget
self.content_layout = content_layout
self.content_widget = content_widget
self.configurations_widget = configurations_widget
self.main_layout = main_layout
self.ui_tweaks()
def ui_tweaks(self):
return
def _add_developer_ui(self, footer_layout):
save_as_default_btn = QtWidgets.QPushButton("Save as Default")
refresh_icon = qtawesome.icon("fa.refresh", color="white")
refresh_button = QtWidgets.QPushButton()
refresh_button.setIcon(refresh_icon)
hide_studio_overrides = QtWidgets.QCheckBox()
hide_studio_overrides.setChecked(self._hide_studio_overrides)
hide_studio_overrides_widget = QtWidgets.QWidget()
hide_studio_overrides_layout = QtWidgets.QHBoxLayout(
hide_studio_overrides_widget
)
_label_widget = QtWidgets.QLabel(
"Hide studio overrides", hide_studio_overrides_widget
)
hide_studio_overrides_layout.addWidget(_label_widget)
hide_studio_overrides_layout.addWidget(hide_studio_overrides)
footer_layout.addWidget(save_as_default_btn, 0)
footer_layout.addWidget(refresh_button, 0)
footer_layout.addWidget(hide_studio_overrides_widget, 0)
save_as_default_btn.clicked.connect(self._save_as_defaults)
refresh_button.clicked.connect(self._on_refresh)
hide_studio_overrides.stateChanged.connect(
self._on_hide_studio_overrides
)
def save(self):
"""Save procedure."""
raise NotImplementedError("Method `save` is not implemented.")
def defaults_dir(self):
"""Path to defaults folder."""
raise NotImplementedError("Method `defaults_dir` is not implemented.")
def update_values(self):
"""Procedure of update values of items on context change or reset."""
raise NotImplementedError("Method `update_values` is not implemented.")
def validate_defaults_to_save(self, value):
raise NotImplementedError(
"Method `validate_defaults_to_save` not implemented."
)
def any_parent_overriden(self):
return False
@property
def ignore_value_changes(self):
return self._ignore_value_changes
@ignore_value_changes.setter
def ignore_value_changes(self, value):
self._ignore_value_changes = value
if value is False:
self.hierarchical_style_update()
def hierarchical_style_update(self):
for input_field in self.input_fields:
input_field.hierarchical_style_update()
def reset(self):
self.set_state(CategoryState.Working)
reset_default_settings()
self.keys.clear()
self.input_fields.clear()
while self.content_layout.count() != 0:
widget = self.content_layout.itemAt(0).widget()
widget.setVisible(False)
self.content_layout.removeWidget(widget)
widget.deleteLater()
self.schema = lib.gui_schema(
self.schema_category, self.initial_schema_name
)
self.main_schema_key = self.schema["key"]
self.add_children_gui(self.schema)
self._update_values()
self.hierarchical_style_update()
self.set_state(CategoryState.Idle)
def items_are_valid(self):
has_invalid = False
for item in self.input_fields:
if item.child_invalid:
has_invalid = True
if not has_invalid:
return True
invalid_items = []
for item in self.input_fields:
invalid_items.extend(item.get_invalid())
msg_box = QtWidgets.QMessageBox(
QtWidgets.QMessageBox.Warning,
"Invalid input",
"There is invalid value in one of inputs."
" Please lead red color and fix them."
)
msg_box.setStandardButtons(QtWidgets.QMessageBox.Ok)
msg_box.exec_()
first_invalid_item = invalid_items[0]
self.scroll_widget.ensureWidgetVisible(first_invalid_item)
if first_invalid_item.isVisible():
first_invalid_item.setFocus(True)
return False
def on_saved(self, saved_tab_widget):
"""Callback on any tab widget save."""
return
def _save(self):
self.set_state(CategoryState.Working)
if self.items_are_valid():
self.save()
self._update_values()
self.set_state(CategoryState.Idle)
self.saved.emit(self)
def _on_refresh(self):
self.reset()
def _on_hide_studio_overrides(self, state):
self._hide_studio_overrides = (state == QtCore.Qt.Checked)
self._update_values()
def _save_as_defaults(self):
if not self.items_are_valid():
return
all_values = {}
for item in self.input_fields:
all_values.update(item.config_value())
for key in reversed(self.keys):
all_values = {key: all_values}
# Skip first key and convert data to store
all_values = lib.convert_gui_data_with_metadata(
all_values[self.main_schema_key]
)
if not self.validate_defaults_to_save(all_values):
return
defaults_dir = self.defaults_dir()
keys_to_file = lib.file_keys_from_schema(self.schema)
for key_sequence in keys_to_file:
# Skip first key
key_sequence = key_sequence[1:]
subpath = "/".join(key_sequence) + ".json"
new_values = all_values
for key in key_sequence:
new_values = new_values[key]
output_path = os.path.join(defaults_dir, subpath)
dirpath = os.path.dirname(output_path)
if not os.path.exists(dirpath):
os.makedirs(dirpath)
print("Saving data to: ", subpath)
with open(output_path, "w") as file_stream:
json.dump(new_values, file_stream, indent=4)
reset_default_settings()
self._update_values()
def _update_values(self):
self.ignore_value_changes = True
self.update_values()
self.ignore_value_changes = False
def add_children_gui(self, child_configuration):
klass = lib.TypeToKlass.types.get(child_configuration["type"])
item = klass(child_configuration, self)
item.create_ui()
self.input_fields.append(item)
self.content_layout.addWidget(item, 0)
# Add spacer to stretch children guis
self.content_layout.addWidget(
QtWidgets.QWidget(self.content_widget), 1
)
class IgnoreInputChangesObj:
def __init__(self, top_widget):
self._ignore_changes = False
self.top_widget = top_widget
def __bool__(self):
return self._ignore_changes
def set_ignore(self, ignore_changes=True):
if self._ignore_changes == ignore_changes:
return
self._ignore_changes = ignore_changes
if not ignore_changes:
self.top_widget.hierarchical_style_update()
class SystemWidget(SettingsCategoryWidget):
schema_category = "system_schema"
initial_schema_name = "schema_main"
def initialize_attributes(self, *args, **kwargs):
self._hide_studio_overrides = False
self.ignore_input_changes = IgnoreInputChangesObj(self)
self.keys = []
self.input_fields = []
def defaults_dir(self):
print("*** defaults_dir")
def validate_defaults_to_save(self, values):
print("*** validate_defaults_to_save")
def trigger_hierarchical_style_update(self):
self.hierarchical_style_update()
def items_are_valid(self):
invalid_items = self.get_invalid()
if not invalid_items:
return True
msg_box = QtWidgets.QMessageBox(
QtWidgets.QMessageBox.Warning,
"Invalid input",
"There is invalid value in one of inputs."
" Please lead red color and fix them.",
parent=self
)
msg_box.setStandardButtons(QtWidgets.QMessageBox.Ok)
msg_box.exec_()
first_invalid_item = invalid_items[0]
self.scroll_widget.ensureWidgetVisible(first_invalid_item)
if first_invalid_item.isVisible():
first_invalid_item.setFocus(True)
return False
def save(self):
if self.items_are_valid():
self.entity.save()
# NOTE There are relations to previous entities and C++ callbacks
# so it is easier to just use new entity and recreate UI but
# would be nice to change this and add cleanup part so this is
# not required.
self.reset()
def get_invalid(self):
invalid = []
for input_field in self.input_fields:
invalid.extend(input_field.get_invalid())
return invalid
def update_values(self):
# TODO remove as it breaks entities. Was used in previous
# implementation of category widget.
pass
def create_ui(self):
self.modify_defaults_checkbox = None
@ -552,6 +207,9 @@ class SystemWidget(SettingsCategoryWidget):
self.ui_tweaks()
def ui_tweaks(self):
return
def _add_developer_ui(self, footer_layout):
refresh_icon = qtawesome.icon("fa.refresh", color="white")
refresh_button = QtWidgets.QPushButton()
@ -577,13 +235,39 @@ class SystemWidget(SettingsCategoryWidget):
)
self.modify_defaults_checkbox = modify_defaults_checkbox
def _on_modify_defaults(self):
if self.modify_defaults_checkbox.isChecked():
if not self.entity.is_in_defaults_state():
self.reset()
else:
if not self.entity.is_in_studio_state():
self.reset()
def get_invalid(self):
invalid = []
for input_field in self.input_fields:
invalid.extend(input_field.get_invalid())
return invalid
def hierarchical_style_update(self):
for input_field in self.input_fields:
input_field.hierarchical_style_update()
def _on_entity_change(self):
self.hierarchical_style_update()
def add_widget_to_layout(self, widget, label_widget=None):
if label_widget:
raise NotImplementedError(
"`add_widget_to_layout` on Category item can't accept labels"
)
self.content_layout.addWidget(widget, 0)
def save(self):
if self.items_are_valid():
self.entity.save()
# NOTE There are relations to previous entities and C++ callbacks
# so it is easier to just use new entity and recreate UI but
# would be nice to change this and add cleanup part so this is
# not required.
self.reset()
def _create_root_entity(self):
raise NotImplementedError(
"`create_root_entity` method not implemented"
)
def reset(self):
self.set_state(CategoryState.Working)
@ -596,6 +280,73 @@ class SystemWidget(SettingsCategoryWidget):
self.content_layout.removeWidget(widget)
widget.deleteLater()
self._create_root_entity()
self.add_children_gui()
self.ignore_input_changes.set_ignore(True)
for input_field in self.input_fields:
input_field.set_entity_value()
self.ignore_input_changes.set_ignore(False)
self.set_state(CategoryState.Idle)
def add_children_gui(self):
for child_obj in self.entity.children:
item = self.create_ui_for_entity(self, child_obj, self)
self.input_fields.append(item)
# Add spacer to stretch children guis
self.content_layout.addWidget(
QtWidgets.QWidget(self.content_widget), 1
)
def items_are_valid(self):
invalid_items = self.get_invalid()
if not invalid_items:
return True
msg_box = QtWidgets.QMessageBox(
QtWidgets.QMessageBox.Warning,
"Invalid input",
"There is invalid value in one of inputs."
" Please lead red color and fix them.",
parent=self
)
msg_box.setStandardButtons(QtWidgets.QMessageBox.Ok)
msg_box.exec_()
first_invalid_item = invalid_items[0]
self.scroll_widget.ensureWidgetVisible(first_invalid_item)
if first_invalid_item.isVisible():
first_invalid_item.setFocus(True)
return False
def on_saved(self, saved_tab_widget):
"""Callback on any tab widget save."""
return
def _save(self):
self.set_state(CategoryState.Working)
if self.items_are_valid():
self.save()
self.set_state(CategoryState.Idle)
self.saved.emit(self)
def _on_refresh(self):
self.reset()
def _on_hide_studio_overrides(self, state):
self._hide_studio_overrides = (state == QtCore.Qt.Checked)
class SystemWidget(SettingsCategoryWidget):
def _create_root_entity(self):
self.entity = SystemSettings(set_studio_state=False)
self.entity.on_change_callbacks.append(self._on_entity_change)
try:
@ -622,198 +373,19 @@ class SystemWidget(SettingsCategoryWidget):
self.modify_defaults_checkbox.setChecked(True)
self.modify_defaults_checkbox.setEnabled(False)
self.add_children_gui()
self.ignore_input_changes.set_ignore(True)
for input_field in self.input_fields:
input_field.set_entity_value()
self.ignore_input_changes.set_ignore(False)
self.set_state(CategoryState.Idle)
def _on_entity_change(self):
self.hierarchical_style_update()
def hierarchical_style_update(self):
for input_field in self.input_fields:
input_field.hierarchical_style_update()
def add_widget_to_layout(self, widget, label_widget=None):
if label_widget:
raise NotImplementedError(
"`add_widget_to_layout` is not implemented on Category item"
)
self.content_layout.addWidget(widget, 0)
def add_children_gui(self):
for child_obj in self.entity.children:
item = self.create_ui_for_entity(child_obj, self)
self.input_fields.append(item)
# Add spacer to stretch children guis
self.content_layout.addWidget(
QtWidgets.QWidget(self.content_widget), 1
)
class ProjectListView(QtWidgets.QListView):
left_mouse_released_at = QtCore.Signal(QtCore.QModelIndex)
def mouseReleaseEvent(self, event):
if event.button() == QtCore.Qt.LeftButton:
index = self.indexAt(event.pos())
self.left_mouse_released_at.emit(index)
super(ProjectListView, self).mouseReleaseEvent(event)
class ProjectListWidget(QtWidgets.QWidget):
default = "< Default >"
project_changed = QtCore.Signal()
def __init__(self, parent):
self._parent = parent
self.current_project = None
super(ProjectListWidget, self).__init__(parent)
self.setObjectName("ProjectListWidget")
label_widget = QtWidgets.QLabel("Projects")
project_list = ProjectListView(self)
project_list.setModel(QtGui.QStandardItemModel())
# Do not allow editing
project_list.setEditTriggers(
QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers
)
# Do not automatically handle selection
project_list.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection)
layout = QtWidgets.QVBoxLayout(self)
layout.setSpacing(3)
layout.addWidget(label_widget, 0)
layout.addWidget(project_list, 1)
project_list.left_mouse_released_at.connect(self.on_item_clicked)
self.project_list = project_list
self.dbcon = None
def on_item_clicked(self, new_index):
new_project_name = new_index.data(QtCore.Qt.DisplayRole)
if new_project_name is None:
return
if self.current_project == new_project_name:
return
save_changes = False
change_project = False
if self.validate_context_change():
change_project = True
def _on_modify_defaults(self):
if self.modify_defaults_checkbox.isChecked():
if not self.entity.is_in_defaults_state():
self.reset()
else:
dialog = UnsavedChangesDialog(self)
result = dialog.exec_()
if result == 1:
save_changes = True
change_project = True
elif result == 2:
change_project = True
if save_changes:
self._parent._save()
if change_project:
self.select_project(new_project_name)
self.current_project = new_project_name
self.project_changed.emit()
else:
self.select_project(self.current_project)
def validate_context_change(self):
# TODO add check if project can be changed (is modified)
for item in self._parent.input_fields:
is_modified = item.child_modified
if is_modified:
return False
return True
def project_name(self):
if self.current_project == self.default:
return None
return self.current_project
def select_project(self, project_name):
model = self.project_list.model()
found_items = model.findItems(project_name)
if not found_items:
found_items = model.findItems(self.default)
index = model.indexFromItem(found_items[0])
self.project_list.selectionModel().clear()
self.project_list.selectionModel().setCurrentIndex(
index, QtCore.QItemSelectionModel.SelectionFlag.SelectCurrent
)
def refresh(self):
selected_project = None
for index in self.project_list.selectedIndexes():
selected_project = index.data(QtCore.Qt.DisplayRole)
break
model = self.project_list.model()
model.clear()
items = [self.default]
system_settings = get_system_settings()
mongo_url = system_settings["modules"]["avalon"]["AVALON_MONGO"]
if not mongo_url:
mongo_url = os.environ["PYPE_MONGO"]
# Force uninstall of whole avalon connection if url does not match
# to current environment and set it as environment
if mongo_url != os.environ["AVALON_MONGO"]:
AvalonMongoConnection.uninstall(self.dbcon, force=True)
os.environ["AVALON_MONGO"] = mongo_url
self.dbcon = None
if not self.dbcon:
try:
self.dbcon = AvalonMongoDB()
self.dbcon.install()
except Exception:
self.dbcon = None
self.current_project = None
if self.dbcon:
for project_doc in tuple(self.dbcon.projects()):
items.append(project_doc["name"])
for item in items:
model.appendRow(QtGui.QStandardItem(item))
self.select_project(selected_project)
self.current_project = self.project_list.currentIndex().data(
QtCore.Qt.DisplayRole
)
if not self.entity.is_in_studio_state():
self.reset()
class ProjectWidget(SettingsCategoryWidget):
schema_category = "projects_schema"
initial_schema_name = "schema_main"
def initialize_attributes(self):
self.project_name = None
super(ProjectWidget, self).initialize_attributes()
def ui_tweaks(self):
project_list_widget = ProjectListWidget(self)
project_list_widget.refresh()
@ -824,13 +396,6 @@ class ProjectWidget(SettingsCategoryWidget):
self.project_list_widget = project_list_widget
def defaults_dir(self):
return DEFAULTS_DIR
def validate_defaults_to_save(self, _):
# Projects does not have any specific validations
return True
def on_saved(self, saved_tab_widget):
"""Callback on any tab widget save.
@ -848,82 +413,62 @@ class ProjectWidget(SettingsCategoryWidget):
if mongo_url != os.environ["AVALON_MONGO"]:
self.project_list_widget.refresh()
def _create_root_entity(self):
self.entity = ProjectSettings(change_state=False)
self.entity.on_change_callbacks.append(self._on_entity_change)
try:
if (
self.modify_defaults_checkbox
and self.modify_defaults_checkbox.isChecked()
):
self.entity.set_defaults_state()
elif self.project_name is None:
self.entity.set_studio_state()
elif self.project_name == self.entity.project_name:
self.entity.set_project_state()
else:
self.entity.change_project(self.project_name)
if self.modify_defaults_checkbox:
self.modify_defaults_checkbox.setEnabled(True)
self.project_list_widget.setEnabled(True)
except DefaultsNotDefined:
if not self.modify_defaults_checkbox:
msg_box = QtWidgets.QMessageBox(
"BUG: Default values are not set and you"
" don't have permissions to modify them."
)
msg_box.exec_()
return
self.entity.set_defaults_state()
self.modify_defaults_checkbox.setChecked(True)
self.modify_defaults_checkbox.setEnabled(False)
self.project_list_widget.setEnabled(False)
except StudioDefaultsNotDefined:
self.select_default_project()
def _on_project_change(self):
project_name = self.project_list_widget.project_name()
if project_name == self.project_name:
return
self.project_name = project_name
self.set_state(CategoryState.Working)
project_name = self.project_list_widget.project_name()
if project_name is None:
_project_overrides = lib.NOT_SET
_project_anatomy = lib.NOT_SET
self.is_overidable = False
else:
_project_overrides = get_project_settings_overrides(project_name)
_project_anatomy = get_project_anatomy_overrides(project_name)
self.is_overidable = True
overrides = {self.main_schema_key: {
PROJECT_SETTINGS_KEY: lib.convert_overrides_to_gui_data(
_project_overrides
),
PROJECT_ANATOMY_KEY: lib.convert_overrides_to_gui_data(
_project_anatomy
)
}}
self.project_name = project_name
self.ignore_value_changes = True
for item in self.input_fields:
item.apply_overrides(overrides)
self.ignore_value_changes = False
self.reset()
self.set_state(CategoryState.Idle)
def save(self):
data = {}
studio_overrides = bool(self.project_name is None)
for item in self.input_fields:
if studio_overrides:
value, _is_group = item.studio_overrides()
else:
value, _is_group = item.overrides()
if value is not lib.NOT_SET:
data.update(value)
output_data = lib.convert_gui_data_to_overrides(
data.get(self.main_schema_key) or {}
)
# Saving overrides data
project_overrides_data = output_data.get(PROJECT_SETTINGS_KEY, {})
save_project_settings(self.project_name, project_overrides_data)
# Saving anatomy data
project_anatomy_data = output_data.get(PROJECT_ANATOMY_KEY, {})
save_project_anatomy(self.project_name, project_anatomy_data)
def update_values(self):
if self.project_name is not None:
self._on_project_change()
return
default_values = lib.convert_data_to_gui_data(
{self.main_schema_key: get_default_settings()}
)
for input_field in self.input_fields:
input_field.update_default_values(default_values)
if self._hide_studio_overrides:
studio_values = lib.NOT_SET
def _on_modify_defaults(self):
if self.modify_defaults_checkbox.isChecked():
if not self.entity.is_in_defaults_state():
self.reset()
else:
studio_values = lib.convert_overrides_to_gui_data({
self.main_schema_key: {
PROJECT_SETTINGS_KEY: (
get_studio_project_settings_overrides()
),
PROJECT_ANATOMY_KEY: (
get_studio_project_anatomy_overrides()
)
}
})
for input_field in self.input_fields:
input_field.update_studio_values(studio_values)
if not self.entity.is_in_studio_state():
self.reset()

View file

@ -13,8 +13,6 @@ from .lib import (
CHILD_OFFSET
)
from pype.settings.entities import NOT_SET
def create_add_btn(parent):
add_btn = QtWidgets.QPushButton("+", parent)
@ -156,14 +154,13 @@ class ModifiableDictItem(QtWidgets.QWidget):
self.entity = entity
self.entity_widget = entity_widget
self.create_ui_for_entity = entity_widget.create_ui_for_entity
self.ignore_input_changes = entity_widget.ignore_input_changes
self.is_key_duplicated = False
self.is_required = False
self.origin_key = NOT_SET
self.origin_key_label = NOT_SET
self.origin_key = None
self.origin_key_label = None
self.temp_key = ""
self.uuid_key = None
@ -172,19 +169,25 @@ class ModifiableDictItem(QtWidgets.QWidget):
self.wrapper_widget = None
self.key_label_input = None
if collapsible_key:
self.create_collapsible_ui()
else:
self.create_addible_ui()
self.update_style()
@property
def category_widget(self):
return self.entity_widget.category_widget
def create_ui_for_entity(self, *args, **kwargs):
return self.entity_widget.create_ui_for_entity(*args, **kwargs)
def create_addible_ui(self):
key_input = QtWidgets.QLineEdit(self)
key_input.setObjectName("DictKey")
spacer_widget = SpacerWidget(self)
spacer_widget.setVisible(False)
add_btn = create_add_btn(self)
remove_btn = create_remove_btn(self)
@ -194,7 +197,6 @@ class ModifiableDictItem(QtWidgets.QWidget):
layout.addWidget(add_btn, 0)
layout.addWidget(remove_btn, 0)
layout.addWidget(key_input, 0)
layout.addWidget(spacer_widget, 1)
key_input.textChanged.connect(self._on_key_change)
key_input.returnPressed.connect(self._on_enter_press)
@ -203,17 +205,18 @@ class ModifiableDictItem(QtWidgets.QWidget):
remove_btn.clicked.connect(self.on_remove_clicked)
self.key_input = key_input
self.spacer_widget = spacer_widget
self.add_btn = add_btn
self.remove_btn = remove_btn
self.content_widget = self
self.content_layout = layout
self.input_field = self.create_ui_for_entity(self.entity, self)
self.input_field = self.create_ui_for_entity(
self.category_widget, self.entity, self
)
def add_widget_to_layout(self, widget, label=None):
self.content_layout.addWidget(widget)
self.content_layout.addWidget(widget, 1)
self.setFocusProxy(widget)
def create_collapsible_ui(self):
@ -269,12 +272,19 @@ class ModifiableDictItem(QtWidgets.QWidget):
key_input.textChanged.connect(self._on_key_change)
key_input.returnPressed.connect(self._on_enter_press)
key_label_input.textChanged.connect(self._on_key_change)
key_label_input.textChanged.connect(self._on_key_label_change)
key_label_input.returnPressed.connect(self._on_enter_press)
edit_btn.clicked.connect(self.on_edit_pressed)
remove_btn.clicked.connect(self.on_remove_clicked)
# Hide edit inputs
key_input.setVisible(False)
key_input_label_widget.setVisible(False)
key_label_input.setVisible(False)
key_label_input_label_widget.setVisible(False)
remove_btn.setVisible(False)
self.key_input = key_input
self.key_input_label_widget = key_input_label_widget
self.key_label_input = key_label_input
@ -286,7 +296,9 @@ class ModifiableDictItem(QtWidgets.QWidget):
self.content_widget = content_widget
self.content_layout = content_layout
self.input_field = self.create_ui_for_entity(self.entity, self)
self.input_field = self.create_ui_for_entity(
self.category_widget, self.entity, self
)
def get_style_state(self):
if self.is_invalid:
@ -327,10 +339,13 @@ class ModifiableDictItem(QtWidgets.QWidget):
def set_key_label(self, key, label):
self.set_key(key)
if label:
self.key_label_input.setText(label)
self.set_label(label)
self.set_edit_mode(False)
def set_label(self, label):
if self.key_label_input and label is not None:
self.key_label_input.setText(label)
def set_as_required(self, key):
self.key_input.setText(key)
self.key_input.setEnabled(False)
@ -360,6 +375,8 @@ class ModifiableDictItem(QtWidgets.QWidget):
self.set_edit_mode(False)
def _on_key_label_change(self):
label = self.key_label_value()
self.entity_widget.change_label(label, self)
self.update_key_label()
def _on_key_change(self):
@ -427,17 +444,6 @@ class ModifiableDictItem(QtWidgets.QWidget):
def is_key_label_modified(self):
return self.key_label_value() != self.origin_key_label
def is_value_modified(self):
return self.input_field.is_modified
@property
def is_modified(self):
return (
self.is_key_modified()
or self.is_key_label_modified()
or self.is_value_modified()
)
def trigger_hierarchical_style_update(self):
self.entity_widget.trigger_hierarchical_style_update()
@ -504,9 +510,9 @@ class ModifiableDictItem(QtWidgets.QWidget):
return self.entity_widget.input_fields.index(self)
def key_label_value(self):
if self.collapsible_key:
if self.key_label_input:
return self.key_label_input.text()
return NOT_SET
return None
def mouseReleaseEvent(self, event):
return QtWidgets.QWidget.mouseReleaseEvent(self, event)
@ -685,6 +691,13 @@ class DictMutableKeysWidget(BaseWidget):
self.entity.children_by_key[sk_old_key]
)
def change_label(self, label, input_field):
entity = input_field.entity
_label = self.entity.get_child_label(entity)
if _label == label:
return
self.entity.set_child_label(entity, label)
def add_widget_for_child(
self, child_entity, after_widget=None, first=False
):
@ -756,6 +769,7 @@ class DictMutableKeysWidget(BaseWidget):
self.update_style()
def _on_entity_change(self):
changed = False
to_remove = []
for input_field in self.input_fields:
found = False
@ -768,6 +782,7 @@ class DictMutableKeysWidget(BaseWidget):
to_remove.append(input_field)
for input_field in to_remove:
changed = True
self.remove_row(input_field)
for key, child_entity in self.entity.items():
@ -781,6 +796,7 @@ class DictMutableKeysWidget(BaseWidget):
break
if not found:
changed = True
args = [previous_input]
if previous_input is None:
args.append(True)
@ -788,12 +804,25 @@ class DictMutableKeysWidget(BaseWidget):
_input_field = self.add_widget_for_child(child_entity, *args)
_input_field.origin_key = key
_input_field.set_key(key)
if self.entity.collapsible_key:
label = self.entity.get_child_label(child_entity)
_input_field.origin_key_label = label
_input_field.set_label(label)
_input_field.set_entity_value()
else:
if input_field.key_value() != key:
changed = True
input_field.set_key(key)
if self.entity.collapsible_key:
label = self.entity.get_child_label(child_entity)
if input_field.key_label_value() != label:
input_field.set_label(label)
if changed:
self.on_shuffle()
def set_entity_value(self):
while self.input_fields:
self.remove_row(self.input_fields[0])
@ -802,6 +831,10 @@ class DictMutableKeysWidget(BaseWidget):
input_field = self.add_widget_for_child(child_entity)
input_field.origin_key = key
input_field.set_key(key)
if self.entity.collapsible_key:
label = self.entity.get_child_label(child_entity)
input_field.origin_key_label = label
input_field.set_label(label)
for input_field in self.input_fields:
input_field.set_entity_value()

File diff suppressed because it is too large Load diff

View file

@ -45,7 +45,9 @@ class DictImmutableKeysWidget(BaseWidget):
for child_obj in self.entity.children:
self.input_fields.append(
self.create_ui_for_entity(child_obj, self)
self.create_ui_for_entity(
self.category_widget, child_obj, self
)
)
self.entity_widget.add_widget_to_layout(self)
@ -87,7 +89,7 @@ class DictImmutableKeysWidget(BaseWidget):
if self.entity.is_dynamic_item:
content_widget.setObjectName("DictAsWidgetBody")
show_borders = str(int(self.show_borders))
show_borders = str(int(self.entity.show_borders))
content_widget.setProperty("show_borders", show_borders)
content_layout_margins = (5, 5, 5, 5)
main_layout_spacing = 5
@ -231,10 +233,10 @@ class DictImmutableKeysWidget(BaseWidget):
@property
def is_invalid(self):
return self._is_invalid or self.child_invalid
return self._is_invalid or self._child_invalid
@property
def child_invalid(self):
def _child_invalid(self):
for input_field in self.input_fields:
if input_field.is_invalid:
return True
@ -508,7 +510,7 @@ class PathWidget(BaseWidget):
self.content_layout.setSpacing(5)
self.input_field = self.create_ui_for_entity(
self.entity.child_obj, self
self.category_widget, self.entity.child_obj, self
)
self.entity_widget.add_widget_to_layout(self, self.entity.label)

View file

@ -54,7 +54,6 @@ class ListItem(QtWidgets.QWidget):
self.entity = entity
self.ignore_input_changes = entity_widget.ignore_input_changes
self.create_ui_for_entity = entity_widget.create_ui_for_entity
char_up = qtawesome.charmap("fa.angle-up")
char_down = qtawesome.charmap("fa.angle-down")
@ -98,7 +97,9 @@ class ListItem(QtWidgets.QWidget):
self.content_widget = self
self.content_layout = layout
self.input_field = self.create_ui_for_entity(self.entity, self)
self.input_field = self.create_ui_for_entity(
self.category_widget, self.entity, self
)
self.input_field.set_entity_value()
spacer_widget = QtWidgets.QWidget(self)
@ -117,6 +118,15 @@ class ListItem(QtWidgets.QWidget):
self.spacer_widget = spacer_widget
@property
def category_widget(self):
return self.entity_widget.category_widget
def create_ui_for_entity(self, *args, **kwargs):
return self.entity_widget.create_ui_for_entity(
*args, **kwargs
)
@property
def is_invalid(self):
return self.input_field.is_invalid
@ -253,36 +263,35 @@ class ListWidget(InputWidget):
def _on_entity_change(self):
# TODO do less inefficient
current_entities = []
for input_field in self.input_fields:
current_entities.append(input_field.entity)
for idx, child_entity in enumerate(self.entity):
found = False
for input_field in self.input_fields:
if input_field.entity is child_entity:
found = True
break
if not found:
self.add_row(child_entity, idx)
input_field_last_idx = len(self.input_fields) - 1
child_len = len(self.entity)
for idx, child_entity in enumerate(tuple(self.entity)):
for idx, child_entity in enumerate(self.entity):
if idx > input_field_last_idx:
self.add_row(child_entity, idx)
input_field_last_idx += 1
continue
if self.input_fields[idx].entity is child_entity:
continue
for _idx in range(idx, child_len):
input_field = self.input_fields[_idx]
if input_field.entity is not child_entity:
continue
input_field_idx = None
for _input_field_idx, input_field in enumerate(self.input_fields):
if input_field.entity is child_entity:
input_field_idx = _input_field_idx
break
self.content_layout.insertWidget(idx, input_field)
break
if input_field_idx is None:
self.add_row(child_entity, idx)
input_field_last_idx += 1
continue
input_field_len = len(self.input_fields)
if child_len != input_field_len:
for _idx in range(child_len, input_field_len):
input_field = self.input_fields.pop(input_field_idx)
self.input_fields.insert(idx, input_field)
self.content_layout.insertWidget(idx, input_field)
new_input_field_len = len(self.input_fields)
if child_len != new_input_field_len:
for _idx in range(child_len, new_input_field_len):
# Remove row at the same index
self.remove_row(self.input_fields[child_len])
@ -295,20 +304,7 @@ class ListWidget(InputWidget):
if row_1 == row_2:
return
if row_1 > row_2:
row_1, row_2 = row_2, row_1
field_1 = self.input_fields[row_1]
field_2 = self.input_fields[row_2]
self.input_fields[row_1] = field_2
self.input_fields[row_2] = field_1
layout_index = self.content_layout.indexOf(field_1)
self.content_layout.insertWidget(layout_index + 1, field_1)
field_1.order_changed()
field_2.order_changed()
self.entity.swap_indexes(row_1, row_2)
def add_new_item(self, row=None):
new_entity = self.entity.add_new_item(row)
@ -338,7 +334,7 @@ class ListWidget(InputWidget):
if row < max_index:
next_field = self.input_fields[row]
self.content_layout.insertWidget(row, item_widget)
self.content_layout.insertWidget(row + 1, item_widget)
self.input_fields.insert(row, item_widget)
if previous_field:

View file

@ -0,0 +1,142 @@
from Qt import QtWidgets, QtCore
from .widgets import (
GridLabelWidget,
SpacerWidget
)
from .base import BaseWidget
class ListStrictWidget(BaseWidget):
def create_ui(self):
self.setObjectName("ListStrictWidget")
self._child_style_state = ""
self.input_fields = []
content_layout = QtWidgets.QGridLayout(self)
content_layout.setContentsMargins(0, 0, 0, 0)
content_layout.setSpacing(3)
self.content_layout = content_layout
self.content_widget = self
any_children_has_label = False
for child_obj in self.entity.children:
if child_obj.label:
any_children_has_label = True
break
self._any_children_has_label = any_children_has_label
# Change column stretch factor for verticall alignment
if not self.entity.is_horizontal:
col_index = 2 if any_children_has_label else 1
content_layout.setColumnStretch(col_index, 1)
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.is_horizontal:
col = self.content_layout.columnCount()
spacer = SpacerWidget(self)
self.content_layout.addWidget(spacer, 0, col, 2, 1)
self.content_layout.setColumnStretch(col, 1)
self.entity_widget.add_widget_to_layout(self, self.entity.label)
@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
def add_widget_to_layout(self, widget, label=None):
# Horizontally added children
if self.entity.is_horizontal:
self._add_child_horizontally(widget, label)
else:
self._add_child_vertically(widget, label)
self.updateGeometry()
def _add_child_horizontally(self, widget, label):
col = self.content_layout.columnCount()
# Expand to whole grid if all children are without label
if not self._any_children_has_label:
self.content_layout.addWidget(widget, 0, col, 1, 2)
else:
if label:
label_widget = GridLabelWidget(label, widget)
label_widget.input_field = widget
widget.label_widget = label_widget
self.content_layout.addWidget(label_widget, 0, col, 1, 1)
col += 1
self.content_layout.addWidget(widget, 0, col, 1, 1)
def _add_child_vertically(self, widget, label):
row = self.content_layout.rowCount()
if not self._any_children_has_label:
self.content_layout.addWidget(widget, row, 0, 1, 1)
spacer_widget = SpacerWidget(self)
self.content_layout.addWidget(spacer_widget, row, 1, 1, 1)
else:
if label:
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,
alignment=QtCore.Qt.AlignRight | QtCore.Qt.AlignTop
)
self.content_layout.addWidget(widget, row, 1, 1, 1)
spacer_widget = SpacerWidget(self)
self.content_layout.addWidget(spacer_widget, row, 2, 1, 1)
def hierarchical_style_update(self):
self.update_style()
for input_field in self.input_fields:
input_field.hierarchical_style_update()
def set_entity_value(self):
for input_field in self.input_fields:
input_field.set_entity_value()
def _on_entity_change(self):
pass
def update_style(self):
if not self.label_widget:
return
style_state = self.get_style_state(
self.is_invalid,
self.entity.has_unsaved_changes,
self.entity.has_project_override,
self.entity.has_studio_override
)
if self._style_state == style_state:
return
self.label_widget.setProperty("state", style_state)
self.label_widget.style().polish(self.label_widget)
self._style_state = style_state

View file

@ -1,5 +1,12 @@
import os
from Qt import QtWidgets, QtCore, QtGui
from avalon.vendor import qtawesome
from avalon.mongodb import (
AvalonMongoConnection,
AvalonMongoDB
)
from pype.settings.lib import get_system_settings
class ShadowWidget(QtWidgets.QWidget):
@ -523,3 +530,148 @@ class NiceCheckbox(QtWidgets.QFrame):
event.accept()
return
return super(NiceCheckbox, self).mouseReleaseEvent(event)
class ProjectListView(QtWidgets.QListView):
left_mouse_released_at = QtCore.Signal(QtCore.QModelIndex)
def mouseReleaseEvent(self, event):
if event.button() == QtCore.Qt.LeftButton:
index = self.indexAt(event.pos())
self.left_mouse_released_at.emit(index)
super(ProjectListView, self).mouseReleaseEvent(event)
class ProjectListWidget(QtWidgets.QWidget):
default = "< Default >"
project_changed = QtCore.Signal()
def __init__(self, parent):
self._parent = parent
self.current_project = None
super(ProjectListWidget, self).__init__(parent)
self.setObjectName("ProjectListWidget")
label_widget = QtWidgets.QLabel("Projects")
project_list = ProjectListView(self)
project_list.setModel(QtGui.QStandardItemModel())
# Do not allow editing
project_list.setEditTriggers(
QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers
)
# Do not automatically handle selection
project_list.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection)
layout = QtWidgets.QVBoxLayout(self)
layout.setSpacing(3)
layout.addWidget(label_widget, 0)
layout.addWidget(project_list, 1)
project_list.left_mouse_released_at.connect(self.on_item_clicked)
self.project_list = project_list
self.dbcon = None
def on_item_clicked(self, new_index):
new_project_name = new_index.data(QtCore.Qt.DisplayRole)
if new_project_name is None:
return
if self.current_project == new_project_name:
return
save_changes = False
change_project = False
if self.validate_context_change():
change_project = True
else:
dialog = UnsavedChangesDialog(self)
result = dialog.exec_()
if result == 1:
save_changes = True
change_project = True
elif result == 2:
change_project = True
if save_changes:
self._parent._save()
if change_project:
self.select_project(new_project_name)
self.current_project = new_project_name
self.project_changed.emit()
else:
self.select_project(self.current_project)
def validate_context_change(self):
return not self._parent.entity.has_unsaved_changes
def project_name(self):
if self.current_project == self.default:
return None
return self.current_project
def select_default_project(self):
self.select_project(self.default)
def select_project(self, project_name):
model = self.project_list.model()
found_items = model.findItems(project_name)
if not found_items:
found_items = model.findItems(self.default)
index = model.indexFromItem(found_items[0])
self.project_list.selectionModel().clear()
self.project_list.selectionModel().setCurrentIndex(
index, QtCore.QItemSelectionModel.SelectionFlag.SelectCurrent
)
def refresh(self):
selected_project = None
for index in self.project_list.selectedIndexes():
selected_project = index.data(QtCore.Qt.DisplayRole)
break
model = self.project_list.model()
model.clear()
items = [self.default]
system_settings = get_system_settings()
mongo_url = system_settings["modules"]["avalon"]["AVALON_MONGO"]
if not mongo_url:
mongo_url = os.environ["PYPE_MONGO"]
# Force uninstall of whole avalon connection if url does not match
# to current environment and set it as environment
if mongo_url != os.environ["AVALON_MONGO"]:
AvalonMongoConnection.uninstall(self.dbcon, force=True)
os.environ["AVALON_MONGO"] = mongo_url
self.dbcon = None
if not self.dbcon:
try:
self.dbcon = AvalonMongoDB()
self.dbcon.install()
except Exception:
self.dbcon = None
self.current_project = None
if self.dbcon:
for project_doc in tuple(self.dbcon.projects()):
items.append(project_doc["name"])
for item in items:
model.appendRow(QtGui.QStandardItem(item))
self.select_project(selected_project)
self.current_project = self.project_list.currentIndex().data(
QtCore.Qt.DisplayRole
)

View file

@ -56,7 +56,7 @@ class FormWrapper(WrapperWidget):
class CollapsibleWrapper(WrapperWidget):
def create_ui(self):
self.collapsible = self.schema_data.get("collapsable", True)
self.collapsible = self.schema_data.get("collapsible", True)
self.collapsed = self.schema_data.get("collapsed", True)
content_widget = QtWidgets.QWidget(self)