{}".format(e))
- else:
- self._close_widget()
-
- def save_credentials(self, username, password):
- self.module.get_auth_token(username, password)
-
- def showEvent(self, event):
- super(MusterLogin, self).showEvent(event)
-
- # Make btns same width
- max_width = max(
- self.btn_ok.sizeHint().width(),
- self.btn_cancel.sizeHint().width()
- )
- self.btn_ok.setMinimumWidth(max_width)
- self.btn_cancel.setMinimumWidth(max_width)
-
- def closeEvent(self, event):
- event.ignore()
- self._close_widget()
-
- def _close_widget(self):
- self.hide()
diff --git a/openpype/modules/python_console_interpreter/window/widgets.py b/openpype/modules/python_console_interpreter/window/widgets.py
index 28950f8369..d046c0de50 100644
--- a/openpype/modules/python_console_interpreter/window/widgets.py
+++ b/openpype/modules/python_console_interpreter/window/widgets.py
@@ -354,7 +354,7 @@ class PythonInterpreterWidget(QtWidgets.QWidget):
default_width = 1000
default_height = 600
- def __init__(self, parent=None):
+ def __init__(self, allow_save_registry=True, parent=None):
super(PythonInterpreterWidget, self).__init__(parent)
self.setWindowTitle("{} Console".format(
@@ -414,6 +414,8 @@ class PythonInterpreterWidget(QtWidgets.QWidget):
self._first_show = True
self._splitter_size_ratio = None
+ self._allow_save_registry = allow_save_registry
+ self._registry_saved = True
self._init_from_registry()
@@ -457,6 +459,11 @@ class PythonInterpreterWidget(QtWidgets.QWidget):
pass
def save_registry(self):
+ # Window was not showed
+ if not self._allow_save_registry or self._registry_saved:
+ return
+
+ self._registry_saved = True
setting_registry = PythonInterpreterRegistry()
setting_registry.set_item("width", self.width())
@@ -650,6 +657,7 @@ class PythonInterpreterWidget(QtWidgets.QWidget):
def showEvent(self, event):
self._line_check_timer.start()
+ self._registry_saved = False
super(PythonInterpreterWidget, self).showEvent(event)
# First show setup
if self._first_show:
diff --git a/openpype/pipeline/farm/pyblish_functions.py b/openpype/pipeline/farm/pyblish_functions.py
index 54ff2627e1..975fdd31cc 100644
--- a/openpype/pipeline/farm/pyblish_functions.py
+++ b/openpype/pipeline/farm/pyblish_functions.py
@@ -582,16 +582,17 @@ def _create_instances_for_aov(instance, skeleton, aov_filter, additional_data,
group_name = subset
# if there are multiple cameras, we need to add camera name
- if isinstance(col, (list, tuple)):
- cam = [c for c in cameras if c in col[0]]
- else:
- # in case of single frame
- cam = [c for c in cameras if c in col]
- if cam:
- if aov:
- subset_name = '{}_{}_{}'.format(group_name, cam, aov)
- else:
- subset_name = '{}_{}'.format(group_name, cam)
+ expected_filepath = col[0] if isinstance(col, (list, tuple)) else col
+ cams = [cam for cam in cameras if cam in expected_filepath]
+ if cams:
+ for cam in cams:
+ if aov:
+ if not aov.startswith(cam):
+ subset_name = '{}_{}_{}'.format(group_name, cam, aov)
+ else:
+ subset_name = "{}_{}".format(group_name, aov)
+ else:
+ subset_name = '{}_{}'.format(group_name, cam)
else:
if aov:
subset_name = '{}_{}'.format(group_name, aov)
diff --git a/openpype/pipeline/project_folders.py b/openpype/pipeline/project_folders.py
index 1bcba5c320..608344ce03 100644
--- a/openpype/pipeline/project_folders.py
+++ b/openpype/pipeline/project_folders.py
@@ -28,13 +28,20 @@ def concatenate_splitted_paths(split_paths, anatomy):
# backward compatibility
if "__project_root__" in path_items:
for root, root_path in anatomy.roots.items():
- if not os.path.exists(str(root_path)):
- log.debug("Root {} path path {} not exist on \
- computer!".format(root, root_path))
+ if not root_path or not os.path.exists(str(root_path)):
+ log.debug(
+ "Root {} path path {} not exist on computer!".format(
+ root, root_path
+ )
+ )
continue
- clean_items = ["{{root[{}]}}".format(root),
- r"{project[name]}"] + clean_items[1:]
- output.append(os.path.normpath(os.path.sep.join(clean_items)))
+
+ root_items = [
+ "{{root[{}]}}".format(root),
+ "{project[name]}"
+ ]
+ root_items.extend(clean_items[1:])
+ output.append(os.path.normpath(os.path.sep.join(root_items)))
continue
output.append(os.path.normpath(os.path.sep.join(clean_items)))
diff --git a/openpype/pipeline/publish/lib.py b/openpype/pipeline/publish/lib.py
index 4ea2f932f1..40cb94e2bf 100644
--- a/openpype/pipeline/publish/lib.py
+++ b/openpype/pipeline/publish/lib.py
@@ -58,41 +58,13 @@ def get_template_name_profiles(
if not project_settings:
project_settings = get_project_settings(project_name)
- profiles = (
+ return copy.deepcopy(
project_settings
["global"]
["tools"]
["publish"]
["template_name_profiles"]
)
- if profiles:
- return copy.deepcopy(profiles)
-
- # Use legacy approach for cases new settings are not filled yet for the
- # project
- legacy_profiles = (
- project_settings
- ["global"]
- ["publish"]
- ["IntegrateAssetNew"]
- ["template_name_profiles"]
- )
- if legacy_profiles:
- if not logger:
- logger = Logger.get_logger("get_template_name_profiles")
-
- logger.warning((
- "Project \"{}\" is using legacy access to publish template."
- " It is recommended to move settings to new location"
- " 'project_settings/global/tools/publish/template_name_profiles'."
- ).format(project_name))
-
- # Replace "tasks" key with "task_names"
- profiles = []
- for profile in copy.deepcopy(legacy_profiles):
- profile["task_names"] = profile.pop("tasks", [])
- profiles.append(profile)
- return profiles
def get_hero_template_name_profiles(
@@ -121,36 +93,13 @@ def get_hero_template_name_profiles(
if not project_settings:
project_settings = get_project_settings(project_name)
- profiles = (
+ return copy.deepcopy(
project_settings
["global"]
["tools"]
["publish"]
["hero_template_name_profiles"]
)
- if profiles:
- return copy.deepcopy(profiles)
-
- # Use legacy approach for cases new settings are not filled yet for the
- # project
- legacy_profiles = copy.deepcopy(
- project_settings
- ["global"]
- ["publish"]
- ["IntegrateHeroVersion"]
- ["template_name_profiles"]
- )
- if legacy_profiles:
- if not logger:
- logger = Logger.get_logger("get_hero_template_name_profiles")
-
- logger.warning((
- "Project \"{}\" is using legacy access to hero publish template."
- " It is recommended to move settings to new location"
- " 'project_settings/global/tools/publish/"
- "hero_template_name_profiles'."
- ).format(project_name))
- return legacy_profiles
def get_publish_template_name(
diff --git a/openpype/pipeline/workfile/workfile_template_builder.py b/openpype/pipeline/workfile/workfile_template_builder.py
index 9dc833061a..3096d22518 100644
--- a/openpype/pipeline/workfile/workfile_template_builder.py
+++ b/openpype/pipeline/workfile/workfile_template_builder.py
@@ -1971,7 +1971,6 @@ class PlaceholderCreateMixin(object):
if not placeholder.data.get("keep_placeholder", True):
self.delete_placeholder(placeholder)
-
def create_failed(self, placeholder, creator_data):
if hasattr(placeholder, "create_failed"):
placeholder.create_failed(creator_data)
@@ -2036,7 +2035,7 @@ class CreatePlaceholderItem(PlaceholderItem):
self._failed_created_publish_instances = []
def get_errors(self):
- if not self._failed_representations:
+ if not self._failed_created_publish_instances:
return []
message = (
"Failed to create {} instance using Creator {}"
diff --git a/openpype/plugins/publish/collect_anatomy_instance_data.py b/openpype/plugins/publish/collect_anatomy_instance_data.py
index 1b4b44e40e..b1b7ecd138 100644
--- a/openpype/plugins/publish/collect_anatomy_instance_data.py
+++ b/openpype/plugins/publish/collect_anatomy_instance_data.py
@@ -190,48 +190,25 @@ class CollectAnatomyInstanceData(pyblish.api.ContextPlugin):
project_task_types = project_doc["config"]["tasks"]
for instance in context:
- asset_doc = instance.data.get("assetEntity")
- anatomy_updates = {
+ anatomy_data = copy.deepcopy(context.data["anatomyData"])
+ anatomy_data.update({
"family": instance.data["family"],
"subset": instance.data["subset"],
- }
- if asset_doc:
- parents = asset_doc["data"].get("parents") or list()
- parent_name = project_doc["name"]
- if parents:
- parent_name = parents[-1]
+ })
- hierarchy = "/".join(parents)
- anatomy_updates.update({
- "asset": asset_doc["name"],
- "hierarchy": hierarchy,
- "parent": parent_name,
- "folder": {
- "name": asset_doc["name"],
- },
- })
-
- # Task
- task_type = None
- task_name = instance.data.get("task")
- if task_name:
- asset_tasks = asset_doc["data"]["tasks"]
- task_type = asset_tasks.get(task_name, {}).get("type")
- task_code = (
- project_task_types
- .get(task_type, {})
- .get("short_name")
- )
- anatomy_updates["task"] = {
- "name": task_name,
- "type": task_type,
- "short": task_code
- }
+ self._fill_asset_data(instance, project_doc, anatomy_data)
+ self._fill_task_data(instance, project_task_types, anatomy_data)
# Define version
+ version_number = None
if self.follow_workfile_version:
- version_number = context.data('version')
- else:
+ version_number = context.data("version")
+
+ # Even if 'follow_workfile_version' is enabled, it may not be set
+ # because workfile version was not collected to 'context.data'
+ # - that can happen e.g. in 'traypublisher' or other hosts without
+ # a workfile
+ if version_number is None:
version_number = instance.data.get("version")
# use latest version (+1) if already any exist
@@ -242,6 +219,9 @@ class CollectAnatomyInstanceData(pyblish.api.ContextPlugin):
# If version is not specified for instance or context
if version_number is None:
+ task_data = anatomy_data.get("task") or {}
+ task_name = task_data.get("name")
+ task_type = task_data.get("type")
version_number = get_versioning_start(
context.data["projectName"],
instance.context.data["hostName"],
@@ -250,29 +230,26 @@ class CollectAnatomyInstanceData(pyblish.api.ContextPlugin):
family=instance.data["family"],
subset=instance.data["subset"]
)
- anatomy_updates["version"] = version_number
+ anatomy_data["version"] = version_number
# Additional data
resolution_width = instance.data.get("resolutionWidth")
if resolution_width:
- anatomy_updates["resolution_width"] = resolution_width
+ anatomy_data["resolution_width"] = resolution_width
resolution_height = instance.data.get("resolutionHeight")
if resolution_height:
- anatomy_updates["resolution_height"] = resolution_height
+ anatomy_data["resolution_height"] = resolution_height
pixel_aspect = instance.data.get("pixelAspect")
if pixel_aspect:
- anatomy_updates["pixel_aspect"] = float(
+ anatomy_data["pixel_aspect"] = float(
"{:0.2f}".format(float(pixel_aspect))
)
fps = instance.data.get("fps")
if fps:
- anatomy_updates["fps"] = float("{:0.2f}".format(float(fps)))
-
- anatomy_data = copy.deepcopy(context.data["anatomyData"])
- anatomy_data.update(anatomy_updates)
+ anatomy_data["fps"] = float("{:0.2f}".format(float(fps)))
# Store anatomy data
instance.data["projectEntity"] = project_doc
@@ -288,3 +265,157 @@ class CollectAnatomyInstanceData(pyblish.api.ContextPlugin):
instance_name,
json.dumps(anatomy_data, indent=4)
))
+
+ def _fill_asset_data(self, instance, project_doc, anatomy_data):
+ # QUESTION should we make sure that all asset data are poped if asset
+ # data cannot be found?
+ # - 'asset', 'hierarchy', 'parent', 'folder'
+ asset_doc = instance.data.get("assetEntity")
+ if asset_doc:
+ parents = asset_doc["data"].get("parents") or list()
+ parent_name = project_doc["name"]
+ if parents:
+ parent_name = parents[-1]
+
+ hierarchy = "/".join(parents)
+ anatomy_data.update({
+ "asset": asset_doc["name"],
+ "hierarchy": hierarchy,
+ "parent": parent_name,
+ "folder": {
+ "name": asset_doc["name"],
+ },
+ })
+ return
+
+ if instance.data.get("newAssetPublishing"):
+ hierarchy = instance.data["hierarchy"]
+ anatomy_data["hierarchy"] = hierarchy
+
+ parent_name = project_doc["name"]
+ if hierarchy:
+ parent_name = hierarchy.split("/")[-1]
+
+ asset_name = instance.data["asset"].split("/")[-1]
+ anatomy_data.update({
+ "asset": asset_name,
+ "hierarchy": hierarchy,
+ "parent": parent_name,
+ "folder": {
+ "name": asset_name,
+ },
+ })
+
+ def _fill_task_data(self, instance, project_task_types, anatomy_data):
+ # QUESTION should we make sure that all task data are poped if task
+ # data cannot be resolved?
+ # - 'task'
+
+ # Skip if there is no task
+ task_name = instance.data.get("task")
+ if not task_name:
+ return
+
+ # Find task data based on asset entity
+ asset_doc = instance.data.get("assetEntity")
+ task_data = self._get_task_data_from_asset(
+ asset_doc, task_name, project_task_types
+ )
+ if task_data:
+ # Fill task data
+ # - if we're in editorial, make sure the task type is filled
+ if (
+ not instance.data.get("newAssetPublishing")
+ or task_data["type"]
+ ):
+ anatomy_data["task"] = task_data
+ return
+
+ # New hierarchy is not created, so we can only skip rest of the logic
+ if not instance.data.get("newAssetPublishing"):
+ return
+
+ # Try to find task data based on hierarchy context and asset name
+ hierarchy_context = instance.context.data.get("hierarchyContext")
+ asset_name = instance.data.get("asset")
+ if not hierarchy_context or not asset_name:
+ return
+
+ project_name = instance.context.data["projectName"]
+ # OpenPype approach vs AYON approach
+ if "/" not in asset_name:
+ tasks_info = self._find_tasks_info_in_hierarchy(
+ hierarchy_context, asset_name
+ )
+ else:
+ current_data = hierarchy_context.get(project_name, {})
+ for key in asset_name.split("/"):
+ if key:
+ current_data = current_data.get("childs", {}).get(key, {})
+ tasks_info = current_data.get("tasks", {})
+
+ task_info = tasks_info.get(task_name, {})
+ task_type = task_info.get("type")
+ task_code = (
+ project_task_types
+ .get(task_type, {})
+ .get("short_name")
+ )
+ anatomy_data["task"] = {
+ "name": task_name,
+ "type": task_type,
+ "short": task_code
+ }
+
+ def _get_task_data_from_asset(
+ self, asset_doc, task_name, project_task_types
+ ):
+ """
+
+ Args:
+ asset_doc (Union[dict[str, Any], None]): Asset document.
+ task_name (Union[str, None]): Task name.
+ project_task_types (dict[str, dict[str, Any]]): Project task
+ types.
+
+ Returns:
+ Union[dict[str, str], None]: Task data or None if not found.
+ """
+
+ if not asset_doc or not task_name:
+ return None
+
+ asset_tasks = asset_doc["data"]["tasks"]
+ task_type = asset_tasks.get(task_name, {}).get("type")
+ task_code = (
+ project_task_types
+ .get(task_type, {})
+ .get("short_name")
+ )
+ return {
+ "name": task_name,
+ "type": task_type,
+ "short": task_code
+ }
+
+ def _find_tasks_info_in_hierarchy(self, hierarchy_context, asset_name):
+ """Find tasks info for an asset in editorial hierarchy.
+
+ Args:
+ hierarchy_context (dict[str, Any]): Editorial hierarchy context.
+ asset_name (str): Asset name.
+
+ Returns:
+ dict[str, dict[str, Any]]: Tasks info by name.
+ """
+
+ hierarchy_queue = collections.deque()
+ hierarchy_queue.append(copy.deepcopy(hierarchy_context))
+ while hierarchy_queue:
+ item = hierarchy_queue.popleft()
+ if asset_name in item:
+ return item[asset_name].get("tasks") or {}
+
+ for subitem in item.values():
+ hierarchy_queue.extend(subitem.get("childs") or [])
+ return {}
diff --git a/openpype/plugins/publish/collect_farm_target.py b/openpype/plugins/publish/collect_farm_target.py
index adcd842b48..2f77c823d7 100644
--- a/openpype/plugins/publish/collect_farm_target.py
+++ b/openpype/plugins/publish/collect_farm_target.py
@@ -19,7 +19,7 @@ class CollectFarmTarget(pyblish.api.InstancePlugin):
farm_name = ""
op_modules = context.data.get("openPypeModules")
- for farm_renderer in ["deadline", "royalrender", "muster"]:
+ for farm_renderer in ["deadline", "royalrender"]:
op_module = op_modules.get(farm_renderer, False)
if op_module and op_module.enabled:
diff --git a/openpype/plugins/publish/collect_rendered_files.py b/openpype/plugins/publish/collect_rendered_files.py
index 6160b4f5c8..baaf454a11 100644
--- a/openpype/plugins/publish/collect_rendered_files.py
+++ b/openpype/plugins/publish/collect_rendered_files.py
@@ -93,14 +93,6 @@ class CollectRenderedFiles(pyblish.api.ContextPlugin):
assert ctx.get("user") == data.get("user"), ctx_err % "user"
assert ctx.get("version") == data.get("version"), ctx_err % "version"
- # ftrack credentials are passed as environment variables by Deadline
- # to publish job, but Muster doesn't pass them.
- if data.get("ftrack") and not os.environ.get("FTRACK_API_USER"):
- ftrack = data.get("ftrack")
- os.environ["FTRACK_API_USER"] = ftrack["FTRACK_API_USER"]
- os.environ["FTRACK_API_KEY"] = ftrack["FTRACK_API_KEY"]
- os.environ["FTRACK_SERVER"] = ftrack["FTRACK_SERVER"]
-
# now we can just add instances from json file and we are done
any_staging_dir_persistent = False
for instance_data in data.get("instances"):
diff --git a/openpype/plugins/publish/collect_resources_path.py b/openpype/plugins/publish/collect_resources_path.py
index c8b67a3d05..6a871124f1 100644
--- a/openpype/plugins/publish/collect_resources_path.py
+++ b/openpype/plugins/publish/collect_resources_path.py
@@ -79,19 +79,6 @@ class CollectResourcesPath(pyblish.api.InstancePlugin):
"representation": "TEMP"
})
- # Add fill keys for editorial publishing creating new entity
- # TODO handle in editorial plugin
- if instance.data.get("newAssetPublishing"):
- if "hierarchy" not in template_data:
- template_data["hierarchy"] = instance.data["hierarchy"]
-
- if "asset" not in template_data:
- asset_name = instance.data["asset"].split("/")[-1]
- template_data["asset"] = asset_name
- template_data["folder"] = {
- "name": asset_name
- }
-
publish_templates = anatomy.templates_obj["publish"]
if "folder" in publish_templates:
publish_folder = publish_templates["folder"].format_strict(
diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py
index faacb7af2e..922df469fe 100644
--- a/openpype/plugins/publish/extract_color_transcode.py
+++ b/openpype/plugins/publish/extract_color_transcode.py
@@ -189,6 +189,13 @@ class ExtractOIIOTranscode(publish.Extractor):
if len(new_repre["files"]) == 1:
new_repre["files"] = new_repre["files"][0]
+ # If the source representation has "review" tag, but its not
+ # part of the output defintion tags, then both the
+ # representations will be transcoded in ExtractReview and
+ # their outputs will clash in integration.
+ if "review" in repre.get("tags", []):
+ added_review = True
+
new_representations.append(new_repre)
added_representations = True
diff --git a/openpype/plugins/publish/extract_hierarchy_to_ayon.py b/openpype/plugins/publish/extract_hierarchy_to_ayon.py
index b601a3fc29..9e84daca30 100644
--- a/openpype/plugins/publish/extract_hierarchy_to_ayon.py
+++ b/openpype/plugins/publish/extract_hierarchy_to_ayon.py
@@ -30,8 +30,7 @@ class ExtractHierarchyToAYON(pyblish.api.ContextPlugin):
if not AYON_SERVER_ENABLED:
return
- hierarchy_context = context.data.get("hierarchyContext")
- if not hierarchy_context:
+ if not context.data.get("hierarchyContext"):
self.log.debug("Skipping ExtractHierarchyToAYON")
return
diff --git a/openpype/plugins/publish/extract_thumbnail.py b/openpype/plugins/publish/extract_thumbnail.py
index 2b4ea0529a..10eb261482 100644
--- a/openpype/plugins/publish/extract_thumbnail.py
+++ b/openpype/plugins/publish/extract_thumbnail.py
@@ -231,7 +231,10 @@ class ExtractThumbnail(pyblish.api.InstancePlugin):
"files": jpeg_file,
"stagingDir": dst_staging,
"thumbnail": True,
- "tags": new_repre_tags
+ "tags": new_repre_tags,
+ # If source image is jpg then there can be clash when
+ # integrating to making the output name explicit.
+ "outputName": "thumbnail"
}
# adding representation
diff --git a/openpype/plugins/publish/extract_thumbnail_from_source.py b/openpype/plugins/publish/extract_thumbnail_from_source.py
index 401a5d615d..33cbf6d9bf 100644
--- a/openpype/plugins/publish/extract_thumbnail_from_source.py
+++ b/openpype/plugins/publish/extract_thumbnail_from_source.py
@@ -65,7 +65,8 @@ class ExtractThumbnailFromSource(pyblish.api.InstancePlugin):
"files": dst_filename,
"stagingDir": dst_staging,
"thumbnail": True,
- "tags": ["thumbnail"]
+ "tags": ["thumbnail"],
+ "outputName": "thumbnail",
}
# adding representation
diff --git a/openpype/plugins/publish/integrate_hero_version.py b/openpype/plugins/publish/integrate_hero_version.py
index 9f0f7fe7f3..59dc6b5c64 100644
--- a/openpype/plugins/publish/integrate_hero_version.py
+++ b/openpype/plugins/publish/integrate_hero_version.py
@@ -54,7 +54,6 @@ class IntegrateHeroVersion(pyblish.api.InstancePlugin):
# permissions error on files (files were used or user didn't have perms)
# *but all other plugins must be sucessfully completed
- template_name_profiles = []
_default_template_name = "hero"
def process(self, instance):
diff --git a/openpype/resources/icons/folder-favorite.png b/openpype/resources/icons/folder-favorite.png
index 198b289e9e..65f04d8c86 100644
Binary files a/openpype/resources/icons/folder-favorite.png and b/openpype/resources/icons/folder-favorite.png differ
diff --git a/openpype/resources/icons/folder-favorite2.png b/openpype/resources/icons/folder-favorite2.png
deleted file mode 100644
index 91bc3f0fbe..0000000000
Binary files a/openpype/resources/icons/folder-favorite2.png and /dev/null differ
diff --git a/openpype/resources/icons/folder-favorite3.png b/openpype/resources/icons/folder-favorite3.png
deleted file mode 100644
index ce1e6d7171..0000000000
Binary files a/openpype/resources/icons/folder-favorite3.png and /dev/null differ
diff --git a/openpype/scripts/ocio_wrapper.py b/openpype/scripts/ocio_wrapper.py
index fa231cd047..0a78e33c1f 100644
--- a/openpype/scripts/ocio_wrapper.py
+++ b/openpype/scripts/ocio_wrapper.py
@@ -21,7 +21,7 @@ Providing functionality:
import click
import json
-from pathlib2 import Path
+from pathlib import Path
import PyOpenColorIO as ocio
diff --git a/openpype/settings/ayon_settings.py b/openpype/settings/ayon_settings.py
index 222ce68c0f..4948f2431c 100644
--- a/openpype/settings/ayon_settings.py
+++ b/openpype/settings/ayon_settings.py
@@ -220,22 +220,6 @@ def _convert_deadline_system_settings(
output["modules"]["deadline"] = deadline_settings
-def _convert_muster_system_settings(
- ayon_settings, output, addon_versions, default_settings
-):
- enabled = addon_versions.get("muster") is not None
- muster_settings = default_settings["modules"]["muster"]
- muster_settings["enabled"] = enabled
- if enabled:
- ayon_muster = ayon_settings["muster"]
- muster_settings["MUSTER_REST_URL"] = ayon_muster["MUSTER_REST_URL"]
- muster_settings["templates_mapping"] = {
- item["name"]: item["value"]
- for item in ayon_muster["templates_mapping"]
- }
- output["modules"]["muster"] = muster_settings
-
-
def _convert_royalrender_system_settings(
ayon_settings, output, addon_versions, default_settings
):
@@ -261,7 +245,6 @@ def _convert_modules_system(
_convert_timers_manager_system_settings,
_convert_clockify_system_settings,
_convert_deadline_system_settings,
- _convert_muster_system_settings,
_convert_royalrender_system_settings,
):
func(ayon_settings, output, addon_versions, default_settings)
@@ -478,15 +461,6 @@ def _convert_maya_project_settings(ayon_settings, output):
for item in ayon_maya["ext_mapping"]
}
- # Publish UI filters
- new_filters = {}
- for item in ayon_maya["filters"]:
- new_filters[item["name"]] = {
- subitem["name"]: subitem["value"]
- for subitem in item["value"]
- }
- ayon_maya["filters"] = new_filters
-
# Maya dirmap
ayon_maya_dirmap = ayon_maya.pop("maya_dirmap")
ayon_maya_dirmap_path = ayon_maya_dirmap["paths"]
@@ -743,16 +717,6 @@ def _convert_nuke_project_settings(ayon_settings, output):
dirmap["paths"][dst_key] = dirmap["paths"].pop(src_key)
ayon_nuke["nuke-dirmap"] = dirmap
- # --- Filters ---
- new_gui_filters = {}
- for item in ayon_nuke.pop("filters"):
- subvalue = {}
- key = item["name"]
- for subitem in item["value"]:
- subvalue[subitem["name"]] = subitem["value"]
- new_gui_filters[key] = subvalue
- ayon_nuke["filters"] = new_gui_filters
-
# --- Load ---
ayon_load = ayon_nuke["load"]
ayon_load["LoadClip"]["_representations"] = (
@@ -896,7 +860,7 @@ def _convert_hiero_project_settings(ayon_settings, output):
_convert_host_imageio(ayon_hiero)
new_gui_filters = {}
- for item in ayon_hiero.pop("filters"):
+ for item in ayon_hiero.pop("filters", []):
subvalue = {}
key = item["name"]
for subitem in item["value"]:
@@ -963,17 +927,6 @@ def _convert_tvpaint_project_settings(ayon_settings, output):
_convert_host_imageio(ayon_tvpaint)
- filters = {}
- for item in ayon_tvpaint["filters"]:
- value = item["value"]
- try:
- value = json.loads(value)
-
- except ValueError:
- value = {}
- filters[item["name"]] = value
- ayon_tvpaint["filters"] = filters
-
ayon_publish_settings = ayon_tvpaint["publish"]
for plugin_name in (
"ValidateProjectSettings",
@@ -1266,6 +1219,8 @@ def _convert_global_project_settings(ayon_settings, output, default_settings):
for profile in extract_oiio_transcode_profiles:
new_outputs = {}
name_counter = {}
+ if "product_names" in profile:
+ profile["subsets"] = profile.pop("product_names")
for profile_output in profile["outputs"]:
if "name" in profile_output:
name = profile_output.pop("name")
@@ -1321,12 +1276,6 @@ def _convert_global_project_settings(ayon_settings, output, default_settings):
for extract_burnin_def in extract_burnin_defs
}
- ayon_integrate_hero = ayon_publish["IntegrateHeroVersion"]
- for profile in ayon_integrate_hero["template_name_profiles"]:
- if "product_types" not in profile:
- break
- profile["families"] = profile.pop("product_types")
-
if "IntegrateProductGroup" in ayon_publish:
subset_group = ayon_publish.pop("IntegrateProductGroup")
subset_group_profiles = subset_group.pop("product_grouping_profiles")
diff --git a/openpype/settings/defaults/project_settings/deadline.json b/openpype/settings/defaults/project_settings/deadline.json
index a19464a5c1..b02cfa8207 100644
--- a/openpype/settings/defaults/project_settings/deadline.json
+++ b/openpype/settings/defaults/project_settings/deadline.json
@@ -65,6 +65,8 @@
"group": "",
"department": "",
"use_gpu": true,
+ "workfile_dependency": true,
+ "use_published_workfile": true,
"env_allowed_keys": [],
"env_search_replace_values": {},
"limit_groups": {}
diff --git a/openpype/settings/defaults/project_settings/fusion.json b/openpype/settings/defaults/project_settings/fusion.json
index 0edcae060a..f890f94b6f 100644
--- a/openpype/settings/defaults/project_settings/fusion.json
+++ b/openpype/settings/defaults/project_settings/fusion.json
@@ -15,6 +15,11 @@
"copy_status": false,
"force_sync": false
},
+ "hooks": {
+ "InstallPySideToFusion": {
+ "enabled": true
+ }
+ },
"create": {
"CreateSaver": {
"temp_rendering_path_template": "{workdir}/renders/fusion/{subset}/{subset}.{frame}.{ext}",
@@ -26,7 +31,21 @@
"reviewable",
"farm_rendering"
],
- "image_format": "exr"
+ "image_format": "exr",
+ "default_frame_range_option": "asset_db"
+ },
+ "CreateImageSaver": {
+ "temp_rendering_path_template": "{workdir}/renders/fusion/{subset}/{subset}.{ext}",
+ "default_variants": [
+ "Main",
+ "Mask"
+ ],
+ "instance_attributes": [
+ "reviewable",
+ "farm_rendering"
+ ],
+ "image_format": "exr",
+ "default_frame": 0
}
},
"publish": {
diff --git a/openpype/settings/defaults/project_settings/max.json b/openpype/settings/defaults/project_settings/max.json
index 19c9d10496..d1610610dc 100644
--- a/openpype/settings/defaults/project_settings/max.json
+++ b/openpype/settings/defaults/project_settings/max.json
@@ -1,4 +1,8 @@
{
+ "unit_scale_settings": {
+ "enabled": true,
+ "scene_unit_scale": "Meters"
+ },
"imageio": {
"activate_host_color_management": true,
"ocio_config": {
diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json
index 34452eb8ce..615000183d 100644
--- a/openpype/settings/defaults/project_settings/maya.json
+++ b/openpype/settings/defaults/project_settings/maya.json
@@ -1289,6 +1289,7 @@
"twoSidedLighting": true,
"lineAAEnable": true,
"multiSample": 8,
+ "loadTextures": false,
"useDefaultMaterial": false,
"wireframeOnShaded": false,
"xray": false,
@@ -1608,14 +1609,5 @@
},
"templated_workfile_build": {
"profiles": []
- },
- "filters": {
- "preset 1": {
- "ValidateNoAnimation": false,
- "ValidateShapeDefaultNames": false
- },
- "preset 2": {
- "ValidateNoAnimation": false
- }
}
}
diff --git a/openpype/settings/defaults/project_settings/nuke.json b/openpype/settings/defaults/project_settings/nuke.json
index 17932c793d..15c2d262e0 100644
--- a/openpype/settings/defaults/project_settings/nuke.json
+++ b/openpype/settings/defaults/project_settings/nuke.json
@@ -540,6 +540,5 @@
},
"templated_workfile_build": {
"profiles": []
- },
- "filters": {}
+ }
}
diff --git a/openpype/settings/defaults/project_settings/tvpaint.json b/openpype/settings/defaults/project_settings/tvpaint.json
index fdbd6d5d0f..d03b8b7227 100644
--- a/openpype/settings/defaults/project_settings/tvpaint.json
+++ b/openpype/settings/defaults/project_settings/tvpaint.json
@@ -107,6 +107,5 @@
"workfile_builder": {
"create_first_version": false,
"custom_templates": []
- },
- "filters": {}
+ }
}
diff --git a/openpype/settings/defaults/system_settings/modules.json b/openpype/settings/defaults/system_settings/modules.json
index bb943524f1..22daae3b34 100644
--- a/openpype/settings/defaults/system_settings/modules.json
+++ b/openpype/settings/defaults/system_settings/modules.json
@@ -164,23 +164,6 @@
"default": "http://127.0.0.1:8082"
}
},
- "muster": {
- "enabled": false,
- "MUSTER_REST_URL": "http://127.0.0.1:9890",
- "templates_mapping": {
- "file_layers": 7,
- "mentalray": 2,
- "mentalray_sf": 6,
- "redshift": 55,
- "renderman": 29,
- "software": 1,
- "software_sf": 5,
- "turtle": 10,
- "vector": 4,
- "vray": 37,
- "ffmpeg": 48
- }
- },
"royalrender": {
"enabled": false,
"rr_paths": {
diff --git a/openpype/settings/entities/schemas/README.md b/openpype/settings/entities/schemas/README.md
index c333628b25..eb74dd7a9c 100644
--- a/openpype/settings/entities/schemas/README.md
+++ b/openpype/settings/entities/schemas/README.md
@@ -645,7 +645,7 @@ How output of the schema could look like on save:
},
"is_group": true,
"key": "templates_mapping",
- "label": "Muster - Templates mapping",
+ "label": "Deadline - Templates mapping",
"is_file": true
}
```
@@ -657,7 +657,7 @@ How output of the schema could look like on save:
"object_type": "text",
"is_group": true,
"key": "templates_mapping",
- "label": "Muster - Templates mapping",
+ "label": "Deadline - Templates mapping",
"is_file": true
}
```
diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json b/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json
index 1aea778e32..42dea33ef9 100644
--- a/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json
+++ b/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json
@@ -362,6 +362,16 @@
"key": "use_gpu",
"label": "Use GPU"
},
+ {
+ "type": "boolean",
+ "key": "workfile_dependency",
+ "label": "Workfile Dependency"
+ },
+ {
+ "type": "boolean",
+ "key": "use_published_workfile",
+ "label": "Use Published Workfile"
+ },
{
"type": "list",
"key": "env_allowed_keys",
diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json b/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json
index 5177d8bc7c..84d1efae78 100644
--- a/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json
+++ b/openpype/settings/entities/schemas/projects_schema/schema_project_fusion.json
@@ -41,6 +41,29 @@
}
]
},
+ {
+ "type": "dict",
+ "collapsible": true,
+ "key": "hooks",
+ "label": "Hooks",
+ "children": [
+ {
+ "type": "dict",
+ "collapsible": true,
+ "checkbox_key": "enabled",
+ "key": "InstallPySideToFusion",
+ "label": "Install PySide2",
+ "is_group": true,
+ "children": [
+ {
+ "type": "boolean",
+ "key": "enabled",
+ "label": "Enabled"
+ }
+ ]
+ }
+ ]
+ },
{
"type": "dict",
"collapsible": true,
@@ -51,7 +74,7 @@
"type": "dict",
"collapsible": true,
"key": "CreateSaver",
- "label": "Create Saver",
+ "label": "Create Render Saver",
"is_group": true,
"children": [
{
@@ -93,6 +116,71 @@
{"tif": "tif"},
{"jpg": "jpg"}
]
+ },
+ {
+ "key": "default_frame_range_option",
+ "label": "Default frame range source",
+ "type": "enum",
+ "multiselect": false,
+ "enum_items": [
+ {"asset_db": "Current asset context"},
+ {"render_range": "From render in/out"},
+ {"comp_range": "From composition timeline"}
+ ]
+ }
+ ]
+ },
+ {
+ "type": "dict",
+ "collapsible": true,
+ "key": "CreateImageSaver",
+ "label": "Create Image Saver",
+ "is_group": true,
+ "children": [
+ {
+ "type": "text",
+ "key": "temp_rendering_path_template",
+ "label": "Temporary rendering path template"
+ },
+ {
+ "type": "list",
+ "key": "default_variants",
+ "label": "Default variants",
+ "object_type": {
+ "type": "text"
+ }
+ },
+ {
+ "key": "instance_attributes",
+ "label": "Instance attributes",
+ "type": "enum",
+ "multiselection": true,
+ "enum_items": [
+ {
+ "reviewable": "Reviewable"
+ },
+ {
+ "farm_rendering": "Farm rendering"
+ }
+ ]
+ },
+ {
+ "key": "image_format",
+ "label": "Output Image Format",
+ "type": "enum",
+ "multiselect": false,
+ "enum_items": [
+ {"exr": "exr"},
+ {"tga": "tga"},
+ {"png": "png"},
+ {"tif": "tif"},
+ {"jpg": "jpg"}
+ ]
+ },
+ {
+ "type": "number",
+ "key": "default_frame",
+ "label": "Default rendered frame"
}
]
}
diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_max.json b/openpype/settings/entities/schemas/projects_schema/schema_project_max.json
index 78cca357a3..e4d4d40ce7 100644
--- a/openpype/settings/entities/schemas/projects_schema/schema_project_max.json
+++ b/openpype/settings/entities/schemas/projects_schema/schema_project_max.json
@@ -5,6 +5,34 @@
"label": "Max",
"is_file": true,
"children": [
+ {
+ "key": "unit_scale_settings",
+ "type": "dict",
+ "label": "Set Unit Scale",
+ "collapsible": true,
+ "is_group": true,
+ "checkbox_key": "enabled",
+ "children": [
+ {
+ "type": "boolean",
+ "key": "enabled",
+ "label": "Enabled"
+ },
+ {
+ "key": "scene_unit_scale",
+ "label": "Scene Unit Scale",
+ "type": "enum",
+ "multiselection": false,
+ "defaults": "exr",
+ "enum_items": [
+ {"Millimeters": "mm"},
+ {"Centimeters": "cm"},
+ {"Meters": "m"},
+ {"Kilometers": "km"}
+ ]
+ }
+ ]
+ },
{
"key": "imageio",
"type": "dict",
diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json b/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json
index dca955dab4..a6fd742b40 100644
--- a/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json
+++ b/openpype/settings/entities/schemas/projects_schema/schema_project_maya.json
@@ -258,10 +258,6 @@
{
"type": "schema",
"name": "schema_templated_workfile_build"
- },
- {
- "type": "schema",
- "name": "schema_publish_gui_filter"
}
]
}
diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_nuke.json b/openpype/settings/entities/schemas/projects_schema/schema_project_nuke.json
index 6b516ddf4a..0b24c8231c 100644
--- a/openpype/settings/entities/schemas/projects_schema/schema_project_nuke.json
+++ b/openpype/settings/entities/schemas/projects_schema/schema_project_nuke.json
@@ -291,10 +291,6 @@
{
"type": "schema",
"name": "schema_templated_workfile_build"
- },
- {
- "type": "schema",
- "name": "schema_publish_gui_filter"
}
]
}
diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_tvpaint.json b/openpype/settings/entities/schemas/projects_schema/schema_project_tvpaint.json
index e9255f426e..5b2647bc6d 100644
--- a/openpype/settings/entities/schemas/projects_schema/schema_project_tvpaint.json
+++ b/openpype/settings/entities/schemas/projects_schema/schema_project_tvpaint.json
@@ -436,10 +436,6 @@
"workfile_builder/builder_on_start",
"workfile_builder/profiles"
]
- },
- {
- "type": "schema",
- "name": "schema_publish_gui_filter"
}
]
}
diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json
index ac2d9e190d..64f292a140 100644
--- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json
+++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json
@@ -1023,49 +1023,6 @@
{
"type": "label",
"label": "NOTE: Hero publish template profiles settings were moved to Tools/Publish/Hero template name profiles. Please move values there."
- },
- {
- "type": "list",
- "key": "template_name_profiles",
- "label": "Template name profiles (DEPRECATED)",
- "use_label_wrap": true,
- "object_type": {
- "type": "dict",
- "children": [
- {
- "key": "families",
- "label": "Families",
- "type": "list",
- "object_type": "text"
- },
- {
- "type": "hosts-enum",
- "key": "hosts",
- "label": "Hosts",
- "multiselection": true
- },
- {
- "key": "task_types",
- "label": "Task types",
- "type": "task-types-enum"
- },
- {
- "key": "task_names",
- "label": "Task names",
- "type": "list",
- "object_type": "text"
- },
- {
- "type": "separator"
- },
- {
- "type": "text",
- "key": "template_name",
- "label": "Template name",
- "tooltip": "Name of template from Anatomy templates"
- }
- ]
- }
}
]
},
diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json
index d90527ac8c..76ad9a3ba2 100644
--- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json
+++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json
@@ -236,6 +236,11 @@
{
"type": "splitter"
},
+ {
+ "type": "boolean",
+ "key": "loadTextures",
+ "label": "Load Textures"
+ },
{
"type": "boolean",
"key": "useDefaultMaterial",
@@ -908,6 +913,12 @@
{
"type": "splitter"
},
+ {
+ "type": "boolean",
+ "key": "loadTextures",
+ "label": "Load Textures",
+ "default": false
+ },
{
"type": "boolean",
"key": "useDefaultMaterial",
diff --git a/openpype/settings/entities/schemas/system_schema/schema_modules.json b/openpype/settings/entities/schemas/system_schema/schema_modules.json
index 5b189eae88..88ef6f7515 100644
--- a/openpype/settings/entities/schemas/system_schema/schema_modules.json
+++ b/openpype/settings/entities/schemas/system_schema/schema_modules.json
@@ -207,37 +207,6 @@
}
]
},
- {
- "type": "dict",
- "key": "muster",
- "label": "Muster",
- "require_restart": true,
- "collapsible": true,
- "checkbox_key": "enabled",
- "children": [
- {
- "type": "boolean",
- "key": "enabled",
- "label": "Enabled"
- },
- {
- "type": "text",
- "key": "MUSTER_REST_URL",
- "label": "Muster Rest URL"
- },
- {
- "type": "dict-modifiable",
- "object_type": {
- "type": "number",
- "minimum": 0,
- "maximum": 300
- },
- "is_group": true,
- "key": "templates_mapping",
- "label": "Templates mapping"
- }
- ]
- },
{
"type": "dict",
"key": "royalrender",
diff --git a/openpype/tools/ayon_loader/models/site_sync.py b/openpype/tools/ayon_loader/models/site_sync.py
index 90852b6954..4b7ddee481 100644
--- a/openpype/tools/ayon_loader/models/site_sync.py
+++ b/openpype/tools/ayon_loader/models/site_sync.py
@@ -140,12 +140,10 @@ class SiteSyncModel:
Union[dict[str, Any], None]: Site icon definition.
"""
- if not project_name:
+ if not project_name or not self.is_site_sync_enabled(project_name):
return None
-
active_site = self.get_active_site(project_name)
- provider = self._get_provider_for_site(project_name, active_site)
- return self._get_provider_icon(provider)
+ return self._get_site_icon_def(project_name, active_site)
def get_remote_site_icon_def(self, project_name):
"""Remote site icon definition.
@@ -160,7 +158,14 @@ class SiteSyncModel:
if not project_name or not self.is_site_sync_enabled(project_name):
return None
remote_site = self.get_remote_site(project_name)
- provider = self._get_provider_for_site(project_name, remote_site)
+ return self._get_site_icon_def(project_name, remote_site)
+
+ def _get_site_icon_def(self, project_name, site_name):
+ # use different icon for studio even if provider is 'local_drive'
+ if site_name == self._site_sync_addon.DEFAULT_SITE:
+ provider = "studio"
+ else:
+ provider = self._get_provider_for_site(project_name, site_name)
return self._get_provider_icon(provider)
def get_version_sync_availability(self, project_name, version_ids):
diff --git a/openpype/tools/ayon_sceneinventory/control.py b/openpype/tools/ayon_sceneinventory/control.py
index 6111d7e43b..3b063ff72e 100644
--- a/openpype/tools/ayon_sceneinventory/control.py
+++ b/openpype/tools/ayon_sceneinventory/control.py
@@ -84,9 +84,9 @@ class SceneInventoryController:
def get_containers(self):
host = self._host
if isinstance(host, ILoadHost):
- return host.get_containers()
+ return list(host.get_containers())
elif hasattr(host, "ls"):
- return host.ls()
+ return list(host.ls())
return []
# Site Sync methods
diff --git a/openpype/tools/ayon_sceneinventory/model.py b/openpype/tools/ayon_sceneinventory/model.py
index 16924b0a7e..f4450f0ac3 100644
--- a/openpype/tools/ayon_sceneinventory/model.py
+++ b/openpype/tools/ayon_sceneinventory/model.py
@@ -23,6 +23,7 @@ from openpype.pipeline import (
)
from openpype.style import get_default_entity_icon_color
from openpype.tools.utils.models import TreeModel, Item
+from openpype.tools.ayon_utils.widgets import get_qt_icon
def walk_hierarchy(node):
@@ -71,8 +72,8 @@ class InventoryModel(TreeModel):
site_icons = self._controller.get_site_provider_icons()
self._site_icons = {
- provider: QtGui.QIcon(icon_path)
- for provider, icon_path in site_icons.items()
+ provider: get_qt_icon(icon_def)
+ for provider, icon_def in site_icons.items()
}
def outdated(self, item):
diff --git a/openpype/tools/ayon_sceneinventory/models/site_sync.py b/openpype/tools/ayon_sceneinventory/models/site_sync.py
index 1297137cb0..bd65ad1778 100644
--- a/openpype/tools/ayon_sceneinventory/models/site_sync.py
+++ b/openpype/tools/ayon_sceneinventory/models/site_sync.py
@@ -42,8 +42,8 @@ class SiteSyncModel:
if not self.is_sync_server_enabled():
return {}
- site_sync = self._get_sync_server_module()
- return site_sync.get_site_icons()
+ site_sync_addon = self._get_sync_server_module()
+ return site_sync_addon.get_site_icons()
def get_sites_information(self):
return {
@@ -150,23 +150,23 @@ class SiteSyncModel:
return self._remote_site_provider
def _cache_sites(self):
- site_sync = self._get_sync_server_module()
active_site = None
remote_site = None
active_site_provider = None
remote_site_provider = None
- if site_sync is not None:
+ if self.is_sync_server_enabled():
+ site_sync = self._get_sync_server_module()
project_name = self._controller.get_current_project_name()
active_site = site_sync.get_active_site(project_name)
remote_site = site_sync.get_remote_site(project_name)
active_site_provider = "studio"
remote_site_provider = "studio"
if active_site != "studio":
- active_site_provider = site_sync.get_active_provider(
+ active_site_provider = site_sync.get_provider_for_site(
project_name, active_site
)
if remote_site != "studio":
- remote_site_provider = site_sync.get_active_provider(
+ remote_site_provider = site_sync.get_provider_for_site(
project_name, remote_site
)
diff --git a/openpype/tools/ayon_workfiles/models/workfiles.py b/openpype/tools/ayon_workfiles/models/workfiles.py
index d74a8e164d..f9f910ac8a 100644
--- a/openpype/tools/ayon_workfiles/models/workfiles.py
+++ b/openpype/tools/ayon_workfiles/models/workfiles.py
@@ -606,7 +606,7 @@ class PublishWorkfilesModel:
print("Failed to format workfile path: {}".format(exc))
dirpath, filename = os.path.split(workfile_path)
- created_at = arrow.get(repre_entity["createdAt"].to("local"))
+ created_at = arrow.get(repre_entity["createdAt"]).to("local")
return FileItem(
dirpath,
filename,
diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py
index b02d83e4f6..47e374edf2 100644
--- a/openpype/tools/publisher/control.py
+++ b/openpype/tools/publisher/control.py
@@ -10,6 +10,7 @@ import inspect
from abc import ABCMeta, abstractmethod
import six
+import arrow
import pyblish.api
from openpype import AYON_SERVER_ENABLED
@@ -285,6 +286,8 @@ class PublishReportMaker:
def get_report(self, publish_plugins=None):
"""Report data with all details of current state."""
+
+ now = arrow.utcnow().to("local")
instances_details = {}
for instance in self._all_instances_by_id.values():
instances_details[instance.id] = self._extract_instance_data(
@@ -334,7 +337,8 @@ class PublishReportMaker:
"context": self._extract_context_data(self._current_context),
"crashed_file_paths": crashed_file_paths,
"id": uuid.uuid4().hex,
- "report_version": "1.0.0"
+ "created_at": now.isoformat(),
+ "report_version": "1.0.1",
}
def _extract_context_data(self, context):
diff --git a/openpype/tools/publisher/publish_report_viewer/model.py b/openpype/tools/publisher/publish_report_viewer/model.py
index 663a67ac70..460a269f1a 100644
--- a/openpype/tools/publisher/publish_report_viewer/model.py
+++ b/openpype/tools/publisher/publish_report_viewer/model.py
@@ -26,14 +26,14 @@ class InstancesModel(QtGui.QStandardItemModel):
return self._items_by_id
def set_report(self, report_item):
- self.clear()
+ root_item = self.invisibleRootItem()
+ if root_item.rowCount() > 0:
+ root_item.removeRows(0, root_item.rowCount())
self._items_by_id.clear()
self._plugin_items_by_id.clear()
if not report_item:
return
- root_item = self.invisibleRootItem()
-
families = set(report_item.instance_items_by_family.keys())
families.remove(None)
all_families = list(sorted(families))
@@ -125,14 +125,14 @@ class PluginsModel(QtGui.QStandardItemModel):
return self._items_by_id
def set_report(self, report_item):
- self.clear()
+ root_item = self.invisibleRootItem()
+ if root_item.rowCount() > 0:
+ root_item.removeRows(0, root_item.rowCount())
self._items_by_id.clear()
self._plugin_items_by_id.clear()
if not report_item:
return
- root_item = self.invisibleRootItem()
-
labels_iter = iter(self.order_label_mapping)
cur_order, cur_label = next(labels_iter)
cur_plugin_items = []
diff --git a/openpype/tools/publisher/publish_report_viewer/window.py b/openpype/tools/publisher/publish_report_viewer/window.py
index dc4ad70934..f9c8c05802 100644
--- a/openpype/tools/publisher/publish_report_viewer/window.py
+++ b/openpype/tools/publisher/publish_report_viewer/window.py
@@ -4,6 +4,7 @@ import six
import uuid
import appdirs
+import arrow
from qtpy import QtWidgets, QtCore, QtGui
from openpype import style
@@ -25,6 +26,7 @@ else:
ITEM_ID_ROLE = QtCore.Qt.UserRole + 1
+ITEM_CREATED_AT_ROLE = QtCore.Qt.UserRole + 2
def get_reports_dir():
@@ -47,47 +49,77 @@ class PublishReportItem:
"""Report item representing one file in report directory."""
def __init__(self, content):
- item_id = content.get("id")
- changed = False
- if not item_id:
- item_id = str(uuid.uuid4())
- changed = True
- content["id"] = item_id
+ changed = self._fix_content(content)
- if not content.get("report_version"):
- changed = True
- content["report_version"] = "0.0.1"
-
- report_path = os.path.join(get_reports_dir(), item_id)
+ report_path = os.path.join(get_reports_dir(), content["id"])
file_modified = None
if os.path.exists(report_path):
file_modified = os.path.getmtime(report_path)
+
+ created_at_obj = arrow.get(content["created_at"]).to("local")
+ created_at = created_at_obj.float_timestamp
+
self.content = content
self.report_path = report_path
self.file_modified = file_modified
+ self.created_at = float(created_at)
self._loaded_label = content.get("label")
self._changed = changed
self.publish_report = PublishReport(content)
@property
def version(self):
+ """Publish report version.
+
+ Returns:
+ str: Publish report version.
+ """
return self.content["report_version"]
@property
def id(self):
+ """Publish report id.
+
+ Returns:
+ str: Publish report id.
+ """
+
return self.content["id"]
def get_label(self):
+ """Publish report label.
+
+ Returns:
+ str: Publish report label showed in UI.
+ """
+
return self.content.get("label") or "Unfilled label"
def set_label(self, label):
+ """Set publish report label.
+
+ Args:
+ label (str): New publish report label.
+ """
+
if not label:
self.content.pop("label", None)
self.content["label"] = label
label = property(get_label, set_label)
+ @property
+ def loaded_label(self):
+ return self._loaded_label
+
+ def mark_as_changed(self):
+ """Mark report as changed."""
+
+ self._changed = True
+
def save(self):
+ """Save publish report to file."""
+
save = False
if (
self._changed
@@ -109,6 +141,15 @@ class PublishReportItem:
@classmethod
def from_filepath(cls, filepath):
+ """Create report item from file.
+
+ Args:
+ filepath (str): Path to report file. Content must be json.
+
+ Returns:
+ PublishReportItem: Report item.
+ """
+
if not os.path.exists(filepath):
return None
@@ -116,15 +157,25 @@ class PublishReportItem:
with open(filepath, "r") as stream:
content = json.load(stream)
- return cls(content)
+ file_modified = os.path.getmtime(filepath)
+ changed = cls._fix_content(content, file_modified=file_modified)
+ obj = cls(content)
+ if changed:
+ obj.mark_as_changed()
+ return obj
+
except Exception:
return None
def remove_file(self):
+ """Remove report file."""
+
if os.path.exists(self.report_path):
os.remove(self.report_path)
def update_file_content(self):
+ """Update report content in file."""
+
if not os.path.exists(self.report_path):
return
@@ -148,9 +199,57 @@ class PublishReportItem:
self.content = content
self.file_modified = file_modified
+ @classmethod
+ def _fix_content(cls, content, file_modified=None):
+ """Fix content for backward compatibility of older report items.
+
+ Args:
+ content (dict[str, Any]): Report content.
+ file_modified (Optional[float]): File modification time.
+
+ Returns:
+ bool: True if content was changed, False otherwise.
+ """
+
+ # Fix created_at key
+ changed = cls._fix_created_at(content, file_modified)
+
+ # NOTE backward compatibility for 'id' and 'report_version' is from
+ # 28.10.2022 https://github.com/ynput/OpenPype/pull/4040
+ # We can probably safely remove it
+
+ # Fix missing 'id'
+ item_id = content.get("id")
+ if not item_id:
+ item_id = str(uuid.uuid4())
+ changed = True
+ content["id"] = item_id
+
+ # Fix missing 'report_version'
+ if not content.get("report_version"):
+ changed = True
+ content["report_version"] = "0.0.1"
+ return changed
+
+ @classmethod
+ def _fix_created_at(cls, content, file_modified):
+ # Key 'create_at' was added in report version 1.0.1
+ created_at = content.get("created_at")
+ if created_at:
+ return False
+
+ # Auto fix 'created_at', use file modification time if it is not set
+ # or current time if modification could not be received.
+ if file_modified is not None:
+ created_at_obj = arrow.Arrow.fromtimestamp(file_modified)
+ else:
+ created_at_obj = arrow.utcnow()
+ content["created_at"] = created_at_obj.to("local").isoformat()
+ return True
+
class PublisherReportHandler:
- """Class handling storing publish report tool."""
+ """Class handling storing publish report items."""
def __init__(self):
self._reports = None
@@ -173,14 +272,23 @@ class PublisherReportHandler:
continue
filepath = os.path.join(report_dir, filename)
item = PublishReportItem.from_filepath(filepath)
- reports.append(item)
- reports_by_id[item.id] = item
+ if item is not None:
+ reports.append(item)
+ reports_by_id[item.id] = item
self._reports = reports
self._reports_by_id = reports_by_id
return reports
- def remove_report_items(self, item_id):
+ def remove_report_item(self, item_id):
+ """Remove report item by id.
+
+ Remove from cache and also remove the file with the content.
+
+ Args:
+ item_id (str): Report item id.
+ """
+
item = self._reports_by_id.get(item_id)
if item:
try:
@@ -191,9 +299,16 @@ class PublisherReportHandler:
class LoadedFilesModel(QtGui.QStandardItemModel):
+ header_labels = ("Reports", "Created")
+
def __init__(self, *args, **kwargs):
super(LoadedFilesModel, self).__init__(*args, **kwargs)
+ # Column count must be set before setting header data
+ self.setColumnCount(len(self.header_labels))
+ for col, label in enumerate(self.header_labels):
+ self.setHeaderData(col, QtCore.Qt.Horizontal, label)
+
self._items_by_id = {}
self._report_items_by_id = {}
@@ -202,10 +317,14 @@ class LoadedFilesModel(QtGui.QStandardItemModel):
self._loading_registry = False
def refresh(self):
- self._handler.reset()
+ root_item = self.invisibleRootItem()
+ if root_item.rowCount() > 0:
+ root_item.removeRows(0, root_item.rowCount())
self._items_by_id = {}
self._report_items_by_id = {}
+ self._handler.reset()
+
new_items = []
for report_item in self._handler.list_reports():
item = self._create_item(report_item)
@@ -217,26 +336,26 @@ class LoadedFilesModel(QtGui.QStandardItemModel):
root_item = self.invisibleRootItem()
root_item.appendRows(new_items)
- def headerData(self, section, orientation, role):
- if role in (QtCore.Qt.DisplayRole, QtCore.Qt.EditRole):
- if section == 0:
- return "Exports"
- if section == 1:
- return "Modified"
- return ""
- super(LoadedFilesModel, self).headerData(section, orientation, role)
-
def data(self, index, role=None):
if role is None:
role = QtCore.Qt.DisplayRole
col = index.column()
+ if col == 1:
+ if role in (
+ QtCore.Qt.DisplayRole, QtCore.Qt.InitialSortOrderRole
+ ):
+ role = ITEM_CREATED_AT_ROLE
+
if col != 0:
index = self.index(index.row(), 0, index.parent())
return super(LoadedFilesModel, self).data(index, role)
- def setData(self, index, value, role):
+ def setData(self, index, value, role=None):
+ if role is None:
+ role = QtCore.Qt.EditRole
+
if role == QtCore.Qt.EditRole:
item_id = index.data(ITEM_ID_ROLE)
report_item = self._report_items_by_id.get(item_id)
@@ -247,6 +366,12 @@ class LoadedFilesModel(QtGui.QStandardItemModel):
return super(LoadedFilesModel, self).setData(index, value, role)
+ def flags(self, index):
+ # Allow editable flag only for first column
+ if index.column() > 0:
+ return QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled
+ return super(LoadedFilesModel, self).flags(index)
+
def _create_item(self, report_item):
if report_item.id in self._items_by_id:
return None
@@ -254,6 +379,7 @@ class LoadedFilesModel(QtGui.QStandardItemModel):
item = QtGui.QStandardItem(report_item.label)
item.setColumnCount(self.columnCount())
item.setData(report_item.id, ITEM_ID_ROLE)
+ item.setData(report_item.created_at, ITEM_CREATED_AT_ROLE)
return item
@@ -278,16 +404,16 @@ class LoadedFilesModel(QtGui.QStandardItemModel):
new_items = []
for normalized_path in filtered_paths:
- try:
- with open(normalized_path, "r") as stream:
- data = json.load(stream)
- report_item = PublishReportItem(data)
- except Exception:
- # TODO handle errors
+ report_item = PublishReportItem.from_filepath(normalized_path)
+ if report_item is None:
continue
- label = data.get("label")
- if not label:
+ # Skip already added report items
+ # QUESTION: Should we replace existing or skip the item?
+ if report_item.id in self._items_by_id:
+ continue
+
+ if not report_item.loaded_label:
report_item.label = (
os.path.splitext(os.path.basename(filepath))[0]
)
@@ -306,15 +432,13 @@ class LoadedFilesModel(QtGui.QStandardItemModel):
root_item.appendRows(new_items)
def remove_item_by_id(self, item_id):
- report_item = self._report_items_by_id.get(item_id)
- if not report_item:
- return
+ self._handler.remove_report_item(item_id)
- self._handler.remove_report_items(item_id)
- item = self._items_by_id.get(item_id)
-
- parent = self.invisibleRootItem()
- parent.removeRow(item.row())
+ self._report_items_by_id.pop(item_id, None)
+ item = self._items_by_id.pop(item_id, None)
+ if item is not None:
+ parent = self.invisibleRootItem()
+ parent.removeRow(item.row())
def get_report_by_id(self, item_id):
report_item = self._report_items_by_id.get(item_id)
@@ -335,13 +459,18 @@ class LoadedFilesView(QtWidgets.QTreeView):
)
self.setIndentation(0)
self.setAlternatingRowColors(True)
+ self.setSortingEnabled(True)
model = LoadedFilesModel()
- self.setModel(model)
+ proxy_model = QtCore.QSortFilterProxyModel()
+ proxy_model.setSourceModel(model)
+ self.setModel(proxy_model)
time_delegate = PrettyTimeDelegate()
self.setItemDelegateForColumn(1, time_delegate)
+ self.sortByColumn(1, QtCore.Qt.AscendingOrder)
+
remove_btn = IconButton(self)
remove_icon_path = resources.get_icon_path("delete")
loaded_remove_image = QtGui.QImage(remove_icon_path)
@@ -356,6 +485,7 @@ class LoadedFilesView(QtWidgets.QTreeView):
)
self._model = model
+ self._proxy_model = proxy_model
self._time_delegate = time_delegate
self._remove_btn = remove_btn
@@ -403,7 +533,8 @@ class LoadedFilesView(QtWidgets.QTreeView):
if index.isValid():
return
- index = self._model.index(0, 0)
+ model = self.model()
+ index = model.index(0, 0)
if index.isValid():
self.setCurrentIndex(index)
diff --git a/openpype/tools/publisher/widgets/screenshot_widget.py b/openpype/tools/publisher/widgets/screenshot_widget.py
index 3504b419b4..37b958c1c7 100644
--- a/openpype/tools/publisher/widgets/screenshot_widget.py
+++ b/openpype/tools/publisher/widgets/screenshot_widget.py
@@ -18,10 +18,11 @@ class ScreenMarquee(QtWidgets.QDialog):
super(ScreenMarquee, self).__init__(parent=parent)
self.setWindowFlags(
- QtCore.Qt.FramelessWindowHint
+ QtCore.Qt.Window
+ | QtCore.Qt.FramelessWindowHint
| QtCore.Qt.WindowStaysOnTopHint
| QtCore.Qt.CustomizeWindowHint
- | QtCore.Qt.Tool)
+ )
self.setAttribute(QtCore.Qt.WA_TranslucentBackground)
self.setCursor(QtCore.Qt.CrossCursor)
self.setMouseTracking(True)
@@ -210,6 +211,9 @@ class ScreenMarquee(QtWidgets.QDialog):
"""
tool = cls()
+ # Activate so Escape event is not ignored.
+ tool.setWindowState(QtCore.Qt.WindowActive)
+ # Exec dialog and return captured pixmap.
tool.exec_()
return tool.get_captured_pixmap()
diff --git a/openpype/tools/publisher/window.py b/openpype/tools/publisher/window.py
index b3138c3f45..5dd6998b24 100644
--- a/openpype/tools/publisher/window.py
+++ b/openpype/tools/publisher/window.py
@@ -42,7 +42,7 @@ from .widgets import (
)
-class PublisherWindow(QtWidgets.QDialog):
+class PublisherWindow(QtWidgets.QWidget):
"""Main window of publisher."""
default_width = 1300
default_height = 800
@@ -50,7 +50,7 @@ class PublisherWindow(QtWidgets.QDialog):
publish_footer_spacer = 2
def __init__(self, parent=None, controller=None, reset_on_show=None):
- super(PublisherWindow, self).__init__(parent)
+ super(PublisherWindow, self).__init__()
self.setObjectName("PublishWindow")
@@ -64,17 +64,12 @@ class PublisherWindow(QtWidgets.QDialog):
if reset_on_show is None:
reset_on_show = True
- if parent is None:
- on_top_flag = QtCore.Qt.WindowStaysOnTopHint
- else:
- on_top_flag = QtCore.Qt.Dialog
-
self.setWindowFlags(
- QtCore.Qt.WindowTitleHint
+ QtCore.Qt.Window
+ | QtCore.Qt.WindowTitleHint
| QtCore.Qt.WindowMaximizeButtonHint
| QtCore.Qt.WindowMinimizeButtonHint
| QtCore.Qt.WindowCloseButtonHint
- | on_top_flag
)
if controller is None:
@@ -189,7 +184,7 @@ class PublisherWindow(QtWidgets.QDialog):
controller, content_stacked_widget
)
- report_widget = ReportPageWidget(controller, parent)
+ report_widget = ReportPageWidget(controller, content_stacked_widget)
# Details - Publish details
publish_details_widget = PublishReportViewerWidget(
@@ -299,6 +294,12 @@ class PublisherWindow(QtWidgets.QDialog):
controller.event_system.add_callback(
"publish.process.stopped", self._on_publish_stop
)
+ controller.event_system.add_callback(
+ "publish.process.instance.changed", self._on_instance_change
+ )
+ controller.event_system.add_callback(
+ "publish.process.plugin.changed", self._on_plugin_change
+ )
controller.event_system.add_callback(
"show.card.message", self._on_overlay_message
)
@@ -557,6 +558,18 @@ class PublisherWindow(QtWidgets.QDialog):
self._reset_on_show = False
self.reset()
+ def _make_sure_on_top(self):
+ """Raise window to top and activate it.
+
+ This may not work for some DCCs without Qt.
+ """
+
+ if not self._window_is_visible:
+ self.show()
+
+ self.setWindowState(QtCore.Qt.WindowActive)
+ self.raise_()
+
def _checks_before_save(self, explicit_save):
"""Save of changes may trigger some issues.
@@ -869,6 +882,12 @@ class PublisherWindow(QtWidgets.QDialog):
if self._is_on_create_tab():
self._go_to_publish_tab()
+ def _on_instance_change(self):
+ self._make_sure_on_top()
+
+ def _on_plugin_change(self):
+ self._make_sure_on_top()
+
def _on_publish_validated_change(self, event):
if event["value"]:
self._validate_btn.setEnabled(False)
@@ -879,6 +898,7 @@ class PublisherWindow(QtWidgets.QDialog):
self._comment_input.setText("")
def _on_publish_stop(self):
+ self._make_sure_on_top()
self._set_publish_overlay_visibility(False)
self._reset_btn.setEnabled(True)
self._stop_btn.setEnabled(False)
diff --git a/openpype/tools/sceneinventory/switch_dialog.py b/openpype/tools/sceneinventory/switch_dialog.py
index ce2272df57..150e369678 100644
--- a/openpype/tools/sceneinventory/switch_dialog.py
+++ b/openpype/tools/sceneinventory/switch_dialog.py
@@ -1230,12 +1230,12 @@ class SwitchAssetDialog(QtWidgets.QDialog):
version_ids = list()
- version_docs_by_parent_id = {}
+ version_docs_by_parent_id_and_name = collections.defaultdict(dict)
for version_doc in version_docs:
parent_id = version_doc["parent"]
- if parent_id not in version_docs_by_parent_id:
- version_ids.append(version_doc["_id"])
- version_docs_by_parent_id[parent_id] = version_doc
+ version_ids.append(version_doc["_id"])
+ name = version_doc["name"]
+ version_docs_by_parent_id_and_name[parent_id][name] = version_doc
hero_version_docs_by_parent_id = {}
for hero_version_doc in hero_version_docs:
@@ -1293,13 +1293,32 @@ class SwitchAssetDialog(QtWidgets.QDialog):
repre_doc = _repres.get(container_repre_name)
if not repre_doc:
- version_doc = version_docs_by_parent_id[subset_id]
- version_id = version_doc["_id"]
- repres_by_name = repre_docs_by_parent_id_by_name[version_id]
- if selected_representation:
- repre_doc = repres_by_name[selected_representation]
+ version_docs_by_name = version_docs_by_parent_id_and_name[
+ subset_id
+ ]
+
+ # If asset or subset are selected for switching, we use latest
+ # version else we try to keep the current container version.
+ if (
+ selected_asset not in (None, container_asset_name)
+ or selected_subset not in (None, container_subset_name)
+ ):
+ version_name = max(version_docs_by_name)
else:
- repre_doc = repres_by_name[container_repre_name]
+ version_name = container_version["name"]
+
+ version_doc = version_docs_by_name[version_name]
+ version_id = version_doc["_id"]
+ repres_docs_by_name = repre_docs_by_parent_id_by_name[
+ version_id
+ ]
+
+ if selected_representation:
+ repres_name = selected_representation
+ else:
+ repres_name = container_repre_name
+
+ repre_doc = repres_docs_by_name[repres_name]
error = None
try:
diff --git a/openpype/tools/settings/settings/README.md b/openpype/tools/settings/settings/README.md
index c29664a907..8f4ec00a76 100644
--- a/openpype/tools/settings/settings/README.md
+++ b/openpype/tools/settings/settings/README.md
@@ -334,7 +334,7 @@
},
"is_group": true,
"key": "templates_mapping",
- "label": "Muster - Templates mapping",
+ "label": "Deadline - Templates mapping",
"is_file": true
}
```
@@ -346,7 +346,7 @@
"object_type": "text",
"is_group": true,
"key": "templates_mapping",
- "label": "Muster - Templates mapping",
+ "label": "Deadline - Templates mapping",
"is_file": true
}
```
diff --git a/openpype/tools/utils/lib.py b/openpype/tools/utils/lib.py
index 723e71e7aa..365caaafd9 100644
--- a/openpype/tools/utils/lib.py
+++ b/openpype/tools/utils/lib.py
@@ -91,7 +91,8 @@ def set_style_property(widget, property_name, property_value):
if cur_value == property_value:
return
widget.setProperty(property_name, property_value)
- widget.style().polish(widget)
+ style = widget.style()
+ style.polish(widget)
def paint_image_with_color(image, color):
diff --git a/openpype/version.py b/openpype/version.py
index c4ff4dde95..ddfb3ebeeb 100644
--- a/openpype/version.py
+++ b/openpype/version.py
@@ -1,3 +1,3 @@
# -*- coding: utf-8 -*-
"""Package declaring Pype version."""
-__version__ = "3.18.2-nightly.2"
+__version__ = "3.18.5"
diff --git a/pyproject.toml b/pyproject.toml
index e64018498f..24172aa77f 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[tool.poetry]
name = "OpenPype"
-version = "3.18.1" # OpenPype
+version = "3.18.5" # OpenPype
description = "Open VFX and Animation pipeline with support."
authors = ["OpenPype Team - Pipeline is the technical backbone of your production. It means, that whatever solution you use, it will cause vendor-lock to some extend. + Pipeline is the technical backbone of your production. It means, that whatever solution you use, it will cause vendor-lock to some extend. You can mitigate this risk by developing purely in-house tools, however, that just shifts the problem from a software vendor to your developers. Sooner or later, you'll hit the limits of such solution. In-house tools tend to be undocumented, narrow focused and heavily dependent on a very few or even a single developer.
@@ -332,7 +332,7 @@ function Home() {
Planned or in development by us and OpenPype community.
Maya
-
+
Flame
@@ -422,7 +422,7 @@ function Home() {
Deadline
-
+
Royal Render
@@ -443,17 +443,12 @@ function Home() {