From de4a5c4953249cc4e2196dc588e20f3ebdeb5401 Mon Sep 17 00:00:00 2001 From: Kaa Maurice Date: Tue, 29 Nov 2022 15:42:33 +0100 Subject: [PATCH 01/33] added settings for validator no colons in name --- openpype/settings/defaults/project_settings/blender.json | 5 +++++ .../projects_schema/schemas/schema_blender_publish.json | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/openpype/settings/defaults/project_settings/blender.json b/openpype/settings/defaults/project_settings/blender.json index 7acecfaae0..2b71e31553 100644 --- a/openpype/settings/defaults/project_settings/blender.json +++ b/openpype/settings/defaults/project_settings/blender.json @@ -24,6 +24,11 @@ "optional": false, "active": true }, + "ValidateNoColonsInName": { + "enabled": true, + "optional": false, + "active": true + }, "ExtractBlend": { "enabled": true, "optional": true, diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_blender_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_blender_publish.json index 58428ad60a..53949f65cb 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_blender_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_blender_publish.json @@ -37,6 +37,10 @@ { "key": "ValidateTransformZero", "label": "Validate Transform Zero" + }, + { + "key": "ValidateNoColonsInName", + "label": "Validate No Colons In Name" } ] } From d32800f959e7252f6349bbfe919735084b2f8d74 Mon Sep 17 00:00:00 2001 From: clement hector Date: Fri, 13 Jan 2023 15:14:11 +0100 Subject: [PATCH 02/33] set default extension to psd if file extension is not psd or psb --- openpype/hosts/photoshop/api/workio.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/hosts/photoshop/api/workio.py b/openpype/hosts/photoshop/api/workio.py index 35b44d6070..9239fe4cb8 100644 --- a/openpype/hosts/photoshop/api/workio.py +++ b/openpype/hosts/photoshop/api/workio.py @@ -25,6 +25,8 @@ def has_unsaved_changes(): def save_file(filepath): _, ext = os.path.splitext(filepath) + if ext not in file_extensions(): + ext = '.psd' lib.stub().saveAs(filepath, ext[1:], True) From 7841e4ea98d3ba3a54340c087f3666e044880140 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 17 Jan 2023 16:57:19 +0100 Subject: [PATCH 03/33] global: collect subset resources for editorial source support --- openpype/plugins/publish/collect_otio_subset_resources.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/openpype/plugins/publish/collect_otio_subset_resources.py b/openpype/plugins/publish/collect_otio_subset_resources.py index 3387cd1176..e72c12d9a9 100644 --- a/openpype/plugins/publish/collect_otio_subset_resources.py +++ b/openpype/plugins/publish/collect_otio_subset_resources.py @@ -1,4 +1,3 @@ -# TODO: this head doc string """ Requires: instance -> otio_clip @@ -153,6 +152,8 @@ class CollectOtioSubsetResources(pyblish.api.InstancePlugin): self.log.debug(collection) repre = self._create_representation( frame_start, frame_end, collection=collection) + + instance.data["originalBasename"] = collection.format("{head}") else: _trim = False dirname, filename = os.path.split(media_ref.target_url) @@ -167,6 +168,10 @@ class CollectOtioSubsetResources(pyblish.api.InstancePlugin): repre = self._create_representation( frame_start, frame_end, file=filename, trim=_trim) + instance.data["originalBasename"] = os.path.splitext(filename)[0] + + instance.data["originalDirname"] = self.staging_dir + if repre: # add representation to instance data instance.data["representations"].append(repre) From 6d19e74361881b4ffab1a2404039da66fc876827 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 17 Jan 2023 16:57:47 +0100 Subject: [PATCH 04/33] global: exception for source publishing renumbering --- openpype/plugins/publish/integrate.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index 5f811ce002..dafcf44a6a 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -630,8 +630,12 @@ class IntegrateAsset(pyblish.api.InstancePlugin): # that `frameStart` index instead. Thus if that frame start # differs from the collection we want to shift the destination # frame indices from the source collection. + # In case source are published in place we need to skip renumbering repre_frame_start = repre.get("frameStart") - if repre_frame_start is not None: + if ( + "originalBasename" not in template + and repre_frame_start is not None + ): index_frame_start = int(repre["frameStart"]) # Shift destination sequence to the start frame destination_indexes = [ From b846aa50235e93fd19ba50f8996416e0e373806f Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 17 Jan 2023 17:08:47 +0100 Subject: [PATCH 05/33] flake8 fix --- openpype/plugins/publish/integrate.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index dafcf44a6a..a869c18b8f 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -630,7 +630,8 @@ class IntegrateAsset(pyblish.api.InstancePlugin): # that `frameStart` index instead. Thus if that frame start # differs from the collection we want to shift the destination # frame indices from the source collection. - # In case source are published in place we need to skip renumbering + # In case source are published in place we need to + # skip renumbering repre_frame_start = repre.get("frameStart") if ( "originalBasename" not in template From 7babd66ee01e86de4263a4ca28d496e77287e8ea Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 18 Jan 2023 00:11:02 +0100 Subject: [PATCH 06/33] Add global unique subsets validator --- .../publish/help/validate_unique_subsets.xml | 17 +++++ .../publish/validate_unique_subsets.py | 76 +++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 openpype/plugins/publish/help/validate_unique_subsets.xml create mode 100644 openpype/plugins/publish/validate_unique_subsets.py diff --git a/openpype/plugins/publish/help/validate_unique_subsets.xml b/openpype/plugins/publish/help/validate_unique_subsets.xml new file mode 100644 index 0000000000..b18f046f84 --- /dev/null +++ b/openpype/plugins/publish/help/validate_unique_subsets.xml @@ -0,0 +1,17 @@ + + + +Subset not unique + +## Clashing subset names found + +Multiples instances from your scene are set to publish into the same asset > subset. + + Non unique subset names: '{non_unique}' + +### How to repair? + +Remove the offending instances or rename to have a unique name. + + + \ No newline at end of file diff --git a/openpype/plugins/publish/validate_unique_subsets.py b/openpype/plugins/publish/validate_unique_subsets.py new file mode 100644 index 0000000000..11fb827770 --- /dev/null +++ b/openpype/plugins/publish/validate_unique_subsets.py @@ -0,0 +1,76 @@ +from collections import defaultdict +import pyblish.api +from openpype.pipeline.publish import ( + PublishXmlValidationError, +) + + +class ValidateSubsetUniqueness(pyblish.api.ContextPlugin): + """Validate all subset names are unique. + + This only validates whether the instances currently set to publish from + the workfile overlap one another for the asset + subset they are publishing + to. + + This does not perform any check against existing publishes in the database + since it is allowed to publish into existing subsets resulting in + versioning. + + A subset may appear twice to publish from the workfile if one + of them is set to publish to another asset than the other. + + """ + + label = "Validate Subset Uniqueness" + order = pyblish.api.ValidatorOrder + families = ["*"] + + def process(self, context): + + # Find instance per (asset,subset) + instance_per_asset_subset = defaultdict(list) + for instance in context: + + # Ignore disabled instances + if not instance.data.get('publish', True): + continue + + # Ignore instance without asset data + asset = instance.data.get("asset") + if asset is None: + self.log.warning("Instance found without `asset` data: " + "{}".format(instance.name)) + continue + + # Ignore instance without subset data + subset = instance.data.get("subset") + if subset is None: + self.log.warning("Instance found without `subset` data: " + "{}".format(instance.name)) + continue + + instance_per_asset_subset[(asset, subset)].append(instance) + + non_unique = [] + for (asset, subset), instances in instance_per_asset_subset.items(): + + # A single instance per asset, subset is fine + if len(instances) < 2: + continue + + non_unique.append("{asset} > {subset}".format(asset=asset, + subset=subset)) + + if not non_unique: + # All is ok + return + + msg = ("Instance subset names {} are not unique. ".format(non_unique) + + "Please remove or rename duplicates.") + formatting_data = { + "non_unique": ",".join(non_unique) + } + + if non_unique: + raise PublishXmlValidationError(self, msg, + formatting_data=formatting_data) From 9df7d43cda49eeb83a4b77d50032cb0105fb46b6 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 18 Jan 2023 00:12:07 +0100 Subject: [PATCH 07/33] Remove fusion validate unique subsets in favor of global one --- .../publish/validate_unique_subsets.py | 29 ------------------- 1 file changed, 29 deletions(-) delete mode 100644 openpype/hosts/fusion/plugins/publish/validate_unique_subsets.py diff --git a/openpype/hosts/fusion/plugins/publish/validate_unique_subsets.py b/openpype/hosts/fusion/plugins/publish/validate_unique_subsets.py deleted file mode 100644 index b218a311ba..0000000000 --- a/openpype/hosts/fusion/plugins/publish/validate_unique_subsets.py +++ /dev/null @@ -1,29 +0,0 @@ -import pyblish.api - - -class ValidateUniqueSubsets(pyblish.api.InstancePlugin): - """Ensure all instances have a unique subset name""" - - order = pyblish.api.ValidatorOrder - label = "Validate Unique Subsets" - families = ["render"] - hosts = ["fusion"] - - @classmethod - def get_invalid(cls, instance): - - context = instance.context - subset = instance.data["subset"] - for other_instance in context: - if other_instance == instance: - continue - - if other_instance.data["subset"] == subset: - return [instance] # current instance is invalid - - return [] - - def process(self, instance): - invalid = self.get_invalid(instance) - if invalid: - raise RuntimeError("Animation content is invalid. See log.") From be715734d47feae003e430996cdd2cdc63179240 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 18 Jan 2023 00:12:22 +0100 Subject: [PATCH 08/33] Remove photoshop validate unique subsets in favor of global one --- .../publish/help/validate_unique_subsets.xml | 14 ------- .../publish/validate_unique_subsets.py | 39 ------------------- 2 files changed, 53 deletions(-) delete mode 100644 openpype/hosts/photoshop/plugins/publish/help/validate_unique_subsets.xml delete mode 100644 openpype/hosts/photoshop/plugins/publish/validate_unique_subsets.py diff --git a/openpype/hosts/photoshop/plugins/publish/help/validate_unique_subsets.xml b/openpype/hosts/photoshop/plugins/publish/help/validate_unique_subsets.xml deleted file mode 100644 index 4b47973193..0000000000 --- a/openpype/hosts/photoshop/plugins/publish/help/validate_unique_subsets.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - -Subset not unique - -## Non unique subset name found - - Non unique subset names: '{non_unique}' -### How to repair? - -Remove offending instance, rename it to have unique name. Maybe layer name wasn't used for multiple instances? - - - \ No newline at end of file diff --git a/openpype/hosts/photoshop/plugins/publish/validate_unique_subsets.py b/openpype/hosts/photoshop/plugins/publish/validate_unique_subsets.py deleted file mode 100644 index 78e84729ce..0000000000 --- a/openpype/hosts/photoshop/plugins/publish/validate_unique_subsets.py +++ /dev/null @@ -1,39 +0,0 @@ -import collections -import pyblish.api -from openpype.pipeline.publish import ( - ValidateContentsOrder, - PublishXmlValidationError, -) - - -class ValidateSubsetUniqueness(pyblish.api.ContextPlugin): - """ - Validate that all subset's names are unique. - """ - - label = "Validate Subset Uniqueness" - hosts = ["photoshop"] - order = ValidateContentsOrder - families = ["image"] - - def process(self, context): - subset_names = [] - - for instance in context: - self.log.info("instance:: {}".format(instance.data)) - if instance.data.get('publish'): - subset_names.append(instance.data.get('subset')) - - non_unique = \ - [item - for item, count in collections.Counter(subset_names).items() - if count > 1] - msg = ("Instance subset names {} are not unique. ".format(non_unique) + - "Remove duplicates via SubsetManager.") - formatting_data = { - "non_unique": ",".join(non_unique) - } - - if non_unique: - raise PublishXmlValidationError(self, msg, - formatting_data=formatting_data) From 40620431a2a0ee4935e079249e63e67fb741ad91 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 18 Jan 2023 00:15:34 +0100 Subject: [PATCH 09/33] Remove maya validate unique subsets in favor of global one --- .../validate_review_subset_uniqueness.xml | 28 -------------- .../validate_review_subset_uniqueness.py | 38 ------------------- 2 files changed, 66 deletions(-) delete mode 100644 openpype/hosts/maya/plugins/publish/help/validate_review_subset_uniqueness.xml delete mode 100644 openpype/hosts/maya/plugins/publish/validate_review_subset_uniqueness.py diff --git a/openpype/hosts/maya/plugins/publish/help/validate_review_subset_uniqueness.xml b/openpype/hosts/maya/plugins/publish/help/validate_review_subset_uniqueness.xml deleted file mode 100644 index fd1bf4cbaa..0000000000 --- a/openpype/hosts/maya/plugins/publish/help/validate_review_subset_uniqueness.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - Review subsets not unique - - ## Non unique subset name found - - Non unique subset names: '{non_unique}' - - ### __Detailed Info__ (optional) - - This might happen if you already published for this asset - review subset with legacy name {task}Review. - This legacy name limits possibility of publishing of multiple - reviews from a single workfile. Proper review subset name should - now - contain variant also (as 'Main', 'Default' etc.). That would - result in completely new subset though, so this situation must - be handled manually. - - ### How to repair? - - Legacy subsets must be removed from Openpype DB, please ask admin - to do that. Please provide them asset and subset names. - - - - \ No newline at end of file diff --git a/openpype/hosts/maya/plugins/publish/validate_review_subset_uniqueness.py b/openpype/hosts/maya/plugins/publish/validate_review_subset_uniqueness.py deleted file mode 100644 index 361c594013..0000000000 --- a/openpype/hosts/maya/plugins/publish/validate_review_subset_uniqueness.py +++ /dev/null @@ -1,38 +0,0 @@ -# -*- coding: utf-8 -*- -import collections -import pyblish.api -from openpype.pipeline.publish import ( - ValidateContentsOrder, - PublishXmlValidationError, -) - - -class ValidateReviewSubsetUniqueness(pyblish.api.ContextPlugin): - """Validates that review subset has unique name.""" - - order = ValidateContentsOrder - hosts = ["maya"] - families = ["review"] - label = "Validate Review Subset Unique" - - def process(self, context): - subset_names = [] - - for instance in context: - self.log.debug("Instance: {}".format(instance.data)) - if instance.data.get('publish'): - subset_names.append(instance.data.get('subset')) - - non_unique = \ - [item - for item, count in collections.Counter(subset_names).items() - if count > 1] - msg = ("Instance subset names {} are not unique. ".format(non_unique) + - "Ask admin to remove subset from DB for multiple reviews.") - formatting_data = { - "non_unique": ",".join(non_unique) - } - - if non_unique: - raise PublishXmlValidationError(self, msg, - formatting_data=formatting_data) From 6cff073b7ae06fcdf0ff4e935625e83a620099c2 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 18 Jan 2023 00:28:32 +0100 Subject: [PATCH 10/33] Clarify label for new publisher "options" --- openpype/plugins/publish/validate_containers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/validate_containers.py b/openpype/plugins/publish/validate_containers.py index 79759450e1..8dc0c61cab 100644 --- a/openpype/plugins/publish/validate_containers.py +++ b/openpype/plugins/publish/validate_containers.py @@ -23,7 +23,7 @@ class ValidateContainers(OptionalPyblishPluginMixin, """Containers are must be updated to latest version on publish.""" - label = "Validate Containers" + label = "Validate Outdated Containers" order = pyblish.api.ValidatorOrder hosts = ["maya", "houdini", "nuke", "harmony", "photoshop", "aftereffects"] optional = True From 57e61666aa3208f083daaa4a80335ba9e71db71b Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 18 Jan 2023 00:48:35 +0100 Subject: [PATCH 11/33] Better validation error for validate version using new publisher --- openpype/plugins/publish/validate_version.py | 27 +++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/openpype/plugins/publish/validate_version.py b/openpype/plugins/publish/validate_version.py index b91633430f..9cda4dae90 100644 --- a/openpype/plugins/publish/validate_version.py +++ b/openpype/plugins/publish/validate_version.py @@ -1,4 +1,5 @@ import pyblish.api +from openpype.pipeline.publish import PublishValidationError class ValidateVersion(pyblish.api.InstancePlugin): @@ -20,11 +21,25 @@ class ValidateVersion(pyblish.api.InstancePlugin): version = instance.data.get("version") latest_version = instance.data.get("latestVersion") - if latest_version is not None: + if latest_version is not None and int(version) <= int(latest_version): + # TODO: Remove full non-html version upon drop of old publisher msg = ( - "Version `{0}` from instance `{1}` that you are trying to" - " publish, already exists in the database. Version in" - " database: `{2}`. Please version up your workfile to a higher" - " version number than: `{2}`." + "Version '{0}' from instance '{1}' that you are " + " trying to publish is lower or equal to an existing version " + " in the database. Version in database: '{2}'." + "Please version up your workfile to a higher version number " + "than: '{2}'." ).format(version, instance.data["name"], latest_version) - assert (int(version) > int(latest_version)), msg + + msg_html = ( + "Version {0} from instance {1} that you are " + " trying to publish is lower or equal to an existing version " + " in the database. Version in database: {2}.

" + "Please version up your workfile to a higher version number " + "than: {2}." + ).format(version, instance.data["name"], latest_version) + raise PublishValidationError( + title="Higher version of publish already exists", + message=msg, + description=msg_html + ) From 462f5d78124902260f88aa4b20b65e05527a3fdf Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 18 Jan 2023 00:55:39 +0100 Subject: [PATCH 12/33] Fix space --- .../houdini/plugins/publish/validate_alembic_input_node.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/houdini/plugins/publish/validate_alembic_input_node.py b/openpype/hosts/houdini/plugins/publish/validate_alembic_input_node.py index bafb206bd3..b0cf4cdc58 100644 --- a/openpype/hosts/houdini/plugins/publish/validate_alembic_input_node.py +++ b/openpype/hosts/houdini/plugins/publish/validate_alembic_input_node.py @@ -22,7 +22,7 @@ class ValidateAlembicInputNode(pyblish.api.InstancePlugin): invalid = self.get_invalid(instance) if invalid: raise PublishValidationError( - ("Primitive types found that are not supported" + ("Primitive types found that are not supported " "for Alembic output."), title=self.label ) From 1bd574d4a8d0648ef912784a1df8693f093c6db6 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 18 Jan 2023 01:05:11 +0100 Subject: [PATCH 13/33] Fix grammar --- openpype/plugins/publish/validate_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/validate_version.py b/openpype/plugins/publish/validate_version.py index 9cda4dae90..2b919a3119 100644 --- a/openpype/plugins/publish/validate_version.py +++ b/openpype/plugins/publish/validate_version.py @@ -5,7 +5,7 @@ from openpype.pipeline.publish import PublishValidationError class ValidateVersion(pyblish.api.InstancePlugin): """Validate instance version. - Pype is not allowing overwiting previously published versions. + OpenPype does not allow overwriting previously published versions. """ order = pyblish.api.ValidatorOrder From 67d8ab27fd5a04ddf44dc86ecac87ac486ea65e3 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 18 Jan 2023 01:26:56 +0100 Subject: [PATCH 14/33] Fix typos --- openpype/plugins/publish/collect_anatomy_instance_data.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/plugins/publish/collect_anatomy_instance_data.py b/openpype/plugins/publish/collect_anatomy_instance_data.py index 3858b4725e..b513ac8591 100644 --- a/openpype/plugins/publish/collect_anatomy_instance_data.py +++ b/openpype/plugins/publish/collect_anatomy_instance_data.py @@ -144,7 +144,7 @@ class CollectAnatomyInstanceData(pyblish.api.ContextPlugin): None """ - self.log.debug("Qeurying latest versions for instances.") + self.log.debug("Querying latest versions for instances.") hierarchy = {} names_by_asset_ids = collections.defaultdict(set) @@ -153,7 +153,7 @@ class CollectAnatomyInstanceData(pyblish.api.ContextPlugin): latest_version = instance.data.get("latestVersion") instance.data["latestVersion"] = latest_version - # Skip instances withou "assetEntity" + # Skip instances without "assetEntity" asset_doc = instance.data.get("assetEntity") if not asset_doc: continue @@ -162,7 +162,7 @@ class CollectAnatomyInstanceData(pyblish.api.ContextPlugin): asset_id = asset_doc["_id"] subset_name = instance.data["subset"] - # Prepare instance hiearchy for faster filling latest versions + # Prepare instance hierarchy for faster filling latest versions if asset_id not in hierarchy: hierarchy[asset_id] = {} if subset_name not in hierarchy[asset_id]: From 8ea08880768618134ee24c3e09010f83bca63874 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 18 Jan 2023 01:29:47 +0100 Subject: [PATCH 15/33] Grammar+typos --- openpype/plugins/publish/collect_anatomy_instance_data.py | 2 +- openpype/settings/lib.py | 2 +- openpype/tools/publisher/widgets/assets_widget.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/plugins/publish/collect_anatomy_instance_data.py b/openpype/plugins/publish/collect_anatomy_instance_data.py index b513ac8591..48171aa957 100644 --- a/openpype/plugins/publish/collect_anatomy_instance_data.py +++ b/openpype/plugins/publish/collect_anatomy_instance_data.py @@ -226,7 +226,7 @@ class CollectAnatomyInstanceData(pyblish.api.ContextPlugin): "version": version_number } - # Hiearchy + # Hierarchy asset_doc = instance.data.get("assetEntity") if ( asset_doc diff --git a/openpype/settings/lib.py b/openpype/settings/lib.py index 288c587d03..3a29f907be 100644 --- a/openpype/settings/lib.py +++ b/openpype/settings/lib.py @@ -581,7 +581,7 @@ def load_jsons_from_dir(path, *args, **kwargs): Data are loaded recursively from a directory and recreate the hierarchy as a dictionary. - Entered path hiearchy: + Entered path hierarchy: |_ folder1 | |_ data1.json |_ folder2 diff --git a/openpype/tools/publisher/widgets/assets_widget.py b/openpype/tools/publisher/widgets/assets_widget.py index 996c9029d4..0eb79e3e08 100644 --- a/openpype/tools/publisher/widgets/assets_widget.py +++ b/openpype/tools/publisher/widgets/assets_widget.py @@ -86,9 +86,9 @@ class CreateWidgetAssetsWidget(SingleSelectAssetsWidget): class AssetsHierarchyModel(QtGui.QStandardItemModel): - """Assets hiearrchy model. + """Assets hierarchy model. - For selecting asset for which should beinstance created. + For selecting asset for which an instance should be created. Uses controller to load asset hierarchy. All asset documents are stored by their parents. From 1dfffce83d84ef13b8a07bd134f686d40910feee Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Wed, 18 Jan 2023 09:26:53 +0100 Subject: [PATCH 16/33] sort families by alphabetical order --- openpype/tools/creator/model.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/tools/creator/model.py b/openpype/tools/creator/model.py index d3d60b96f2..f7323abe3c 100644 --- a/openpype/tools/creator/model.py +++ b/openpype/tools/creator/model.py @@ -39,6 +39,7 @@ class CreatorsModel(QtGui.QStandardItemModel): item.setData(QtCore.Qt.ItemIsEnabled, False) items.append(item) + items.sort(key=lambda item: item.text()) self.invisibleRootItem().appendRows(items) def get_creator_by_id(self, item_id): From 9bb6ad9fcb3caf571ee0994fe27ca02587877c18 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 18 Jan 2023 10:23:11 +0100 Subject: [PATCH 17/33] Fix typos --- openpype/plugins/publish/cleanup_explicit.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/plugins/publish/cleanup_explicit.py b/openpype/plugins/publish/cleanup_explicit.py index 88bba34532..983c9223c6 100644 --- a/openpype/plugins/publish/cleanup_explicit.py +++ b/openpype/plugins/publish/cleanup_explicit.py @@ -73,7 +73,7 @@ class ExplicitCleanUp(pyblish.api.ContextPlugin): ) # Delete folders with it's content - succeded_dirs = set() + succeeded = set() for dirpath in dirpaths: # Check if directory still exists # - it is possible that directory was already deleted with @@ -81,13 +81,13 @@ class ExplicitCleanUp(pyblish.api.ContextPlugin): if os.path.exists(dirpath): try: shutil.rmtree(dirpath) - succeded_dirs.add(dirpath) + succeeded.add(dirpath) except Exception: failed.append(dirpath) - if succeded_dirs: + if succeeded: self.log.info( - "Removed direcoties:\n{}".format("\n".join(succeded_dirs)) + "Removed directories:\n{}".format("\n".join(succeeded)) ) # Prepare lines for report of failed removements From f771d6f24ba6ab8799ea9aee38a7c025adfd7560 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 18 Jan 2023 10:37:45 +0100 Subject: [PATCH 18/33] Cosmetics --- openpype/hosts/houdini/plugins/publish/collect_inputs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/houdini/plugins/publish/collect_inputs.py b/openpype/hosts/houdini/plugins/publish/collect_inputs.py index 9ee0248bd9..0b54b244bb 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_inputs.py +++ b/openpype/hosts/houdini/plugins/publish/collect_inputs.py @@ -106,7 +106,7 @@ class CollectUpstreamInputs(pyblish.api.InstancePlugin): # If no valid output node is set then ignore it as validation # will be checking those cases. self.log.debug( - "No output node found, skipping " "collecting of inputs.." + "No output node found, skipping collecting of inputs.." ) return From 0d6ce14a2a099ef6f4e050c893e2d5a4ce109734 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 18 Jan 2023 13:36:02 +0100 Subject: [PATCH 19/33] Fix typo --- openpype/plugins/publish/integrate_hero_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/integrate_hero_version.py b/openpype/plugins/publish/integrate_hero_version.py index 5f4d284740..05d383415b 100644 --- a/openpype/plugins/publish/integrate_hero_version.py +++ b/openpype/plugins/publish/integrate_hero_version.py @@ -65,7 +65,7 @@ class IntegrateHeroVersion(pyblish.api.InstancePlugin): published_repres = instance.data.get("published_representations") if not published_repres: self.log.debug( - "*** There are not published representations on the instance." + "*** There are no published representations on the instance." ) return From 17d31793a95cf78fc6f27f4ad3abc23c12d98a75 Mon Sep 17 00:00:00 2001 From: Oscar Domingo Date: Wed, 18 Jan 2023 11:43:46 +0000 Subject: [PATCH 20/33] documentation: Split tools into separate entries Having the different tools displayed in the sidebar will allow the reader to form a overall picture of these faster as well as allowing to reference these easier. Any reference (link) to the tools has been updated to reflect the changes above. --- website/docs/artist_hosts_aftereffects.md | 16 +- website/docs/artist_hosts_blender.md | 32 +- website/docs/artist_hosts_harmony.md | 10 +- website/docs/artist_hosts_hiero.md | 12 +- website/docs/artist_hosts_houdini.md | 14 +- website/docs/artist_hosts_maya.md | 30 +- website/docs/artist_hosts_nuke_tut.md | 20 +- website/docs/artist_hosts_photoshop.md | 10 +- website/docs/artist_hosts_resolve.md | 12 +- website/docs/artist_hosts_tvpaint.md | 14 +- website/docs/artist_hosts_unreal.md | 10 +- website/docs/artist_tools.md | 489 +----------------- website/docs/artist_tools_context_manager.md | 17 + website/docs/artist_tools_creator.md | 25 + website/docs/artist_tools_inventory.md | 129 +++++ website/docs/artist_tools_library_loader.md | 42 ++ website/docs/artist_tools_loader.md | 121 +++++ website/docs/artist_tools_look_assigner.md | 26 + website/docs/artist_tools_publisher.md | 38 ++ website/docs/artist_tools_subset_manager.md | 38 ++ website/docs/artist_tools_sync_queu.md | 46 ++ website/docs/artist_tools_workfiles.md | 49 ++ .../settings_project_global.md | 6 +- website/sidebars.js | 19 +- website/src/pages/features.js | 12 +- 25 files changed, 660 insertions(+), 577 deletions(-) create mode 100644 website/docs/artist_tools_context_manager.md create mode 100644 website/docs/artist_tools_creator.md create mode 100644 website/docs/artist_tools_inventory.md create mode 100644 website/docs/artist_tools_library_loader.md create mode 100644 website/docs/artist_tools_loader.md create mode 100644 website/docs/artist_tools_look_assigner.md create mode 100644 website/docs/artist_tools_publisher.md create mode 100644 website/docs/artist_tools_subset_manager.md create mode 100644 website/docs/artist_tools_sync_queu.md create mode 100644 website/docs/artist_tools_workfiles.md diff --git a/website/docs/artist_hosts_aftereffects.md b/website/docs/artist_hosts_aftereffects.md index a9660bd13c..dfc61c4dbb 100644 --- a/website/docs/artist_hosts_aftereffects.md +++ b/website/docs/artist_hosts_aftereffects.md @@ -6,12 +6,12 @@ sidebar_label: AfterEffects ## Available Tools -- [Work Files](artist_tools.md#workfiles) -- [Create](artist_tools.md#creator) -- [Load](artist_tools.md#loader) -- [Publish](artist_tools.md#publisher) -- [Manage](artist_tools.md#inventory) -- [Subset Manager](artist_tools.md#subset-manager) +- [Work Files](artist_tools_workfiles) +- [Create](artist_tools_creator) +- [Load](artist_tools_loader) +- [Publish](artist_tools_publisher) +- [Manage](artist_tools_inventory) +- [Subset Manager](artist_tools_subset_manager) ## Setup @@ -65,7 +65,7 @@ When you are ready to share your work, you will need to publish it. This is done This tool will run through checks to make sure the contents you are publishing is correct. Hit the "Play" button to start publishing. -You may encounter issues with publishing which will be indicated with red squares. If these issues are within the validation section, then you can fix the issue. If there are issues outside of validation section, please let the OpenPype team know. For More details have a look at the general [Publish](artist_tools.md#publisher) documentation. +You may encounter issues with publishing which will be indicated with red squares. If these issues are within the validation section, then you can fix the issue. If there are issues outside of validation section, please let the OpenPype team know. For More details have a look at the general [Publish](artist_tools_publisher) documentation. ### Load @@ -110,4 +110,4 @@ All created compositions will be shown in a simple list. If user decides, that t published after all, right click on that item in the list and select 'Remove instance' Removing composition directly in the AE would result to worfile contain phantom metadata which could result in -errors during publishing! \ No newline at end of file +errors during publishing! diff --git a/website/docs/artist_hosts_blender.md b/website/docs/artist_hosts_blender.md index cfbcced22f..b2d4c001b4 100644 --- a/website/docs/artist_hosts_blender.md +++ b/website/docs/artist_hosts_blender.md @@ -6,13 +6,13 @@ sidebar_label: Blender ## OpenPype global tools -- [Set Context](artist_tools.md#set-context) -- [Work Files](artist_tools.md#workfiles) -- [Create](artist_tools.md#creator) -- [Load](artist_tools.md#loader) -- [Manage (Inventory)](artist_tools.md#inventory) -- [Publish](artist_tools.md#publisher) -- [Library Loader](artist_tools.md#library-loader) +- [Set Context](artist_tools_context_manager) +- [Work Files](artist_tools_workfiles) +- [Create](artist_tools_creator) +- [Load](artist_tools_loader) +- [Manage (Inventory)](artist_tools_inventory) +- [Publish](artist_tools_publisher) +- [Library Loader](artist_tools_library_loader) ## Working with OpenPype in Blender @@ -60,7 +60,7 @@ low resolution stuff. See [Subset](artist_concepts.md#subset). - -
-
- -### Refresh data -Data are not auto-refreshed to avoid database issues. To refresh assets or subsets press refresh button. - -
-
- -![tools_loader_50](assets/tools/tools_loader_50.png) - -
-
- -### Load another version -Loader by default load last version, but you can of course load another versions. Double-click on the subset in the version column to expose the drop down, choose version you want to load and continue from point 4 of the [Usage](#usage-1). - -
-
- - ![tools_loader_21](assets/tools/tools_loader_21.png) -
-
- - ![tools_loader_22](assets/tools/tools_loader_22.png) -
-
- - -### Filtering - -#### Filter Assets and Subsets by name -To filter assets/subsets by name just type name or part of name to filter text input. Only assets/subsets containing the entered string remain. - -- **Assets filtering example** *(it works the same for subsets)*: - -
-
- -![tools_loader_4](assets/tools/tools_loader_4-small.png) - -
-
- -![tools_loader_5](assets/tools/tools_loader_5-small.png) - -
-
- - -#### Filter Subsets by Family - -
-
- -To filter [subsets](artist_concepts.md#subset) by their [families](artist_publish.md#families) you can use families list where you can check families you want to see or uncheck families you are not interested in. - -
-
- -![tools_loader_30](assets/tools/tools_loader_30-small.png) - -
-
- - - -### Subset groups -Subsets may be grouped which can help to make the subset list more transparent. You can toggle visibility of groups with `Enable Grouping` checkbox. - -![tools_loader_40](assets/tools/tools_loader_40-small.png) - - -#### Add to group or change current group -You can set group of selected subsets with shortcut `Ctrl + G`. - -![tools_loader_41](assets/tools/tools_loader_41-small.png) - - -:::warning -You'll set the group in Avalon database so your changes will take effect for all users. -::: - -### Site Sync support - -If **Site Sync** is enabled additional widget is shown in right bottom corner. -It contains list of all representations of selected version(s). It also shows availability of representation files -on particular site (*active* - mine, *remote* - theirs). - -![site_sync_support](assets/site_sync_loader.png) - -On this picture you see that representation files are available only on remote site (could be GDrive or other). -If artist wants to work with the file(s) they need to be downloaded first. That could be done by right mouse click on -particular representation (or multiselect all) and select *Download*. - -This will mark representation to be download which will happen in the background if OpenPype Tray is running. - -For more details of progress, state or possible error details artist should open **[Sync Queue](#Sync-Queue)** item in Tray app. - -Work in progress... - -## Library Loader - -Library loader is extended [loader](#loader) which allows to load published subsets from Library projects. Controls are same but library loader has extra Combo Box which allows you to choose project you want to load from. - -
-
- -![tools_library_1](assets/tools/tools_library_1-small.png) - -
-
- -![tools_library_2](assets/tools/tools_library_2-small.png) - -
-
- -### Delivery Action ### - -Library Loader contains functionality to export any selected asset, subsets and their version to configurable folder. -Delivery follows structure based on defined template, this template must be configured first by Admin in the Settings. - -![delivery_action](assets/tools/tools_delivery_loader.png) - -* Usage -- Select all required subsets for export (you can change theirs versions by double clicking on 'Version' value) -- Right click and select **Deliver Versions** from context menu -- Select predefined Delivery template (must be configured by Admin system or project wide) -- Fill value for root folder (folder will be created if it doesn't exist) -- Filter out type of representation you are not interested in -- Push **Deliver** button -- Dialog must be kept open until export is finished -- In a case of problems with any of the representation, that one will be skipped, description of error will be provided in the dialog -* * * - -## Publisher - -> Use publish to share your work with others. It collects, validates and exports the data in standardized way. - -### Details - -When you run pyblish, the UI is made of 2 main parts. On the left, you see all the items pyblish will be working with (called instances), and on the right a list of actions that are going to process these items. -Even though every task type has some pre-defined settings of what should be collected from the scene and what items will be published by default. You can technically publish any output type from any task type. -Each item is passed through multiple plugins, each doing a small piece of work. These are organized into 4 areas and run in sequence. - -### Using Pyblish - -In the best case scenario, you open pyblish from the Avalon menu, press play, wait for it to finish, and you’re done. -These are the steps in detail, for cases, where the default settings don’t work for you or you know that the task you’re working on, requires a different treatment. - -#### Collect - -Finds all the important data in the scene and makes it ready for publishing - -#### Validate - -Each validator makes sure your output complies to one particular condition. This could be anything from naming conventions, scene setting, to plugin usage. An item can only be published if all validators pass. - -#### Extract - -Extractor takes the item and saves it to the disk. Usually to temporary location. Each extractor represents one file format and there can be multiple file formats exported for each item. - -#### Integrate - -Integrator takes the extracted files, categorizes and moves them to a correct location on the disk or on the server. - -* * * - -## Inventory - -With Scene Inventory, you can browse, update and change subsets loaded with [Loader](#loader) into your scene or script. - -:::note -You should first understand [Key concepts](artist_concepts) to understand how you can use this tool. -::: - -### Details - - -Once a subset is loaded, it turns into a container within a scene. This containerization allows us to have a good overview of everything in the scene, but also makes it possible to change versions, notify user if something is outdated, replace one asset for another, etc. - - -The scene manager has a simple GUI focused on efficiency. You can see everything that has been previously loaded into the scene, how many time it's been loaded, what version and a lot of other information. Loaded assets are grouped by their asset name, subset name and representation. This grouping gives ability to apply changes for all instances of the loaded asset *(e.g. when __tree__ is loaded 20 times you can easily update version for all of them)*. - -![tools_scene_inventory_10](assets/tools/tools_scene_inventory_10-small.png) - -To interact with any container, you need to right click it and you'll see a drop down with possible actions. The key actions for production are already implemented, but more will be added over time. - -![tools_scene_inventory_20](assets/tools/tools_scene_inventory_20.png) - -### Usage - -#### Change version -You can change versions of loaded subsets with scene inventory tool. Version of loaded assets is colored to red when newer version is available. - - -![tools_scene_inventory_40](assets/tools/tools_scene_inventory_40.png) - -##### Update to the latest version -Select containers or subsets you want to update, right-click selection and press `Update to latest`. - -##### Change to specific version -Select containers or subsets you want to change, right-click selection, press `Set version`, select from dropdown version you want change to and press `OK` button to confirm. - - -![tools_scene_inventory_30](assets/tools/tools_scene_inventory_30.png) - - -#### Switch Asset -It's tool in Scene inventory tool that gives ability to switch asset, subset and representation of loaded assets. - - -![tools_scene_inventory_50](assets/tools/tools_scene_inventory_50.png) - - -Because loaded asset is in fact representation of version published in asset's subset it is possible to switch each of this part *(representation, version, subset and asset)*, but with limitations. Limitations are obvious as you can imagine when you have loaded `.ma` representation of `modelMain` subset from `car` asset it is not possible to switch subset to `modelHD` and keep same representation if `modelHD` does not have published `.ma` representation. It is possible to switch multiple loaded assets at once that makes this tool very powerful helper if all published assets contain same subsets and representations. - -Switch tool won't let you cross the border of limitations and inform you when you have to specify more if impossible combination occurs *(It is also possible that there will be no possible combination for selected assets)*. Border is colored to red and confirm button is not enabled when specification is required. - - -![tools_scene_inventory_55](assets/tools/tools_scene_inventory_55.png) - - -Possible switches: -- switch **representation** (`.ma` to `.abc`, `.exr` to `.dpx`, etc.) -- switch **subset** (`modelMain` to `modelHD`, etc.) - - `AND` keep same **representation** *(with limitations)* - - `AND` switch **representation** *(with limitations)* -- switch **asset** (`oak` to `elm`, etc.) - - `AND` keep same **subset** and **representation** *(with limitations)* - - `AND` keep same **subset** and switch **representation** *(with limitations)* - - `AND` switch **subset** and keep same **representation** *(with limitations)* - - `AND` switch **subset** and **representation** *(with limitations)* - -We added one more switch layer above subset for LOD (Level Of Depth). That requires to have published subsets with name ending with **"_LOD{number}"** where number represents level (e.g. modelMain_LOD1). Has the same limitations as mentioned above. This is handy when you want to change only subset but keep same LOD or keep same subset but change LOD for multiple assets. This option is hidden if you didn't select subset that have published subset with LODs. - -![tools_scene_inventory_54](assets/tools/tools_scene_inventory_54.png) -### Filtering - -#### Filter by name - -There is a search bar on the top for cases when you have a complex scene with many assets and need to find a specific one. - -
-
- -![tools_scene_inventory_60](assets/tools/tools_scene_inventory_60-small.png) - -
-
- -![tools_scene_inventory_61](assets/tools/tools_scene_inventory_61-small.png) - -
-
- - -#### Filter with Cherry-pick selection - -
-
- -To keep only selected subsets right-click selection and press `Cherry-Pick (Hierarchy)` *(Border of subset list change to **orange** color when Cherry-pick filtering is set so you know filter is applied).* - -
-
- -![tools_scene_inventory_62-small](assets/tools/tools_scene_inventory_62-small.png) - -
-
- -
-
- -To return to original state right-click anywhere in subsets list and press `Back to Full-View`. - -
-
- -![tools_scene_inventory_63-small](assets/tools/tools_scene_inventory_63-small.png) - -
-
- - -:::tip -You can Cherry-pick from Cherry-picked subsets. -::: - -* * * - -## Workfiles - -Save new working scenes or scripts, or open the ones you previously worked on. - -### Details - -Instead of digging through your software native file browser, you can simply open the workfiles app and see all the files for the asset or shot you're currently working with. The app takes care of all the naming and the location of your work files. - -When saving a scene you can also add a comment. It is completely up to you how you use this, however we recommend using it for subversion within your current working version. - -Let's say that the last version of the comp you published was v003 and now you're working on the file prj_sh010_compositing_v004.nk if you want to keep snapshots of your work, but not iterate on the main version because the supervisor is expecting next publish to be v004, you can use the comment to do this, so you can save the file under the name prj_sh010_compositing_v004_001 , prj_sh010_compositing_v004_002. the main version is automatically iterated every time you publish something. - -### Usage - -
-
- -#### To open existing file: - -1. Open Workfiles tool from OpenPype menu -2. Select file from list - the latest version is the highest *(descendent ordering)* -3. Press `Open` button - -
-
- -![workfiles_1](assets/workfiles_1.png) - -
-
- - -#### To save new workfile -1. Open Workfiles tool from OpenPype menu -2. Press `Save As` button -3. You can add optional comment to the filename, that will be appended at the end -4. Press `OK` - -:::note -You can manually override the workfile version by unticking next available version and using the version menu to choose your own. -::: - -## Look Assigner - -> The Look Manager takes care of assigning published looks to the correct model in the scene. - -### Details - -When a look is published it also stores the information about what shading networks need to be assigned to which models, but it also stores all the render attributes on the mesh necessary for a successful render. - -### Usage - -Look Assigner has GUI is made of two parts. On the left you will see the list of all the available models in the scene and on the right side, all the looks that can be associate with them. To assign a look to a model you just need to: - -1. Click on "load all subsets" -2. Choose a subset from the menu on the left -3. Right click on a look from the list on the right -4. Choose "Assign" - -At this point you should have a model with all it's shaders applied correctly. The tool automatically loads the latest look available. - - -## Subset Manager - -> Subset Manager lists all items which are meant for publishig and will be published if Publish is triggered - -### Details - -One or more items (instances) could be published any time Publish process is started. Each this publishable -item must be created by Creator tool previously. Subset Manager provides easy way how to check which items, -and how many, will be published. - -It also provides clean and preferable way how to remove unwanted item from publishing. - -### Usage - -Subset Manager has GUI is made of two parts. On the left you will see the list of all the available publishable items in the scene and on the right side, details about these items. - -
- -![subset_manager](assets/tools_subset_manager.png) -
- -Any time new item is Created, it will show up here. - -Currently there is only single action, 'Remove instance' which cleans workfile file from publishable item metadata. -This might not remove underlying host item, it depends on host and implementation! - -It might also happen that user deletes underlying host item(for example layer in Photoshop) directly in the host, but metadata will stay. -This could result in phantom issues during publishing. Use Subset Manager to purge workfile from abandoned items. - -Please check behaviour in host of your choice. - -## Sync Queue - -### Details - -If **Site Sync** is configured for a project, each asset is marked to be synchronized to a remote site during publishing. -Each artist's OpenPype Tray application handles synchronization in background, it looks for all representation which -are marked with the site of the user (unique site name per artist) and remote site. - -Artists then can see progress of synchronization via **Sync Queue** link in the Tray application. - -Artists can see all synced representation in this dialog with helpful information such as when representation was created, when it was synched, -status of synchronization (OK or Fail) etc. - -### Usage - -With this app artists can modify synchronized representation, for example mark failed representation for re-sync etc. - -![Sync Queue](assets/site_sync_sync_queue.png) - -Actions accessible by context menu on single (or multiple representations): -- *Open in Explorer* - if site is locally accessible, open folder with it with OS based explorer -- *Re-sync Active Site* - mark artist own side for re-download (repre must be accessible on remote side) -- *Re-sync Remote Site* - mark representation for re-upload -- *Completely remove from local* - removes tag of synchronization to artist's local site, removes files from disk (available only for personal sites) -- *Change priority* - mark representations with higher priority for faster synchronization run - -Double click on any of the representation open Detail dialog with information about all files for particular representation. -In this dialog error details could be accessed in the context menu. - -#### Context menu on project name -Artists can also Pause whole server or specific project for synchronization. In that state no download/upload is being run. -This might be helpful if the artist is not interested in a particular project for a while or wants to save bandwidth data limit for a bit. - -Another option is `Validate files on active site`. This option triggers process where all representation of the selected project are looped through, file paths are resolved for active site and -if paths point to local system, paths are physically checked if files are existing. If file exists and representation is not marked to be present on 'active_site' in DB, DB is updated -to follow that. - -This might be useful if artist has representation files that Site Sync doesn't know about (newly attached external drive with representations from studio). -This project might take a while! \ No newline at end of file diff --git a/website/docs/artist_tools_context_manager.md b/website/docs/artist_tools_context_manager.md new file mode 100644 index 0000000000..254401e9de --- /dev/null +++ b/website/docs/artist_tools_context_manager.md @@ -0,0 +1,17 @@ +--- +id: artist_tools_context_manager +title: Context Manager +sidebar_label: Context Manager +description: A tool to manage the context within a host app. +--- + +# Context Manager + +Any time your host app is open in a defined context it can be changed to different hierarchy, asset or task within a project. This will allow you to change your opened session to any other asset, shot and tasks within the same project. This is useful particularly in cases where your host takes long time to start. + +![workfiles_1](assets/tools_context_manager.png) + + +:::note +Notice that the window doesn't close after hitting `Accept` and confirming the change of context. This behaviour let's you keep the window open and change the context multiple times in a row. +::: diff --git a/website/docs/artist_tools_creator.md b/website/docs/artist_tools_creator.md new file mode 100644 index 0000000000..e2f3f3b482 --- /dev/null +++ b/website/docs/artist_tools_creator.md @@ -0,0 +1,25 @@ +--- +id: artist_tools_creator +title: Creator +sidebar_label: Creator +description: A tool to generate metadata for asset publishing. +--- + +# Creator + +## Details + +Despite the name, Creator isn't for making new content in your scene, but rather taking what's already in it and creating all the metadata your content needs to be published. + +In Maya this means creating a set with everything you want to publish and assigning custom attributes to it so it gets picked up during publishing stage. + +In Nuke it's either converting an existing write node to a publishable one, or simply creating a write node with all the correct settings and outputs already set. + +## Usage + +1. Select what you want to publish from your scenes. +2. Open *Creator* from OpenPype menu. +3. Choose what family (data type) you need to export. +4. Type the name for you export. This name is how others are going to be able to refer to this particular subset when loading it into their scenes. Every assets should have a Main subset, but can have any number of other variants. +5. Click on *Create*. + diff --git a/website/docs/artist_tools_inventory.md b/website/docs/artist_tools_inventory.md new file mode 100644 index 0000000000..95207e2b47 --- /dev/null +++ b/website/docs/artist_tools_inventory.md @@ -0,0 +1,129 @@ +--- +id: artist_tools_inventory +title: Inventory +sidebar_label: Inventory +description: Manage already loaded subsets. +--- + +# Inventory + +With Scene Inventory, you can browse, update and change subsets loaded with [Loader](artist_tools_loader) into your scene or script. + +:::note +You should first understand [Key concepts](artist_concepts) to understand how you can use this tool. +::: + +## Details + + +Once a subset is loaded, it turns into a container within a scene. This containerization allows us to have a good overview of everything in the scene, but also makes it possible to change versions, notify user if something is outdated, replace one asset for another, etc. + + +The scene manager has a simple GUI focused on efficiency. You can see everything that has been previously loaded into the scene, how many time it's been loaded, what version and a lot of other information. Loaded assets are grouped by their asset name, subset name and representation. This grouping gives ability to apply changes for all instances of the loaded asset *(e.g. when __tree__ is loaded 20 times you can easily update version for all of them)*. + +![tools_scene_inventory_10](assets/tools/tools_scene_inventory_10-small.png) + +To interact with any container, you need to right click it and you'll see a drop down with possible actions. The key actions for production are already implemented, but more will be added over time. + +![tools_scene_inventory_20](assets/tools/tools_scene_inventory_20.png) + +## Usage + +### Change version +You can change versions of loaded subsets with scene inventory tool. Version of loaded assets is colored to red when newer version is available. + + +![tools_scene_inventory_40](assets/tools/tools_scene_inventory_40.png) + +#### Update to the latest version +Select containers or subsets you want to update, right-click selection and press `Update to latest`. + +#### Change to specific version +Select containers or subsets you want to change, right-click selection, press `Set version`, select from dropdown version you want change to and press `OK` button to confirm. + + +![tools_scene_inventory_30](assets/tools/tools_scene_inventory_30.png) + + +### Switch Asset +It's tool in Scene inventory tool that gives ability to switch asset, subset and representation of loaded assets. + + +![tools_scene_inventory_50](assets/tools/tools_scene_inventory_50.png) + + +Because loaded asset is in fact representation of version published in asset's subset it is possible to switch each of this part *(representation, version, subset and asset)*, but with limitations. Limitations are obvious as you can imagine when you have loaded `.ma` representation of `modelMain` subset from `car` asset it is not possible to switch subset to `modelHD` and keep same representation if `modelHD` does not have published `.ma` representation. It is possible to switch multiple loaded assets at once that makes this tool very powerful helper if all published assets contain same subsets and representations. + +Switch tool won't let you cross the border of limitations and inform you when you have to specify more if impossible combination occurs *(It is also possible that there will be no possible combination for selected assets)*. Border is colored to red and confirm button is not enabled when specification is required. + + +![tools_scene_inventory_55](assets/tools/tools_scene_inventory_55.png) + + +Possible switches: +- switch **representation** (`.ma` to `.abc`, `.exr` to `.dpx`, etc.) +- switch **subset** (`modelMain` to `modelHD`, etc.) + - `AND` keep same **representation** *(with limitations)* + - `AND` switch **representation** *(with limitations)* +- switch **asset** (`oak` to `elm`, etc.) + - `AND` keep same **subset** and **representation** *(with limitations)* + - `AND` keep same **subset** and switch **representation** *(with limitations)* + - `AND` switch **subset** and keep same **representation** *(with limitations)* + - `AND` switch **subset** and **representation** *(with limitations)* + +We added one more switch layer above subset for LOD (Level Of Depth). That requires to have published subsets with name ending with **"_LOD{number}"** where number represents level (e.g. modelMain_LOD1). Has the same limitations as mentioned above. This is handy when you want to change only subset but keep same LOD or keep same subset but change LOD for multiple assets. This option is hidden if you didn't select subset that have published subset with LODs. + +![tools_scene_inventory_54](assets/tools/tools_scene_inventory_54.png) + +## Filtering + +### Filter by name + +There is a search bar on the top for cases when you have a complex scene with many assets and need to find a specific one. + +
+
+ +![tools_scene_inventory_60](assets/tools/tools_scene_inventory_60-small.png) + +
+
+ +![tools_scene_inventory_61](assets/tools/tools_scene_inventory_61-small.png) + +
+
+ + +### Filter with Cherry-pick selection + +
+
+ +To keep only selected subsets right-click selection and press `Cherry-Pick (Hierarchy)` *(Border of subset list change to **orange** color when Cherry-pick filtering is set so you know filter is applied).* + +
+
+ +![tools_scene_inventory_62-small](assets/tools/tools_scene_inventory_62-small.png) + +
+
+ +
+
+ +To return to original state right-click anywhere in subsets list and press `Back to Full-View`. + +
+
+ +![tools_scene_inventory_63-small](assets/tools/tools_scene_inventory_63-small.png) + +
+
+ + +:::tip +You can Cherry-pick from Cherry-picked subsets. +::: diff --git a/website/docs/artist_tools_library_loader.md b/website/docs/artist_tools_library_loader.md new file mode 100644 index 0000000000..f85d4e6117 --- /dev/null +++ b/website/docs/artist_tools_library_loader.md @@ -0,0 +1,42 @@ +--- +id: artist_tools_library_loader +title: Library Loader +sidebar_label: Library Loader +description: Allows loading published subsets from projects of type "Library". +--- + +# Library Loader + +Library loader is extended [loader](artist_tools_loader) which allows to load published subsets from Library projects. Controls are same but library loader has extra Combo Box which allows you to choose project you want to load from. + +
+
+ +![tools_library_1](assets/tools/tools_library_1-small.png) + +
+
+ +![tools_library_2](assets/tools/tools_library_2-small.png) + +
+
+ +## Delivery Action + +Library Loader contains functionality to export any selected asset, subsets and their version to configurable folder. +Delivery follows structure based on defined template, this template must be configured first by Admin in the Settings. + +![delivery_action](assets/tools/tools_delivery_loader.png) + +* Usage +- Select all required subsets for export (you can change theirs versions by double clicking on 'Version' value) +- Right click and select **Deliver Versions** from context menu +- Select predefined Delivery template (must be configured by Admin system or project wide) +- Fill value for root folder (folder will be created if it doesn't exist) +- Filter out type of representation you are not interested in +- Push **Deliver** button +- Dialog must be kept open until export is finished +- In a case of problems with any of the representation, that one will be skipped, description of error will be provided in the dialog + + diff --git a/website/docs/artist_tools_loader.md b/website/docs/artist_tools_loader.md new file mode 100644 index 0000000000..3ae69b1cf6 --- /dev/null +++ b/website/docs/artist_tools_loader.md @@ -0,0 +1,121 @@ +--- +id: artist_tools_loader +title: Loader +sidebar_label: Loader +description: Allows loading published subsets from the same project. +--- + +# Loader +Loader loads published subsets into your current scene or script. + +## Usage +1. Open *Loader* from OpenPype menu. +2. Select the asset where the subset you want to load is published. +3. From subset list select the subset you want. +4. Right-click the subset. +5. From action menu select what you want to do *(load, reference, ...)*. + + +![tools_loader_1](assets/tools/tools_loader_1.png) + +
+
+ +## Refresh data +Data are not auto-refreshed to avoid database issues. To refresh assets or subsets press refresh button. + +
+
+ +![tools_loader_50](assets/tools/tools_loader_50.png) + +
+
+ +## Load another version +Loader by default load last version, but you can of course load another versions. Double-click on the subset in the version column to expose the drop down, choose version you want to load and continue from point 4 of the [Usage](#usage-1). + +
+
+ + ![tools_loader_21](assets/tools/tools_loader_21.png) +
+
+ + ![tools_loader_22](assets/tools/tools_loader_22.png) +
+
+ + +## Filtering + +### Filter Assets and Subsets by name +To filter assets/subsets by name just type name or part of name to filter text input. Only assets/subsets containing the entered string remain. + +- **Assets filtering example** *(it works the same for subsets)*: + +
+
+ +![tools_loader_4](assets/tools/tools_loader_4-small.png) + +
+
+ +![tools_loader_5](assets/tools/tools_loader_5-small.png) + +
+
+ + +### Filter Subsets by Family + +
+
+ +To filter [subsets](artist_concepts.md#subset) by their [families](artist_publish.md#families) you can use families list where you can check families you want to see or uncheck families you are not interested in. + +
+
+ +![tools_loader_30](assets/tools/tools_loader_30-small.png) + +
+
+ + + +## Subset groups +Subsets may be grouped which can help to make the subset list more transparent. You can toggle visibility of groups with `Enable Grouping` checkbox. + +![tools_loader_40](assets/tools/tools_loader_40-small.png) + + +### Add to group or change current group +You can set group of selected subsets with shortcut `Ctrl + G`. + +![tools_loader_41](assets/tools/tools_loader_41-small.png) + + +:::warning +You'll set the group in Avalon database so your changes will take effect for all users. +::: + +## Site Sync support + +If **Site Sync** is enabled additional widget is shown in right bottom corner. +It contains list of all representations of selected version(s). It also shows availability of representation files +on particular site (*active* - mine, *remote* - theirs). + +![site_sync_support](assets/site_sync_loader.png) + +On this picture you see that representation files are available only on remote site (could be GDrive or other). +If artist wants to work with the file(s) they need to be downloaded first. That could be done by right mouse click on +particular representation (or multiselect all) and select *Download*. + +This will mark representation to be download which will happen in the background if OpenPype Tray is running. + +For more details of progress, state or possible error details artist should open **[Sync Queue](#Sync-Queue)** item in Tray app. + +Work in progress... + diff --git a/website/docs/artist_tools_look_assigner.md b/website/docs/artist_tools_look_assigner.md new file mode 100644 index 0000000000..29002802b0 --- /dev/null +++ b/website/docs/artist_tools_look_assigner.md @@ -0,0 +1,26 @@ +--- +id: artist_tools_look_assigner +title: Look Assigner +sidebar_label: Look Assigner +description: Manage published looks to their respective model(s). +--- + +# Look Assigner + +The Look Manager takes care of assigning published looks to the correct model in the scene. + +## Details + +When a look is published it also stores the information about what shading networks need to be assigned to which models, but it also stores all the render attributes on the mesh necessary for a successful render. + +## Usage + +Look Assigner has GUI is made of two parts. On the left you will see the list of all the available models in the scene and on the right side, all the looks that can be associate with them. To assign a look to a model you just need to: + +1. Click on "load all subsets". +2. Choose a subset from the menu on the left. +3. Right click on a look from the list on the right. +4. Choose "Assign". + +At this point you should have a model with all it's shaders applied correctly. The tool automatically loads the latest look available. + diff --git a/website/docs/artist_tools_publisher.md b/website/docs/artist_tools_publisher.md new file mode 100644 index 0000000000..456049d824 --- /dev/null +++ b/website/docs/artist_tools_publisher.md @@ -0,0 +1,38 @@ +--- +id: artist_tools_publisher +title: Publisher +sidebar_label: Publisher +description: Publish versioned work progress into the project. +--- + +# Publisher + +Use publish to share your work with others. It collects, validates and exports the data in standardized way. + +## Details + +When you run pyblish, the UI is made of 2 main parts. On the left, you see all the items pyblish will be working with (called instances), and on the right a list of actions that are going to process these items. +Even though every task type has some pre-defined settings of what should be collected from the scene and what items will be published by default. You can technically publish any output type from any task type. +Each item is passed through multiple plugins, each doing a small piece of work. These are organized into 4 areas and run in sequence. + +## Using Pyblish + +In the best case scenario, you open pyblish from the Avalon menu, press play, wait for it to finish, and you’re done. +These are the steps in detail, for cases, where the default settings don’t work for you or you know that the task you’re working on, requires a different treatment. + +### Collect + +Finds all the important data in the scene and makes it ready for publishing + +### Validate + +Each validator makes sure your output complies to one particular condition. This could be anything from naming conventions, scene setting, to plugin usage. An item can only be published if all validators pass. + +### Extract + +Extractor takes the item and saves it to the disk. Usually to temporary location. Each extractor represents one file format and there can be multiple file formats exported for each item. + +### Integrate + +Integrator takes the extracted files, categorizes and moves them to a correct location on the disk or on the server. + diff --git a/website/docs/artist_tools_subset_manager.md b/website/docs/artist_tools_subset_manager.md new file mode 100644 index 0000000000..fd1bc5f477 --- /dev/null +++ b/website/docs/artist_tools_subset_manager.md @@ -0,0 +1,38 @@ +--- +id: artist_tools_subset_manager +title: Subset Manager +sidebar_label: Subset Manager +description: Manage all the publish-able elements. +--- + +# Subset Manager + +Subset Manager lists all items which are meant for publishig and will be published if Publish is triggered + +## Details + +One or more items (instances) could be published any time Publish process is started. Each this publishable +item must be created by Creator tool previously. Subset Manager provides easy way how to check which items, +and how many, will be published. + +It also provides clean and preferable way how to remove unwanted item from publishing. + +## Usage + +Subset Manager has GUI is made of two parts. On the left you will see the list of all the available publishable items in the scene and on the right side, details about these items. + +
+ +![subset_manager](assets/tools_subset_manager.png) +
+ +Any time new item is Created, it will show up here. + +Currently there is only single action, 'Remove instance' which cleans workfile file from publishable item metadata. +This might not remove underlying host item, it depends on host and implementation! + +It might also happen that user deletes underlying host item(for example layer in Photoshop) directly in the host, but metadata will stay. +This could result in phantom issues during publishing. Use Subset Manager to purge workfile from abandoned items. + +Please check behaviour in host of your choice. + diff --git a/website/docs/artist_tools_sync_queu.md b/website/docs/artist_tools_sync_queu.md new file mode 100644 index 0000000000..770c2f77ad --- /dev/null +++ b/website/docs/artist_tools_sync_queu.md @@ -0,0 +1,46 @@ +--- +id: artist_tools_sync_queue +title: Sync Queue +sidebar_label: Sync Queue +description: Track sites syncronization progress. +--- + +# Sync Queue + +## Details + +If **Site Sync** is configured for a project, each asset is marked to be synchronized to a remote site during publishing. +Each artist's OpenPype Tray application handles synchronization in background, it looks for all representation which +are marked with the site of the user (unique site name per artist) and remote site. + +Artists then can see progress of synchronization via **Sync Queue** link in the Tray application. + +Artists can see all synced representation in this dialog with helpful information such as when representation was created, when it was synched, +status of synchronization (OK or Fail) etc. + +## Usage + +With this app artists can modify synchronized representation, for example mark failed representation for re-sync etc. + +![Sync Queue](assets/site_sync_sync_queue.png) + +Actions accessible by context menu on single (or multiple representations): +- *Open in Explorer* - if site is locally accessible, open folder with it with OS based explorer +- *Re-sync Active Site* - mark artist own side for re-download (repre must be accessible on remote side) +- *Re-sync Remote Site* - mark representation for re-upload +- *Completely remove from local* - removes tag of synchronization to artist's local site, removes files from disk (available only for personal sites) +- *Change priority* - mark representations with higher priority for faster synchronization run + +Double click on any of the representation open Detail dialog with information about all files for particular representation. +In this dialog error details could be accessed in the context menu. + +#### Context menu on project name +Artists can also Pause whole server or specific project for synchronization. In that state no download/upload is being run. +This might be helpful if the artist is not interested in a particular project for a while or wants to save bandwidth data limit for a bit. + +Another option is `Validate files on active site`. This option triggers process where all representation of the selected project are looped through, file paths are resolved for active site and +if paths point to local system, paths are physically checked if files are existing. If file exists and representation is not marked to be present on 'active_site' in DB, DB is updated +to follow that. + +This might be useful if artist has representation files that Site Sync doesn't know about (newly attached external drive with representations from studio). +This project might take a while! diff --git a/website/docs/artist_tools_workfiles.md b/website/docs/artist_tools_workfiles.md new file mode 100644 index 0000000000..2e1d939c97 --- /dev/null +++ b/website/docs/artist_tools_workfiles.md @@ -0,0 +1,49 @@ +--- +id: artist_tools_workfiles +title: Workfiles +sidebar_label: Workfiles +description: Save versioned progress files. +--- + +# Workfiles + +Save new working scenes or scripts, or open the ones you previously worked on. + +## Details + +Instead of digging through your software native file browser, you can simply open the workfiles app and see all the files for the asset or shot you're currently working with. The app takes care of all the naming and the location of your work files. + +When saving a scene you can also add a comment. It is completely up to you how you use this, however we recommend using it for subversion within your current working version. + +Let's say that the last version of the comp you published was v003 and now you're working on the file prj_sh010_compositing_v004.nk if you want to keep snapshots of your work, but not iterate on the main version because the supervisor is expecting next publish to be v004, you can use the comment to do this, so you can save the file under the name prj_sh010_compositing_v004_001 , prj_sh010_compositing_v004_002. the main version is automatically iterated every time you publish something. + +## Usage + +
+
+ +### To open existing file: + +1. Open Workfiles tool from OpenPype menu +2. Select file from list - the latest version is the highest *(descendent ordering)* +3. Press `Open` button + +
+
+ +![workfiles_1](assets/workfiles_1.png) + +
+
+ + +### To save new workfile +1. Open Workfiles tool from OpenPype menu +2. Press `Save As` button +3. You can add optional comment to the filename, that will be appended at the end +4. Press `OK` + +:::note +You can manually override the workfile version by unticking next available version and using the version menu to choose your own. +::: + diff --git a/website/docs/project_settings/settings_project_global.md b/website/docs/project_settings/settings_project_global.md index 9666c6568a..34b03fe32a 100644 --- a/website/docs/project_settings/settings_project_global.md +++ b/website/docs/project_settings/settings_project_global.md @@ -162,7 +162,7 @@ Applicable context filters: #### Subset grouping profiles -Published subsets might be grouped together for cleaner and easier selection in **[Loader](artist_tools.md#subset-groups)** +Published subsets might be grouped together for cleaner and easier selection in the **[Subset Manager](artist_tools_subset_manager)** Group name is chosen with use of [profile filtering](#profile-filters) @@ -179,7 +179,7 @@ Applicable context filters: Settings for OpenPype tools. ## Creator -Settings related to [Creator tool](artist_tools.md#details). +Settings related to [Creator tool](artist_tools_creator). ### Subset name profiles ![global_tools_creator_subset_template](assets/global_tools_creator_subset_template.png) @@ -217,4 +217,4 @@ All settings related to Workfile tool. ### Open last workfile at launch This feature allows you to define a rule for each task/host or toggle the feature globally to all tasks as they are visible in the picture. -![global_tools_workfile_open_last_version](assets/global_tools_workfile_open_last_version.png) \ No newline at end of file +![global_tools_workfile_open_last_version](assets/global_tools_workfile_open_last_version.png) diff --git a/website/sidebars.js b/website/sidebars.js index f2d9ffee06..a19aade712 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -8,7 +8,24 @@ module.exports = { "artist_getting_started", "artist_concepts", "artist_publish", - "artist_tools", + { + type: "category", + collapsed: true, + label: "Tools", + link: {type: 'doc', id: 'artist_tools'}, + items: [ + "artist_tools_context_manager", + "artist_tools_creator", + "artist_tools_loader", + "artist_tools_library_loader", + "artist_tools_publisher", + "artist_tools_inventory", + "artist_tools_workfiles", + "artist_tools_look_assigner", + "artist_tools_subset_manager", + "artist_tools_sync_queue" + ], + }, "artist_install" ], }, diff --git a/website/src/pages/features.js b/website/src/pages/features.js index d5c036eb89..8f3a085784 100644 --- a/website/src/pages/features.js +++ b/website/src/pages/features.js @@ -15,32 +15,32 @@ const key_features = [ label: "Workfiles", description: "Save and load workfiles in progress. Change the context inside of the application.", - docs: "/docs/artist_tools#workfiles", + docs: "/docs/artist_tools_workfiles", }, { label: "Creator", description: "Universal GUI for defining content for publishing from your DCC app.", - docs: "/docs/artist_tools#creator", + docs: "/docs/artist_tools_creator", }, { label: "Loader", description: "Universal GUI for loading published assets into your DCC app.", - docs: "/docs/artist_tools#loader", + docs: "/docs/artist_tools_loader", }, { label: "Publisher", description: "Universal GUI for validating and publishng content from your DCC app.", image: "", - docs: "/docs/artist_tools#publisher", + docs: "/docs/artist_tools_publisher", }, { label: "Scene manager", description: "Universal GUI for managing versions of assets loaded into your working scene.", - docs: "docs/artist_tools#inventory", + docs: "docs/artist_tools_inventory", }, { label: "Project manager", @@ -52,7 +52,7 @@ const key_features = [ label: "Library Loader", description: "A loader GUI that allows yo to load content from dedicated cross project asset library", - docs: "docs/artist_tools#library-loader", + docs: "docs/artist_tool_library_loader", image: "", }, { From 67e10bfd43e0193c755017eb6a6fcf05235e2a27 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 19 Jan 2023 03:43:48 +0100 Subject: [PATCH 21/33] fix change of project --- openpype/tools/push_to_project/window.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/openpype/tools/push_to_project/window.py b/openpype/tools/push_to_project/window.py index e62650ec53..c0c47fd40e 100644 --- a/openpype/tools/push_to_project/window.py +++ b/openpype/tools/push_to_project/window.py @@ -230,9 +230,13 @@ class AssetsModel(QtGui.QStandardItemModel): item = self._items.pop(item_id, None) if item is None: continue + row = item.row() + if row < 0: + continue parent = item.parent() - if parent is not None: - parent.takeRow(item.row()) + if parent is None: + parent = root_item + parent.takeRow(row) self.items_changed.emit() From 3400304cef1d11fc352f1c8d040978e11ec0f1ed Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 19 Jan 2023 12:16:11 +0100 Subject: [PATCH 22/33] use 'apply_settings' method to get value of 'delete_unmatched_assets' --- .../unreal/plugins/load/load_layout_existing.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/unreal/plugins/load/load_layout_existing.py b/openpype/hosts/unreal/plugins/load/load_layout_existing.py index 3ce99f8ef6..ceb64ab19d 100644 --- a/openpype/hosts/unreal/plugins/load/load_layout_existing.py +++ b/openpype/hosts/unreal/plugins/load/load_layout_existing.py @@ -15,7 +15,6 @@ from openpype.pipeline import ( AVALON_CONTAINER_ID, legacy_io, ) -from openpype.api import get_current_project_settings from openpype.hosts.unreal.api import plugin from openpype.hosts.unreal.api import pipeline as upipeline @@ -33,6 +32,17 @@ class ExistingLayoutLoader(plugin.Loader): color = "orange" ASSET_ROOT = "/Game/OpenPype" + delete_unmatched_assets = True + + @classmethod + def apply_settings(cls, project_settings, *args, **kwargs): + super(ExistingLayoutLoader, cls).apply_settings( + project_settings, *args, **kwargs + ) + cls.delete_unmatched_assets = ( + project_settings["unreal"]["delete_unmatched_assets"] + ) + @staticmethod def _create_container( asset_name, asset_dir, asset, representation, parent, family @@ -222,8 +232,6 @@ class ExistingLayoutLoader(plugin.Loader): return assets def _process(self, lib_path): - data = get_current_project_settings() - delete_unmatched = data["unreal"]["delete_unmatched_assets"] ar = unreal.AssetRegistryHelpers.get_asset_registry() @@ -360,7 +368,7 @@ class ExistingLayoutLoader(plugin.Loader): continue if actor not in actors_matched: self.log.warning(f"Actor {actor.get_name()} not matched.") - if delete_unmatched: + if self.delete_unmatched_assets: EditorLevelLibrary.destroy_actor(actor) return containers From 7c6dd8503df98a48e8292b2e3ac90df7ad809c4e Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 19 Jan 2023 12:20:45 +0100 Subject: [PATCH 23/33] pass project name to '_process' --- .../hosts/unreal/plugins/load/load_layout_existing.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/unreal/plugins/load/load_layout_existing.py b/openpype/hosts/unreal/plugins/load/load_layout_existing.py index ceb64ab19d..5ac9b88709 100644 --- a/openpype/hosts/unreal/plugins/load/load_layout_existing.py +++ b/openpype/hosts/unreal/plugins/load/load_layout_existing.py @@ -231,8 +231,8 @@ class ExistingLayoutLoader(plugin.Loader): return assets - def _process(self, lib_path): + def _process(self, lib_path, project_name): ar = unreal.AssetRegistryHelpers.get_asset_registry() actors = EditorLevelLibrary.get_all_level_actors() @@ -385,7 +385,8 @@ class ExistingLayoutLoader(plugin.Loader): if not curr_level: raise AssertionError("Current level not saved") - containers = self._process(self.fname) + project_name = context["project"]["name"] + containers = self._process(self.fname, project_name) curr_level_path = Path( curr_level.get_outer().get_path_name()).parent.as_posix() @@ -415,7 +416,8 @@ class ExistingLayoutLoader(plugin.Loader): asset_dir = container.get('namespace') source_path = get_representation_path(representation) - containers = self._process(source_path) + project_name = legacy_io.active_project() + containers = self._process(source_path, project_name) data = { "representation": str(representation["_id"]), From 90d409714341d90a28cd768b51da57bb9e37e770 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 19 Jan 2023 12:21:11 +0100 Subject: [PATCH 24/33] more efficient queries without using 'legacy_io' --- .../plugins/load/load_layout_existing.py | 78 ++++++++++++------- 1 file changed, 48 insertions(+), 30 deletions(-) diff --git a/openpype/hosts/unreal/plugins/load/load_layout_existing.py b/openpype/hosts/unreal/plugins/load/load_layout_existing.py index 5ac9b88709..8dfbcb1f84 100644 --- a/openpype/hosts/unreal/plugins/load/load_layout_existing.py +++ b/openpype/hosts/unreal/plugins/load/load_layout_existing.py @@ -4,9 +4,7 @@ from pathlib import Path import unreal from unreal import EditorLevelLibrary -from bson.objectid import ObjectId - -from openpype import pipeline +from openpype.client import get_representations from openpype.pipeline import ( discover_loader_plugins, loaders_from_representation, @@ -189,14 +187,7 @@ class ExistingLayoutLoader(plugin.Loader): return None - def _load_asset(self, representation, version, instance_name, family): - valid_formats = ['fbx', 'abc'] - - repr_data = legacy_io.find_one({ - "type": "representation", - "parent": ObjectId(version), - "name": {"$in": valid_formats} - }) + def _load_asset(self, repr_data, representation, instance_name, family): repr_format = repr_data.get('name') all_loaders = discover_loader_plugins() @@ -231,6 +222,19 @@ class ExistingLayoutLoader(plugin.Loader): return assets + def _get_valid_repre_docs(self, project_name, version_ids): + valid_formats = ['fbx', 'abc'] + + repre_docs = list(get_representations( + project_name, + representation_names=valid_formats, + version_ids=version_ids + )) + repre_doc_by_version_id = {} + for repre_doc in repre_docs: + version_id = str(repre_doc["parent"]) + repre_doc_by_version_id[version_id] = repre_doc + return repre_doc_by_version_id def _process(self, lib_path, project_name): ar = unreal.AssetRegistryHelpers.get_asset_registry() @@ -240,31 +244,45 @@ class ExistingLayoutLoader(plugin.Loader): with open(lib_path, "r") as fp: data = json.load(fp) - layout_data = [] - + elements = [] + repre_ids = set() # Get all the representations in the JSON from the database. for element in data: - if element.get('representation'): - layout_data.append(( - pipeline.legacy_io.find_one({ - "_id": ObjectId(element.get('representation')) - }), - element - )) + repre_id = element.get('representation') + if repre_id: + repre_ids.append(repre_id) + elements.append(element) + repre_docs = get_representations( + project_name, representation_ids=repre_ids + ) + repre_docs_by_id = { + str(repre_doc["_id"]): repre_doc + for repre_doc in repre_docs + } + layout_data = [] + version_ids = set() + for element in elements: + repre_id = element.get("representation") + repre_doc = repre_docs_by_id.get(repre_id) + if not repre_doc: + raise AssertionError("Representation not found") + if not (repre_doc.get('data') or repre_doc['data'].get('path')): + raise AssertionError("Representation does not have path") + if not repre_doc.get('context'): + raise AssertionError("Representation does not have context") + + layout_data.append((repre_doc, element)) + version_ids.add(repre_doc["parent"]) + + # Prequery valid repre documents for all elements at once + valid_repre_doc_by_version_id = self._get_valid_repre_docs( + project_name, version_ids) containers = [] actors_matched = [] for (repr_data, lasset) in layout_data: - if not repr_data: - raise AssertionError("Representation not found") - if not (repr_data.get('data') or - repr_data.get('data').get('path')): - raise AssertionError("Representation does not have path") - if not repr_data.get('context'): - raise AssertionError("Representation does not have context") - - # For every actor in the scene, check if it has a representation in + # For every actor in the scene, check if it has a representation in # those we got from the JSON. If so, create a container for it. # Otherwise, remove it from the scene. found = False @@ -347,8 +365,8 @@ class ExistingLayoutLoader(plugin.Loader): continue assets = self._load_asset( + valid_repre_doc_by_version_id.get(lasset.get('version')), lasset.get('representation'), - lasset.get('version'), lasset.get('instance_name'), lasset.get('family') ) From 0bc73832b00041a50fa3931863f135a742f7f67b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 19 Jan 2023 12:35:56 +0100 Subject: [PATCH 25/33] fix comment indentation --- openpype/hosts/unreal/plugins/load/load_layout_existing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/unreal/plugins/load/load_layout_existing.py b/openpype/hosts/unreal/plugins/load/load_layout_existing.py index 8dfbcb1f84..f24288fecc 100644 --- a/openpype/hosts/unreal/plugins/load/load_layout_existing.py +++ b/openpype/hosts/unreal/plugins/load/load_layout_existing.py @@ -282,7 +282,7 @@ class ExistingLayoutLoader(plugin.Loader): actors_matched = [] for (repr_data, lasset) in layout_data: - # For every actor in the scene, check if it has a representation in + # For every actor in the scene, check if it has a representation in # those we got from the JSON. If so, create a container for it. # Otherwise, remove it from the scene. found = False From bfed990e4fb9b20957d86e2817c2c61669519488 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 19 Jan 2023 12:38:22 +0100 Subject: [PATCH 26/33] normalize path --- openpype/tools/push_to_project/control_integrate.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/tools/push_to_project/control_integrate.py b/openpype/tools/push_to_project/control_integrate.py index 819724ad4c..5cc9dbc3bb 100644 --- a/openpype/tools/push_to_project/control_integrate.py +++ b/openpype/tools/push_to_project/control_integrate.py @@ -419,7 +419,9 @@ class ProjectPushRepreItem: src_basename_regex = re.compile("^{}$".format(src_basename)) for file_info in self._repre_doc["files"]: filepath_template = file_info["path"].replace("\\", "/") - filepath = filepath_template.format(root=self._roots) + filepath = os.path.normpath( + filepath_template.format(root=self._roots) + ) dirpath, basename = os.path.split(filepath_template) if ( dirpath != src_dirpath From 5fa000803a8def99fade6614b0003640cc51edf7 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 19 Jan 2023 14:36:25 +0100 Subject: [PATCH 27/33] add universal clean path method --- .../push_to_project/control_integrate.py | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/openpype/tools/push_to_project/control_integrate.py b/openpype/tools/push_to_project/control_integrate.py index 5cc9dbc3bb..7e518d8042 100644 --- a/openpype/tools/push_to_project/control_integrate.py +++ b/openpype/tools/push_to_project/control_integrate.py @@ -326,6 +326,13 @@ class ProjectPushRepreItem: self.get_source_files() return self._resource_files + @staticmethod + def _clean_path(path): + new_value = path.replace("\\", "/") + while "//" in new_value: + new_value = new_value.replace("//", "/") + return new_value + @property def frame(self): """First frame of representation files. @@ -407,8 +414,8 @@ class ProjectPushRepreItem: fill_roots = fill_repre_context["root"] for root_name in tuple(fill_roots.keys()): fill_roots[root_name] = "{{root[{}]}}".format(root_name) - repre_path = StringTemplate.format_template(template, - fill_repre_context) + repre_path = StringTemplate.format_template( + template, fill_repre_context) repre_path = repre_path.replace("\\", "/") src_dirpath, src_basename = os.path.split(repre_path) src_basename = ( @@ -418,10 +425,11 @@ class ProjectPushRepreItem: ) src_basename_regex = re.compile("^{}$".format(src_basename)) for file_info in self._repre_doc["files"]: - filepath_template = file_info["path"].replace("\\", "/") - filepath = os.path.normpath( + filepath_template = self._clean_path(file_info["path"]) + filepath = self._clean_path( filepath_template.format(root=self._roots) ) + dirpath, basename = os.path.split(filepath_template) if ( dirpath != src_dirpath @@ -463,8 +471,10 @@ class ProjectPushRepreItem: repre_path = repre_path.replace("\\", "/") src_dirpath = os.path.dirname(repre_path) for file_info in self._repre_doc["files"]: - filepath_template = file_info["path"].replace("\\", "/") - filepath = filepath_template.format(root=self._roots) + filepath_template = self._clean_path(file_info["path"]) + filepath = self._clean_path( + filepath_template.format(root=self._roots)) + if filepath_template == repre_path: src_files.append(SourceFile(filepath)) else: From aab842e4b7223df6b547dce69f62e0f69a599328 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 19 Jan 2023 15:21:47 +0100 Subject: [PATCH 28/33] fix find of root templates in anatomy --- openpype/pipeline/anatomy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/anatomy.py b/openpype/pipeline/anatomy.py index 72f30d85cb..a18b46d9ac 100644 --- a/openpype/pipeline/anatomy.py +++ b/openpype/pipeline/anatomy.py @@ -1126,7 +1126,7 @@ class RootItem(FormatObject): if _mod_path.startswith(root_path): result = True replacement = "{" + self.full_key() + "}" - output = replacement + _mod_path[len(root_path):] + output = replacement + mod_path[len(root_path):] break return (result, output) From 813dc264888c6a3a4c136ba07bc93cdf8046434c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 19 Jan 2023 15:33:32 +0100 Subject: [PATCH 29/33] clean repre path too --- openpype/tools/push_to_project/control_integrate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/tools/push_to_project/control_integrate.py b/openpype/tools/push_to_project/control_integrate.py index 7e518d8042..8457f2d910 100644 --- a/openpype/tools/push_to_project/control_integrate.py +++ b/openpype/tools/push_to_project/control_integrate.py @@ -416,7 +416,7 @@ class ProjectPushRepreItem: fill_roots[root_name] = "{{root[{}]}}".format(root_name) repre_path = StringTemplate.format_template( template, fill_repre_context) - repre_path = repre_path.replace("\\", "/") + repre_path = self._clean_path(repre_path) src_dirpath, src_basename = os.path.split(repre_path) src_basename = ( re.escape(src_basename) @@ -468,7 +468,7 @@ class ProjectPushRepreItem: fill_roots[root_name] = "{{root[{}]}}".format(root_name) repre_path = StringTemplate.format_template(template, fill_repre_context) - repre_path = repre_path.replace("\\", "/") + repre_path = self._clean_path(repre_path) src_dirpath = os.path.dirname(repre_path) for file_info in self._repre_doc["files"]: filepath_template = self._clean_path(file_info["path"]) From 281f8bc86e716de0ff822d31052ccd46ea52b84b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 19 Jan 2023 15:40:44 +0100 Subject: [PATCH 30/33] handle lowered paths in representation files --- .../push_to_project/control_integrate.py | 45 ++++++++++++------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/openpype/tools/push_to_project/control_integrate.py b/openpype/tools/push_to_project/control_integrate.py index 8457f2d910..bb95fdb26f 100644 --- a/openpype/tools/push_to_project/control_integrate.py +++ b/openpype/tools/push_to_project/control_integrate.py @@ -84,6 +84,12 @@ class ResourceFile(FileItem): def __repr__(self): return "<{}> '{}'".format(self.__class__.__name__, self.relative_path) + @property + def is_valid_file(self): + if not self.relative_path: + return False + return super(ResourceFile, self).is_valid_file + class ProjectPushItem: def __init__( @@ -333,6 +339,19 @@ class ProjectPushRepreItem: new_value = new_value.replace("//", "/") return new_value + @staticmethod + def _get_relative_path(path, src_dirpath): + dirpath, basename = os.path.split(path) + if not dirpath.lower().startswith(src_dirpath.lower()): + return None + + relative_dir = dirpath[len(src_dirpath):].lstrip("/") + if relative_dir: + relative_path = "/".join([relative_dir, basename]) + else: + relative_path = basename + return relative_path + @property def frame(self): """First frame of representation files. @@ -429,20 +448,16 @@ class ProjectPushRepreItem: filepath = self._clean_path( filepath_template.format(root=self._roots) ) - dirpath, basename = os.path.split(filepath_template) if ( - dirpath != src_dirpath + dirpath.lower() != src_dirpath.lower() or not src_basename_regex.match(basename) ): - relative_dir = dirpath.replace(src_dirpath, "") - if relative_dir: - relative_path = "/".join([relative_dir, basename]) - else: - relative_path = basename + relative_path = self._get_relative_path(filepath, src_dirpath) resource_files.append(ResourceFile(filepath, relative_path)) continue + filepath = os.path.join(src_dirpath, basename) frame = None udim = None for item in src_basename_regex.finditer(basename): @@ -475,16 +490,14 @@ class ProjectPushRepreItem: filepath = self._clean_path( filepath_template.format(root=self._roots)) - if filepath_template == repre_path: - src_files.append(SourceFile(filepath)) + if filepath_template.lower() == repre_path.lower(): + src_files.append( + SourceFile(repre_path.format(root=self._roots)) + ) else: - dirpath, basename = os.path.split(filepath_template) - relative_dir = dirpath.replace(src_dirpath, "") - if relative_dir: - relative_path = "/".join([relative_dir, basename]) - else: - relative_path = basename - + relative_path = self._get_relative_path( + filepath_template, src_dirpath + ) resource_files.append( ResourceFile(filepath, relative_path) ) From 028c4766227593fa6ad82e22834c145130fe2442 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 19 Jan 2023 18:51:30 +0100 Subject: [PATCH 31/33] Fix adding to set Co-authored-by: Simone Barbieri --- openpype/hosts/unreal/plugins/load/load_layout_existing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/unreal/plugins/load/load_layout_existing.py b/openpype/hosts/unreal/plugins/load/load_layout_existing.py index f24288fecc..092b273ded 100644 --- a/openpype/hosts/unreal/plugins/load/load_layout_existing.py +++ b/openpype/hosts/unreal/plugins/load/load_layout_existing.py @@ -250,7 +250,7 @@ class ExistingLayoutLoader(plugin.Loader): for element in data: repre_id = element.get('representation') if repre_id: - repre_ids.append(repre_id) + repre_ids.add(repre_id) elements.append(element) repre_docs = get_representations( From 9d611b040595f13093c28e24ce49d665518b69aa Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 20 Jan 2023 11:07:27 +0100 Subject: [PATCH 32/33] fix pyproject version for newer poetry version validation --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index f74f40c561..292da7199b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.14.2-nightly.2" # OpenPype +version = "3.14.10" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" From 99152fd35c534b641895403483d7dd4375bc342a Mon Sep 17 00:00:00 2001 From: OpenPype Date: Sat, 21 Jan 2023 03:28:17 +0000 Subject: [PATCH 33/33] [Automated] Bump version --- openpype/version.py | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/version.py b/openpype/version.py index c6becce4fd..f1009a197f 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.14.11-nightly.2" +__version__ = "3.14.11-nightly.3" diff --git a/pyproject.toml b/pyproject.toml index 292da7199b..3b77d3535c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.14.10" # OpenPype +version = "3.14.11-nightly.3" # OpenPype description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License"