Merge branch 'develop' into enhancement/review_collect_otio_subset_resource

This commit is contained in:
Jakub Ježek 2024-12-05 16:57:57 +01:00 committed by GitHub
commit 13e0ad9cdd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 762 additions and 482 deletions

View file

@ -1,9 +1,15 @@
import os
import re
import copy
import numbers
import warnings
from string import Formatter
import typing
from typing import List, Dict, Any, Set
if typing.TYPE_CHECKING:
from typing import Union
KEY_PATTERN = re.compile(r"(\{.*?[^{0]*\})")
KEY_PADDING_PATTERN = re.compile(r"([^:]+)\S+[><]\S+")
SUB_DICT_PATTERN = re.compile(r"([^\[\]]+)")
OPTIONAL_PATTERN = re.compile(r"(<.*?[^{0]*>)[^0-9]*?")
@ -18,9 +24,7 @@ class TemplateUnsolved(Exception):
def __init__(self, template, missing_keys, invalid_types):
invalid_type_items = []
for _key, _type in invalid_types.items():
invalid_type_items.append(
"\"{0}\" {1}".format(_key, str(_type))
)
invalid_type_items.append(f"\"{_key}\" {str(_type)}")
invalid_types_msg = ""
if invalid_type_items:
@ -33,31 +37,32 @@ class TemplateUnsolved(Exception):
missing_keys_msg = self.missing_keys_msg.format(
", ".join(missing_keys)
)
super(TemplateUnsolved, self).__init__(
super().__init__(
self.msg.format(template, missing_keys_msg, invalid_types_msg)
)
class StringTemplate:
"""String that can be formatted."""
def __init__(self, template):
def __init__(self, template: str):
if not isinstance(template, str):
raise TypeError("<{}> argument must be a string, not {}.".format(
self.__class__.__name__, str(type(template))
))
raise TypeError(
f"<{self.__class__.__name__}> argument must be a string,"
f" not {str(type(template))}."
)
self._template = template
self._template: str = template
parts = []
last_end_idx = 0
for item in KEY_PATTERN.finditer(template):
start, end = item.span()
if start > last_end_idx:
parts.append(template[last_end_idx:start])
parts.append(FormattingPart(template[start:end]))
last_end_idx = end
formatter = Formatter()
if last_end_idx < len(template):
parts.append(template[last_end_idx:len(template)])
for item in formatter.parse(template):
literal_text, field_name, format_spec, conversion = item
if literal_text:
parts.append(literal_text)
if field_name:
parts.append(
FormattingPart(field_name, format_spec, conversion)
)
new_parts = []
for part in parts:
@ -77,15 +82,17 @@ class StringTemplate:
if substr:
new_parts.append(substr)
self._parts = self.find_optional_parts(new_parts)
self._parts: List["Union[str, OptionalPart, FormattingPart]"] = (
self.find_optional_parts(new_parts)
)
def __str__(self):
def __str__(self) -> str:
return self.template
def __repr__(self):
return "<{}> {}".format(self.__class__.__name__, self.template)
def __repr__(self) -> str:
return f"<{self.__class__.__name__}> {self.template}"
def __contains__(self, other):
def __contains__(self, other: str) -> bool:
return other in self.template
def replace(self, *args, **kwargs):
@ -93,10 +100,10 @@ class StringTemplate:
return self
@property
def template(self):
def template(self) -> str:
return self._template
def format(self, data):
def format(self, data: Dict[str, Any]) -> "TemplateResult":
""" Figure out with whole formatting.
Separate advanced keys (*Like '{project[name]}') from string which must
@ -108,6 +115,7 @@ class StringTemplate:
Returns:
TemplateResult: Filled or partially filled template containing all
data needed or missing for filling template.
"""
result = TemplatePartResult()
for part in self._parts:
@ -135,23 +143,29 @@ class StringTemplate:
invalid_types
)
def format_strict(self, *args, **kwargs):
result = self.format(*args, **kwargs)
def format_strict(self, data: Dict[str, Any]) -> "TemplateResult":
result = self.format(data)
result.validate()
return result
@classmethod
def format_template(cls, template, data):
def format_template(
cls, template: str, data: Dict[str, Any]
) -> "TemplateResult":
objected_template = cls(template)
return objected_template.format(data)
@classmethod
def format_strict_template(cls, template, data):
def format_strict_template(
cls, template: str, data: Dict[str, Any]
) -> "TemplateResult":
objected_template = cls(template)
return objected_template.format_strict(data)
@staticmethod
def find_optional_parts(parts):
def find_optional_parts(
parts: List["Union[str, FormattingPart]"]
) -> List["Union[str, OptionalPart, FormattingPart]"]:
new_parts = []
tmp_parts = {}
counted_symb = -1
@ -216,11 +230,11 @@ class TemplateResult(str):
of number.
"""
used_values = None
solved = None
template = None
missing_keys = None
invalid_types = None
used_values: Dict[str, Any] = None
solved: bool = None
template: str = None
missing_keys: List[str] = None
invalid_types: Dict[str, Any] = None
def __new__(
cls, filled_template, template, solved,
@ -248,7 +262,7 @@ class TemplateResult(str):
self.invalid_types
)
def copy(self):
def copy(self) -> "TemplateResult":
cls = self.__class__
return cls(
str(self),
@ -259,7 +273,7 @@ class TemplateResult(str):
self.invalid_types
)
def normalized(self):
def normalized(self) -> "TemplateResult":
"""Convert to normalized path."""
cls = self.__class__
@ -275,27 +289,28 @@ class TemplateResult(str):
class TemplatePartResult:
"""Result to store result of template parts."""
def __init__(self, optional=False):
def __init__(self, optional: bool = False):
# Missing keys or invalid value types of required keys
self._missing_keys = set()
self._invalid_types = {}
self._missing_keys: Set[str] = set()
self._invalid_types: Dict[str, Any] = {}
# Missing keys or invalid value types of optional keys
self._missing_optional_keys = set()
self._invalid_optional_types = {}
self._missing_optional_keys: Set[str] = set()
self._invalid_optional_types: Dict[str, Any] = {}
# Used values stored by key with origin type
# - key without any padding or key modifiers
# - value from filling data
# Example: {"version": 1}
self._used_values = {}
self._used_values: Dict[str, Any] = {}
# Used values stored by key with all modifirs
# - value is already formatted string
# Example: {"version:0>3": "001"}
self._realy_used_values = {}
self._really_used_values: Dict[str, Any] = {}
# Concatenated string output after formatting
self._output = ""
self._output: str = ""
# Is this result from optional part
self._optional = True
# TODO find out why we don't use 'optional' from args
self._optional: bool = True
def add_output(self, other):
if isinstance(other, str):
@ -313,7 +328,7 @@ class TemplatePartResult:
if other.optional and not other.solved:
return
self._used_values.update(other.used_values)
self._realy_used_values.update(other.realy_used_values)
self._really_used_values.update(other.really_used_values)
else:
raise TypeError("Cannot add data from \"{}\" to \"{}\"".format(
@ -321,7 +336,7 @@ class TemplatePartResult:
)
@property
def solved(self):
def solved(self) -> bool:
if self.optional:
if (
len(self.missing_optional_keys) > 0
@ -334,45 +349,53 @@ class TemplatePartResult:
)
@property
def optional(self):
def optional(self) -> bool:
return self._optional
@property
def output(self):
def output(self) -> str:
return self._output
@property
def missing_keys(self):
def missing_keys(self) -> Set[str]:
return self._missing_keys
@property
def missing_optional_keys(self):
def missing_optional_keys(self) -> Set[str]:
return self._missing_optional_keys
@property
def invalid_types(self):
def invalid_types(self) -> Dict[str, Any]:
return self._invalid_types
@property
def invalid_optional_types(self):
def invalid_optional_types(self) -> Dict[str, Any]:
return self._invalid_optional_types
@property
def realy_used_values(self):
return self._realy_used_values
def really_used_values(self) -> Dict[str, Any]:
return self._really_used_values
@property
def used_values(self):
def realy_used_values(self) -> Dict[str, Any]:
warnings.warn(
"Property 'realy_used_values' is deprecated."
" Use 'really_used_values' instead.",
DeprecationWarning
)
return self._really_used_values
@property
def used_values(self) -> Dict[str, Any]:
return self._used_values
@staticmethod
def split_keys_to_subdicts(values):
def split_keys_to_subdicts(values: Dict[str, Any]) -> Dict[str, Any]:
output = {}
formatter = Formatter()
for key, value in values.items():
key_padding = list(KEY_PADDING_PATTERN.findall(key))
if key_padding:
key = key_padding[0]
key_subdict = list(SUB_DICT_PATTERN.findall(key))
_, field_name, _, _ = next(formatter.parse(f"{{{key}}}"))
key_subdict = list(SUB_DICT_PATTERN.findall(field_name))
data = output
last_key = key_subdict.pop(-1)
for subkey in key_subdict:
@ -382,7 +405,7 @@ class TemplatePartResult:
data[last_key] = value
return output
def get_clean_used_values(self):
def get_clean_used_values(self) -> Dict[str, Any]:
new_used_values = {}
for key, value in self.used_values.items():
if isinstance(value, FormatObject):
@ -391,19 +414,27 @@ class TemplatePartResult:
return self.split_keys_to_subdicts(new_used_values)
def add_realy_used_value(self, key, value):
self._realy_used_values[key] = value
def add_really_used_value(self, key: str, value: Any):
self._really_used_values[key] = value
def add_used_value(self, key, value):
def add_realy_used_value(self, key: str, value: Any):
warnings.warn(
"Method 'add_realy_used_value' is deprecated."
" Use 'add_really_used_value' instead.",
DeprecationWarning
)
self.add_really_used_value(key, value)
def add_used_value(self, key: str, value: Any):
self._used_values[key] = value
def add_missing_key(self, key):
def add_missing_key(self, key: str):
if self._optional:
self._missing_optional_keys.add(key)
else:
self._missing_keys.add(key)
def add_invalid_type(self, key, value):
def add_invalid_type(self, key: str, value: Any):
if self._optional:
self._invalid_optional_types[key] = type(value)
else:
@ -421,10 +452,10 @@ class FormatObject:
def __format__(self, *args, **kwargs):
return self.value.__format__(*args, **kwargs)
def __str__(self):
def __str__(self) -> str:
return str(self.value)
def __repr__(self):
def __repr__(self) -> str:
return self.__str__()
@ -434,23 +465,44 @@ class FormattingPart:
Containt only single key to format e.g. "{project[name]}".
Args:
template(str): String containing the formatting key.
field_name (str): Name of key.
format_spec (str): Format specification.
conversion (Union[str, None]): Conversion type.
"""
def __init__(self, template):
self._template = template
def __init__(
self,
field_name: str,
format_spec: str,
conversion: "Union[str, None]",
):
format_spec_v = ""
if format_spec:
format_spec_v = f":{format_spec}"
conversion_v = ""
if conversion:
conversion_v = f"!{conversion}"
self._field_name: str = field_name
self._format_spec: str = format_spec_v
self._conversion: str = conversion_v
template_base = f"{field_name}{format_spec_v}{conversion_v}"
self._template_base: str = template_base
self._template: str = f"{{{template_base}}}"
@property
def template(self):
def template(self) -> str:
return self._template
def __repr__(self):
def __repr__(self) -> str:
return "<Format:{}>".format(self._template)
def __str__(self):
def __str__(self) -> str:
return self._template
@staticmethod
def validate_value_type(value):
def validate_value_type(value: Any) -> bool:
"""Check if value can be used for formatting of single key."""
if isinstance(value, (numbers.Number, FormatObject)):
return True
@ -461,7 +513,7 @@ class FormattingPart:
return False
@staticmethod
def validate_key_is_matched(key):
def validate_key_is_matched(key: str) -> bool:
"""Validate that opening has closing at correct place.
Future-proof, only square brackets are currently used in keys.
@ -488,16 +540,29 @@ class FormattingPart:
return False
return not queue
def format(self, data, result):
@staticmethod
def keys_to_template_base(keys: List[str]):
if not keys:
return None
# Create copy of keys
keys = list(keys)
template_base = keys.pop(0)
joined_keys = "".join([f"[{key}]" for key in keys])
return f"{template_base}{joined_keys}"
def format(
self, data: Dict[str, Any], result: TemplatePartResult
) -> TemplatePartResult:
"""Format the formattings string.
Args:
data(dict): Data that should be used for formatting.
result(TemplatePartResult): Object where result is stored.
"""
key = self.template[1:-1]
if key in result.realy_used_values:
result.add_output(result.realy_used_values[key])
key = self._template_base
if key in result.really_used_values:
result.add_output(result.really_used_values[key])
return result
# ensure key is properly formed [({})] properly closed.
@ -507,17 +572,38 @@ class FormattingPart:
return result
# check if key expects subdictionary keys (e.g. project[name])
existence_check = key
key_padding = list(KEY_PADDING_PATTERN.findall(existence_check))
if key_padding:
existence_check = key_padding[0]
key_subdict = list(SUB_DICT_PATTERN.findall(existence_check))
key_subdict = list(SUB_DICT_PATTERN.findall(self._field_name))
value = data
missing_key = False
invalid_type = False
used_keys = []
keys_to_value = None
used_value = None
for sub_key in key_subdict:
if isinstance(value, list):
if not sub_key.lstrip("-").isdigit():
invalid_type = True
break
sub_key = int(sub_key)
if sub_key < 0:
sub_key = len(value) + sub_key
invalid = 0 > sub_key < len(data)
if invalid:
used_keys.append(sub_key)
missing_key = True
break
used_keys.append(sub_key)
if keys_to_value is None:
keys_to_value = list(used_keys)
keys_to_value.pop(-1)
used_value = copy.deepcopy(value)
value = value[sub_key]
continue
if (
value is None
or (hasattr(value, "items") and sub_key not in value)
@ -533,45 +619,57 @@ class FormattingPart:
used_keys.append(sub_key)
value = value.get(sub_key)
if missing_key or invalid_type:
if len(used_keys) == 0:
invalid_key = key_subdict[0]
else:
invalid_key = used_keys[0]
for idx, sub_key in enumerate(used_keys):
if idx == 0:
continue
invalid_key += "[{0}]".format(sub_key)
field_name = key_subdict[0]
if used_keys:
field_name = self.keys_to_template_base(used_keys)
if missing_key or invalid_type:
if missing_key:
result.add_missing_key(invalid_key)
result.add_missing_key(field_name)
elif invalid_type:
result.add_invalid_type(invalid_key, value)
result.add_invalid_type(field_name, value)
result.add_output(self.template)
return result
if self.validate_value_type(value):
fill_data = {}
first_value = True
for used_key in reversed(used_keys):
if first_value:
first_value = False
fill_data[used_key] = value
else:
_fill_data = {used_key: fill_data}
fill_data = _fill_data
formatted_value = self.template.format(**fill_data)
result.add_realy_used_value(key, formatted_value)
result.add_used_value(existence_check, formatted_value)
result.add_output(formatted_value)
if not self.validate_value_type(value):
result.add_invalid_type(key, value)
result.add_output(self.template)
return result
result.add_invalid_type(key, value)
result.add_output(self.template)
fill_data = root_fill_data = {}
parent_fill_data = None
parent_key = None
fill_value = data
value_filled = False
for used_key in used_keys:
if isinstance(fill_value, list):
parent_fill_data[parent_key] = fill_value
value_filled = True
break
fill_value = fill_value[used_key]
parent_fill_data = fill_data
fill_data = parent_fill_data.setdefault(used_key, {})
parent_key = used_key
if not value_filled:
parent_fill_data[used_keys[-1]] = value
template = f"{{{field_name}{self._format_spec}{self._conversion}}}"
formatted_value = template.format(**root_fill_data)
used_key = key
if keys_to_value is not None:
used_key = self.keys_to_template_base(keys_to_value)
if used_value is None:
if isinstance(value, numbers.Number):
used_value = value
else:
used_value = formatted_value
result.add_really_used_value(self._field_name, used_value)
result.add_used_value(used_key, used_value)
result.add_output(formatted_value)
return result
@ -585,20 +683,27 @@ class OptionalPart:
'FormattingPart'.
"""
def __init__(self, parts):
self._parts = parts
def __init__(
self,
parts: List["Union[str, OptionalPart, FormattingPart]"]
):
self._parts: List["Union[str, OptionalPart, FormattingPart]"] = parts
@property
def parts(self):
def parts(self) -> List["Union[str, OptionalPart, FormattingPart]"]:
return self._parts
def __str__(self):
def __str__(self) -> str:
return "<{}>".format("".join([str(p) for p in self._parts]))
def __repr__(self):
def __repr__(self) -> str:
return "<Optional:{}>".format("".join([str(p) for p in self._parts]))
def format(self, data, result):
def format(
self,
data: Dict[str, Any],
result: TemplatePartResult,
) -> TemplatePartResult:
new_result = TemplatePartResult(True)
for part in self._parts:
if isinstance(part, str):

View file

@ -465,7 +465,9 @@ def update_container(container, version=-1):
from ayon_core.pipeline import get_current_project_name
# Compute the different version from 'representation'
project_name = get_current_project_name()
project_name = container.get("project_name")
if project_name is None:
project_name = get_current_project_name()
repre_id = container["representation"]
if not _is_valid_representation_id(repre_id):
raise ValueError(
@ -542,9 +544,6 @@ def update_container(container, version=-1):
)
)
path = get_representation_path(new_representation)
if not path or not os.path.exists(path):
raise ValueError("Path {} doesn't exist".format(path))
project_entity = ayon_api.get_project(project_name)
context = {
"project": project_entity,
@ -553,6 +552,9 @@ def update_container(container, version=-1):
"version": new_version,
"representation": new_representation,
}
path = get_representation_path_from_context(context)
if not path or not os.path.exists(path):
raise ValueError("Path {} doesn't exist".format(path))
return Loader().update(container, context)
@ -588,7 +590,9 @@ def switch_container(container, representation, loader_plugin=None):
)
# Get the new representation to switch to
project_name = get_current_project_name()
project_name = container.get("project_name")
if project_name is None:
project_name = get_current_project_name()
context = get_representation_context(
project_name, representation["id"]

View file

@ -87,14 +87,13 @@ def get_folder_template_data(folder_entity, project_name):
"""
path = folder_entity["path"]
hierarchy_parts = path.split("/")
# Remove empty string from the beginning
hierarchy_parts.pop(0)
# Remove empty string from the beginning and split by '/'
parents = path.lstrip("/").split("/")
# Remove last part which is folder name
folder_name = hierarchy_parts.pop(-1)
hierarchy = "/".join(hierarchy_parts)
if hierarchy_parts:
parent_name = hierarchy_parts[-1]
folder_name = parents.pop(-1)
hierarchy = "/".join(parents)
if parents:
parent_name = parents[-1]
else:
parent_name = project_name
@ -103,6 +102,7 @@ def get_folder_template_data(folder_entity, project_name):
"name": folder_name,
"type": folder_entity["folderType"],
"path": path,
"parents": parents,
},
"asset": folder_name,
"hierarchy": hierarchy,

View file

@ -434,6 +434,7 @@ class CollectAnatomyInstanceData(pyblish.api.ContextPlugin):
# Using 'Shot' is current default behavior of editorial
# (or 'newHierarchyIntegration') publishing.
"type": "Shot",
"parents": parents,
},
})

View file

@ -86,8 +86,9 @@ class SceneInventoryController:
self._current_folder_set = True
return self._current_folder_id
def get_project_status_items(self):
project_name = self.get_current_project_name()
def get_project_status_items(self, project_name=None):
if project_name is None:
project_name = self.get_current_project_name()
return self._projects_model.get_project_status_items(
project_name, None
)
@ -105,32 +106,39 @@ class SceneInventoryController:
def get_container_items_by_id(self, item_ids):
return self._containers_model.get_container_items_by_id(item_ids)
def get_representation_info_items(self, representation_ids):
def get_representation_info_items(self, project_name, representation_ids):
return self._containers_model.get_representation_info_items(
representation_ids
project_name, representation_ids
)
def get_version_items(self, product_ids):
return self._containers_model.get_version_items(product_ids)
def get_version_items(self, project_name, product_ids):
return self._containers_model.get_version_items(
project_name, product_ids)
# Site Sync methods
def is_sitesync_enabled(self):
return self._sitesync_model.is_sitesync_enabled()
def get_sites_information(self):
return self._sitesync_model.get_sites_information()
def get_sites_information(self, project_name):
return self._sitesync_model.get_sites_information(project_name)
def get_site_provider_icons(self):
return self._sitesync_model.get_site_provider_icons()
def get_representations_site_progress(self, representation_ids):
def get_representations_site_progress(
self, project_name, representation_ids
):
return self._sitesync_model.get_representations_site_progress(
representation_ids
project_name, representation_ids
)
def resync_representations(self, representation_ids, site_type):
def resync_representations(
self, project_name, representation_ids, site_type
):
return self._sitesync_model.resync_representations(
representation_ids, site_type
project_name,
representation_ids,
site_type
)
# Switch dialog methods

View file

@ -36,6 +36,7 @@ REMOTE_SITE_ICON_ROLE = QtCore.Qt.UserRole + 23
# This value hold unique value of container that should be used to identify
# containers inbetween refresh.
ITEM_UNIQUE_NAME_ROLE = QtCore.Qt.UserRole + 24
PROJECT_NAME_ROLE = QtCore.Qt.UserRole + 25
class InventoryModel(QtGui.QStandardItemModel):
@ -52,6 +53,7 @@ class InventoryModel(QtGui.QStandardItemModel):
"Object name",
"Active site",
"Remote site",
"Project",
]
name_col = column_labels.index("Name")
version_col = column_labels.index("Version")
@ -63,6 +65,7 @@ class InventoryModel(QtGui.QStandardItemModel):
object_name_col = column_labels.index("Object name")
active_site_col = column_labels.index("Active site")
remote_site_col = column_labels.index("Remote site")
project_col = column_labels.index("Project")
display_role_by_column = {
name_col: QtCore.Qt.DisplayRole,
version_col: VERSION_LABEL_ROLE,
@ -72,6 +75,7 @@ class InventoryModel(QtGui.QStandardItemModel):
product_group_col: PRODUCT_GROUP_NAME_ROLE,
loader_col: LOADER_NAME_ROLE,
object_name_col: OBJECT_NAME_ROLE,
project_col: PROJECT_NAME_ROLE,
active_site_col: ACTIVE_SITE_PROGRESS_ROLE,
remote_site_col: REMOTE_SITE_PROGRESS_ROLE,
}
@ -85,7 +89,7 @@ class InventoryModel(QtGui.QStandardItemModel):
foreground_role_by_column = {
name_col: NAME_COLOR_ROLE,
version_col: VERSION_COLOR_ROLE,
status_col: STATUS_COLOR_ROLE
status_col: STATUS_COLOR_ROLE,
}
width_by_column = {
name_col: 250,
@ -95,6 +99,7 @@ class InventoryModel(QtGui.QStandardItemModel):
product_type_col: 150,
product_group_col: 120,
loader_col: 150,
project_col: 150,
}
OUTDATED_COLOR = QtGui.QColor(235, 30, 30)
@ -116,8 +121,8 @@ class InventoryModel(QtGui.QStandardItemModel):
self._default_icon_color = get_default_entity_icon_color()
self._last_project_statuses = {}
self._last_status_icons_by_name = {}
self._last_project_statuses = collections.defaultdict(dict)
self._last_status_icons_by_name = collections.defaultdict(dict)
def outdated(self, item):
return item.get("isOutdated", True)
@ -129,45 +134,73 @@ class InventoryModel(QtGui.QStandardItemModel):
self._clear_items()
items_by_repre_id = {}
project_names = set()
repre_ids_by_project = collections.defaultdict(set)
version_items_by_project = collections.defaultdict(dict)
repre_info_by_id_by_project = collections.defaultdict(dict)
item_by_repre_id_by_project = collections.defaultdict(
lambda: collections.defaultdict(list))
for container_item in container_items:
# if (
# selected is not None
# and container_item.item_id not in selected
# ):
# continue
repre_id = container_item.representation_id
items = items_by_repre_id.setdefault(repre_id, [])
items.append(container_item)
project_name = container_item.project_name
representation_id = container_item.representation_id
project_names.add(project_name)
repre_ids_by_project[project_name].add(representation_id)
(
item_by_repre_id_by_project
[project_name]
[representation_id]
).append(container_item)
for project_name, representation_ids in repre_ids_by_project.items():
repre_info = self._controller.get_representation_info_items(
project_name, representation_ids
)
repre_info_by_id_by_project[project_name] = repre_info
product_ids = {
repre_info.product_id
for repre_info in repre_info.values()
if repre_info.is_valid
}
version_items = self._controller.get_version_items(
project_name, product_ids
)
version_items_by_project[project_name] = version_items
repre_id = set(items_by_repre_id.keys())
repre_info_by_id = self._controller.get_representation_info_items(
repre_id
)
product_ids = {
repre_info.product_id
for repre_info in repre_info_by_id.values()
if repre_info.is_valid
}
version_items_by_product_id = self._controller.get_version_items(
product_ids
)
# SiteSync addon information
progress_by_id = self._controller.get_representations_site_progress(
repre_id
)
sites_info = self._controller.get_sites_information()
progress_by_project = {
project_name: self._controller.get_representations_site_progress(
project_name, repre_ids
)
for project_name, repre_ids in repre_ids_by_project.items()
}
sites_info_by_project_name = {
project_name: self._controller.get_sites_information(project_name)
for project_name in project_names
}
site_icons = {
provider: get_qt_icon(icon_def)
for provider, icon_def in (
self._controller.get_site_provider_icons().items()
)
}
self._last_project_statuses = {
status_item.name: status_item
for status_item in self._controller.get_project_status_items()
}
self._last_status_icons_by_name = {}
last_project_statuses = collections.defaultdict(dict)
for project_name in project_names:
status_items_by_name = {
status_item.name: status_item
for status_item in self._controller.get_project_status_items(
project_name
)
}
last_project_statuses[project_name] = status_items_by_name
self._last_project_statuses = last_project_statuses
self._last_status_icons_by_name = collections.defaultdict(dict)
group_item_icon = qtawesome.icon(
"fa.folder", color=self._default_icon_color
@ -187,118 +220,130 @@ class InventoryModel(QtGui.QStandardItemModel):
group_item_font = QtGui.QFont()
group_item_font.setBold(True)
active_site_icon = site_icons.get(sites_info["active_site_provider"])
remote_site_icon = site_icons.get(sites_info["remote_site_provider"])
root_item = self.invisibleRootItem()
group_items = []
for repre_id, container_items in items_by_repre_id.items():
repre_info = repre_info_by_id[repre_id]
version_color = None
if not repre_info.is_valid:
version_label = "N/A"
group_name = "< Entity N/A >"
item_icon = invalid_item_icon
is_latest = False
is_hero = False
status_name = None
for project_name, items_by_repre_id in (
item_by_repre_id_by_project.items()
):
sites_info = sites_info_by_project_name[project_name]
active_site_icon = site_icons.get(
sites_info["active_site_provider"]
)
remote_site_icon = site_icons.get(
sites_info["remote_site_provider"]
)
else:
group_name = "{}_{}: ({})".format(
repre_info.folder_path.rsplit("/")[-1],
repre_info.product_name,
repre_info.representation_name
progress_by_id = progress_by_project[project_name]
repre_info_by_id = repre_info_by_id_by_project[project_name]
version_items_by_product_id = (
version_items_by_project[project_name]
)
for repre_id, container_items in items_by_repre_id.items():
repre_info = repre_info_by_id[repre_id]
version_color = None
if not repre_info.is_valid:
version_label = "N/A"
group_name = "< Entity N/A >"
item_icon = invalid_item_icon
is_latest = False
is_hero = False
status_name = None
else:
group_name = "{}_{}: ({})".format(
repre_info.folder_path.rsplit("/")[-1],
repre_info.product_name,
repre_info.representation_name
)
item_icon = valid_item_icon
version_items = (
version_items_by_product_id[repre_info.product_id]
)
version_item = version_items[repre_info.version_id]
version_label = format_version(version_item.version)
is_hero = version_item.version < 0
is_latest = version_item.is_latest
if not version_item.is_latest:
version_color = self.OUTDATED_COLOR
status_name = version_item.status
(
status_color, status_short, status_icon
) = self._get_status_data(project_name, status_name)
repre_name = (
repre_info.representation_name or
"<unknown representation>"
)
item_icon = valid_item_icon
container_model_items = []
for container_item in container_items:
object_name = container_item.object_name or "<none>"
unique_name = repre_name + object_name
item = QtGui.QStandardItem()
item.setColumnCount(root_item.columnCount())
item.setData(container_item.namespace,
QtCore.Qt.DisplayRole)
item.setData(self.GRAYOUT_COLOR, NAME_COLOR_ROLE)
item.setData(self.GRAYOUT_COLOR, VERSION_COLOR_ROLE)
item.setData(item_icon, QtCore.Qt.DecorationRole)
item.setData(repre_info.product_id, PRODUCT_ID_ROLE)
item.setData(container_item.item_id, ITEM_ID_ROLE)
item.setData(version_label, VERSION_LABEL_ROLE)
item.setData(container_item.loader_name, LOADER_NAME_ROLE)
item.setData(container_item.object_name, OBJECT_NAME_ROLE)
item.setData(True, IS_CONTAINER_ITEM_ROLE)
item.setData(unique_name, ITEM_UNIQUE_NAME_ROLE)
container_model_items.append(item)
version_items = (
version_items_by_product_id[repre_info.product_id]
progress = progress_by_id[repre_id]
active_site_progress = "{}%".format(
max(progress["active_site"], 0) * 100
)
remote_site_progress = "{}%".format(
max(progress["remote_site"], 0) * 100
)
version_item = version_items[repre_info.version_id]
version_label = format_version(version_item.version)
is_hero = version_item.version < 0
is_latest = version_item.is_latest
if not version_item.is_latest:
version_color = self.OUTDATED_COLOR
status_name = version_item.status
status_color, status_short, status_icon = self._get_status_data(
status_name
)
group_item = QtGui.QStandardItem()
group_item.setColumnCount(root_item.columnCount())
group_item.setData(group_name, QtCore.Qt.DisplayRole)
group_item.setData(group_name, ITEM_UNIQUE_NAME_ROLE)
group_item.setData(group_item_icon, QtCore.Qt.DecorationRole)
group_item.setData(group_item_font, QtCore.Qt.FontRole)
group_item.setData(repre_info.product_id, PRODUCT_ID_ROLE)
group_item.setData(repre_info.product_type, PRODUCT_TYPE_ROLE)
group_item.setData(product_type_icon, PRODUCT_TYPE_ICON_ROLE)
group_item.setData(is_latest, VERSION_IS_LATEST_ROLE)
group_item.setData(is_hero, VERSION_IS_HERO_ROLE)
group_item.setData(version_label, VERSION_LABEL_ROLE)
group_item.setData(len(container_items), COUNT_ROLE)
group_item.setData(status_name, STATUS_NAME_ROLE)
group_item.setData(status_short, STATUS_SHORT_ROLE)
group_item.setData(status_color, STATUS_COLOR_ROLE)
group_item.setData(status_icon, STATUS_ICON_ROLE)
group_item.setData(project_name, PROJECT_NAME_ROLE)
repre_name = (
repre_info.representation_name or "<unknown representation>"
)
container_model_items = []
for container_item in container_items:
object_name = container_item.object_name or "<none>"
unique_name = repre_name + object_name
item = QtGui.QStandardItem()
item.setColumnCount(root_item.columnCount())
item.setData(container_item.namespace, QtCore.Qt.DisplayRole)
item.setData(self.GRAYOUT_COLOR, NAME_COLOR_ROLE)
item.setData(self.GRAYOUT_COLOR, VERSION_COLOR_ROLE)
item.setData(item_icon, QtCore.Qt.DecorationRole)
item.setData(repre_info.product_id, PRODUCT_ID_ROLE)
item.setData(container_item.item_id, ITEM_ID_ROLE)
item.setData(version_label, VERSION_LABEL_ROLE)
item.setData(container_item.loader_name, LOADER_NAME_ROLE)
item.setData(container_item.object_name, OBJECT_NAME_ROLE)
item.setData(True, IS_CONTAINER_ITEM_ROLE)
item.setData(unique_name, ITEM_UNIQUE_NAME_ROLE)
container_model_items.append(item)
if not container_model_items:
continue
progress = progress_by_id[repre_id]
active_site_progress = "{}%".format(
max(progress["active_site"], 0) * 100
)
remote_site_progress = "{}%".format(
max(progress["remote_site"], 0) * 100
)
group_item = QtGui.QStandardItem()
group_item.setColumnCount(root_item.columnCount())
group_item.setData(group_name, QtCore.Qt.DisplayRole)
group_item.setData(group_name, ITEM_UNIQUE_NAME_ROLE)
group_item.setData(group_item_icon, QtCore.Qt.DecorationRole)
group_item.setData(group_item_font, QtCore.Qt.FontRole)
group_item.setData(repre_info.product_id, PRODUCT_ID_ROLE)
group_item.setData(repre_info.product_type, PRODUCT_TYPE_ROLE)
group_item.setData(product_type_icon, PRODUCT_TYPE_ICON_ROLE)
group_item.setData(is_latest, VERSION_IS_LATEST_ROLE)
group_item.setData(is_hero, VERSION_IS_HERO_ROLE)
group_item.setData(version_label, VERSION_LABEL_ROLE)
group_item.setData(len(container_items), COUNT_ROLE)
group_item.setData(status_name, STATUS_NAME_ROLE)
group_item.setData(status_short, STATUS_SHORT_ROLE)
group_item.setData(status_color, STATUS_COLOR_ROLE)
group_item.setData(status_icon, STATUS_ICON_ROLE)
group_item.setData(
active_site_progress, ACTIVE_SITE_PROGRESS_ROLE
)
group_item.setData(
remote_site_progress, REMOTE_SITE_PROGRESS_ROLE
)
group_item.setData(active_site_icon, ACTIVE_SITE_ICON_ROLE)
group_item.setData(remote_site_icon, REMOTE_SITE_ICON_ROLE)
group_item.setData(False, IS_CONTAINER_ITEM_ROLE)
if version_color is not None:
group_item.setData(version_color, VERSION_COLOR_ROLE)
if repre_info.product_group:
group_item.setData(
repre_info.product_group, PRODUCT_GROUP_NAME_ROLE
active_site_progress, ACTIVE_SITE_PROGRESS_ROLE
)
group_item.setData(group_icon, PRODUCT_GROUP_ICON_ROLE)
group_item.setData(
remote_site_progress, REMOTE_SITE_PROGRESS_ROLE
)
group_item.setData(active_site_icon, ACTIVE_SITE_ICON_ROLE)
group_item.setData(remote_site_icon, REMOTE_SITE_ICON_ROLE)
group_item.setData(False, IS_CONTAINER_ITEM_ROLE)
group_item.appendRows(container_model_items)
group_items.append(group_item)
if version_color is not None:
group_item.setData(version_color, VERSION_COLOR_ROLE)
if repre_info.product_group:
group_item.setData(
repre_info.product_group, PRODUCT_GROUP_NAME_ROLE
)
group_item.setData(group_icon, PRODUCT_GROUP_ICON_ROLE)
group_item.appendRows(container_model_items)
group_items.append(group_item)
if group_items:
root_item.appendRows(group_items)
@ -359,17 +404,21 @@ class InventoryModel(QtGui.QStandardItemModel):
root_item = self.invisibleRootItem()
root_item.removeRows(0, root_item.rowCount())
def _get_status_data(self, status_name):
status_item = self._last_project_statuses.get(status_name)
status_icon = self._get_status_icon(status_name, status_item)
def _get_status_data(self, project_name, status_name):
status_item = self._last_project_statuses[project_name].get(
status_name
)
status_icon = self._get_status_icon(
project_name, status_name, status_item
)
status_color = status_short = None
if status_item is not None:
status_color = status_item.color
status_short = status_item.short
return status_color, status_short, status_icon
def _get_status_icon(self, status_name, status_item):
icon = self._last_status_icons_by_name.get(status_name)
def _get_status_icon(self, project_name, status_name, status_item):
icon = self._last_status_icons_by_name[project_name].get(status_name)
if icon is not None:
return icon
@ -382,7 +431,7 @@ class InventoryModel(QtGui.QStandardItemModel):
})
if icon is None:
icon = QtGui.QIcon()
self._last_status_icons_by_name[status_name] = icon
self._last_status_icons_by_name[project_name][status_name] = icon
return icon

View file

@ -93,22 +93,27 @@ class ContainerItem:
loader_name,
namespace,
object_name,
item_id
item_id,
project_name
):
self.representation_id = representation_id
self.loader_name = loader_name
self.object_name = object_name
self.namespace = namespace
self.item_id = item_id
self.project_name = project_name
@classmethod
def from_container_data(cls, container):
def from_container_data(cls, current_project_name, container):
return cls(
representation_id=container["representation"],
loader_name=container["loader"],
namespace=container["namespace"],
object_name=container["objectName"],
item_id=uuid.uuid4().hex,
project_name=container.get(
"project_name", current_project_name
)
)
@ -219,7 +224,7 @@ class ContainersModel:
for item_id in item_ids
}
def get_representation_info_items(self, representation_ids):
def get_representation_info_items(self, project_name, representation_ids):
output = {}
missing_repre_ids = set()
for repre_id in representation_ids:
@ -228,17 +233,14 @@ class ContainersModel:
except ValueError:
output[repre_id] = RepresentationInfo.new_invalid()
continue
repre_info = self._repre_info_by_id.get(repre_id)
if repre_info is None:
missing_repre_ids.add(repre_id)
else:
output[repre_id] = repre_info
if not missing_repre_ids:
return output
project_name = self._controller.get_current_project_name()
repre_hierarchy_by_id = get_representations_hierarchy(
project_name, missing_repre_ids
)
@ -276,10 +278,9 @@ class ContainersModel:
output[repre_id] = repre_info
return output
def get_version_items(self, product_ids):
def get_version_items(self, project_name, product_ids):
if not product_ids:
return {}
missing_ids = {
product_id
for product_id in product_ids
@ -294,7 +295,6 @@ class ContainersModel:
def version_sorted(entity):
return entity["version"]
project_name = self._controller.get_current_project_name()
version_entities_by_product_id = {
product_id: []
for product_id in missing_ids
@ -359,9 +359,11 @@ class ContainersModel:
containers_by_id = {}
container_items_by_id = {}
invalid_ids_mapping = {}
current_project_name = self._controller.get_current_project_name()
for container in containers:
try:
item = ContainerItem.from_container_data(container)
item = ContainerItem.from_container_data(
current_project_name, container)
repre_id = item.representation_id
try:
uuid.UUID(repre_id)

View file

@ -11,18 +11,18 @@ class SiteSyncModel:
self._sitesync_addon = NOT_SET
self._sitesync_enabled = None
self._active_site = NOT_SET
self._remote_site = NOT_SET
self._active_site_provider = NOT_SET
self._remote_site_provider = NOT_SET
self._active_site = {}
self._remote_site = {}
self._active_site_provider = {}
self._remote_site_provider = {}
def reset(self):
self._sitesync_addon = NOT_SET
self._sitesync_enabled = None
self._active_site = NOT_SET
self._remote_site = NOT_SET
self._active_site_provider = NOT_SET
self._remote_site_provider = NOT_SET
self._active_site = {}
self._remote_site = {}
self._active_site_provider = {}
self._remote_site_provider = {}
def is_sitesync_enabled(self):
"""Site sync is enabled.
@ -46,15 +46,21 @@ class SiteSyncModel:
sitesync_addon = self._get_sitesync_addon()
return sitesync_addon.get_site_icons()
def get_sites_information(self):
def get_sites_information(self, project_name):
return {
"active_site": self._get_active_site(),
"active_site_provider": self._get_active_site_provider(),
"remote_site": self._get_remote_site(),
"remote_site_provider": self._get_remote_site_provider()
"active_site": self._get_active_site(project_name),
"remote_site": self._get_remote_site(project_name),
"active_site_provider": self._get_active_site_provider(
project_name
),
"remote_site_provider": self._get_remote_site_provider(
project_name
)
}
def get_representations_site_progress(self, representation_ids):
def get_representations_site_progress(
self, project_name, representation_ids
):
"""Get progress of representations sync."""
representation_ids = set(representation_ids)
@ -68,13 +74,12 @@ class SiteSyncModel:
if not self.is_sitesync_enabled():
return output
project_name = self._controller.get_current_project_name()
sitesync_addon = self._get_sitesync_addon()
repre_entities = ayon_api.get_representations(
project_name, representation_ids
)
active_site = self._get_active_site()
remote_site = self._get_remote_site()
active_site = self._get_active_site(project_name)
remote_site = self._get_remote_site(project_name)
for repre_entity in repre_entities:
repre_output = output[repre_entity["id"]]
@ -86,20 +91,21 @@ class SiteSyncModel:
return output
def resync_representations(self, representation_ids, site_type):
def resync_representations(
self, project_name, representation_ids, site_type
):
"""
Args:
project_name (str): Project name.
representation_ids (Iterable[str]): Representation ids.
site_type (Literal[active_site, remote_site]): Site type.
"""
project_name = self._controller.get_current_project_name()
sitesync_addon = self._get_sitesync_addon()
active_site = self._get_active_site()
remote_site = self._get_remote_site()
active_site = self._get_active_site(project_name)
remote_site = self._get_remote_site(project_name)
progress = self.get_representations_site_progress(
representation_ids
project_name, representation_ids
)
for repre_id in representation_ids:
repre_progress = progress.get(repre_id)
@ -132,48 +138,49 @@ class SiteSyncModel:
self._sitesync_addon = sitesync_addon
self._sitesync_enabled = sync_enabled
def _get_active_site(self):
if self._active_site is NOT_SET:
self._cache_sites()
return self._active_site
def _get_active_site(self, project_name):
if project_name not in self._active_site:
self._cache_sites(project_name)
return self._active_site[project_name]
def _get_remote_site(self):
if self._remote_site is NOT_SET:
self._cache_sites()
return self._remote_site
def _get_remote_site(self, project_name):
if project_name not in self._remote_site:
self._cache_sites(project_name)
return self._remote_site[project_name]
def _get_active_site_provider(self):
if self._active_site_provider is NOT_SET:
self._cache_sites()
return self._active_site_provider
def _get_active_site_provider(self, project_name):
if project_name not in self._active_site_provider:
self._cache_sites(project_name)
return self._active_site_provider[project_name]
def _get_remote_site_provider(self):
if self._remote_site_provider is NOT_SET:
self._cache_sites()
return self._remote_site_provider
def _get_remote_site_provider(self, project_name):
if project_name not in self._remote_site_provider:
self._cache_sites(project_name)
return self._remote_site_provider[project_name]
def _cache_sites(self):
active_site = None
remote_site = None
active_site_provider = None
remote_site_provider = None
if self.is_sitesync_enabled():
sitesync_addon = self._get_sitesync_addon()
project_name = self._controller.get_current_project_name()
active_site = sitesync_addon.get_active_site(project_name)
remote_site = sitesync_addon.get_remote_site(project_name)
active_site_provider = "studio"
remote_site_provider = "studio"
if active_site != "studio":
active_site_provider = sitesync_addon.get_provider_for_site(
project_name, active_site
)
if remote_site != "studio":
remote_site_provider = sitesync_addon.get_provider_for_site(
project_name, remote_site
)
def _cache_sites(self, project_name):
self._active_site[project_name] = None
self._remote_site[project_name] = None
self._active_site_provider[project_name] = None
self._remote_site_provider[project_name] = None
if not self.is_sitesync_enabled():
return
self._active_site = active_site
self._remote_site = remote_site
self._active_site_provider = active_site_provider
self._remote_site_provider = remote_site_provider
sitesync_addon = self._get_sitesync_addon()
active_site = sitesync_addon.get_active_site(project_name)
remote_site = sitesync_addon.get_remote_site(project_name)
active_site_provider = "studio"
remote_site_provider = "studio"
if active_site != "studio":
active_site_provider = sitesync_addon.get_provider_for_site(
project_name, active_site
)
if remote_site != "studio":
remote_site_provider = sitesync_addon.get_provider_for_site(
project_name, remote_site
)
self._active_site[project_name] = active_site
self._remote_site[project_name] = remote_site
self._active_site_provider[project_name] = active_site_provider
self._remote_site_provider[project_name] = remote_site_provider

View file

@ -46,8 +46,13 @@ class SwitchAssetDialog(QtWidgets.QDialog):
switched = QtCore.Signal()
def __init__(self, controller, parent=None, items=None):
super(SwitchAssetDialog, self).__init__(parent)
def __init__(self, controller, project_name, items, parent=None):
super().__init__(parent)
current_project_name = controller.get_current_project_name()
folder_id = None
if current_project_name == project_name:
folder_id = controller.get_current_folder_id()
self.setWindowTitle("Switch selected items ...")
@ -147,11 +152,10 @@ class SwitchAssetDialog(QtWidgets.QDialog):
self._init_repre_name = None
self._fill_check = False
self._project_name = project_name
self._folder_id = folder_id
self._project_name = controller.get_current_project_name()
self._folder_id = controller.get_current_folder_id()
self._current_folder_btn.setEnabled(self._folder_id is not None)
self._current_folder_btn.setEnabled(folder_id is not None)
self._controller = controller
@ -159,7 +163,7 @@ class SwitchAssetDialog(QtWidgets.QDialog):
self._prepare_content_data()
def showEvent(self, event):
super(SwitchAssetDialog, self).showEvent(event)
super().showEvent(event)
self._show_timer.start()
def refresh(self, init_refresh=False):

View file

@ -192,29 +192,46 @@ class SceneInventoryView(QtWidgets.QTreeView):
container_item = container_items_by_id[item_id]
active_repre_id = container_item.representation_id
break
repre_ids_by_project = collections.defaultdict(set)
for container_item in container_items_by_id.values():
repre_id = container_item.representation_id
project_name = container_item.project_name
repre_ids_by_project[project_name].add(repre_id)
repre_info_by_id = self._controller.get_representation_info_items({
container_item.representation_id
for container_item in container_items_by_id.values()
})
valid_repre_ids = {
repre_id
for repre_id, repre_info in repre_info_by_id.items()
if repre_info.is_valid
}
repre_info_by_project = {}
repre_ids_by_project_name = {}
version_ids_by_project = {}
product_ids_by_project = {}
for project_name, repre_ids in repre_ids_by_project.items():
repres_info = self._controller.get_representation_info_items(
project_name, repre_ids
)
repre_info_by_project[project_name] = repres_info
repre_ids = set()
version_ids = set()
product_ids = set()
for repre_id, repre_info in repres_info.items():
if not repre_info.is_valid:
continue
repre_ids.add(repre_id)
version_ids.add(repre_info.version_id)
product_ids.add(repre_info.product_id)
repre_ids_by_project_name[project_name] = repre_ids
version_ids_by_project[project_name] = version_ids
product_ids_by_project[project_name] = product_ids
# Exclude items that are "NOT FOUND" since setting versions, updating
# and removal won't work for those items.
filtered_items = []
product_ids = set()
version_ids = set()
for container_item in container_items_by_id.values():
project_name = container_item.project_name
repre_id = container_item.representation_id
repre_info_by_id = repre_info_by_project.get(project_name, {})
repre_info = repre_info_by_id.get(repre_id)
if repre_info and repre_info.is_valid:
filtered_items.append(container_item)
version_ids.add(repre_info.version_id)
product_ids.add(repre_info.product_id)
# remove
remove_icon = qtawesome.icon("fa.remove", color=DEFAULT_COLOR)
@ -227,43 +244,51 @@ class SceneInventoryView(QtWidgets.QTreeView):
menu.addAction(remove_action)
return
version_items_by_product_id = self._controller.get_version_items(
product_ids
)
version_items_by_project = {
project_name: self._controller.get_version_items(
project_name, product_ids
)
for project_name, product_ids in product_ids_by_project.items()
}
has_outdated = False
has_loaded_hero_versions = False
has_available_hero_version = False
has_outdated_approved = False
last_version_by_product_id = {}
for product_id, version_items_by_id in (
version_items_by_product_id.items()
for project_name, version_items_by_product_id in (
version_items_by_project.items()
):
_has_outdated_approved = False
_last_approved_version_item = None
for version_item in version_items_by_id.values():
if version_item.is_hero:
has_available_hero_version = True
elif version_item.is_last_approved:
_last_approved_version_item = version_item
_has_outdated_approved = True
if version_item.version_id not in version_ids:
continue
if version_item.is_hero:
has_loaded_hero_versions = True
elif not version_item.is_latest:
has_outdated = True
if (
_has_outdated_approved
and _last_approved_version_item is not None
version_ids = version_ids_by_project[project_name]
for product_id, version_items_by_id in (
version_items_by_product_id.items()
):
last_version_by_product_id[product_id] = (
_last_approved_version_item
)
has_outdated_approved = True
_has_outdated_approved = False
_last_approved_version_item = None
for version_item in version_items_by_id.values():
if version_item.is_hero:
has_available_hero_version = True
elif version_item.is_last_approved:
_last_approved_version_item = version_item
_has_outdated_approved = True
if version_item.version_id not in version_ids:
continue
if version_item.is_hero:
has_loaded_hero_versions = True
elif not version_item.is_latest:
has_outdated = True
if (
_has_outdated_approved
and _last_approved_version_item is not None
):
last_version_by_product_id[product_id] = (
_last_approved_version_item
)
has_outdated_approved = True
switch_to_versioned = None
if has_loaded_hero_versions:
@ -284,8 +309,9 @@ class SceneInventoryView(QtWidgets.QTreeView):
approved_version_by_item_id = {}
if has_outdated_approved:
for container_item in container_items_by_id.values():
project_name = container_item.project_name
repre_id = container_item.representation_id
repre_info = repre_info_by_id.get(repre_id)
repre_info = repre_info_by_project[project_name][repre_id]
if not repre_info or not repre_info.is_valid:
continue
version_item = last_version_by_product_id.get(
@ -397,14 +423,15 @@ class SceneInventoryView(QtWidgets.QTreeView):
menu.addAction(remove_action)
self._handle_sitesync(menu, valid_repre_ids)
self._handle_sitesync(menu, repre_ids_by_project_name)
def _handle_sitesync(self, menu, repre_ids):
def _handle_sitesync(self, menu, repre_ids_by_project_name):
"""Adds actions for download/upload when SyncServer is enabled
Args:
menu (OptionMenu)
repre_ids (list) of object_ids
repre_ids_by_project_name (Dict[str, Set[str]]): Representation
ids by project name.
Returns:
(OptionMenu)
@ -413,7 +440,7 @@ class SceneInventoryView(QtWidgets.QTreeView):
if not self._controller.is_sitesync_enabled():
return
if not repre_ids:
if not repre_ids_by_project_name:
return
menu.addSeparator()
@ -425,7 +452,10 @@ class SceneInventoryView(QtWidgets.QTreeView):
menu
)
download_active_action.triggered.connect(
lambda: self._add_sites(repre_ids, "active_site"))
lambda: self._add_sites(
repre_ids_by_project_name, "active_site"
)
)
upload_icon = qtawesome.icon("fa.upload", color=DEFAULT_COLOR)
upload_remote_action = QtWidgets.QAction(
@ -434,23 +464,30 @@ class SceneInventoryView(QtWidgets.QTreeView):
menu
)
upload_remote_action.triggered.connect(
lambda: self._add_sites(repre_ids, "remote_site"))
lambda: self._add_sites(
repre_ids_by_project_name, "remote_site"
)
)
menu.addAction(download_active_action)
menu.addAction(upload_remote_action)
def _add_sites(self, repre_ids, site_type):
def _add_sites(self, repre_ids_by_project_name, site_type):
"""(Re)sync all 'repre_ids' to specific site.
It checks if opposite site has fully available content to limit
accidents. (ReSync active when no remote >> losing active content)
Args:
repre_ids (list)
repre_ids_by_project_name (Dict[str, Set[str]]): Representation
ids by project name.
site_type (Literal[active_site, remote_site]): Site type.
"""
self._controller.resync_representations(repre_ids, site_type)
"""
for project_name, repre_ids in repre_ids_by_project_name.items():
self._controller.resync_representations(
project_name, repre_ids, site_type
)
self.data_changed.emit()
@ -735,42 +772,68 @@ class SceneInventoryView(QtWidgets.QTreeView):
container_items_by_id = self._controller.get_container_items_by_id(
item_ids
)
repre_ids = {
container_item.representation_id
for container_item in container_items_by_id.values()
}
repre_info_by_id = self._controller.get_representation_info_items(
repre_ids
)
project_names = set()
repre_ids_by_project = collections.defaultdict(set)
for container_item in container_items_by_id.values():
repre_id = container_item.representation_id
project_name = container_item.project_name
project_names.add(project_name)
repre_ids_by_project[project_name].add(repre_id)
# active_project_name = None
active_repre_info = None
repre_info_by_project = {}
version_items_by_project = {}
for project_name, repre_ids in repre_ids_by_project.items():
repres_info = self._controller.get_representation_info_items(
project_name, repre_ids
)
if active_repre_info is None:
# active_project_name = project_name
active_repre_info = repres_info.get(active_repre_id)
product_ids = {
repre_info.product_id
for repre_info in repres_info.values()
if repre_info.is_valid
}
version_items_by_product_id = self._controller.get_version_items(
project_name, product_ids
)
repre_info_by_project[project_name] = repres_info
version_items_by_project[project_name] = (
version_items_by_product_id
)
product_ids = {
repre_info.product_id
for repre_info in repre_info_by_id.values()
}
active_repre_info = repre_info_by_id[active_repre_id]
active_version_id = active_repre_info.version_id
active_product_id = active_repre_info.product_id
version_items_by_product_id = self._controller.get_version_items(
product_ids
)
version_items = list(
version_items_by_product_id[active_product_id].values()
)
versions = {version_item.version for version_item in version_items}
product_ids_by_version = collections.defaultdict(set)
for version_items_by_id in version_items_by_product_id.values():
for version_item in version_items_by_id.values():
version = version_item.version
_prod_version = version
if _prod_version < 0:
_prod_version = -1
product_ids_by_version[_prod_version].add(
version_item.product_id
)
if version in versions:
continue
versions.add(version)
version_items.append(version_item)
# active_product_id = active_repre_info.product_id
versions = set()
product_ids = set()
version_items = []
product_ids_by_version_by_project = {}
for project_name, version_items_by_product_id in (
version_items_by_project.items()
):
product_ids_by_version = collections.defaultdict(set)
product_ids_by_version_by_project[project_name] = (
product_ids_by_version
)
for version_items_by_id in version_items_by_product_id.values():
for version_item in version_items_by_id.values():
version = version_item.version
_prod_version = version
if _prod_version < 0:
_prod_version = -1
product_ids_by_version[_prod_version].add(
version_item.product_id
)
product_ids.add(version_item.product_id)
if version in versions:
continue
versions.add(version)
version_items.append(version_item)
def version_sorter(item):
hero_value = 0
@ -831,12 +894,15 @@ class SceneInventoryView(QtWidgets.QTreeView):
product_version = -1
version = HeroVersionType(version)
product_ids = product_ids_by_version[product_version]
filtered_item_ids = set()
for container_item in container_items_by_id.values():
project_name = container_item.project_name
product_ids_by_version = (
product_ids_by_version_by_project[project_name]
)
product_ids = product_ids_by_version[product_version]
repre_id = container_item.representation_id
repre_info = repre_info_by_id[repre_id]
repre_info = repre_info_by_project[project_name][repre_id]
if repre_info.product_id in product_ids:
filtered_item_ids.add(container_item.item_id)
@ -846,14 +912,28 @@ class SceneInventoryView(QtWidgets.QTreeView):
def _show_switch_dialog(self, item_ids):
"""Display Switch dialog"""
containers_by_id = self._controller.get_containers_by_item_ids(
container_items_by_id = self._controller.get_container_items_by_id(
item_ids
)
dialog = SwitchAssetDialog(
self._controller, self, list(containers_by_id.values())
)
dialog.switched.connect(self.data_changed.emit)
dialog.show()
container_ids_by_project_name = collections.defaultdict(set)
for container_id, container_item in container_items_by_id.items():
project_name = container_item.project_name
container_ids_by_project_name[project_name].add(container_id)
for project_name, container_ids in (
container_ids_by_project_name.items()
):
containers_by_id = self._controller.get_containers_by_item_ids(
container_ids
)
dialog = SwitchAssetDialog(
self._controller,
project_name,
list(containers_by_id.values()),
self
)
dialog.switched.connect(self.data_changed.emit)
dialog.show()
def _show_remove_warning_dialog(self, item_ids):
"""Prompt a dialog to inform the user the action will remove items"""
@ -927,38 +1007,58 @@ class SceneInventoryView(QtWidgets.QTreeView):
self._update_containers_to_version(item_ids, version=-1)
def _on_switch_to_versioned(self, item_ids):
# Get container items by ID
containers_items_by_id = self._controller.get_container_items_by_id(
item_ids
)
repre_ids = {
container_item.representation_id
for container_item in containers_items_by_id.values()
}
repre_info_by_id = self._controller.get_representation_info_items(
repre_ids
)
product_ids = {
repre_info.product_id
for repre_info in repre_info_by_id.values()
if repre_info.is_valid
}
version_items_by_product_id = self._controller.get_version_items(
product_ids
)
item_ids)
# Extract project names and their corresponding representation IDs
repre_ids_by_project = collections.defaultdict(set)
for container_item in containers_items_by_id.values():
project_name = container_item.project_name
repre_id = container_item.representation_id
repre_ids_by_project[project_name].add(repre_id)
# Get representation info items by ID
repres_info_by_project = {}
version_items_by_project = {}
for project_name, repre_ids in repre_ids_by_project.items():
repre_info_by_id = self._controller.get_representation_info_items(
project_name, repre_ids
)
repres_info_by_project[project_name] = repre_info_by_id
product_ids = {
repre_info.product_id
for repre_info in repre_info_by_id.values()
if repre_info.is_valid
}
version_items_by_product_id = self._controller.get_version_items(
project_name, product_ids
)
version_items_by_project[project_name] = (
version_items_by_product_id
)
update_containers = []
update_versions = []
for item_id, container_item in containers_items_by_id.items():
for container_item in containers_items_by_id.values():
project_name = container_item.project_name
repre_id = container_item.representation_id
repre_info_by_id = repres_info_by_project[project_name]
repre_info = repre_info_by_id[repre_id]
version_items_by_product_id = (
version_items_by_project[project_name]
)
product_id = repre_info.product_id
version_items_id = version_items_by_product_id[product_id]
version_item = version_items_id.get(repre_info.version_id, {})
version_items_by_id = version_items_by_product_id[product_id]
version_item = version_items_by_id.get(repre_info.version_id, {})
if not version_item or not version_item.is_hero:
continue
version = abs(version_item.version)
version_found = False
for version_item in version_items_id.values():
for version_item in version_items_by_id.values():
if version_item.is_hero:
continue
if version_item.version == version:
@ -971,8 +1071,8 @@ class SceneInventoryView(QtWidgets.QTreeView):
update_containers.append(container_item.item_id)
update_versions.append(version)
# Specify version per item to update to
self._update_containers(update_containers, update_versions)
# Specify version per item to update to
self._update_containers(update_containers, update_versions)
def _update_containers(self, item_ids, versions):
"""Helper to update items to given version (or version per item)