mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-25 05:14:40 +01:00
Merge pull request #1028 from pypeclub/feature/project_settings_to_entities
Project settings to entities
This commit is contained in:
commit
c236cea8b8
77 changed files with 2112 additions and 5988 deletions
|
|
@ -5,7 +5,8 @@ from .settings import (
|
|||
get_anatomy_settings,
|
||||
get_environments,
|
||||
|
||||
SystemSettings
|
||||
SystemSettings,
|
||||
ProjectSettings
|
||||
)
|
||||
from .lib import (
|
||||
PypeLogger,
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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"]
|
||||
}]
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
)
|
||||
|
|
|
|||
102
pype/settings/entities/anatomy_entities.py
Normal file
102
pype/settings/entities/anatomy_entities.py
Normal 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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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 = "{}"
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
490
pype/settings/entities/schemas/README.md
Normal file
490
pype/settings/entities/schemas/README.md
Normal 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"
|
||||
}, {
|
||||
...
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -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",
|
||||
|
|
@ -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": [
|
||||
|
|
@ -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",
|
||||
|
|
@ -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",
|
||||
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -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": [
|
||||
|
|
@ -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",
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"type": "dict",
|
||||
"collapsable": true,
|
||||
"collapsible": true,
|
||||
"key": "maya",
|
||||
"label": "Maya",
|
||||
"is_file": true,
|
||||
|
|
@ -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,
|
||||
|
|
@ -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",
|
||||
|
|
@ -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",
|
||||
|
|
@ -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":
|
||||
{
|
||||
|
|
@ -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": [
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
{
|
||||
"type": "dict",
|
||||
"collapsable": true,
|
||||
"collapsible": true,
|
||||
"key": "attributes",
|
||||
"label": "Attributes",
|
||||
"is_file": true,
|
||||
"is_group": true,
|
||||
"children": [
|
||||
{
|
||||
"type": "number",
|
||||
|
|
@ -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",
|
||||
|
|
@ -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",
|
||||
|
|
@ -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",
|
||||
|
|
@ -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,
|
||||
|
|
@ -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": [
|
||||
|
|
@ -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": [
|
||||
|
|
@ -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",
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"type": "dict-modifiable",
|
||||
"collapsable": true,
|
||||
"collapsible": true,
|
||||
"key": "filters",
|
||||
"label": "Publish GUI Filters",
|
||||
"object_type": {
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"type": "dict",
|
||||
"collapsable": true,
|
||||
"collapsible": true,
|
||||
"key": "workfile_build",
|
||||
"label": "Workfile Build Settings",
|
||||
"children": [
|
||||
|
|
@ -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": [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"type": "dict",
|
||||
"key": "aftereffects",
|
||||
"label": "Adobe AfterEffects",
|
||||
"collapsable": true,
|
||||
"collapsible": true,
|
||||
"checkbox_key": "enabled",
|
||||
"children": [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"type": "dict",
|
||||
"key": "blender",
|
||||
"label": "Blender",
|
||||
"collapsable": true,
|
||||
"collapsible": true,
|
||||
"checkbox_key": "enabled",
|
||||
"children": [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"type": "dict",
|
||||
"key": "celaction",
|
||||
"label": "CelAction2D",
|
||||
"collapsable": true,
|
||||
"collapsible": true,
|
||||
"checkbox_key": "enabled",
|
||||
"children": [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"type": "dict",
|
||||
"key": "djvview",
|
||||
"label": "DJV View",
|
||||
"collapsable": true,
|
||||
"collapsible": true,
|
||||
"checkbox_key": "enabled",
|
||||
"children": [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"type": "dict",
|
||||
"key": "fusion",
|
||||
"label": "Blackmagic Fusion",
|
||||
"collapsable": true,
|
||||
"collapsible": true,
|
||||
"checkbox_key": "enabled",
|
||||
"children": [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"type": "dict",
|
||||
"key": "harmony",
|
||||
"label": "Toon Boom Harmony",
|
||||
"collapsable": true,
|
||||
"collapsible": true,
|
||||
"checkbox_key": "enabled",
|
||||
"children": [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"type": "dict",
|
||||
"key": "houdini",
|
||||
"label": "SideFX Houdini",
|
||||
"collapsable": true,
|
||||
"collapsible": true,
|
||||
"checkbox_key": "enabled",
|
||||
"children": [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"type": "dict",
|
||||
"key": "maya",
|
||||
"label": "Autodesk Maya",
|
||||
"collapsable": true,
|
||||
"collapsible": true,
|
||||
"checkbox_key": "enabled",
|
||||
"children": [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"type": "dict",
|
||||
"key": "mayabatch",
|
||||
"label": "Autodesk Maya Batch",
|
||||
"collapsable": true,
|
||||
"collapsible": true,
|
||||
"checkbox_key": "enabled",
|
||||
"children": [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"type": "dict",
|
||||
"key": "photoshop",
|
||||
"label": "Adobe Photoshop",
|
||||
"collapsable": true,
|
||||
"collapsible": true,
|
||||
"checkbox_key": "enabled",
|
||||
"children": [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"type": "dict",
|
||||
"key": "resolve",
|
||||
"label": "Blackmagic DaVinci Resolve",
|
||||
"collapsable": true,
|
||||
"collapsible": true,
|
||||
"checkbox_key": "enabled",
|
||||
"children": [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"type": "dict",
|
||||
"key": "shell",
|
||||
"label": "Shell",
|
||||
"collapsable": true,
|
||||
"collapsible": true,
|
||||
"checkbox_key": "enabled",
|
||||
"children": [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"type": "dict",
|
||||
"key": "tvpaint",
|
||||
"label": "TVPaint",
|
||||
"collapsable": true,
|
||||
"collapsible": true,
|
||||
"checkbox_key": "enabled",
|
||||
"children": [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"type": "dict",
|
||||
"key": "unreal",
|
||||
"label": "Unreal Editor",
|
||||
"collapsable": true,
|
||||
"collapsible": true,
|
||||
"checkbox_key": "enabled",
|
||||
"children": [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
"type": "dict",
|
||||
"key": "{host_name}_{host_version}",
|
||||
"label": "{host_version}",
|
||||
"collapsable": true,
|
||||
"collapsible": true,
|
||||
"checkbox_key": "enabled",
|
||||
"children": [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"type": "dict",
|
||||
"key": "{nuke_type}",
|
||||
"label": "Foundry {nuke_label}",
|
||||
"collapsable": true,
|
||||
"collapsible": true,
|
||||
"checkbox_key": "enabled",
|
||||
"children": [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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": [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"key": "applications",
|
||||
"type": "dict",
|
||||
"label": "Applications",
|
||||
"collapsable": true,
|
||||
"collapsible": true,
|
||||
"is_file": true,
|
||||
"children": [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"key": "general",
|
||||
"type": "dict",
|
||||
"label": "General",
|
||||
"collapsable": true,
|
||||
"collapsible": true,
|
||||
"is_file": true,
|
||||
"children": [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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": [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"key": "tools",
|
||||
"type": "dict",
|
||||
"label": "Tools",
|
||||
"collapsable": true,
|
||||
"collapsible": true,
|
||||
"is_file": true,
|
||||
"children": [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"type": "dict",
|
||||
"key": "mtoa",
|
||||
"label": "Autodesk Arnold",
|
||||
"collapsable": true,
|
||||
"collapsible": true,
|
||||
"checkbox_key": "enabled",
|
||||
"children": [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"type": "dict",
|
||||
"key": "vray",
|
||||
"label": "Chaos Group Vray",
|
||||
"collapsable": true,
|
||||
"collapsible": true,
|
||||
"checkbox_key": "enabled",
|
||||
"children": [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"type": "dict",
|
||||
"key": "yeti",
|
||||
"label": "Pergrine Labs Yeti",
|
||||
"collapsable": true,
|
||||
"collapsible": true,
|
||||
"checkbox_key": "enabled",
|
||||
"children": [
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -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"
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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 []
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
142
pype/tools/settings/settings/widgets/list_strict_widget.py
Normal file
142
pype/tools/settings/settings/widgets/list_strict_widget.py
Normal 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
|
||||
|
|
@ -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
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue