From 59b4f5823719e168bdf4d6f0ccb33a380e83405b Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 1 Dec 2022 12:59:49 +0100 Subject: [PATCH 01/35] OP-4504 - added originalBasename and originalDirname to instance Will be used to fill 'source' (and 'online') template. Source template is used to publish in-situ, eg. without copying possibly massive files (as pointcaches etc.) --- .../hosts/traypublisher/plugins/publish/collect_source.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_source.py b/openpype/hosts/traypublisher/plugins/publish/collect_source.py index 6ff22be13a..3d983a89ee 100644 --- a/openpype/hosts/traypublisher/plugins/publish/collect_source.py +++ b/openpype/hosts/traypublisher/plugins/publish/collect_source.py @@ -1,3 +1,5 @@ +import os.path + import pyblish.api @@ -22,3 +24,9 @@ class CollectSource(pyblish.api.ContextPlugin): self.log.info(( "Source of instance \"{}\" was already set to \"{}\"" ).format(instance.data["name"], source)) + + if not instance.data.get("originalBasename"): + instance.data["originalBasename"] = os.path.basename(source) + + if not instance.data.get("originalDirname"): + instance.data["originalDirname"] = os.path.dirname(source) From 5aed3f9bd817b722f529d9289885a7af1cd155e4 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 1 Dec 2022 13:01:54 +0100 Subject: [PATCH 02/35] OP-4504 - added originalDirname to data filling template Will be used to fill 'source' (and 'online') template. --- 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 401270a788..a5df678332 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -535,7 +535,8 @@ class IntegrateAsset(pyblish.api.InstancePlugin): "resolutionHeight": "resolution_height", "fps": "fps", "outputName": "output", - "originalBasename": "originalBasename" + "originalBasename": "originalBasename", + "originalDirname": "originalDirname" }.items(): # Allow to take value from representation # if not found also consider instance.data From 07ee68eea1d13960450a170cc86e8285638d1552 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 1 Dec 2022 13:02:49 +0100 Subject: [PATCH 03/35] OP-4504 - added checks for not overwriting same file Added check for publishing to project folder --- openpype/plugins/publish/integrate.py | 35 ++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index a5df678332..08b59a3574 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -268,6 +268,8 @@ class IntegrateAsset(pyblish.api.InstancePlugin): ) instance.data["versionEntity"] = version + anatomy = instance.context.data["anatomy"] + # Get existing representations (if any) existing_repres_by_name = { repre_doc["name"].lower(): repre_doc @@ -291,6 +293,19 @@ class IntegrateAsset(pyblish.api.InstancePlugin): instance) for src, dst in prepared["transfers"]: + if src == dst: + self.log.info( + "Source '{}' same as destination '{}'. Skipping." + .format(src, dst)) + continue + + if not self._is_path_in_project_roots(anatomy.all_root_paths, + dst): + self.log.warning( + "Destination '{}' is not in project folder. Skipping" + .format(dst)) + continue + # todo: add support for hardlink transfers file_transactions.add(src, dst) @@ -340,7 +355,6 @@ class IntegrateAsset(pyblish.api.InstancePlugin): # Compute the resource file infos once (files belonging to the # version instance instead of an individual representation) so # we can re-use those file infos per representation - anatomy = instance.context.data["anatomy"] resource_file_infos = self.get_files_info(resource_destinations, sites=sites, anatomy=anatomy) @@ -889,3 +903,22 @@ class IntegrateAsset(pyblish.api.InstancePlugin): "hash": source_hash(path), "sites": sites } + + def _is_path_in_project_roots(self, roots, file_path): + """Checks if 'file_path' starts with any of the roots. + + Used to check that published path belongs to project, eg. we are not + trying to publish to local only folder. + Args: + roots (list of RootItem): {ROOT_NAME: ROOT_PATH} + file_path (str) + Returns: + (bool) + """ + file_path = str(file_path).replace("\\", "/") + for root_item in roots.values(): + if file_path.startswith(root_item.clean_value): + return True + + return False + From 0aa0080f1122fdafde739258f127056ab37a7620 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 1 Dec 2022 15:36:33 +0100 Subject: [PATCH 04/35] OP-4504 - fixed check file in project folder --- openpype/plugins/publish/integrate.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index 08b59a3574..44cee664b5 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -299,7 +299,7 @@ class IntegrateAsset(pyblish.api.InstancePlugin): .format(src, dst)) continue - if not self._is_path_in_project_roots(anatomy.all_root_paths, + if not self._is_path_in_project_roots(anatomy.all_root_paths(), dst): self.log.warning( "Destination '{}' is not in project folder. Skipping" @@ -915,9 +915,9 @@ class IntegrateAsset(pyblish.api.InstancePlugin): Returns: (bool) """ - file_path = str(file_path).replace("\\", "/") - for root_item in roots.values(): - if file_path.startswith(root_item.clean_value): + file_path = str(file_path).replace("\\", "/").lower() + for root_item in roots: + if file_path.startswith(root_item.lower()): return True return False From 232743854e06ad68eff562272372177437d4059e Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 1 Dec 2022 15:58:01 +0100 Subject: [PATCH 05/35] OP-4504 - moved path parsing to global plugin CollectSources should run on all hosts whenever some output is expected. It seems to be best location to put parsing of source file, unless we want to create completely new separate plugin. --- openpype/hosts/traypublisher/api/plugin.py | 7 +++++- .../plugins/publish/collect_source.py | 6 ----- .../plugins/publish/collect_resources_path.py | 22 ++++++++++++++++++- 3 files changed, 27 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/traypublisher/api/plugin.py b/openpype/hosts/traypublisher/api/plugin.py index 75930f0f31..d559853fd1 100644 --- a/openpype/hosts/traypublisher/api/plugin.py +++ b/openpype/hosts/traypublisher/api/plugin.py @@ -1,4 +1,4 @@ -from openpype.lib.attribute_definitions import FileDef +from openpype.lib.attribute_definitions import FileDef, BoolDef from openpype.lib.transcoding import IMAGE_EXTENSIONS, VIDEO_EXTENSIONS from openpype.pipeline.create import ( Creator, @@ -129,6 +129,11 @@ class SettingsCreator(TrayPublishCreator): single_item=True, label="Reviewable representations", extensions_label="Single reviewable item" + ), + BoolDef( + "publish_prepared", + default=False, + label="Just publish already prepared" ) ] diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_source.py b/openpype/hosts/traypublisher/plugins/publish/collect_source.py index 3d983a89ee..5121452ca8 100644 --- a/openpype/hosts/traypublisher/plugins/publish/collect_source.py +++ b/openpype/hosts/traypublisher/plugins/publish/collect_source.py @@ -24,9 +24,3 @@ class CollectSource(pyblish.api.ContextPlugin): self.log.info(( "Source of instance \"{}\" was already set to \"{}\"" ).format(instance.data["name"], source)) - - if not instance.data.get("originalBasename"): - instance.data["originalBasename"] = os.path.basename(source) - - if not instance.data.get("originalDirname"): - instance.data["originalDirname"] = os.path.dirname(source) diff --git a/openpype/plugins/publish/collect_resources_path.py b/openpype/plugins/publish/collect_resources_path.py index 00f65b8b67..383cea4a25 100644 --- a/openpype/plugins/publish/collect_resources_path.py +++ b/openpype/plugins/publish/collect_resources_path.py @@ -15,7 +15,11 @@ import pyblish.api class CollectResourcesPath(pyblish.api.InstancePlugin): - """Generate directory path where the files and resources will be stored""" + """Generate directory path where the files and resources will be stored. + + Collects folder name and file name from files, if exists, for in-situ + publishing. + """ label = "Collect Resources Path" order = pyblish.api.CollectorOrder + 0.495 @@ -100,3 +104,19 @@ class CollectResourcesPath(pyblish.api.InstancePlugin): self.log.debug("publishDir: \"{}\"".format(publish_folder)) self.log.debug("resourcesDir: \"{}\"".format(resources_folder)) + + # parse folder name and file name for online and source templates + # currentFile comes from hosts workfiles + # source comes from Publisher + current_file = instance.data.get("currentFile") + source = instance.data.get("source") + source_file = current_file or source + if os.path.exists(source_file): + self.log.debug("Parsing paths for {}".format(source_file)) + if not instance.data.get("originalBasename"): + instance.data["originalBasename"] = \ + os.path.basename(source_file) + + if not instance.data.get("originalDirname"): + instance.data["originalDirname"] = \ + os.path.dirname(source_file) From 499c32110cbf498234032c389585fb02d4aa9d5e Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 1 Dec 2022 16:02:47 +0100 Subject: [PATCH 06/35] OP-4504 - revert of unwanted change --- openpype/hosts/traypublisher/api/plugin.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/openpype/hosts/traypublisher/api/plugin.py b/openpype/hosts/traypublisher/api/plugin.py index d559853fd1..75930f0f31 100644 --- a/openpype/hosts/traypublisher/api/plugin.py +++ b/openpype/hosts/traypublisher/api/plugin.py @@ -1,4 +1,4 @@ -from openpype.lib.attribute_definitions import FileDef, BoolDef +from openpype.lib.attribute_definitions import FileDef from openpype.lib.transcoding import IMAGE_EXTENSIONS, VIDEO_EXTENSIONS from openpype.pipeline.create import ( Creator, @@ -129,11 +129,6 @@ class SettingsCreator(TrayPublishCreator): single_item=True, label="Reviewable representations", extensions_label="Single reviewable item" - ), - BoolDef( - "publish_prepared", - default=False, - label="Just publish already prepared" ) ] From 9c381b4b51e9e017bb4366eb14e647d5f61389a6 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 1 Dec 2022 18:37:47 +0100 Subject: [PATCH 07/35] OP-4504 - added logic for originalDirname into integrate Adding originalDirname to all hosts could be an ordeal, adding logic here is simpler, but might not be best solution. --- openpype/plugins/publish/integrate.py | 35 +++++++++++++++------------ 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index 44cee664b5..b48020860b 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -541,8 +541,25 @@ class IntegrateAsset(pyblish.api.InstancePlugin): template_data["representation"] = repre["name"] template_data["ext"] = repre["ext"] + stagingdir = repre.get("stagingDir") + if not stagingdir: + # Fall back to instance staging dir if not explicitly + # set for representation in the instance + self.log.debug(( + "Representation uses instance staging dir: {}" + ).format(instance_stagingdir)) + stagingdir = instance_stagingdir + + if not stagingdir: + raise KnownPublishError( + "No staging directory set for representation: {}".format(repre) + ) + # optionals # retrieve additional anatomy data from representation if exists + if not instance.data.get("originalDirname"): + instance.data["originalDirname"] = stagingdir + for key, anatomy_key in { # Representation Key: Anatomy data key "resolutionWidth": "resolution_width", @@ -561,20 +578,6 @@ class IntegrateAsset(pyblish.api.InstancePlugin): if value is not None: template_data[anatomy_key] = value - stagingdir = repre.get("stagingDir") - if not stagingdir: - # Fall back to instance staging dir if not explicitly - # set for representation in the instance - self.log.debug(( - "Representation uses instance staging dir: {}" - ).format(instance_stagingdir)) - stagingdir = instance_stagingdir - - if not stagingdir: - raise KnownPublishError( - "No staging directory set for representation: {}".format(repre) - ) - self.log.debug("Anatomy template name: {}".format(template_name)) anatomy = instance.context.data["anatomy"] publish_template_category = anatomy.templates[template_name] @@ -600,6 +603,7 @@ class IntegrateAsset(pyblish.api.InstancePlugin): )) src_collection = src_collections[0] + template_data["originalBasename"] = src_collection.head[:-1] destination_indexes = list(src_collection.indexes) # Use last frame for minimum padding # - that should cover both 'udim' and 'frame' minimum padding @@ -684,7 +688,8 @@ class IntegrateAsset(pyblish.api.InstancePlugin): raise KnownPublishError( "This is a bug. Representation file name is full path" ) - + if not template_data.get("originalBasename"): + template_data["originalBasename"] = fname # Manage anatomy template data template_data.pop("frame", None) if is_udim: From 1106f8cf5ad6d23051643c83fd894846127ec600 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 1 Dec 2022 18:38:24 +0100 Subject: [PATCH 08/35] OP-4504 - check for None --- openpype/plugins/publish/collect_resources_path.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/collect_resources_path.py b/openpype/plugins/publish/collect_resources_path.py index 383cea4a25..0f55e65c9e 100644 --- a/openpype/plugins/publish/collect_resources_path.py +++ b/openpype/plugins/publish/collect_resources_path.py @@ -111,7 +111,7 @@ class CollectResourcesPath(pyblish.api.InstancePlugin): current_file = instance.data.get("currentFile") source = instance.data.get("source") source_file = current_file or source - if os.path.exists(source_file): + if source_file and os.path.exists(source_file): self.log.debug("Parsing paths for {}".format(source_file)) if not instance.data.get("originalBasename"): instance.data["originalBasename"] = \ From b67c8e28a9115c16970596dfceaf2ada1e425ea0 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 1 Dec 2022 19:16:39 +0100 Subject: [PATCH 09/35] OP-4504 - cleanup of logic --- openpype/plugins/publish/integrate.py | 43 ++++++++++++++++++--------- 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index b48020860b..dbad90af93 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -293,17 +293,12 @@ class IntegrateAsset(pyblish.api.InstancePlugin): instance) for src, dst in prepared["transfers"]: - if src == dst: - self.log.info( - "Source '{}' same as destination '{}'. Skipping." - .format(src, dst)) + + if self._are_paths_same(src, dst): continue if not self._is_path_in_project_roots(anatomy.all_root_paths(), dst): - self.log.warning( - "Destination '{}' is not in project folder. Skipping" - .format(dst)) continue # todo: add support for hardlink transfers @@ -316,13 +311,20 @@ class IntegrateAsset(pyblish.api.InstancePlugin): # .ma representation. Those destination paths are pre-defined, etc. # todo: should we move or simplify this logic? resource_destinations = set() - for src, dst in instance.data.get("transfers", []): - file_transactions.add(src, dst, mode=FileTransaction.MODE_COPY) - resource_destinations.add(os.path.abspath(dst)) - for src, dst in instance.data.get("hardlinks", []): - file_transactions.add(src, dst, mode=FileTransaction.MODE_HARDLINK) - resource_destinations.add(os.path.abspath(dst)) + file_copy_modes = [ + ("transfers", FileTransaction.MODE_COPY), + ("hardlinks", FileTransaction.MODE_HARDLINK) + ] + for files_type, copy_mode in zip(*file_copy_modes): # unpack + for src, dst in instance.data.get(files_type, []): + if self._are_paths_same(src, dst): + continue + if not self._is_path_in_project_roots(anatomy.all_root_paths(), + dst): + continue + file_transactions.add(src, dst, mode=copy_mode) + resource_destinations.add(os.path.abspath(dst)) # Bulk write to the database # We write the subset and version to the database before the File @@ -924,6 +926,19 @@ class IntegrateAsset(pyblish.api.InstancePlugin): for root_item in roots: if file_path.startswith(root_item.lower()): return True - + self.log.warning( + "Destination '{}' is not in project folder. Skipping" + .format(file_path)) return False + def _are_paths_same(self, src, dst): + src = str(src).replace("\\", "/").lower() + dst = str(dst).replace("\\", "/").lower() + + same = src == dst + if same: + self.log.info( + "Source '{}' same as destination '{}'. Skipping." + .format(src, dst)) + return same + From ddfaae8a798c9ba7ea12b56c4547f2e3214c8c6c Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 2 Dec 2022 14:28:26 +0100 Subject: [PATCH 10/35] OP-4504 - added source template to defaults Source template is used in-situ publishing, eg. use files at their location, don't copy them anywhere. --- openpype/settings/defaults/project_anatomy/templates.json | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/openpype/settings/defaults/project_anatomy/templates.json b/openpype/settings/defaults/project_anatomy/templates.json index 0ac56a4dad..e4814257bc 100644 --- a/openpype/settings/defaults/project_anatomy/templates.json +++ b/openpype/settings/defaults/project_anatomy/templates.json @@ -53,11 +53,17 @@ "file": "{originalBasename}<.{@frame}><_{udim}>.{ext}", "path": "{@folder}/{@file}" }, + "source": { + "folder": "{originalBasename}<.{@frame}><_{udim}>.{ext}", + "file": "{originalDirname}", + "path": "{@folder}/{@file}" + }, "__dynamic_keys_labels__": { "maya2unreal": "Maya to Unreal", "simpleUnrealTextureHero": "Simple Unreal Texture - Hero", "simpleUnrealTexture": "Simple Unreal Texture", - "online": "online" + "online": "online", + "source": "source" } } } \ No newline at end of file From af3ebebb264ae559d98fa296c18da534d3590c66 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 2 Dec 2022 14:47:01 +0100 Subject: [PATCH 11/35] OP-4504 - Hound --- openpype/hosts/traypublisher/plugins/publish/collect_source.py | 2 -- openpype/plugins/publish/integrate.py | 1 - 2 files changed, 3 deletions(-) diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_source.py b/openpype/hosts/traypublisher/plugins/publish/collect_source.py index 5121452ca8..6ff22be13a 100644 --- a/openpype/hosts/traypublisher/plugins/publish/collect_source.py +++ b/openpype/hosts/traypublisher/plugins/publish/collect_source.py @@ -1,5 +1,3 @@ -import os.path - import pyblish.api diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index dbad90af93..4c26f28862 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -941,4 +941,3 @@ class IntegrateAsset(pyblish.api.InstancePlugin): "Source '{}' same as destination '{}'. Skipping." .format(src, dst)) return same - From d1ee451b9a547d3ff8d480022efe4d020d9f7bc7 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 6 Dec 2022 15:06:21 +0100 Subject: [PATCH 12/35] OP-4504 - update logging --- openpype/plugins/publish/extract_thumbnail_from_source.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_thumbnail_from_source.py b/openpype/plugins/publish/extract_thumbnail_from_source.py index 8da1213807..1165c80318 100644 --- a/openpype/plugins/publish/extract_thumbnail_from_source.py +++ b/openpype/plugins/publish/extract_thumbnail_from_source.py @@ -76,7 +76,7 @@ class ExtractThumbnailFromSource(pyblish.api.InstancePlugin): def _create_thumbnail(self, context, thumbnail_source): if not thumbnail_source: - self.log.debug("Thumbnail source not filled. Skipping.") + self.log.debug("Thumbnail source on context not filled. Skipping.") return if not os.path.exists(thumbnail_source): From c9496bcfe3271631124d557af9166c0a2c3879cc Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 7 Dec 2022 12:22:53 +0100 Subject: [PATCH 13/35] OP-4504 - change boolean test to validation with exception It actually shouldn't allow to publish into non project folder (like artists own c:/ drive). --- openpype/plugins/publish/integrate.py | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index f3683c4214..5e76521550 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -293,11 +293,10 @@ class IntegrateAsset(pyblish.api.InstancePlugin): instance) for src, dst in prepared["transfers"]: - if self._are_paths_same(src, dst): - continue + self._validate_path_in_project_roots(anatomy.all_root_paths(), + dst) - if not self._is_path_in_project_roots(anatomy.all_root_paths(), - dst): + if self._are_paths_same(src, dst): continue # todo: add support for hardlink transfers @@ -317,11 +316,10 @@ class IntegrateAsset(pyblish.api.InstancePlugin): ] for files_type, copy_mode in zip(*file_copy_modes): # unpack for src, dst in instance.data.get(files_type, []): + self._validate_path_in_project_roots(anatomy.all_root_paths(), + dst) if self._are_paths_same(src, dst): continue - if not self._is_path_in_project_roots(anatomy.all_root_paths(), - dst): - continue file_transactions.add(src, dst, mode=copy_mode) resource_destinations.add(os.path.abspath(dst)) @@ -910,7 +908,7 @@ class IntegrateAsset(pyblish.api.InstancePlugin): "sites": sites } - def _is_path_in_project_roots(self, roots, file_path): + def _validate_path_in_project_roots(self, roots, file_path): """Checks if 'file_path' starts with any of the roots. Used to check that published path belongs to project, eg. we are not @@ -918,17 +916,17 @@ class IntegrateAsset(pyblish.api.InstancePlugin): Args: roots (list of RootItem): {ROOT_NAME: ROOT_PATH} file_path (str) - Returns: - (bool) + Raises + (KnownPublishError) """ file_path = str(file_path).replace("\\", "/").lower() for root_item in roots: if file_path.startswith(root_item.lower()): return True - self.log.warning( - "Destination '{}' is not in project folder. Skipping" - .format(file_path)) - return False + raise KnownPublishError(( + "Destination path {} ".format(file_path) + + "must be in project dir" + )) def _are_paths_same(self, src, dst): src = str(src).replace("\\", "/").lower() From 3fabd516ea02b762505377e4d60853e6ebc60c9e Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 7 Dec 2022 12:25:16 +0100 Subject: [PATCH 14/35] OP-4504 - weird unpacking not necessary --- openpype/plugins/publish/integrate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index 5e76521550..6b359af1d2 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -314,7 +314,7 @@ class IntegrateAsset(pyblish.api.InstancePlugin): ("transfers", FileTransaction.MODE_COPY), ("hardlinks", FileTransaction.MODE_HARDLINK) ] - for files_type, copy_mode in zip(*file_copy_modes): # unpack + for files_type, copy_mode in file_copy_modes: for src, dst in instance.data.get(files_type, []): self._validate_path_in_project_roots(anatomy.all_root_paths(), dst) From 0a12a42460cef6770b4761d935cce38a2f6fc1cb Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 7 Dec 2022 12:29:44 +0100 Subject: [PATCH 15/35] OP-4504 - use existing method to check if path in project --- openpype/plugins/publish/integrate.py | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index 6b359af1d2..ce31831f1e 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -293,8 +293,7 @@ class IntegrateAsset(pyblish.api.InstancePlugin): instance) for src, dst in prepared["transfers"]: - self._validate_path_in_project_roots(anatomy.all_root_paths(), - dst) + self._validate_path_in_project_roots(anatomy, dst) if self._are_paths_same(src, dst): continue @@ -316,8 +315,7 @@ class IntegrateAsset(pyblish.api.InstancePlugin): ] for files_type, copy_mode in file_copy_modes: for src, dst in instance.data.get(files_type, []): - self._validate_path_in_project_roots(anatomy.all_root_paths(), - dst) + self._validate_path_in_project_roots(anatomy, dst) if self._are_paths_same(src, dst): continue file_transactions.add(src, dst, mode=copy_mode) @@ -908,25 +906,23 @@ class IntegrateAsset(pyblish.api.InstancePlugin): "sites": sites } - def _validate_path_in_project_roots(self, roots, file_path): + def _validate_path_in_project_roots(self, anatomy, file_path): """Checks if 'file_path' starts with any of the roots. Used to check that published path belongs to project, eg. we are not trying to publish to local only folder. Args: - roots (list of RootItem): {ROOT_NAME: ROOT_PATH} + anatomy (Anatomy) file_path (str) Raises (KnownPublishError) """ - file_path = str(file_path).replace("\\", "/").lower() - for root_item in roots: - if file_path.startswith(root_item.lower()): - return True - raise KnownPublishError(( - "Destination path {} ".format(file_path) + - "must be in project dir" - )) + found, _ = anatomy.find_root_template_from_path(file_path) + if not found: + raise KnownPublishError(( + "Destination path {} ".format(file_path) + + "must be in project dir" + )) def _are_paths_same(self, src, dst): src = str(src).replace("\\", "/").lower() From cccd9c61ddac217d59480c8fafee4eba8b569412 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 7 Dec 2022 12:34:26 +0100 Subject: [PATCH 16/35] OP-4504 - logging message is wrong as it is called on instances also --- openpype/plugins/publish/extract_thumbnail_from_source.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_thumbnail_from_source.py b/openpype/plugins/publish/extract_thumbnail_from_source.py index 084815915f..03df1455e2 100644 --- a/openpype/plugins/publish/extract_thumbnail_from_source.py +++ b/openpype/plugins/publish/extract_thumbnail_from_source.py @@ -77,7 +77,7 @@ class ExtractThumbnailFromSource(pyblish.api.InstancePlugin): def _create_thumbnail(self, context, thumbnail_source): if not thumbnail_source: - self.log.debug("Thumbnail source on context not filled. Skipping.") + self.log.debug("Thumbnail source not filled. Skipping.") return if not os.path.exists(thumbnail_source): From 30eca6f6ca3487bee2b758e85cce6dbeb0d26354 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 7 Dec 2022 14:10:52 +0100 Subject: [PATCH 17/35] OP-4504 - fix default source template --- openpype/settings/defaults/project_anatomy/templates.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/settings/defaults/project_anatomy/templates.json b/openpype/settings/defaults/project_anatomy/templates.json index e4814257bc..32230e0625 100644 --- a/openpype/settings/defaults/project_anatomy/templates.json +++ b/openpype/settings/defaults/project_anatomy/templates.json @@ -54,8 +54,8 @@ "path": "{@folder}/{@file}" }, "source": { - "folder": "{originalBasename}<.{@frame}><_{udim}>.{ext}", - "file": "{originalDirname}", + "folder": "{root[work]}/{originalDirname}", + "file": "{originalBasename}<.{@frame}><_{udim}>.{ext}", "path": "{@folder}/{@file}" }, "__dynamic_keys_labels__": { From 4c53a77a83fd4d79825f11339d7561c23f3797f4 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 7 Dec 2022 17:48:59 +0100 Subject: [PATCH 18/35] OP-4504 - make root comparison case insensitive for windows find_root_template_from_path tries to find root in passed path with case sensitivity, on Windows it doesn't make sense C:// == c://. Keep all other paths case sensitive. --- openpype/pipeline/anatomy.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/openpype/pipeline/anatomy.py b/openpype/pipeline/anatomy.py index 908dc2b187..a50f8f67bb 100644 --- a/openpype/pipeline/anatomy.py +++ b/openpype/pipeline/anatomy.py @@ -1106,17 +1106,21 @@ class RootItem(FormatObject): result = False output = str(path) - root_paths = list(self.cleaned_data.values()) mod_path = self.clean_path(path) - for root_path in root_paths: + for root_os, root_path in self.cleaned_data.items(): # Skip empty paths if not root_path: continue - if mod_path.startswith(root_path): + _mod_path = mod_path # reset to original cleaned value + if root_os == "windows": + root_path = root_path.lower() + _mod_path = _mod_path.lower() + + 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) @@ -1206,6 +1210,7 @@ class Roots: Raises: ValueError: When roots are not entered and can't be loaded. """ + print("!roots::{}".format(roots)) if roots is None: log.debug( "Looking for matching root in path \"{}\".".format(path) @@ -1216,10 +1221,12 @@ class Roots: raise ValueError("Roots are not set. Can't find path.") if isinstance(roots, RootItem): + print("here") return roots.find_root_template_from_path(path) for root_name, _root in roots.items(): - success, result = self.find_root_template_from_path(path, _root) + print("root::{}".format(_root)) + success, result = self.find_root_template_from_path(path.lower(), _root) if success: log.info("Found match in root \"{}\".".format(root_name)) return success, result From 726c8f2cc12f5362fc255f2b269c5b356794b0e6 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 7 Dec 2022 17:51:41 +0100 Subject: [PATCH 19/35] OP-4504 - fix resolving of originalDirname If instance has originalDirname collected, all repres should use this as a target folder (that allows copying transient items from temporary folders into folder where source item comes from). --- openpype/plugins/publish/integrate.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index ce31831f1e..45710c8f41 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -554,17 +554,13 @@ class IntegrateAsset(pyblish.api.InstancePlugin): # optionals # retrieve additional anatomy data from representation if exists - if not instance.data.get("originalDirname"): - instance.data["originalDirname"] = stagingdir - for key, anatomy_key in { # Representation Key: Anatomy data key "resolutionWidth": "resolution_width", "resolutionHeight": "resolution_height", "fps": "fps", "outputName": "output", - "originalBasename": "originalBasename", - "originalDirname": "originalDirname" + "originalBasename": "originalBasename" }.items(): # Allow to take value from representation # if not found also consider instance.data @@ -582,6 +578,14 @@ class IntegrateAsset(pyblish.api.InstancePlugin): is_udim = bool(repre.get("udim")) + # store as originalDirname only original value without project root + # if instance collected originalDirname it should be used for all repre + # useful to storing transient items, eg. thumbnails, from temp to final + original_directory = instance.data.get("originalDirname") or stagingdir + _rootless = self.get_rootless_path(anatomy, original_directory) + without_root = _rootless[_rootless.rfind('}')+2:] + template_data["originalDirname"] = without_root + is_sequence_representation = isinstance(files, (list, tuple)) if is_sequence_representation: # Collection of files (sequence) @@ -685,8 +689,7 @@ class IntegrateAsset(pyblish.api.InstancePlugin): raise KnownPublishError( "This is a bug. Representation file name is full path" ) - if not template_data.get("originalBasename"): - template_data["originalBasename"] = fname + template_data["originalBasename"] = fname # Manage anatomy template data template_data.pop("frame", None) if is_udim: @@ -917,8 +920,8 @@ class IntegrateAsset(pyblish.api.InstancePlugin): Raises (KnownPublishError) """ - found, _ = anatomy.find_root_template_from_path(file_path) - if not found: + path = self.get_rootless_path(anatomy, file_path) + if not path: raise KnownPublishError(( "Destination path {} ".format(file_path) + "must be in project dir" From a1f85d3978e0ad87c13c8f415b9b91a375429a67 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 7 Dec 2022 17:57:10 +0100 Subject: [PATCH 20/35] OP-4504 - use always stagingDir from instance instead of repre Representation stagingDir might be in temporary folders (for thumbnails etc.), use value from instance as a backup instead. --- openpype/plugins/publish/integrate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index 45710c8f41..1b79b5b858 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -581,7 +581,8 @@ class IntegrateAsset(pyblish.api.InstancePlugin): # store as originalDirname only original value without project root # if instance collected originalDirname it should be used for all repre # useful to storing transient items, eg. thumbnails, from temp to final - original_directory = instance.data.get("originalDirname") or stagingdir + original_directory = ( + instance.data.get("originalDirname") or instance_stagingdir) _rootless = self.get_rootless_path(anatomy, original_directory) without_root = _rootless[_rootless.rfind('}')+2:] template_data["originalDirname"] = without_root @@ -694,7 +695,6 @@ class IntegrateAsset(pyblish.api.InstancePlugin): template_data.pop("frame", None) if is_udim: template_data["udim"] = repre["udim"][0] - # Construct destination filepath from template anatomy_filled = anatomy.format(template_data) template_filled = anatomy_filled[template_name]["path"] From d8ed8998b2274699760a88f99bb4d09a97fd1d51 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 7 Dec 2022 18:00:24 +0100 Subject: [PATCH 21/35] OP-4504 - removed unwanted lower Removed logging --- openpype/pipeline/anatomy.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/openpype/pipeline/anatomy.py b/openpype/pipeline/anatomy.py index a50f8f67bb..969e1570fb 100644 --- a/openpype/pipeline/anatomy.py +++ b/openpype/pipeline/anatomy.py @@ -1210,7 +1210,6 @@ class Roots: Raises: ValueError: When roots are not entered and can't be loaded. """ - print("!roots::{}".format(roots)) if roots is None: log.debug( "Looking for matching root in path \"{}\".".format(path) @@ -1221,12 +1220,10 @@ class Roots: raise ValueError("Roots are not set. Can't find path.") if isinstance(roots, RootItem): - print("here") return roots.find_root_template_from_path(path) for root_name, _root in roots.items(): - print("root::{}".format(_root)) - success, result = self.find_root_template_from_path(path.lower(), _root) + success, result = self.find_root_template_from_path(path, _root) if success: log.info("Found match in root \"{}\".".format(root_name)) return success, result From 98f45c24a6a697ae533a48c17cc5ebc9b7930dbf Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 7 Dec 2022 18:02:54 +0100 Subject: [PATCH 22/35] OP-4504 - Hound --- openpype/plugins/publish/integrate.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index 1b79b5b858..7ef279d787 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -584,7 +584,8 @@ class IntegrateAsset(pyblish.api.InstancePlugin): original_directory = ( instance.data.get("originalDirname") or instance_stagingdir) _rootless = self.get_rootless_path(anatomy, original_directory) - without_root = _rootless[_rootless.rfind('}')+2:] + relative_path_start = _rootless.rfind('}') + 2 + without_root = _rootless[relative_path_start:] template_data["originalDirname"] = without_root is_sequence_representation = isinstance(files, (list, tuple)) @@ -923,8 +924,8 @@ class IntegrateAsset(pyblish.api.InstancePlugin): path = self.get_rootless_path(anatomy, file_path) if not path: raise KnownPublishError(( - "Destination path {} ".format(file_path) + - "must be in project dir" + "Destination path {} ".format(file_path) + + "must be in project dir" )) def _are_paths_same(self, src, dst): From fe0336c4359c519dd787aea5f5683e4fa871c171 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 12 Dec 2022 10:34:01 +0100 Subject: [PATCH 23/35] OP-4504 - removed path comparison function Obsolete as it is part of file_transaction file --- openpype/plugins/publish/integrate.py | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index 0a885733bd..041f7b1b19 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -297,9 +297,6 @@ class IntegrateAsset(pyblish.api.InstancePlugin): for src, dst in prepared["transfers"]: self._validate_path_in_project_roots(anatomy, dst) - if self._are_paths_same(src, dst): - continue - # todo: add support for hardlink transfers file_transactions.add(src, dst) @@ -318,8 +315,7 @@ class IntegrateAsset(pyblish.api.InstancePlugin): for files_type, copy_mode in file_copy_modes: for src, dst in instance.data.get(files_type, []): self._validate_path_in_project_roots(anatomy, dst) - if self._are_paths_same(src, dst): - continue + file_transactions.add(src, dst, mode=copy_mode) resource_destinations.add(os.path.abspath(dst)) @@ -929,14 +925,3 @@ class IntegrateAsset(pyblish.api.InstancePlugin): "Destination path {} ".format(file_path) + "must be in project dir" )) - - def _are_paths_same(self, src, dst): - src = str(src).replace("\\", "/").lower() - dst = str(dst).replace("\\", "/").lower() - - same = src == dst - if same: - self.log.info( - "Source '{}' same as destination '{}'. Skipping." - .format(src, dst)) - return same From d18fc94c01bb2044ef209658f4cab32f8cd87ee8 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 12 Dec 2022 12:41:26 +0100 Subject: [PATCH 24/35] OP-4504 - fix for deadline publishing --- openpype/plugins/publish/integrate.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index 041f7b1b19..ce37a53c65 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -581,10 +581,11 @@ class IntegrateAsset(pyblish.api.InstancePlugin): # useful to storing transient items, eg. thumbnails, from temp to final original_directory = ( instance.data.get("originalDirname") or instance_stagingdir) - _rootless = self.get_rootless_path(anatomy, original_directory) - relative_path_start = _rootless.rfind('}') + 2 - without_root = _rootless[relative_path_start:] - template_data["originalDirname"] = without_root + if original_directory: + _rootless = self.get_rootless_path(anatomy, original_directory) + relative_path_start = _rootless.rfind('}') + 2 + without_root = _rootless[relative_path_start:] + template_data["originalDirname"] = without_root is_sequence_representation = isinstance(files, (list, tuple)) if is_sequence_representation: From 8a267f6c340bc31d4c15dd5d90bc7c534d1a03af Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 12 Dec 2022 16:18:25 +0100 Subject: [PATCH 25/35] OP-4504 - handle originalDirname only if in template --- openpype/plugins/publish/integrate.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index ce37a53c65..4692cefe4d 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -295,8 +295,6 @@ class IntegrateAsset(pyblish.api.InstancePlugin): instance) for src, dst in prepared["transfers"]: - self._validate_path_in_project_roots(anatomy, dst) - # todo: add support for hardlink transfers file_transactions.add(src, dst) @@ -576,13 +574,21 @@ class IntegrateAsset(pyblish.api.InstancePlugin): is_udim = bool(repre.get("udim")) - # store as originalDirname only original value without project root - # if instance collected originalDirname it should be used for all repre - # useful to storing transient items, eg. thumbnails, from temp to final - original_directory = ( - instance.data.get("originalDirname") or instance_stagingdir) - if original_directory: + # handle publish in place + if "originalDirname" in template: + # store as originalDirname only original value without project root + # if instance collected originalDirname is present, it should be + # used for all represe + # from temp to final + original_directory = ( + instance.data.get("originalDirname") or instance_stagingdir) + _rootless = self.get_rootless_path(anatomy, original_directory) + if _rootless == original_directory: + raise KnownPublishError(( + "Destination path '{}' ".format(original_directory) + + "must be in project dir" + )) relative_path_start = _rootless.rfind('}') + 2 without_root = _rootless[relative_path_start:] template_data["originalDirname"] = without_root @@ -923,6 +929,6 @@ class IntegrateAsset(pyblish.api.InstancePlugin): path = self.get_rootless_path(anatomy, file_path) if not path: raise KnownPublishError(( - "Destination path {} ".format(file_path) + + "Destination path '{}' ".format(file_path) + "must be in project dir" )) From 9bf00f9cfebb4c5b0ec6586672308d1a74cd6376 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 14 Dec 2022 14:41:52 +0100 Subject: [PATCH 26/35] OP-4504 - added collector for originalDirname --- .../plugins/publish/collect_resources_path.py | 16 ------- .../publish/collect_source_for_source.py | 43 +++++++++++++++++++ 2 files changed, 43 insertions(+), 16 deletions(-) create mode 100644 openpype/plugins/publish/collect_source_for_source.py diff --git a/openpype/plugins/publish/collect_resources_path.py b/openpype/plugins/publish/collect_resources_path.py index ea86ab93b4..dcd80fbbdf 100644 --- a/openpype/plugins/publish/collect_resources_path.py +++ b/openpype/plugins/publish/collect_resources_path.py @@ -106,19 +106,3 @@ class CollectResourcesPath(pyblish.api.InstancePlugin): self.log.debug("publishDir: \"{}\"".format(publish_folder)) self.log.debug("resourcesDir: \"{}\"".format(resources_folder)) - - # parse folder name and file name for online and source templates - # currentFile comes from hosts workfiles - # source comes from Publisher - current_file = instance.data.get("currentFile") - source = instance.data.get("source") - source_file = current_file or source - if source_file and os.path.exists(source_file): - self.log.debug("Parsing paths for {}".format(source_file)) - if not instance.data.get("originalBasename"): - instance.data["originalBasename"] = \ - os.path.basename(source_file) - - if not instance.data.get("originalDirname"): - instance.data["originalDirname"] = \ - os.path.dirname(source_file) diff --git a/openpype/plugins/publish/collect_source_for_source.py b/openpype/plugins/publish/collect_source_for_source.py new file mode 100644 index 0000000000..345daa6fe8 --- /dev/null +++ b/openpype/plugins/publish/collect_source_for_source.py @@ -0,0 +1,43 @@ +""" +Requires: + instance -> currentFile + instance -> source + +Provides: + instance -> originalBasename + instance -> originalDirname +""" + +import os +import copy + +import pyblish.api + + +class CollectSourceForSource(pyblish.api.InstancePlugin): + """Collects source location of file for instance. + + Used for 'source' template name which handles in place publishing. + For this kind of publishing files are present with correct file name + pattern and correct location. + """ + + label = "Collect Source" + order = pyblish.api.CollectorOrder + 0.495 + + def process(self, instance): + # parse folder name and file name for online and source templates + # currentFile comes from hosts workfiles + # source comes from Publisher + current_file = instance.data.get("currentFile") + source = instance.data.get("source") + source_file = current_file or source + if source_file and os.path.exists(source_file): + self.log.debug("Parsing paths for {}".format(source_file)) + if not instance.data.get("originalBasename"): + instance.data["originalBasename"] = \ + os.path.basename(source_file) + + if not instance.data.get("originalDirname"): + instance.data["originalDirname"] = \ + os.path.dirname(source_file) From 3cf3fd65b1fe61ee339caf5fc69352e3c9ab1851 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 14 Dec 2022 14:42:51 +0100 Subject: [PATCH 27/35] OP-4504 - added validator for source template Check is output template is 'source' if originalDirname is collected and if it is inside of project --- .../plugins/publish/validate_publish_dir.py | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 openpype/plugins/publish/validate_publish_dir.py diff --git a/openpype/plugins/publish/validate_publish_dir.py b/openpype/plugins/publish/validate_publish_dir.py new file mode 100644 index 0000000000..03fc47347d --- /dev/null +++ b/openpype/plugins/publish/validate_publish_dir.py @@ -0,0 +1,69 @@ +import pyblish.api +from openpype.pipeline.publish import ValidateContentsOrder +from openpype.pipeline.publish import ( + KnownPublishError, + get_publish_template_name, +) + + +class ValidatePublishDir(pyblish.api.InstancePlugin): + """Validates if 'publishDir' is a project directory + + 'publishDir' is collected based on publish templates. In specific cases + ('source' template) source folder of items is used as a 'publishDir', this + validates if it is inside any project dir for the project. + (eg. files are not published from local folder, unaccessible for studio' + + """ + + order = ValidateContentsOrder + label = "Validate publish dir" + + checked_template_names = ["source"] + # validate instances might have interim family, needs to be mapped to final + family_mapping = { + "renderLayer": "render", + "renderLocal": "render" + } + + def process(self, instance): + + template_name = self._get_template_name_from_instance(instance) + + if template_name not in self.checked_template_names: + return + + original_dirname = instance.data.get("originalDirname") + if not original_dirname: + raise KnownPublishError("Instance meant for in place publishing." + " Its 'originalDirname' must be collected." + " Contact OP developer to modify collector" + ) + + anatomy = instance.context.data["anatomy"] + + success, _ = anatomy.find_root_template_from_path(original_dirname) + self.log.info(_) + if not success: + raise KnownPublishError( + "Path '{}' not in project folder.".format(original_dirname) + + " Please publish from inside of project folder." + ) + + def _get_template_name_from_instance(self, instance): + project_name = instance.context.data["projectName"] + host_name = instance.context.data["hostName"] + anatomy_data = instance.data["anatomyData"] + family = anatomy_data["family"] + family = self.family_mapping.get("family") or family + task_info = anatomy_data.get("task") or {} + + return get_publish_template_name( + project_name, + host_name, + family, + task_name=task_info.get("name"), + task_type=task_info.get("type"), + project_settings=instance.context.data["project_settings"], + logger=self.log + ) From d9a7d5cb802d1aec7ab71db7f576b2dde09c5f15 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 14 Dec 2022 14:46:01 +0100 Subject: [PATCH 28/35] OP-4504 - Hound --- openpype/plugins/publish/collect_source_for_source.py | 1 - openpype/plugins/publish/validate_publish_dir.py | 8 ++++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/openpype/plugins/publish/collect_source_for_source.py b/openpype/plugins/publish/collect_source_for_source.py index 345daa6fe8..fd08d28c74 100644 --- a/openpype/plugins/publish/collect_source_for_source.py +++ b/openpype/plugins/publish/collect_source_for_source.py @@ -9,7 +9,6 @@ Provides: """ import os -import copy import pyblish.api diff --git a/openpype/plugins/publish/validate_publish_dir.py b/openpype/plugins/publish/validate_publish_dir.py index 03fc47347d..eabf4810f3 100644 --- a/openpype/plugins/publish/validate_publish_dir.py +++ b/openpype/plugins/publish/validate_publish_dir.py @@ -35,10 +35,10 @@ class ValidatePublishDir(pyblish.api.InstancePlugin): original_dirname = instance.data.get("originalDirname") if not original_dirname: - raise KnownPublishError("Instance meant for in place publishing." - " Its 'originalDirname' must be collected." - " Contact OP developer to modify collector" - ) + raise KnownPublishError( + "Instance meant for in place publishing." + " Its 'originalDirname' must be collected." + " Contact OP developer to modify collector.") anatomy = instance.context.data["anatomy"] From a3969f8d1a6c901cfa2abb6dd8871527004724cf Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 15 Dec 2022 12:51:11 +0100 Subject: [PATCH 29/35] Update openpype/plugins/publish/validate_publish_dir.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ondřej Samohel <33513211+antirotor@users.noreply.github.com> --- openpype/plugins/publish/validate_publish_dir.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/validate_publish_dir.py b/openpype/plugins/publish/validate_publish_dir.py index eabf4810f3..e375fadf49 100644 --- a/openpype/plugins/publish/validate_publish_dir.py +++ b/openpype/plugins/publish/validate_publish_dir.py @@ -45,7 +45,7 @@ class ValidatePublishDir(pyblish.api.InstancePlugin): success, _ = anatomy.find_root_template_from_path(original_dirname) self.log.info(_) if not success: - raise KnownPublishError( + raise PublishValidationError( "Path '{}' not in project folder.".format(original_dirname) + " Please publish from inside of project folder." ) From ee58c0ce48dc7230a18a575e7089aeaf20616eaf Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 15 Dec 2022 13:15:14 +0100 Subject: [PATCH 30/35] OP-4504 - changed to XMLPublishError --- .../publish/help/validate_publish_dir.xml | 31 +++++++++++++++++++ .../plugins/publish/validate_publish_dir.py | 21 ++++++++----- 2 files changed, 44 insertions(+), 8 deletions(-) create mode 100644 openpype/plugins/publish/help/validate_publish_dir.xml diff --git a/openpype/plugins/publish/help/validate_publish_dir.xml b/openpype/plugins/publish/help/validate_publish_dir.xml new file mode 100644 index 0000000000..9f62b264bf --- /dev/null +++ b/openpype/plugins/publish/help/validate_publish_dir.xml @@ -0,0 +1,31 @@ + + + +Source directory not collected + +## Source directory not collected + +Instance is marked for in place publishing. Its 'originalDirname' must be collected. Contact OP developer to modify collector. + + + +### __Detailed Info__ (optional) + +In place publishing uses source directory and file name in resulting path and file name of published item. For this instance + all required metadata weren't filled. This is not recoverable error, unless instance itself is removed. + Collector for this instance must be updated for instance to be published. + + + +Source file not in project dir + +## Source file not in project dir + +Path '{original_dirname}' not in project folder. Please publish from inside of project folder. + +### How to repair? + +Restart publish after you moved source file into project directory. + + + \ No newline at end of file diff --git a/openpype/plugins/publish/validate_publish_dir.py b/openpype/plugins/publish/validate_publish_dir.py index e375fadf49..2f41127548 100644 --- a/openpype/plugins/publish/validate_publish_dir.py +++ b/openpype/plugins/publish/validate_publish_dir.py @@ -1,7 +1,7 @@ import pyblish.api from openpype.pipeline.publish import ValidateContentsOrder from openpype.pipeline.publish import ( - KnownPublishError, + PublishXmlValidationError, get_publish_template_name, ) @@ -35,20 +35,25 @@ class ValidatePublishDir(pyblish.api.InstancePlugin): original_dirname = instance.data.get("originalDirname") if not original_dirname: - raise KnownPublishError( + raise PublishXmlValidationError( + self, "Instance meant for in place publishing." " Its 'originalDirname' must be collected." - " Contact OP developer to modify collector.") + " Contact OP developer to modify collector." + ) anatomy = instance.context.data["anatomy"] success, _ = anatomy.find_root_template_from_path(original_dirname) - self.log.info(_) + + formatting_data = { + "original_dirname": original_dirname, + } + msg = "Path '{}' not in project folder.".format(original_dirname) + \ + " Please publish from inside of project folder." if not success: - raise PublishValidationError( - "Path '{}' not in project folder.".format(original_dirname) + - " Please publish from inside of project folder." - ) + raise PublishXmlValidationError(self, msg, key="not_in_dir", + formatting_data=formatting_data) def _get_template_name_from_instance(self, instance): project_name = instance.context.data["projectName"] From da274a7c8db1df524efd29a8a00cd54d2f30022a Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 15 Dec 2022 16:28:49 +0100 Subject: [PATCH 31/35] OP-4504 - safer comparison of two paths --- openpype/lib/file_transaction.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/openpype/lib/file_transaction.py b/openpype/lib/file_transaction.py index f265b8815c..4ebede0174 100644 --- a/openpype/lib/file_transaction.py +++ b/openpype/lib/file_transaction.py @@ -92,7 +92,9 @@ class FileTransaction(object): def process(self): # Backup any existing files for dst, (src, _) in self._transfers.items(): - if dst == src or not os.path.exists(dst): + self.log.debug("Checking file ... {} -> {}".format(src, dst)) + path_same = self._same_paths(src, dst) + if path_same or not os.path.exists(dst): continue # Backup original file @@ -105,7 +107,8 @@ class FileTransaction(object): # Copy the files to transfer for dst, (src, opts) in self._transfers.items(): - if dst == src: + path_same = self._same_paths(src, dst) + if path_same: self.log.debug( "Source and destionation are same files {} -> {}".format( src, dst)) @@ -182,3 +185,10 @@ class FileTransaction(object): else: self.log.critical("An unexpected error occurred.") six.reraise(*sys.exc_info()) + + def _same_paths(self, src, dst): + # handles same paths but with C:/project vs c:/project + if os.path.exists(src) and os.path.exists(dst): + return os.path.samefile(src, dst) + + return False \ No newline at end of file From b6873a063eaac8f8e3d1463cd0f82681ae3c7f6e Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 15 Dec 2022 16:29:56 +0100 Subject: [PATCH 32/35] OP-4504 - added '_thumb' suffix to thumbnail Without it thumbnail would overwrite source file --- openpype/plugins/publish/extract_thumbnail.py | 2 +- openpype/plugins/publish/extract_thumbnail_from_source.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/plugins/publish/extract_thumbnail.py b/openpype/plugins/publish/extract_thumbnail.py index 14b43beae8..a3c428fe97 100644 --- a/openpype/plugins/publish/extract_thumbnail.py +++ b/openpype/plugins/publish/extract_thumbnail.py @@ -91,7 +91,7 @@ class ExtractThumbnail(pyblish.api.InstancePlugin): full_input_path = os.path.join(src_staging, input_file) self.log.info("input {}".format(full_input_path)) filename = os.path.splitext(input_file)[0] - jpeg_file = filename + ".jpg" + jpeg_file = filename + "_thumb.jpg" full_output_path = os.path.join(dst_staging, jpeg_file) if oiio_supported: diff --git a/openpype/plugins/publish/extract_thumbnail_from_source.py b/openpype/plugins/publish/extract_thumbnail_from_source.py index 03df1455e2..a92f762cde 100644 --- a/openpype/plugins/publish/extract_thumbnail_from_source.py +++ b/openpype/plugins/publish/extract_thumbnail_from_source.py @@ -100,7 +100,7 @@ class ExtractThumbnailFromSource(pyblish.api.InstancePlugin): self.log.info("Thumbnail source: {}".format(thumbnail_source)) src_basename = os.path.basename(thumbnail_source) - dst_filename = os.path.splitext(src_basename)[0] + ".jpg" + dst_filename = os.path.splitext(src_basename)[0] + "_thumb.jpg" full_output_path = os.path.join(dst_staging, dst_filename) if oiio_supported: From e3866dff5abd2022d37d90f0da4eb634e35c995e Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 15 Dec 2022 16:37:45 +0100 Subject: [PATCH 33/35] OP-4504 - remove check for existence For sequences source contains `%d` placeholder --- openpype/plugins/publish/collect_source_for_source.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/collect_source_for_source.py b/openpype/plugins/publish/collect_source_for_source.py index fd08d28c74..aa94238b4f 100644 --- a/openpype/plugins/publish/collect_source_for_source.py +++ b/openpype/plugins/publish/collect_source_for_source.py @@ -31,7 +31,7 @@ class CollectSourceForSource(pyblish.api.InstancePlugin): current_file = instance.data.get("currentFile") source = instance.data.get("source") source_file = current_file or source - if source_file and os.path.exists(source_file): + if source_file: self.log.debug("Parsing paths for {}".format(source_file)) if not instance.data.get("originalBasename"): instance.data["originalBasename"] = \ From 3c09dfc80e003021b930b28288664eedff82002e Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 15 Dec 2022 16:38:19 +0100 Subject: [PATCH 34/35] OP-4504 - remove extension from originalBasename It would produce weird concatenation of extensions. --- openpype/plugins/publish/integrate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/integrate.py b/openpype/plugins/publish/integrate.py index 4692cefe4d..94789bb778 100644 --- a/openpype/plugins/publish/integrate.py +++ b/openpype/plugins/publish/integrate.py @@ -696,7 +696,7 @@ class IntegrateAsset(pyblish.api.InstancePlugin): raise KnownPublishError( "This is a bug. Representation file name is full path" ) - template_data["originalBasename"] = fname + template_data["originalBasename"], _ = os.path.splitext(fname) # Manage anatomy template data template_data.pop("frame", None) if is_udim: From 5d8ddb6e55cca8a3bc7bb058bc87080f329fc156 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 19 Dec 2022 12:15:09 +0100 Subject: [PATCH 35/35] OP-4504 - added explicit check Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/lib/file_transaction.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/lib/file_transaction.py b/openpype/lib/file_transaction.py index 4ebede0174..cba361a8d4 100644 --- a/openpype/lib/file_transaction.py +++ b/openpype/lib/file_transaction.py @@ -191,4 +191,4 @@ class FileTransaction(object): if os.path.exists(src) and os.path.exists(dst): return os.path.samefile(src, dst) - return False \ No newline at end of file + return src == dst