From 58808104280eba59fed182af0849a66e366450f3 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 16 Jan 2023 13:44:57 +0100 Subject: [PATCH 001/184] OP-4642 - refactor weird assignment Co-authored-by: Roy Nieterau --- openpype/plugins/publish/extract_color_transcode.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 62cf8f0dee..835b64b685 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -138,8 +138,7 @@ class ExtractColorTranscode(publish.Extractor): new_file_name = '{}.{}'.format(file_name, output_extension) renamed_files.append(new_file_name) - files_to_convert = renamed_files - return files_to_convert + return renamed_files def _get_profile(self, instance): """Returns profile if and how repre should be color transcoded.""" From b35ee739bcfcc158daa9ef2d9261f3bda328d4cc Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 Jan 2023 11:57:51 +0100 Subject: [PATCH 002/184] OP-4643 - added Settings for ExtractColorTranscode --- .../defaults/project_settings/global.json | 4 + .../schemas/schema_global_publish.json | 73 +++++++++++++++++++ 2 files changed, 77 insertions(+) diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index 0e078dc157..474e878cb6 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -68,6 +68,10 @@ "output": [] } }, + "ExtractColorTranscode": { + "enabled": true, + "profiles": [] + }, "ExtractReview": { "enabled": true, "profiles": [ diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json index 5388d04bc9..46ae6ba554 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json @@ -197,6 +197,79 @@ } ] }, + { + "type": "dict", + "collapsible": true, + "key": "ExtractColorTranscode", + "label": "ExtractColorTranscode", + "checkbox_key": "enabled", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "list", + "key": "profiles", + "label": "Profiles", + "object_type": { + "type": "dict", + "children": [ + { + "key": "families", + "label": "Families", + "type": "list", + "object_type": "text" + }, + { + "key": "hosts", + "label": "Host names", + "type": "hosts-enum", + "multiselection": true + }, + { + "key": "task_types", + "label": "Task types", + "type": "task-types-enum" + }, + { + "key": "task_names", + "label": "Task names", + "type": "list", + "object_type": "text" + }, + { + "key": "subsets", + "label": "Subset names", + "type": "list", + "object_type": "text" + }, + { + "type": "splitter" + }, + { + "key": "ext", + "label": "Output extension", + "type": "text" + }, + { + "key": "output_colorspace", + "label": "Output colorspace", + "type": "text" + }, + { + "key": "custom_tags", + "label": "Custom Tags", + "type": "list", + "object_type": "text" + } + ] + } + } + ] + }, { "type": "dict", "collapsible": true, From 687d6dbf2811db3b8c6f46ee10726b26c1120772 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 Jan 2023 11:58:51 +0100 Subject: [PATCH 003/184] OP-4643 - added ExtractColorTranscode Added method to convert from one colorspace to another to transcoding lib --- openpype/lib/transcoding.py | 53 ++++++++ .../publish/extract_color_transcode.py | 124 ++++++++++++++++++ 2 files changed, 177 insertions(+) create mode 100644 openpype/plugins/publish/extract_color_transcode.py diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index 57279d0380..6899811ed5 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -1037,3 +1037,56 @@ def convert_ffprobe_fps_to_float(value): if divisor == 0.0: return 0.0 return dividend / divisor + + +def convert_colorspace_for_input_paths( + input_paths, + output_dir, + source_color_space, + target_color_space, + logger=None +): + """Convert source files from one color space to another. + + Filenames of input files are kept so make sure that output directory + is not the same directory as input files have. + - This way it can handle gaps and can keep input filenames without handling + frame template + + Args: + input_paths (str): Paths that should be converted. It is expected that + contains single file or image sequence of samy type. + output_dir (str): Path to directory where output will be rendered. + Must not be same as input's directory. + source_color_space (str): ocio valid color space of source files + target_color_space (str): ocio valid target color space + logger (logging.Logger): Logger used for logging. + + """ + if logger is None: + logger = logging.getLogger(__name__) + + input_arg = "-i" + oiio_cmd = [ + get_oiio_tools_path(), + + # Don't add any additional attributes + "--nosoftwareattrib", + "--colorconvert", source_color_space, target_color_space + ] + for input_path in input_paths: + # Prepare subprocess arguments + + oiio_cmd.extend([ + input_arg, input_path, + ]) + + # Add last argument - path to output + base_filename = os.path.basename(input_path) + output_path = os.path.join(output_dir, base_filename) + oiio_cmd.extend([ + "-o", output_path + ]) + + logger.debug("Conversion command: {}".format(" ".join(oiio_cmd))) + run_subprocess(oiio_cmd, logger=logger) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py new file mode 100644 index 0000000000..58508ab18f --- /dev/null +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -0,0 +1,124 @@ +import pyblish.api + +from openpype.pipeline import publish +from openpype.lib import ( + + is_oiio_supported, +) + +from openpype.lib.transcoding import ( + convert_colorspace_for_input_paths, + get_transcode_temp_directory, +) + +from openpype.lib.profiles_filtering import filter_profiles + + +class ExtractColorTranscode(publish.Extractor): + """ + Extractor to convert colors from one colorspace to different. + """ + + label = "Transcode color spaces" + order = pyblish.api.ExtractorOrder + 0.01 + + optional = True + + # Configurable by Settings + profiles = None + options = None + + def process(self, instance): + if not self.profiles: + self.log.warning("No profiles present for create burnin") + return + + if "representations" not in instance.data: + self.log.warning("No representations, skipping.") + return + + if not is_oiio_supported(): + self.log.warning("OIIO not supported, no transcoding possible.") + return + + colorspace_data = instance.data.get("colorspaceData") + if not colorspace_data: + # TODO get_colorspace ?? + self.log.warning("Instance has not colorspace data, skipping") + return + source_color_space = colorspace_data["colorspace"] + + host_name = instance.context.data["hostName"] + family = instance.data["family"] + task_data = instance.data["anatomyData"].get("task", {}) + task_name = task_data.get("name") + task_type = task_data.get("type") + subset = instance.data["subset"] + + filtering_criteria = { + "hosts": host_name, + "families": family, + "task_names": task_name, + "task_types": task_type, + "subset": subset + } + profile = filter_profiles(self.profiles, filtering_criteria, + logger=self.log) + + if not profile: + self.log.info(( + "Skipped instance. None of profiles in presets are for" + " Host: \"{}\" | Families: \"{}\" | Task \"{}\"" + " | Task type \"{}\" | Subset \"{}\" " + ).format(host_name, family, task_name, task_type, subset)) + return + + self.log.debug("profile: {}".format(profile)) + + target_colorspace = profile["output_colorspace"] + if not target_colorspace: + raise RuntimeError("Target colorspace must be set") + + repres = instance.data.get("representations") or [] + for idx, repre in enumerate(repres): + self.log.debug("repre ({}): `{}`".format(idx + 1, repre["name"])) + if not self.repre_is_valid(repre): + continue + + new_staging_dir = get_transcode_temp_directory() + repre["stagingDir"] = new_staging_dir + files_to_remove = repre["files"] + if not isinstance(files_to_remove, list): + files_to_remove = [files_to_remove] + instance.context.data["cleanupFullPaths"].extend(files_to_remove) + + convert_colorspace_for_input_paths( + repre["files"], + new_staging_dir, + source_color_space, + target_colorspace, + self.log + ) + + def repre_is_valid(self, repre): + """Validation if representation should be processed. + + Args: + repre (dict): Representation which should be checked. + + Returns: + bool: False if can't be processed else True. + """ + + if "review" not in (repre.get("tags") or []): + self.log.info(( + "Representation \"{}\" don't have \"review\" tag. Skipped." + ).format(repre["name"])) + return False + + if not repre.get("files"): + self.log.warning(( + "Representation \"{}\" have empty files. Skipped." + ).format(repre["name"])) + return False + return True From e36cf8004706a7d70133d79ea5e0ddd1f208f4c3 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 Jan 2023 12:05:57 +0100 Subject: [PATCH 004/184] OP-4643 - extractor must run just before ExtractReview Nuke render local is set to 0.01 --- openpype/plugins/publish/extract_color_transcode.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 58508ab18f..5163cd4045 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -20,7 +20,7 @@ class ExtractColorTranscode(publish.Extractor): """ label = "Transcode color spaces" - order = pyblish.api.ExtractorOrder + 0.01 + order = pyblish.api.ExtractorOrder + 0.019 optional = True From cb27e5a4d6b512dfa193b031ab266340975245f0 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 Jan 2023 14:03:22 +0100 Subject: [PATCH 005/184] OP-4643 - fix for full file paths --- .../publish/extract_color_transcode.py | 34 ++++++++++++------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 5163cd4045..6ad7599f2c 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -1,3 +1,4 @@ +import os import pyblish.api from openpype.pipeline import publish @@ -41,13 +42,6 @@ class ExtractColorTranscode(publish.Extractor): self.log.warning("OIIO not supported, no transcoding possible.") return - colorspace_data = instance.data.get("colorspaceData") - if not colorspace_data: - # TODO get_colorspace ?? - self.log.warning("Instance has not colorspace data, skipping") - return - source_color_space = colorspace_data["colorspace"] - host_name = instance.context.data["hostName"] family = instance.data["family"] task_data = instance.data["anatomyData"].get("task", {}) @@ -82,18 +76,32 @@ class ExtractColorTranscode(publish.Extractor): repres = instance.data.get("representations") or [] for idx, repre in enumerate(repres): self.log.debug("repre ({}): `{}`".format(idx + 1, repre["name"])) - if not self.repre_is_valid(repre): + # if not self.repre_is_valid(repre): + # continue + + colorspace_data = repre.get("colorspaceData") + if not colorspace_data: + # TODO get_colorspace ?? + self.log.warning("Repre has not colorspace data, skipping") + continue + source_color_space = colorspace_data["colorspace"] + config_path = colorspace_data.get("configData", {}).get("path") + if not os.path.exists(config_path): + self.log.warning("Config file doesn't exist, skipping") continue new_staging_dir = get_transcode_temp_directory() + original_staging_dir = repre["stagingDir"] repre["stagingDir"] = new_staging_dir - files_to_remove = repre["files"] - if not isinstance(files_to_remove, list): - files_to_remove = [files_to_remove] - instance.context.data["cleanupFullPaths"].extend(files_to_remove) + files_to_convert = repre["files"] + if not isinstance(files_to_convert, list): + files_to_convert = [files_to_convert] + files_to_convert = [os.path.join(original_staging_dir, path) + for path in files_to_convert] + instance.context.data["cleanupFullPaths"].extend(files_to_convert) convert_colorspace_for_input_paths( - repre["files"], + files_to_convert, new_staging_dir, source_color_space, target_colorspace, From 7201c57ddc71cac47f6ea38fdbf7d6c1d2d03577 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 Jan 2023 14:04:06 +0100 Subject: [PATCH 006/184] OP-4643 - pass path for ocio config --- openpype/lib/transcoding.py | 3 +++ openpype/plugins/publish/extract_color_transcode.py | 1 + 2 files changed, 4 insertions(+) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index 6899811ed5..792e8ddd1e 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -1042,6 +1042,7 @@ def convert_ffprobe_fps_to_float(value): def convert_colorspace_for_input_paths( input_paths, output_dir, + config_path, source_color_space, target_color_space, logger=None @@ -1058,6 +1059,7 @@ def convert_colorspace_for_input_paths( contains single file or image sequence of samy type. output_dir (str): Path to directory where output will be rendered. Must not be same as input's directory. + config_path (str): path to OCIO config file source_color_space (str): ocio valid color space of source files target_color_space (str): ocio valid target color space logger (logging.Logger): Logger used for logging. @@ -1072,6 +1074,7 @@ def convert_colorspace_for_input_paths( # Don't add any additional attributes "--nosoftwareattrib", + "--colorconfig", config_path, "--colorconvert", source_color_space, target_color_space ] for input_path in input_paths: diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 6ad7599f2c..fdb13a47e8 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -103,6 +103,7 @@ class ExtractColorTranscode(publish.Extractor): convert_colorspace_for_input_paths( files_to_convert, new_staging_dir, + config_path, source_color_space, target_colorspace, self.log From dc796b71b4c50d2bb0240667cf9d0f19d85ad5dc Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 Jan 2023 14:15:33 +0100 Subject: [PATCH 007/184] OP-4643 - add custom_tags --- openpype/plugins/publish/extract_color_transcode.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index fdb13a47e8..ab932b2476 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -72,6 +72,7 @@ class ExtractColorTranscode(publish.Extractor): target_colorspace = profile["output_colorspace"] if not target_colorspace: raise RuntimeError("Target colorspace must be set") + custom_tags = profile["custom_tags"] repres = instance.data.get("representations") or [] for idx, repre in enumerate(repres): @@ -109,6 +110,11 @@ class ExtractColorTranscode(publish.Extractor): self.log ) + if custom_tags: + if not repre.get("custom_tags"): + repre["custom_tags"] = [] + repre["custom_tags"].extend(custom_tags) + def repre_is_valid(self, repre): """Validation if representation should be processed. From 50ff228070729e87dc0a846aa82461fdcc8b08b7 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 Jan 2023 14:18:38 +0100 Subject: [PATCH 008/184] OP-4643 - added docstring --- openpype/plugins/publish/extract_color_transcode.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index ab932b2476..88e2eed90f 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -18,6 +18,17 @@ from openpype.lib.profiles_filtering import filter_profiles class ExtractColorTranscode(publish.Extractor): """ Extractor to convert colors from one colorspace to different. + + Expects "colorspaceData" on representation. This dictionary is collected + previously and denotes that representation files should be converted. + This dict contains source colorspace information, collected by hosts. + + Target colorspace is selected by profiles in the Settings, based on: + - families + - host + - task types + - task names + - subset names """ label = "Transcode color spaces" From 7a162f9dba79e1c829b9a01338917033607ec074 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 Jan 2023 18:15:44 +0100 Subject: [PATCH 009/184] OP-4643 - updated Settings schema --- .../schemas/schema_global_publish.json | 48 +++++++++++++------ 1 file changed, 34 insertions(+), 14 deletions(-) diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json index 46ae6ba554..c2c911d7d6 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json @@ -246,24 +246,44 @@ "type": "list", "object_type": "text" }, + { + "type": "boolean", + "key": "delete_original", + "label": "Delete Original Representation" + }, { "type": "splitter" }, { - "key": "ext", - "label": "Output extension", - "type": "text" - }, - { - "key": "output_colorspace", - "label": "Output colorspace", - "type": "text" - }, - { - "key": "custom_tags", - "label": "Custom Tags", - "type": "list", - "object_type": "text" + "key": "outputs", + "label": "Output Definitions", + "type": "dict-modifiable", + "highlight_content": true, + "object_type": { + "type": "dict", + "children": [ + { + "key": "output_extension", + "label": "Output extension", + "type": "text" + }, + { + "key": "output_colorspace", + "label": "Output colorspace", + "type": "text" + }, + { + "type": "schema", + "name": "schema_representation_tags" + }, + { + "key": "custom_tags", + "label": "Custom Tags", + "type": "list", + "object_type": "text" + } + ] + } } ] } From 171af695c3ebfdb5a946af8897002e853f34af81 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 Jan 2023 18:17:25 +0100 Subject: [PATCH 010/184] OP-4643 - skip video files Only frames currently supported. --- .../plugins/publish/extract_color_transcode.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 88e2eed90f..a0714c9a33 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -36,6 +36,9 @@ class ExtractColorTranscode(publish.Extractor): optional = True + # Supported extensions + supported_exts = ["exr", "jpg", "jpeg", "png", "dpx"] + # Configurable by Settings profiles = None options = None @@ -88,13 +91,7 @@ class ExtractColorTranscode(publish.Extractor): repres = instance.data.get("representations") or [] for idx, repre in enumerate(repres): self.log.debug("repre ({}): `{}`".format(idx + 1, repre["name"])) - # if not self.repre_is_valid(repre): - # continue - - colorspace_data = repre.get("colorspaceData") - if not colorspace_data: - # TODO get_colorspace ?? - self.log.warning("Repre has not colorspace data, skipping") + if not self._repre_is_valid(repre): continue source_color_space = colorspace_data["colorspace"] config_path = colorspace_data.get("configData", {}).get("path") @@ -136,9 +133,9 @@ class ExtractColorTranscode(publish.Extractor): bool: False if can't be processed else True. """ - if "review" not in (repre.get("tags") or []): - self.log.info(( - "Representation \"{}\" don't have \"review\" tag. Skipped." + if repre.get("ext") not in self.supported_exts: + self.log.warning(( + "Representation \"{}\" of unsupported extension. Skipped." ).format(repre["name"])) return False From c7b443519e220ca14dc32ee135c6dcc8085c7d88 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 Jan 2023 18:19:08 +0100 Subject: [PATCH 011/184] OP-4643 - refactored profile, delete of original Implemented multiple outputs from single input representation --- .../publish/extract_color_transcode.py | 156 ++++++++++++------ 1 file changed, 109 insertions(+), 47 deletions(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index a0714c9a33..b0c851d5f4 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -1,4 +1,6 @@ import os +import copy + import pyblish.api from openpype.pipeline import publish @@ -56,13 +58,94 @@ class ExtractColorTranscode(publish.Extractor): self.log.warning("OIIO not supported, no transcoding possible.") return + profile = self._get_profile(instance) + if not profile: + return + + repres = instance.data.get("representations") or [] + for idx, repre in enumerate(list(repres)): + self.log.debug("repre ({}): `{}`".format(idx + 1, repre["name"])) + if not self._repre_is_valid(repre): + continue + + colorspace_data = repre["colorspaceData"] + source_color_space = colorspace_data["colorspace"] + config_path = colorspace_data.get("configData", {}).get("path") + if not os.path.exists(config_path): + self.log.warning("Config file doesn't exist, skipping") + continue + + repre = self._handle_original_repre(repre, profile) + + for _, output_def in profile.get("outputs", {}).items(): + new_repre = copy.deepcopy(repre) + + new_staging_dir = get_transcode_temp_directory() + original_staging_dir = new_repre["stagingDir"] + new_repre["stagingDir"] = new_staging_dir + files_to_convert = new_repre["files"] + if not isinstance(files_to_convert, list): + files_to_convert = [files_to_convert] + + files_to_delete = copy.deepcopy(files_to_convert) + + output_extension = output_def["output_extension"] + files_to_convert = self._rename_output_files(files_to_convert, + output_extension) + + files_to_convert = [os.path.join(original_staging_dir, path) + for path in files_to_convert] + + target_colorspace = output_def["output_colorspace"] + if not target_colorspace: + raise RuntimeError("Target colorspace must be set") + + convert_colorspace_for_input_paths( + files_to_convert, + new_staging_dir, + config_path, + source_color_space, + target_colorspace, + self.log + ) + + instance.context.data["cleanupFullPaths"].extend( + files_to_delete) + + custom_tags = output_def.get("custom_tags") + if custom_tags: + if not new_repre.get("custom_tags"): + new_repre["custom_tags"] = [] + new_repre["custom_tags"].extend(custom_tags) + + # Add additional tags from output definition to representation + for tag in output_def["tags"]: + if tag not in new_repre["tags"]: + new_repre["tags"].append(tag) + + instance.data["representations"].append(new_repre) + + def _rename_output_files(self, files_to_convert, output_extension): + """Change extension of converted files.""" + if output_extension: + output_extension = output_extension.replace('.', '') + renamed_files = [] + for file_name in files_to_convert: + file_name, _ = os.path.splitext(file_name) + new_file_name = '{}.{}'.format(file_name, + output_extension) + renamed_files.append(new_file_name) + files_to_convert = renamed_files + return files_to_convert + + def _get_profile(self, instance): + """Returns profile if and how repre should be color transcoded.""" host_name = instance.context.data["hostName"] family = instance.data["family"] task_data = instance.data["anatomyData"].get("task", {}) task_name = task_data.get("name") task_type = task_data.get("type") subset = instance.data["subset"] - filtering_criteria = { "hosts": host_name, "families": family, @@ -75,55 +158,15 @@ class ExtractColorTranscode(publish.Extractor): if not profile: self.log.info(( - "Skipped instance. None of profiles in presets are for" - " Host: \"{}\" | Families: \"{}\" | Task \"{}\"" - " | Task type \"{}\" | Subset \"{}\" " - ).format(host_name, family, task_name, task_type, subset)) - return + "Skipped instance. None of profiles in presets are for" + " Host: \"{}\" | Families: \"{}\" | Task \"{}\"" + " | Task type \"{}\" | Subset \"{}\" " + ).format(host_name, family, task_name, task_type, subset)) self.log.debug("profile: {}".format(profile)) + return profile - target_colorspace = profile["output_colorspace"] - if not target_colorspace: - raise RuntimeError("Target colorspace must be set") - custom_tags = profile["custom_tags"] - - repres = instance.data.get("representations") or [] - for idx, repre in enumerate(repres): - self.log.debug("repre ({}): `{}`".format(idx + 1, repre["name"])) - if not self._repre_is_valid(repre): - continue - source_color_space = colorspace_data["colorspace"] - config_path = colorspace_data.get("configData", {}).get("path") - if not os.path.exists(config_path): - self.log.warning("Config file doesn't exist, skipping") - continue - - new_staging_dir = get_transcode_temp_directory() - original_staging_dir = repre["stagingDir"] - repre["stagingDir"] = new_staging_dir - files_to_convert = repre["files"] - if not isinstance(files_to_convert, list): - files_to_convert = [files_to_convert] - files_to_convert = [os.path.join(original_staging_dir, path) - for path in files_to_convert] - instance.context.data["cleanupFullPaths"].extend(files_to_convert) - - convert_colorspace_for_input_paths( - files_to_convert, - new_staging_dir, - config_path, - source_color_space, - target_colorspace, - self.log - ) - - if custom_tags: - if not repre.get("custom_tags"): - repre["custom_tags"] = [] - repre["custom_tags"].extend(custom_tags) - - def repre_is_valid(self, repre): + def _repre_is_valid(self, repre): """Validation if representation should be processed. Args: @@ -144,4 +187,23 @@ class ExtractColorTranscode(publish.Extractor): "Representation \"{}\" have empty files. Skipped." ).format(repre["name"])) return False + + if not repre.get("colorspaceData"): + self.log.warning("Repre has not colorspace data, skipping") + return False + return True + + def _handle_original_repre(self, repre, profile): + delete_original = profile["delete_original"] + + if delete_original: + if not repre.get("tags"): + repre["tags"] = [] + + if "review" in repre["tags"]: + repre["tags"].remove("review") + if "delete" not in repre["tags"]: + repre["tags"].append("delete") + + return repre From 69c04bb01dc7bc220318a3d6f63e4ed568bddff2 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 Jan 2023 18:23:01 +0100 Subject: [PATCH 012/184] OP-4643 - switched logging levels Do not use warning unnecessary. --- openpype/plugins/publish/extract_color_transcode.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index b0c851d5f4..4d38514b8b 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -47,11 +47,11 @@ class ExtractColorTranscode(publish.Extractor): def process(self, instance): if not self.profiles: - self.log.warning("No profiles present for create burnin") + self.log.debug("No profiles present for color transcode") return if "representations" not in instance.data: - self.log.warning("No representations, skipping.") + self.log.debug("No representations, skipping.") return if not is_oiio_supported(): @@ -177,19 +177,19 @@ class ExtractColorTranscode(publish.Extractor): """ if repre.get("ext") not in self.supported_exts: - self.log.warning(( + self.log.debug(( "Representation \"{}\" of unsupported extension. Skipped." ).format(repre["name"])) return False if not repre.get("files"): - self.log.warning(( + self.log.debug(( "Representation \"{}\" have empty files. Skipped." ).format(repre["name"])) return False if not repre.get("colorspaceData"): - self.log.warning("Repre has not colorspace data, skipping") + self.log.debug("Repre has no colorspace data. Skipped.") return False return True From 44e12b05b95db1f0b1a43ad506850b5578705942 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 Jan 2023 18:46:14 +0100 Subject: [PATCH 013/184] OP-4643 - propagate new extension to representation --- .../publish/extract_color_transcode.py | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index 4d38514b8b..62cf8f0dee 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -90,8 +90,13 @@ class ExtractColorTranscode(publish.Extractor): files_to_delete = copy.deepcopy(files_to_convert) output_extension = output_def["output_extension"] - files_to_convert = self._rename_output_files(files_to_convert, - output_extension) + output_extension = output_extension.replace('.', '') + if output_extension: + new_repre["name"] = output_extension + new_repre["ext"] = output_extension + + files_to_convert = self._rename_output_files( + files_to_convert, output_extension) files_to_convert = [os.path.join(original_staging_dir, path) for path in files_to_convert] @@ -127,15 +132,13 @@ class ExtractColorTranscode(publish.Extractor): def _rename_output_files(self, files_to_convert, output_extension): """Change extension of converted files.""" - if output_extension: - output_extension = output_extension.replace('.', '') - renamed_files = [] - for file_name in files_to_convert: - file_name, _ = os.path.splitext(file_name) - new_file_name = '{}.{}'.format(file_name, - output_extension) - renamed_files.append(new_file_name) - files_to_convert = renamed_files + renamed_files = [] + for file_name in files_to_convert: + file_name, _ = os.path.splitext(file_name) + new_file_name = '{}.{}'.format(file_name, + output_extension) + renamed_files.append(new_file_name) + files_to_convert = renamed_files return files_to_convert def _get_profile(self, instance): From 85fc41bd7427a395fbf1454b12a6ee6fea34e594 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 6 Jan 2023 18:46:35 +0100 Subject: [PATCH 014/184] OP-4643 - added label to Settings --- .../projects_schema/schemas/schema_global_publish.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json index c2c911d7d6..7155510fef 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json @@ -201,10 +201,14 @@ "type": "dict", "collapsible": true, "key": "ExtractColorTranscode", - "label": "ExtractColorTranscode", + "label": "ExtractColorTranscode (ImageIO)", "checkbox_key": "enabled", "is_group": true, "children": [ + { + "type": "label", + "label": "Configure output format(s) and color spaces for matching representations. Empty 'Output extension' denotes keeping source extension." + }, { "type": "boolean", "key": "enabled", From 53470bb0333cd8662e3bf4433fa78898bb29eb19 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 18 Jan 2023 09:56:29 +0000 Subject: [PATCH 015/184] Initial draft --- openpype/hosts/maya/api/lib_renderproducts.py | 73 ++++++++++++++----- .../maya/plugins/publish/collect_render.py | 4 +- openpype/hosts/maya/startup/userSetup.py | 8 +- .../plugins/publish/submit_publish_job.py | 33 ++++++++- 4 files changed, 92 insertions(+), 26 deletions(-) diff --git a/openpype/hosts/maya/api/lib_renderproducts.py b/openpype/hosts/maya/api/lib_renderproducts.py index c54e3ab3e0..b76b441588 100644 --- a/openpype/hosts/maya/api/lib_renderproducts.py +++ b/openpype/hosts/maya/api/lib_renderproducts.py @@ -127,6 +127,7 @@ class RenderProduct(object): """ productName = attr.ib() ext = attr.ib() # extension + colorspace = attr.ib() # colorspace aov = attr.ib(default=None) # source aov driver = attr.ib(default=None) # source driver multipart = attr.ib(default=False) # multichannel file @@ -344,7 +345,6 @@ class ARenderProducts: separator = file_prefix[matches[0].end(1):matches[1].start(1)] return separator - def _get_layer_data(self): # type: () -> LayerMetadata # ______________________________________________ @@ -553,6 +553,9 @@ class RenderProductsArnold(ARenderProducts): ] for ai_driver in ai_drivers: + colorspace = self._get_colorspace( + ai_driver + ".colorManagement" + ) # todo: check aiAOVDriver.prefix as it could have # a custom path prefix set for this driver @@ -590,12 +593,15 @@ class RenderProductsArnold(ARenderProducts): global_aov = self._get_attr(aov, "globalAov") if global_aov: for camera in cameras: - product = RenderProduct(productName=name, - ext=ext, - aov=aov_name, - driver=ai_driver, - multipart=multipart, - camera=camera) + product = RenderProduct( + productName=name, + ext=ext, + aov=aov_name, + driver=ai_driver, + multipart=multipart, + camera=camera, + colorspace=colorspace + ) products.append(product) all_light_groups = self._get_attr(aov, "lightGroups") @@ -603,13 +609,16 @@ class RenderProductsArnold(ARenderProducts): # All light groups is enabled. A single multipart # Render Product for camera in cameras: - product = RenderProduct(productName=name + "_lgroups", - ext=ext, - aov=aov_name, - driver=ai_driver, - # Always multichannel output - multipart=True, - camera=camera) + product = RenderProduct( + productName=name + "_lgroups", + ext=ext, + aov=aov_name, + driver=ai_driver, + # Always multichannel output + multipart=True, + camera=camera, + colorspace=colorspace + ) products.append(product) else: value = self._get_attr(aov, "lightGroupsList") @@ -625,12 +634,28 @@ class RenderProductsArnold(ARenderProducts): aov=aov_name, driver=ai_driver, ext=ext, - camera=camera + camera=camera, + colorspace=colorspace ) products.append(product) return products + def _get_colorspace(self, attribute): + """Resolve colorspace from Arnold settings.""" + + def _view_transform(): + preferences = lib.get_color_management_preferences() + return preferences["view_transform"] + + resolved_values = { + "Raw": lambda: "Raw", + "Use View Transform": _view_transform, + # Default. Same as Maya Preferences. + "Use Output Transform": lib.get_color_management_output_transform + } + return resolved_values[self._get_attr(attribute)]() + def get_render_products(self): """Get all AOVs. @@ -659,11 +684,19 @@ class RenderProductsArnold(ARenderProducts): ] default_ext = self._get_attr("defaultRenderGlobals.imfPluginKey") - beauty_products = [RenderProduct( - productName="beauty", - ext=default_ext, - driver="defaultArnoldDriver", - camera=camera) for camera in cameras] + colorspace = self._get_colorspace( + "defaultArnoldDriver.colorManagement" + ) + beauty_products = [ + RenderProduct( + productName="beauty", + ext=default_ext, + driver="defaultArnoldDriver", + camera=camera, + colorspace=colorspace + ) for camera in cameras + ] + # AOVs > Legacy > Maya Render View > Mode aovs_enabled = bool( self._get_attr("defaultArnoldRenderOptions.aovMode") diff --git a/openpype/hosts/maya/plugins/publish/collect_render.py b/openpype/hosts/maya/plugins/publish/collect_render.py index b1ad3ca58e..2c89424381 100644 --- a/openpype/hosts/maya/plugins/publish/collect_render.py +++ b/openpype/hosts/maya/plugins/publish/collect_render.py @@ -42,7 +42,6 @@ Provides: import re import os import platform -import json from maya import cmds import maya.app.renderSetup.model.renderSetup as renderSetup @@ -318,6 +317,9 @@ class CollectMayaRender(pyblish.api.ContextPlugin): "aovSeparator": layer_render_products.layer_data.aov_separator, # noqa: E501 "renderSetupIncludeLights": render_instance.data.get( "renderSetupIncludeLights" + ), + "colorspaceConfig": ( + lib.get_color_management_preferences()["config"] ) } diff --git a/openpype/hosts/maya/startup/userSetup.py b/openpype/hosts/maya/startup/userSetup.py index 40cd51f2d8..cb5aa4a898 100644 --- a/openpype/hosts/maya/startup/userSetup.py +++ b/openpype/hosts/maya/startup/userSetup.py @@ -4,10 +4,16 @@ from openpype.pipeline import install_host from openpype.hosts.maya.api import MayaHost from maya import cmds +# MAYA_RESOURCES enviornment variable is referenced in default OCIO path but +# it's not part of the environment. Patching this so it works as expected. +if "MAYA_RESOURCES" not in os.environ: + os.environ["MAYA_RESOURCES"] = os.path.join( + os.environ["MAYA_LOCATION"], "resources" + ).replace("\\", "/") + host = MayaHost() install_host(host) - print("starting OpenPype usersetup") # build a shelf diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 7e39a644a2..8811fa5d34 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -427,7 +427,9 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): self.log.info( "Finished copying %i files" % len(resource_files)) - def _create_instances_for_aov(self, instance_data, exp_files): + def _create_instances_for_aov( + self, instance_data, exp_files, additional_data + ): """Create instance for each AOV found. This will create new instance for every aov it can detect in expected @@ -528,6 +530,14 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): else: files = os.path.basename(col) + # Copy render product "colorspace" data to representation. + colorspace = "" + products = additional_data["renderProducts"].layer_data.products + for product in products: + if product.productName == aov: + colorspace = product.colorspace + break + rep = { "name": ext, "ext": ext, @@ -537,7 +547,14 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): # If expectedFile are absolute, we need only filenames "stagingDir": staging, "fps": new_instance.get("fps"), - "tags": ["review"] if preview else [] + "tags": ["review"] if preview else [], + "colorspaceData": { + "colorspace": colorspace, + "configData": { + "path": additional_data["colorspaceConfig"], + "template": "" + } + } } # support conversion from tiled to scanline @@ -561,7 +578,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): self.log.debug("instances:{}".format(instances)) return instances - def _get_representations(self, instance, exp_files): + def _get_representations(self, instance, exp_files, additional_data): """Create representations for file sequences. This will return representations of expected files if they are not @@ -897,6 +914,11 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): self.log.info(data.get("expectedFiles")) + additional_data = { + "renderProducts": instance.data["renderProducts"], + "colorspaceConfig": instance.data["colorspaceConfig"] + } + if isinstance(data.get("expectedFiles")[0], dict): # we cannot attach AOVs to other subsets as we consider every # AOV subset of its own. @@ -911,12 +933,15 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): # there are multiple renderable cameras in scene) instances = self._create_instances_for_aov( instance_skeleton_data, - data.get("expectedFiles")) + data.get("expectedFiles"), + additional_data + ) self.log.info("got {} instance{}".format( len(instances), "s" if len(instances) > 1 else "")) else: + #Need to inject colorspace here. representations = self._get_representations( instance_skeleton_data, data.get("expectedFiles") From ab9b2d5970b458447e442d9d031bc3d8e76ddc7e Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 18 Jan 2023 15:13:12 +0000 Subject: [PATCH 016/184] Missing initial commit --- openpype/hosts/maya/api/lib.py | 45 ++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index dd5da275e8..95b4db1b42 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -5,6 +5,7 @@ import sys import platform import uuid import math +import re import json import logging @@ -3446,3 +3447,47 @@ def iter_visible_nodes_in_range(nodes, start, end): def get_attribute_input(attr): connections = cmds.listConnections(attr, plugs=True, destination=False) return connections[0] if connections else None + + +def get_color_management_preferences(): + """Get and resolve OCIO preferences.""" + data = { + # Is color management enabled. + "enabled": cmds.colorManagementPrefs( + query=True, cmEnabled=True + ), + "rendering_space": cmds.colorManagementPrefs( + query=True, renderingSpaceName=True + ), + "output_transform": cmds.colorManagementPrefs( + query=True, outputTransformName=True + ), + "output_transform_enabled": cmds.colorManagementPrefs( + query=True, outputTransformEnabled=True + ), + "view_transform": cmds.colorManagementPrefs( + query=True, viewTransformName=True + ) + } + + path = cmds.colorManagementPrefs( + query=True, configFilePath=True + ) + # Resolve environment variables in config path. "MAYA_RESOURCES" are in the + # path by default. + for group in re.search(r'(<.*>)', path).groups(): + path = path.replace( + group, os.environ[group[1:-1]] + ) + + data["config"] = path + + return data + + +def get_color_management_output_transform(): + preferences = get_color_management_preferences() + colorspace = preferences["rendering_space"] + if preferences["output_transform_enabled"]: + colorspace = preferences["output_transform"] + return colorspace From 3279634dacd18e6d38a29708596c822d276781c6 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 18 Jan 2023 17:57:28 +0000 Subject: [PATCH 017/184] Fix Environment variable substitution in path --- openpype/hosts/maya/api/lib.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 95b4db1b42..b4aa18af65 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -3473,12 +3473,14 @@ def get_color_management_preferences(): path = cmds.colorManagementPrefs( query=True, configFilePath=True ) + # Resolve environment variables in config path. "MAYA_RESOURCES" are in the # path by default. - for group in re.search(r'(<.*>)', path).groups(): - path = path.replace( - group, os.environ[group[1:-1]] - ) + def _subst_with_env_value(match): + key = match.group(1) + return os.environ.get(key, "") + + path = re.sub(r'<([^>]+)>', _subst_with_env_value, path) data["config"] = path From 7a7f87e861abf4aae7fb3a96f660d74fe4866329 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 19 Jan 2023 09:30:11 +0000 Subject: [PATCH 018/184] Improvement Cleaner way to resolve in path. --- openpype/hosts/maya/api/lib.py | 11 ++++------- openpype/hosts/maya/startup/userSetup.py | 6 ------ 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index b4aa18af65..0807db88dc 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -3474,13 +3474,10 @@ def get_color_management_preferences(): query=True, configFilePath=True ) - # Resolve environment variables in config path. "MAYA_RESOURCES" are in the - # path by default. - def _subst_with_env_value(match): - key = match.group(1) - return os.environ.get(key, "") - - path = re.sub(r'<([^>]+)>', _subst_with_env_value, path) + # The OCIO config supports a custom token. + maya_resources_token = "" + maya_resources_path = om.MGlobal.getAbsolutePathToResources() + path = path.replace(maya_resources_token, maya_resources_path) data["config"] = path diff --git a/openpype/hosts/maya/startup/userSetup.py b/openpype/hosts/maya/startup/userSetup.py index cb5aa4a898..1104421cd9 100644 --- a/openpype/hosts/maya/startup/userSetup.py +++ b/openpype/hosts/maya/startup/userSetup.py @@ -4,12 +4,6 @@ from openpype.pipeline import install_host from openpype.hosts.maya.api import MayaHost from maya import cmds -# MAYA_RESOURCES enviornment variable is referenced in default OCIO path but -# it's not part of the environment. Patching this so it works as expected. -if "MAYA_RESOURCES" not in os.environ: - os.environ["MAYA_RESOURCES"] = os.path.join( - os.environ["MAYA_LOCATION"], "resources" - ).replace("\\", "/") host = MayaHost() install_host(host) From c2d0bc8f39f69fc8f2cea36972242e946a6c9deb Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 19 Jan 2023 09:34:55 +0000 Subject: [PATCH 019/184] HOund --- openpype/hosts/maya/api/lib.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 0807db88dc..23f7319d4a 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -5,7 +5,6 @@ import sys import platform import uuid import math -import re import json import logging From 3fa7610061298e4a3de96d176b74f5bd3ecca652 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 31 Jan 2023 18:35:50 +0000 Subject: [PATCH 020/184] Working version for Arnold. --- openpype/hosts/maya/api/lib.py | 13 +++++++++++-- openpype/hosts/maya/api/lib_renderproducts.py | 6 +++++- .../hosts/maya/plugins/publish/collect_render.py | 8 ++++---- .../deadline/plugins/publish/submit_publish_job.py | 10 +++++++--- 4 files changed, 27 insertions(+), 10 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index f869dadaad..b31ab2408b 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -14,7 +14,7 @@ from math import ceil from six import string_types from maya import cmds, mel -import maya.api.OpenMaya as om +from maya.api import OpenMaya from openpype.client import ( get_project, @@ -3402,13 +3402,22 @@ def get_color_management_preferences(): ) } + # Split view and display from view_transform. view_transform comes in + # format of "{view} ({display})". + display = data["view_transform"].split("(")[-1].replace(")", "") + data.update({ + "display": display, + "view": data["view_transform"].replace("({})".format(display), "")[:-1] + }) + + # Get config absolute path. path = cmds.colorManagementPrefs( query=True, configFilePath=True ) # The OCIO config supports a custom token. maya_resources_token = "" - maya_resources_path = om.MGlobal.getAbsolutePathToResources() + maya_resources_path = OpenMaya.MGlobal.getAbsolutePathToResources() path = path.replace(maya_resources_token, maya_resources_path) data["config"] = path diff --git a/openpype/hosts/maya/api/lib_renderproducts.py b/openpype/hosts/maya/api/lib_renderproducts.py index 0b585dc8cb..58ccbfd5a2 100644 --- a/openpype/hosts/maya/api/lib_renderproducts.py +++ b/openpype/hosts/maya/api/lib_renderproducts.py @@ -648,8 +648,12 @@ class RenderProductsArnold(ARenderProducts): preferences = lib.get_color_management_preferences() return preferences["view_transform"] + def _raw(): + preferences = lib.get_color_management_preferences() + return preferences["rendering_space"] + resolved_values = { - "Raw": lambda: "Raw", + "Raw": _raw, "Use View Transform": _view_transform, # Default. Same as Maya Preferences. "Use Output Transform": lib.get_color_management_output_transform diff --git a/openpype/hosts/maya/plugins/publish/collect_render.py b/openpype/hosts/maya/plugins/publish/collect_render.py index 2c89424381..d0164f30a8 100644 --- a/openpype/hosts/maya/plugins/publish/collect_render.py +++ b/openpype/hosts/maya/plugins/publish/collect_render.py @@ -264,7 +264,7 @@ class CollectMayaRender(pyblish.api.ContextPlugin): self.log.info(full_exp_files) self.log.info("collecting layer: {}".format(layer_name)) # Get layer specific settings, might be overrides - + colorspace_data = lib.get_color_management_preferences() data = { "subset": expected_layer_name, "attachTo": attach_to, @@ -318,9 +318,9 @@ class CollectMayaRender(pyblish.api.ContextPlugin): "renderSetupIncludeLights": render_instance.data.get( "renderSetupIncludeLights" ), - "colorspaceConfig": ( - lib.get_color_management_preferences()["config"] - ) + "colorspaceConfig": colorspace_data["config"], + "colorspaceDisplay": colorspace_data["display"], + "colorspaceView": colorspace_data["view"] } # Collect Deadline url if Deadline module is enabled diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 8811fa5d34..02aa1043d1 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -550,10 +550,12 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "tags": ["review"] if preview else [], "colorspaceData": { "colorspace": colorspace, - "configData": { + "config": { "path": additional_data["colorspaceConfig"], "template": "" - } + }, + "display": additional_data["display"], + "view": additional_data["view"] } } @@ -916,7 +918,9 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): additional_data = { "renderProducts": instance.data["renderProducts"], - "colorspaceConfig": instance.data["colorspaceConfig"] + "colorspaceConfig": instance.data["colorspaceConfig"], + "display": instance.data["colorspaceDisplay"], + "view": instance.data["colorspaceView"] } if isinstance(data.get("expectedFiles")[0], dict): From 5c4856d72a302db1132b21f8fac87e751e045899 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 1 Feb 2023 12:05:38 +0000 Subject: [PATCH 021/184] BigRoy feedback --- openpype/hosts/maya/api/lib.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index b31ab2408b..d83f18bf30 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -5,6 +5,7 @@ import sys import platform import uuid import math +import re import json import logging @@ -3404,10 +3405,11 @@ def get_color_management_preferences(): # Split view and display from view_transform. view_transform comes in # format of "{view} ({display})". - display = data["view_transform"].split("(")[-1].replace(")", "") + regex = re.compile(r"^(?P.+) \((?P.+)\)$") + match = regex.match(data["view_transform"]) data.update({ - "display": display, - "view": data["view_transform"].replace("({})".format(display), "")[:-1] + "display": match.group("display"), + "view": match.group("view") }) # Get config absolute path. From 89d7edd8dfe0e6917c18870c92628659a8635541 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 1 Feb 2023 12:06:26 +0000 Subject: [PATCH 022/184] Support for Maya Hardware --- openpype/hosts/maya/api/lib_renderproducts.py | 7 ++++++- openpype/hosts/maya/api/lib_rendersettings.py | 3 ++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/api/lib_renderproducts.py b/openpype/hosts/maya/api/lib_renderproducts.py index 58ccbfd5a2..6a607e7ff3 100644 --- a/openpype/hosts/maya/api/lib_renderproducts.py +++ b/openpype/hosts/maya/api/lib_renderproducts.py @@ -1354,7 +1354,12 @@ class RenderProductsMayaHardware(ARenderProducts): products = [] for cam in self.get_renderable_cameras(): - product = RenderProduct(productName="beauty", ext=ext, camera=cam) + product = RenderProduct( + productName="beauty", + ext=ext, + camera=cam, + colorspace=lib.get_color_management_output_transform() + ) products.append(product) return products diff --git a/openpype/hosts/maya/api/lib_rendersettings.py b/openpype/hosts/maya/api/lib_rendersettings.py index 6190a49401..4710b10128 100644 --- a/openpype/hosts/maya/api/lib_rendersettings.py +++ b/openpype/hosts/maya/api/lib_rendersettings.py @@ -23,7 +23,8 @@ class RenderSettings(object): 'vray': 'vraySettings.fileNamePrefix', 'arnold': 'defaultRenderGlobals.imageFilePrefix', 'renderman': 'rmanGlobals.imageFileFormat', - 'redshift': 'defaultRenderGlobals.imageFilePrefix' + 'redshift': 'defaultRenderGlobals.imageFilePrefix', + 'mayahardware2': 'defaultRenderGlobals.imageFilePrefix' } _image_prefixes = { From 21d275d46ae2e7d8c1cc9fae67cd8512da46dec1 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 1 Feb 2023 12:27:48 +0000 Subject: [PATCH 023/184] Support for Vray --- openpype/hosts/maya/api/lib_renderproducts.py | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/maya/api/lib_renderproducts.py b/openpype/hosts/maya/api/lib_renderproducts.py index 6a607e7ff3..38415c2ae2 100644 --- a/openpype/hosts/maya/api/lib_renderproducts.py +++ b/openpype/hosts/maya/api/lib_renderproducts.py @@ -841,9 +841,13 @@ class RenderProductsVray(ARenderProducts): if not dont_save_rgb: for camera in cameras: products.append( - RenderProduct(productName="", - ext=default_ext, - camera=camera)) + RenderProduct( + productName="", + ext=default_ext, + camera=camera, + colorspace=lib.get_color_management_output_transform() + ) + ) # separate alpha file separate_alpha = self._get_attr("vraySettings.separateAlpha") @@ -895,10 +899,13 @@ class RenderProductsVray(ARenderProducts): aov_name = self._get_vray_aov_name(aov) for camera in cameras: - product = RenderProduct(productName=aov_name, - ext=default_ext, - aov=aov, - camera=camera) + product = RenderProduct( + productName=aov_name, + ext=default_ext, + aov=aov, + camera=camera, + colorspace=lib.get_color_management_output_transform() + ) products.append(product) return products From e2565dc2d205e45e939d2ecef05933928520632a Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 10 Feb 2023 17:12:19 +0100 Subject: [PATCH 024/184] Nuke: adding solution for originalBasename frame temlate formating https://github.com/ynput/OpenPype/pull/4452#issuecomment-1426020567 --- openpype/hosts/nuke/plugins/load/load_clip.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/nuke/plugins/load/load_clip.py b/openpype/hosts/nuke/plugins/load/load_clip.py index 565d777811..f9364172ea 100644 --- a/openpype/hosts/nuke/plugins/load/load_clip.py +++ b/openpype/hosts/nuke/plugins/load/load_clip.py @@ -220,8 +220,21 @@ class LoadClip(plugin.NukeLoader): dict: altered representation data """ representation = deepcopy(representation) - frame = representation["context"]["frame"] - representation["context"]["frame"] = "#" * len(str(frame)) + context = representation["context"] + template = representation["data"]["template"] + frame = context["frame"] + hashed_frame = "#" * len(str(frame)) + + if ( + "{originalBasename}" in template + and "frame" in context + ): + origin_basename = context["originalBasename"] + context["originalBasename"] = origin_basename.replace( + frame, hashed_frame + ) + + representation["context"]["frame"] = hashed_frame return representation def update(self, container, representation): From 8233b1ad8229ebc8388d453f08c3cdeec3196a9f Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 10 Feb 2023 17:51:29 +0100 Subject: [PATCH 025/184] publishing files with fixed versionData to fit originalBasename tempate --- .../publish/collect_otio_subset_resources.py | 43 ++++++++++++++++--- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/openpype/plugins/publish/collect_otio_subset_resources.py b/openpype/plugins/publish/collect_otio_subset_resources.py index e72c12d9a9..537aef683c 100644 --- a/openpype/plugins/publish/collect_otio_subset_resources.py +++ b/openpype/plugins/publish/collect_otio_subset_resources.py @@ -14,16 +14,41 @@ from openpype.pipeline.editorial import ( range_from_frames, make_sequence_collection ) - +from openpype.pipeline.publish import ( + get_publish_template_name +) class CollectOtioSubsetResources(pyblish.api.InstancePlugin): """Get Resources for a subset version""" label = "Collect OTIO Subset Resources" - order = pyblish.api.CollectorOrder - 0.077 + order = pyblish.api.CollectorOrder + 0.0021 families = ["clip"] hosts = ["resolve", "hiero", "flame"] + def get_template_name(self, instance): + """Return anatomy template name to use for integration""" + + # Anatomy data is pre-filled by Collectors + context = instance.context + project_name = context.data["projectName"] + + # Task can be optional in anatomy data + host_name = context.data["hostName"] + family = instance.data["family"] + anatomy_data = instance.context.data["anatomyData"] + 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=context.data["project_settings"], + logger=self.log + ) + def process(self, instance): if "audio" in instance.data["family"]: @@ -35,6 +60,13 @@ class CollectOtioSubsetResources(pyblish.api.InstancePlugin): if not instance.data.get("versionData"): instance.data["versionData"] = {} + template_name = self.get_template_name(instance) + anatomy = instance.context.data["anatomy"] + publish_template_category = anatomy.templates[template_name] + template = os.path.normpath(publish_template_category["path"]) + self.log.debug( + ">> template: {}".format(template)) + handle_start = instance.data["handleStart"] handle_end = instance.data["handleEnd"] @@ -84,6 +116,10 @@ class CollectOtioSubsetResources(pyblish.api.InstancePlugin): frame_start = instance.data["frameStart"] frame_end = frame_start + (media_out - media_in) + if "{originalDirname}" in template: + frame_start = media_in + frame_end = media_out + # add to version data start and end range data # for loader plugins to be correctly displayed and loaded instance.data["versionData"].update({ @@ -153,7 +189,6 @@ class CollectOtioSubsetResources(pyblish.api.InstancePlugin): 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) @@ -168,8 +203,6 @@ 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: From a1ab32b32961a7785e97fdd74024fd7fb7f261a6 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 10 Feb 2023 17:53:36 +0100 Subject: [PATCH 026/184] polishing fixes --- .../publish/collect_otio_subset_resources.py | 46 ++++++++++--------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/openpype/plugins/publish/collect_otio_subset_resources.py b/openpype/plugins/publish/collect_otio_subset_resources.py index 537aef683c..5daa1b40fe 100644 --- a/openpype/plugins/publish/collect_otio_subset_resources.py +++ b/openpype/plugins/publish/collect_otio_subset_resources.py @@ -26,28 +26,6 @@ class CollectOtioSubsetResources(pyblish.api.InstancePlugin): families = ["clip"] hosts = ["resolve", "hiero", "flame"] - def get_template_name(self, instance): - """Return anatomy template name to use for integration""" - - # Anatomy data is pre-filled by Collectors - context = instance.context - project_name = context.data["projectName"] - - # Task can be optional in anatomy data - host_name = context.data["hostName"] - family = instance.data["family"] - anatomy_data = instance.context.data["anatomyData"] - 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=context.data["project_settings"], - logger=self.log - ) def process(self, instance): @@ -116,6 +94,7 @@ class CollectOtioSubsetResources(pyblish.api.InstancePlugin): frame_start = instance.data["frameStart"] frame_end = frame_start + (media_out - media_in) + # Fit start /end frame to media in /out if "{originalDirname}" in template: frame_start = media_in frame_end = media_out @@ -258,3 +237,26 @@ class CollectOtioSubsetResources(pyblish.api.InstancePlugin): if kwargs.get("trim") is True: representation_data["tags"] = ["trim"] return representation_data + + def get_template_name(self, instance): + """Return anatomy template name to use for integration""" + + # Anatomy data is pre-filled by Collectors + context = instance.context + project_name = context.data["projectName"] + + # Task can be optional in anatomy data + host_name = context.data["hostName"] + family = instance.data["family"] + anatomy_data = instance.context.data["anatomyData"] + 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=context.data["project_settings"], + logger=self.log + ) \ No newline at end of file From 6fbf7be560cc68704a2ed2933955cac10a80176e Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 10 Feb 2023 17:54:54 +0100 Subject: [PATCH 027/184] wrong template key name --- openpype/plugins/publish/collect_otio_subset_resources.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/plugins/publish/collect_otio_subset_resources.py b/openpype/plugins/publish/collect_otio_subset_resources.py index 5daa1b40fe..dab52986da 100644 --- a/openpype/plugins/publish/collect_otio_subset_resources.py +++ b/openpype/plugins/publish/collect_otio_subset_resources.py @@ -95,7 +95,7 @@ class CollectOtioSubsetResources(pyblish.api.InstancePlugin): frame_end = frame_start + (media_out - media_in) # Fit start /end frame to media in /out - if "{originalDirname}" in template: + if "{originalBasename}" in template: frame_start = media_in frame_end = media_out From d39dd9ce1de40544cbdf612c6f9e5ff00b126129 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 13 Feb 2023 17:59:40 +0000 Subject: [PATCH 028/184] Correct colorspace when using "View Transform" --- openpype/hosts/maya/api/lib_renderproducts.py | 7 +- openpype/pipeline/colorspace.py | 99 +++++++++---------- openpype/scripts/ocio_wrapper.py | 20 +++- 3 files changed, 70 insertions(+), 56 deletions(-) diff --git a/openpype/hosts/maya/api/lib_renderproducts.py b/openpype/hosts/maya/api/lib_renderproducts.py index 38415c2ae2..4930143fb1 100644 --- a/openpype/hosts/maya/api/lib_renderproducts.py +++ b/openpype/hosts/maya/api/lib_renderproducts.py @@ -46,6 +46,7 @@ import attr from . import lib from . import lib_rendersetup +from openpype.pipeline.colorspace import get_ocio_config_views from maya import cmds, mel @@ -646,7 +647,11 @@ class RenderProductsArnold(ARenderProducts): def _view_transform(): preferences = lib.get_color_management_preferences() - return preferences["view_transform"] + views_data = get_ocio_config_views(preferences["config"]) + view_data = views_data[ + "{}/{}".format(preferences["display"], preferences["view"]) + ] + return view_data["colorspace"] def _raw(): preferences = lib.get_color_management_preferences() diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index cb37b2c4ae..6f68bdc5bf 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -198,41 +198,19 @@ def validate_imageio_colorspace_in_config(config_path, colorspace_name): return True -def get_ocio_config_colorspaces(config_path): - """Get all colorspace data - - Wrapper function for aggregating all names and its families. - Families can be used for building menu and submenus in gui. - - Args: - config_path (str): path leading to config.ocio file - - Returns: - dict: colorspace and family in couple - """ - if sys.version_info[0] == 2: - return get_colorspace_data_subprocess(config_path) - - from ..scripts.ocio_wrapper import _get_colorspace_data - return _get_colorspace_data(config_path) - - -def get_colorspace_data_subprocess(config_path): - """Get colorspace data via subprocess +def get_data_subprocess(config_path, data_type): + """Get data via subprocess Wrapper for Python 2 hosts. Args: config_path (str): path leading to config.ocio file - - Returns: - dict: colorspace and family in couple """ with _make_temp_json_file() as tmp_json_path: # Prepare subprocess arguments args = [ "run", get_ocio_config_script_path(), - "config", "get_colorspace", + "config", data_type, "--in_path", config_path, "--out_path", tmp_json_path @@ -251,6 +229,47 @@ def get_colorspace_data_subprocess(config_path): return json.loads(return_json_data) +def compatible_python(): + """Only 3.9 or higher can directly use PyOpenColorIO in ocio_wrapper""" + compatible = False + if sys.version[0] == 3 and sys.version[1] >= 9: + compatible = True + return compatible + + +def get_ocio_config_colorspaces(config_path): + """Get all colorspace data + + Wrapper function for aggregating all names and its families. + Families can be used for building menu and submenus in gui. + + Args: + config_path (str): path leading to config.ocio file + + Returns: + dict: colorspace and family in couple + """ + if compatible_python(): + from ..scripts.ocio_wrapper import _get_colorspace_data + return _get_colorspace_data(config_path) + else: + return get_colorspace_data_subprocess(config_path) + + +def get_colorspace_data_subprocess(config_path): + """Get colorspace data via subprocess + + Wrapper for Python 2 hosts. + + Args: + config_path (str): path leading to config.ocio file + + Returns: + dict: colorspace and family in couple + """ + return get_data_subprocess(config_path, "get_colorspace") + + def get_ocio_config_views(config_path): """Get all viewer data @@ -263,12 +282,12 @@ def get_ocio_config_views(config_path): Returns: dict: `display/viewer` and viewer data """ - if sys.version_info[0] == 2: + if compatible_python(): + from ..scripts.ocio_wrapper import _get_views_data + return _get_views_data(config_path) + else: return get_views_data_subprocess(config_path) - from ..scripts.ocio_wrapper import _get_views_data - return _get_views_data(config_path) - def get_views_data_subprocess(config_path): """Get viewers data via subprocess @@ -281,27 +300,7 @@ def get_views_data_subprocess(config_path): Returns: dict: `display/viewer` and viewer data """ - with _make_temp_json_file() as tmp_json_path: - # Prepare subprocess arguments - args = [ - "run", get_ocio_config_script_path(), - "config", "get_views", - "--in_path", config_path, - "--out_path", tmp_json_path - - ] - log.info("Executing: {}".format(" ".join(args))) - - process_kwargs = { - "logger": log, - "env": {} - } - - run_openpype_process(*args, **process_kwargs) - - # return all colorspaces - return_json_data = open(tmp_json_path).read() - return json.loads(return_json_data) + return get_data_subprocess(config_path, "get_views") def get_imageio_config( diff --git a/openpype/scripts/ocio_wrapper.py b/openpype/scripts/ocio_wrapper.py index 0685b2e52a..aa9e11c841 100644 --- a/openpype/scripts/ocio_wrapper.py +++ b/openpype/scripts/ocio_wrapper.py @@ -157,11 +157,21 @@ def _get_views_data(config_path): config = ocio.Config().CreateFromFile(str(config_path)) - return { - f"{d}/{v}": {"display": d, "view": v} - for d in config.getDisplays() - for v in config.getViews(d) - } + data = {} + for display in config.getDisplays(): + for view in config.getViews(display): + colorspace = config.getDisplayViewColorSpaceName(display, view) + # Special token. See https://opencolorio.readthedocs.io/en/latest/guides/authoring/authoring.html#shared-views + if colorspace == "": + colorspace = display + + data[f"{display}/{view}"] = { + "display": display, + "view": view, + "colorspace": colorspace + } + + return data if __name__ == '__main__': From e21323c78841902bf66f8670b1890a7cde11773c Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 13 Feb 2023 18:01:38 +0000 Subject: [PATCH 029/184] Hound --- openpype/scripts/ocio_wrapper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/scripts/ocio_wrapper.py b/openpype/scripts/ocio_wrapper.py index aa9e11c841..16558642c6 100644 --- a/openpype/scripts/ocio_wrapper.py +++ b/openpype/scripts/ocio_wrapper.py @@ -161,7 +161,7 @@ def _get_views_data(config_path): for display in config.getDisplays(): for view in config.getViews(display): colorspace = config.getDisplayViewColorSpaceName(display, view) - # Special token. See https://opencolorio.readthedocs.io/en/latest/guides/authoring/authoring.html#shared-views + # Special token. See https://opencolorio.readthedocs.io/en/latest/guides/authoring/authoring.html#shared-views # noqa if colorspace == "": colorspace = display From 6653df6369afbe53905af5eccacd29a2faed72d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Tue, 14 Feb 2023 12:18:00 +0100 Subject: [PATCH 030/184] Update openpype/hosts/nuke/plugins/load/load_clip.py Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/hosts/nuke/plugins/load/load_clip.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/nuke/plugins/load/load_clip.py b/openpype/hosts/nuke/plugins/load/load_clip.py index f9364172ea..8f9b463037 100644 --- a/openpype/hosts/nuke/plugins/load/load_clip.py +++ b/openpype/hosts/nuke/plugins/load/load_clip.py @@ -222,13 +222,12 @@ class LoadClip(plugin.NukeLoader): representation = deepcopy(representation) context = representation["context"] template = representation["data"]["template"] - frame = context["frame"] - hashed_frame = "#" * len(str(frame)) - if ( "{originalBasename}" in template and "frame" in context ): + frame = context["frame"] + hashed_frame = "#" * len(str(frame)) origin_basename = context["originalBasename"] context["originalBasename"] = origin_basename.replace( frame, hashed_frame From 1abe920b5f18e32e51dbdaa8ac60b6dff175e22f Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 14 Feb 2023 14:27:42 +0100 Subject: [PATCH 031/184] anatomy data from instance rather then context --- .../plugins/publish/collect_otio_subset_resources.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/openpype/plugins/publish/collect_otio_subset_resources.py b/openpype/plugins/publish/collect_otio_subset_resources.py index dab52986da..f659791d95 100644 --- a/openpype/plugins/publish/collect_otio_subset_resources.py +++ b/openpype/plugins/publish/collect_otio_subset_resources.py @@ -22,7 +22,7 @@ class CollectOtioSubsetResources(pyblish.api.InstancePlugin): """Get Resources for a subset version""" label = "Collect OTIO Subset Resources" - order = pyblish.api.CollectorOrder + 0.0021 + order = pyblish.api.CollectorOrder + 0.491 families = ["clip"] hosts = ["resolve", "hiero", "flame"] @@ -50,9 +50,9 @@ class CollectOtioSubsetResources(pyblish.api.InstancePlugin): # get basic variables otio_clip = instance.data["otioClip"] - otio_avalable_range = otio_clip.available_range() - media_fps = otio_avalable_range.start_time.rate - available_duration = otio_avalable_range.duration.value + otio_available_range = otio_clip.available_range() + media_fps = otio_available_range.start_time.rate + available_duration = otio_available_range.duration.value # get available range trimmed with processed retimes retimed_attributes = get_media_range_with_retimes( @@ -248,7 +248,7 @@ class CollectOtioSubsetResources(pyblish.api.InstancePlugin): # Task can be optional in anatomy data host_name = context.data["hostName"] family = instance.data["family"] - anatomy_data = instance.context.data["anatomyData"] + anatomy_data = instance.data["anatomyData"] task_info = anatomy_data.get("task") or {} return get_publish_template_name( @@ -259,4 +259,4 @@ class CollectOtioSubsetResources(pyblish.api.InstancePlugin): task_type=task_info.get("type"), project_settings=context.data["project_settings"], logger=self.log - ) \ No newline at end of file + ) From 2621016015b0b2c3e36f36d4c3853851d5154256 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 14 Feb 2023 16:27:43 +0000 Subject: [PATCH 032/184] Template for config. --- .../modules/deadline/plugins/publish/submit_publish_job.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 02aa1043d1..89a4e5d377 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -552,7 +552,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "colorspace": colorspace, "config": { "path": additional_data["colorspaceConfig"], - "template": "" + "template": additional_data["colorspaceTemplate"] }, "display": additional_data["display"], "view": additional_data["view"] @@ -920,7 +920,10 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "renderProducts": instance.data["renderProducts"], "colorspaceConfig": instance.data["colorspaceConfig"], "display": instance.data["colorspaceDisplay"], - "view": instance.data["colorspaceView"] + "view": instance.data["colorspaceView"], + "colorspaceTemplate": instance.data["colorspaceConfig"].replace( + context.data["anatomy"].roots["work"], "{root[work]}" + ) } if isinstance(data.get("expectedFiles")[0], dict): From 2fce95a9df1cf89fb801e1228698be03ad93aaa0 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 16 Feb 2023 16:10:03 +0100 Subject: [PATCH 033/184] OP-3026 - first implementation of saving settings changes to DB This logs all changes in Settings to separate collection ('setting_log') to help debug/trace changes. This functionality is pretty basic as it will be replaced by Ayon implementation in the future. --- openpype/settings/handlers.py | 34 ++++++++++++++++++++++++++++++++++ openpype/settings/lib.py | 4 +++- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/openpype/settings/handlers.py b/openpype/settings/handlers.py index 373029d9df..3706433692 100644 --- a/openpype/settings/handlers.py +++ b/openpype/settings/handlers.py @@ -235,6 +235,18 @@ class SettingsHandler(object): """ pass + @abstractmethod + def save_change_log(self, project_name, changes, settings_type): + """Stores changes to settings to separate logging collection. + + Args: + project_name(str, null): Project name for which overrides are + or None for global settings. + changes(dict): Data of project overrides with override metadata. + settings_type (str): system|project|anatomy + """ + pass + @abstractmethod def get_studio_system_settings_overrides(self, return_version): """Studio overrides of system settings.""" @@ -913,6 +925,28 @@ class MongoSettingsHandler(SettingsHandler): return data + def save_change_log(self, project_name, changes, settings_type): + """Log all settings changes to separate collection""" + if not changes: + return + + from openpype.lib import get_local_site_id + + if settings_type == "project" and not project_name: + project_name = "default" + + document = { + "user": get_local_site_id(), + "date_created": datetime.datetime.now(), + "project": project_name, + "settings_type": settings_type, + "changes": changes + } + collection_name = "settings_log" + collection = (self.settings_collection[self.database_name] + [collection_name]) + collection.insert_one(document) + def _save_project_anatomy_data(self, project_name, data_cache): # Create copy of data as they will be modified during save new_data = data_cache.data_copy() diff --git a/openpype/settings/lib.py b/openpype/settings/lib.py index 796eaeda01..73554df236 100644 --- a/openpype/settings/lib.py +++ b/openpype/settings/lib.py @@ -159,6 +159,7 @@ def save_studio_settings(data): except SaveWarningExc as exc: warnings.extend(exc.warnings) + _SETTINGS_HANDLER.save_change_log(None, changes, "system") _SETTINGS_HANDLER.save_studio_settings(data) if warnings: raise SaveWarningExc(warnings) @@ -218,7 +219,7 @@ def save_project_settings(project_name, overrides): ) except SaveWarningExc as exc: warnings.extend(exc.warnings) - + _SETTINGS_HANDLER.save_change_log(project_name, changes, "project") _SETTINGS_HANDLER.save_project_settings(project_name, overrides) if warnings: @@ -280,6 +281,7 @@ def save_project_anatomy(project_name, anatomy_data): except SaveWarningExc as exc: warnings.extend(exc.warnings) + _SETTINGS_HANDLER.save_change_log(project_name, changes, "anatomy") _SETTINGS_HANDLER.save_project_anatomy(project_name, anatomy_data) if warnings: From 43f88ca797b286f6dfaf0bcc8f1a45e099f4886c Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 16 Feb 2023 16:31:27 +0100 Subject: [PATCH 034/184] OP-3026 - Hound --- openpype/settings/handlers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/settings/handlers.py b/openpype/settings/handlers.py index 3706433692..3fa5737b6f 100644 --- a/openpype/settings/handlers.py +++ b/openpype/settings/handlers.py @@ -238,7 +238,7 @@ class SettingsHandler(object): @abstractmethod def save_change_log(self, project_name, changes, settings_type): """Stores changes to settings to separate logging collection. - + Args: project_name(str, null): Project name for which overrides are or None for global settings. From a189a8df8a1aed35e7b393c4ec8027911d38d47f Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 20 Feb 2023 13:23:25 +0100 Subject: [PATCH 035/184] OP-3026 - enhanced log with host info --- openpype/settings/handlers.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/openpype/settings/handlers.py b/openpype/settings/handlers.py index 3fa5737b6f..a1f3331ccc 100644 --- a/openpype/settings/handlers.py +++ b/openpype/settings/handlers.py @@ -9,6 +9,7 @@ import six import openpype.version from openpype.client.mongo import OpenPypeMongoConnection from openpype.client.entities import get_project_connection, get_project +from openpype.lib.pype_info import get_workstation_info from .constants import ( GLOBAL_SETTINGS_KEY, @@ -930,13 +931,17 @@ class MongoSettingsHandler(SettingsHandler): if not changes: return - from openpype.lib import get_local_site_id - if settings_type == "project" and not project_name: project_name = "default" + host_info = get_workstation_info() + document = { - "user": get_local_site_id(), + "local_id": host_info["local_id"], + "username": host_info["username"], + "hostname": host_info["hostname"], + "hostip": host_info["hostip"], + "system_name": host_info["system_name"], "date_created": datetime.datetime.now(), "project": project_name, "settings_type": settings_type, From 7b6fb46cd191ab39ae43b9664365a0422203ad58 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Mon, 20 Feb 2023 16:05:24 +0000 Subject: [PATCH 036/184] Fix colorspaceTemplate --- openpype/modules/deadline/plugins/publish/submit_publish_job.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 89a4e5d377..bd5933926c 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -922,7 +922,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "display": instance.data["colorspaceDisplay"], "view": instance.data["colorspaceView"], "colorspaceTemplate": instance.data["colorspaceConfig"].replace( - context.data["anatomy"].roots["work"], "{root[work]}" + str(context.data["anatomy"].roots["work"]), "{root[work]}" ) } From 8f7b6c6a0e9f8b708e618fb5fec9277305786859 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 22 Feb 2023 06:58:20 +0000 Subject: [PATCH 037/184] Hound. --- openpype/modules/deadline/plugins/publish/submit_publish_job.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 425c236b7f..c651782392 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -962,7 +962,6 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "s" if len(instances) > 1 else "")) else: - #Need to inject colorspace here. representations = self._get_representations( instance_skeleton_data, data.get("expectedFiles") From 24715ff2de8e8c673670643ebb9263f3bd7219e3 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Wed, 22 Feb 2023 12:34:45 +0100 Subject: [PATCH 038/184] global: template should not have frame or other optional elements in tempate, since they are already part of the `originalBasename` --- 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 32230e0625..02c0e35377 100644 --- a/openpype/settings/defaults/project_anatomy/templates.json +++ b/openpype/settings/defaults/project_anatomy/templates.json @@ -55,7 +55,7 @@ }, "source": { "folder": "{root[work]}/{originalDirname}", - "file": "{originalBasename}<.{@frame}><_{udim}>.{ext}", + "file": "{originalBasename}.{ext}", "path": "{@folder}/{@file}" }, "__dynamic_keys_labels__": { @@ -66,4 +66,4 @@ "source": "source" } } -} \ No newline at end of file +} From fad36c75a9e485b765a83a24a459540a9d9e7aed Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 23 Feb 2023 16:11:33 +0100 Subject: [PATCH 039/184] nuke: rendersettins lib with farm rendering class --- openpype/hosts/nuke/api/lib_rendersettings.py | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 openpype/hosts/nuke/api/lib_rendersettings.py diff --git a/openpype/hosts/nuke/api/lib_rendersettings.py b/openpype/hosts/nuke/api/lib_rendersettings.py new file mode 100644 index 0000000000..959b461c29 --- /dev/null +++ b/openpype/hosts/nuke/api/lib_rendersettings.py @@ -0,0 +1,86 @@ + +from openpype.lib import Logger +from openpype.settings import ( + get_current_project_settings, + get_system_settings +) + + +class RenderFarmSettings: + """ Class for getting farm settings from project settings + """ + log = Logger.get_logger("RenderFarmSettings") + + _active_farm_module: str = None + _farm_modules: list = [ + "deadline", "muster", "royalrender"] + _farm_plugins: dict = { + "deadline": "NukeSubmitDeadline" + } + _creator_farm_keys: list = [ + "chunk_size", "priority", "concurrent_tasks"] + + def __init__(self, project_settings=None): + """ Get project settings and active farm module + """ + self._project_settings = ( + project_settings or get_current_project_settings() + ) + # Get active farm module from system settings + self._get_active_farm_module_from_system_settings() + + def _get_active_farm_module_from_system_settings(self): + """ Get active farm module from system settings + """ + active_modules = [ + module_ + for module_ in self._farm_modules + if get_system_settings()["modules"][module_]["enabled"] + ] + if not active_modules: + raise ValueError(( + "No active farm module found in system settings." + )) + if len(active_modules) > 1: + raise ValueError(( + "Multiple active farm modules " + "found in system settings. {}".format(active_modules) + )) + + self._active_farm_module = active_modules.pop() + + @property + def active_farm_module(self): + return self._active_farm_module + + def get_rendering_attributes(self): + ''' Get rendering attributes from project settings + + Returns: + dict: rendering attributes + ''' + return_dict = {} + farm_plugin = self._farm_plugins.get(self.active_farm_module) + + if farm_plugin: + raise ValueError(( + "Farm plugin \"{}\" not found in farm plugins." + ).format(farm_plugin)) + + # Get farm module settings + module_settings = self._project_settings[self.active_farm_module] + + # Get farm plugin settings + farm_plugin_settings = ( + module_settings["publish"][farm_plugin]) + + # Get all keys from farm_plugin_settings + for key in self._creator_farm_keys: + if key not in farm_plugin_settings: + self.log.warning(( + "Key \"{}\" not found in farm plugin \"{}\" settings." + ).format(key, farm_plugin)) + continue + return_dict[key] = farm_plugin_settings[key] + + return return_dict From 073f0be7f706fd6ae222c3900cb1c02c523d1c04 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 23 Feb 2023 16:12:49 +0100 Subject: [PATCH 040/184] nuke: creators with new RenderFarmSettings class --- openpype/hosts/nuke/api/plugin.py | 2 +- .../nuke/plugins/create/create_write_prerender.py | 13 ++++++++----- .../nuke/plugins/create/create_write_render.py | 13 ++++++++----- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/openpype/hosts/nuke/api/plugin.py b/openpype/hosts/nuke/api/plugin.py index d3f8357f7d..ef77e029ad 100644 --- a/openpype/hosts/nuke/api/plugin.py +++ b/openpype/hosts/nuke/api/plugin.py @@ -1236,7 +1236,7 @@ def convert_to_valid_instaces(): creator_attr["farm_chunk"] = ( node["deadlineChunkSize"].value()) if "deadlineConcurrentTasks" in node.knobs(): - creator_attr["farm_concurency"] = ( + creator_attr["farm_concurrency"] = ( node["deadlineConcurrentTasks"].value()) _remove_old_knobs(node) diff --git a/openpype/hosts/nuke/plugins/create/create_write_prerender.py b/openpype/hosts/nuke/plugins/create/create_write_prerender.py index a15f362dd1..a99bd0c4ab 100644 --- a/openpype/hosts/nuke/plugins/create/create_write_prerender.py +++ b/openpype/hosts/nuke/plugins/create/create_write_prerender.py @@ -12,6 +12,7 @@ from openpype.lib import ( UILabelDef ) from openpype.hosts.nuke import api as napi +from openpype.hosts.nuke.api.lib_rendersettings import RenderFarmSettings class CreateWritePrerender(napi.NukeWriteCreator): @@ -50,6 +51,8 @@ class CreateWritePrerender(napi.NukeWriteCreator): self._get_reviewable_bool() ] if "farm_rendering" in self.instance_attributes: + render_farm_settings = RenderFarmSettings().get_rendering_attributes() + attr_defs.extend([ UISeparatorDef(), UILabelDef("Farm rendering attributes"), @@ -59,21 +62,21 @@ class CreateWritePrerender(napi.NukeWriteCreator): label="Priority", minimum=1, maximum=99, - default=50 + default=render_farm_settings.get("priority", 50) ), NumberDef( "farm_chunk", label="Chunk size", minimum=1, maximum=99, - default=10 + default=render_farm_settings.get("chunk_size", 10) ), NumberDef( - "farm_concurency", - label="Concurent tasks", + "farm_concurrency", + label="Concurrent tasks", minimum=1, maximum=10, - default=1 + default=render_farm_settings.get("concurrent_tasks", 1) ) ]) return attr_defs diff --git a/openpype/hosts/nuke/plugins/create/create_write_render.py b/openpype/hosts/nuke/plugins/create/create_write_render.py index 481d1d2201..bbaba212c2 100644 --- a/openpype/hosts/nuke/plugins/create/create_write_render.py +++ b/openpype/hosts/nuke/plugins/create/create_write_render.py @@ -12,6 +12,7 @@ from openpype.lib import ( UILabelDef ) from openpype.hosts.nuke import api as napi +from openpype.hosts.nuke.api.lib_rendersettings import RenderFarmSettings class CreateWriteRender(napi.NukeWriteCreator): @@ -47,6 +48,8 @@ class CreateWriteRender(napi.NukeWriteCreator): self._get_reviewable_bool() ] if "farm_rendering" in self.instance_attributes: + render_farm_settings = RenderFarmSettings().get_rendering_attributes() + attr_defs.extend([ UISeparatorDef(), UILabelDef("Farm rendering attributes"), @@ -56,21 +59,21 @@ class CreateWriteRender(napi.NukeWriteCreator): label="Priority", minimum=1, maximum=99, - default=50 + default=render_farm_settings.get("priority", 50) ), NumberDef( "farm_chunk", label="Chunk size", minimum=1, maximum=99, - default=10 + default=render_farm_settings.get("chunk_size", 10) ), NumberDef( - "farm_concurency", - label="Concurent tasks", + "farm_concurrency", + label="Concurrent tasks", minimum=1, maximum=10, - default=1 + default=render_farm_settings.get("concurrent_tasks", 1) ) ]) return attr_defs From 5aaf8eb18a021b76249c561d499e3e664c983886 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 23 Feb 2023 16:13:10 +0100 Subject: [PATCH 041/184] nuke: publishing with new settings --- .../hosts/nuke/plugins/publish/collect_writes.py | 16 ++++++++-------- .../plugins/publish/submit_nuke_deadline.py | 10 +++++----- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/openpype/hosts/nuke/plugins/publish/collect_writes.py b/openpype/hosts/nuke/plugins/publish/collect_writes.py index 3054e5a30c..0aa044f06d 100644 --- a/openpype/hosts/nuke/plugins/publish/collect_writes.py +++ b/openpype/hosts/nuke/plugins/publish/collect_writes.py @@ -132,14 +132,14 @@ class CollectNukeWrites(pyblish.api.InstancePlugin): self.log.info("Publishing rendered frames ...") elif render_target == "farm": - farm_priority = creator_attributes.get("farm_priority") - farm_chunk = creator_attributes.get("farm_chunk") - farm_concurency = creator_attributes.get("farm_concurency") - instance.data.update({ - "deadlineChunkSize": farm_chunk or 1, - "deadlinePriority": farm_priority or 50, - "deadlineConcurrentTasks": farm_concurency or 0 - }) + farm_keys = ["farm_chunk", "farm_priority", "farm_concurrency"] + for key in farm_keys: + # Skip if key is not in creator attributes + if key not in creator_attributes: + continue + # Add farm attributes to instance + instance.data[key] = creator_attributes[key] + # Farm rendering instance.data["transfer"] = False instance.data["farm"] = True diff --git a/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py b/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py index faa66effbd..0f8c69629e 100644 --- a/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py @@ -162,16 +162,16 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): pass # define chunk and priority - chunk_size = instance.data["deadlineChunkSize"] - if chunk_size == 0 and self.chunk_size: + chunk_size = instance.data.get("farm_chunk") + if not chunk_size: chunk_size = self.chunk_size # define chunk and priority - concurrent_tasks = instance.data["deadlineConcurrentTasks"] - if concurrent_tasks == 0 and self.concurrent_tasks: + concurrent_tasks = instance.data.get("farm_concurrency") + if not concurrent_tasks: concurrent_tasks = self.concurrent_tasks - priority = instance.data["deadlinePriority"] + priority = instance.data.get("farm_priority") if not priority: priority = self.priority From 656318f122fe4cdc167e780b7737c8780890582b Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 23 Feb 2023 16:59:40 +0100 Subject: [PATCH 042/184] adding log to render farm settings class init --- openpype/hosts/nuke/api/lib_rendersettings.py | 10 ++++++++-- .../nuke/plugins/create/create_write_prerender.py | 4 +++- .../hosts/nuke/plugins/create/create_write_render.py | 3 ++- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/nuke/api/lib_rendersettings.py b/openpype/hosts/nuke/api/lib_rendersettings.py index 959b461c29..4d5440fe48 100644 --- a/openpype/hosts/nuke/api/lib_rendersettings.py +++ b/openpype/hosts/nuke/api/lib_rendersettings.py @@ -20,9 +20,12 @@ class RenderFarmSettings: _creator_farm_keys: list = [ "chunk_size", "priority", "concurrent_tasks"] - def __init__(self, project_settings=None): + def __init__(self, project_settings=None, log=None): """ Get project settings and active farm module """ + if log: + self.log = log + self._project_settings = ( project_settings or get_current_project_settings() ) @@ -61,8 +64,9 @@ class RenderFarmSettings: ''' return_dict = {} farm_plugin = self._farm_plugins.get(self.active_farm_module) + self.log.debug("Farm plugin: \"{}\"".format(farm_plugin)) - if farm_plugin: + if not farm_plugin: raise ValueError(( "Farm plugin \"{}\" not found in farm plugins." ).format(farm_plugin)) @@ -73,6 +77,8 @@ class RenderFarmSettings: # Get farm plugin settings farm_plugin_settings = ( module_settings["publish"][farm_plugin]) + self.log.debug( + "Farm plugin settings: \"{}\"".format(farm_plugin_settings)) # Get all keys from farm_plugin_settings for key in self._creator_farm_keys: diff --git a/openpype/hosts/nuke/plugins/create/create_write_prerender.py b/openpype/hosts/nuke/plugins/create/create_write_prerender.py index a99bd0c4ab..411a79dbf4 100644 --- a/openpype/hosts/nuke/plugins/create/create_write_prerender.py +++ b/openpype/hosts/nuke/plugins/create/create_write_prerender.py @@ -51,7 +51,9 @@ class CreateWritePrerender(napi.NukeWriteCreator): self._get_reviewable_bool() ] if "farm_rendering" in self.instance_attributes: - render_farm_settings = RenderFarmSettings().get_rendering_attributes() + render_farm_settings = RenderFarmSettings( + log=self.log).get_rendering_attributes() + attr_defs.extend([ UISeparatorDef(), diff --git a/openpype/hosts/nuke/plugins/create/create_write_render.py b/openpype/hosts/nuke/plugins/create/create_write_render.py index bbaba212c2..a51661425f 100644 --- a/openpype/hosts/nuke/plugins/create/create_write_render.py +++ b/openpype/hosts/nuke/plugins/create/create_write_render.py @@ -48,7 +48,8 @@ class CreateWriteRender(napi.NukeWriteCreator): self._get_reviewable_bool() ] if "farm_rendering" in self.instance_attributes: - render_farm_settings = RenderFarmSettings().get_rendering_attributes() + render_farm_settings = RenderFarmSettings( + log=self.log).get_rendering_attributes() attr_defs.extend([ UISeparatorDef(), From b09bc8d4a3e85eb3187b08b1672ca8bd0528dd99 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 23 Feb 2023 17:00:19 +0100 Subject: [PATCH 043/184] removing unused items from settings --- openpype/settings/defaults/project_settings/deadline.json | 1 - .../schemas/projects_schema/schema_project_deadline.json | 5 ----- 2 files changed, 6 deletions(-) diff --git a/openpype/settings/defaults/project_settings/deadline.json b/openpype/settings/defaults/project_settings/deadline.json index 7183603c4b..6b6f2d465b 100644 --- a/openpype/settings/defaults/project_settings/deadline.json +++ b/openpype/settings/defaults/project_settings/deadline.json @@ -52,7 +52,6 @@ "enabled": true, "optional": false, "active": true, - "use_published": true, "priority": 50, "chunk_size": 10, "concurrent_tasks": 1, diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json b/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json index a320dfca4f..bb5a65e1b7 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_deadline.json @@ -285,11 +285,6 @@ "key": "active", "label": "Active" }, - { - "type": "boolean", - "key": "use_published", - "label": "Use Published scene" - }, { "type": "splitter" }, From f2274ec2e963e6eb5e10233ff593804e02ab1f5c Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 23 Feb 2023 17:00:51 +0100 Subject: [PATCH 044/184] removing none existent host also fix typo --- .../plugins/publish/submit_nuke_deadline.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py b/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py index 0f8c69629e..b4b59c4c77 100644 --- a/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py @@ -23,7 +23,7 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): label = "Submit to Deadline" order = pyblish.api.IntegratorOrder + 0.1 - hosts = ["nuke", "nukestudio"] + hosts = ["nuke"] families = ["render.farm", "prerender.farm"] optional = True targets = ["local"] @@ -141,7 +141,7 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): exe_node_name, start_frame, end_frame, - responce_data=None + response_data=None ): render_dir = os.path.normpath(os.path.dirname(render_path)) batch_name = os.path.basename(script_path) @@ -152,8 +152,8 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): output_filename_0 = self.preview_fname(render_path) - if not responce_data: - responce_data = {} + if not response_data: + response_data = {} try: # Ensure render folder exists @@ -244,11 +244,11 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): "AuxFiles": [] } - if responce_data.get("_id"): + if response_data.get("_id"): payload["JobInfo"].update({ "JobType": "Normal", - "BatchName": responce_data["Props"]["Batch"], - "JobDependency0": responce_data["_id"], + "BatchName": response_data["Props"]["Batch"], + "JobDependency0": response_data["_id"], "ChunkSize": 99999999 }) From eae25bbf0b4dcf4b62bbdeab8d61ce9f4f82cfd9 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 23 Feb 2023 11:24:36 +0000 Subject: [PATCH 045/184] Publish user defined attributes option. --- .../maya/plugins/create/create_pointcache.py | 1 + .../maya/plugins/publish/collect_pointcache.py | 18 ++++++++++++++++++ .../maya/plugins/publish/extract_pointcache.py | 3 +-- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/create/create_pointcache.py b/openpype/hosts/maya/plugins/create/create_pointcache.py index 63c0490dc7..51eab94a1a 100644 --- a/openpype/hosts/maya/plugins/create/create_pointcache.py +++ b/openpype/hosts/maya/plugins/create/create_pointcache.py @@ -33,6 +33,7 @@ class CreatePointCache(plugin.Creator): self.data["refresh"] = False # Default to suspend refresh. # Add options for custom attributes + self.data["includeUserDefinedAttributes"] = True self.data["attr"] = "" self.data["attrPrefix"] = "" diff --git a/openpype/hosts/maya/plugins/publish/collect_pointcache.py b/openpype/hosts/maya/plugins/publish/collect_pointcache.py index 332992ca92..72aa37fc11 100644 --- a/openpype/hosts/maya/plugins/publish/collect_pointcache.py +++ b/openpype/hosts/maya/plugins/publish/collect_pointcache.py @@ -42,3 +42,21 @@ class CollectPointcache(pyblish.api.InstancePlugin): if proxy_set: instance.remove(proxy_set) instance.data["setMembers"].remove(proxy_set) + + # Collect user defined attributes. + if not instance.data.get("includeUserDefinedAttributes", False): + return + + all_nodes = ( + instance.data["setMembers"] + instance.data.get("proxy", []) + ) + user_defined_attributes = set() + for node in all_nodes: + attrs = cmds.listAttr(node, userDefined=True) or list() + shapes = cmds.listRelatives(node, shapes=True) or list() + for shape in shapes: + attrs.extend(cmds.listAttr(shape, userDefined=True) or list()) + + user_defined_attributes.update(attrs) + + instance.data["userDefinedAttributes"] = list(user_defined_attributes) diff --git a/openpype/hosts/maya/plugins/publish/extract_pointcache.py b/openpype/hosts/maya/plugins/publish/extract_pointcache.py index 0eb65e4226..8e794d4e17 100644 --- a/openpype/hosts/maya/plugins/publish/extract_pointcache.py +++ b/openpype/hosts/maya/plugins/publish/extract_pointcache.py @@ -39,8 +39,7 @@ class ExtractAlembic(publish.Extractor): start = float(instance.data.get("frameStartHandle", 1)) end = float(instance.data.get("frameEndHandle", 1)) - attrs = instance.data.get("attr", "").split(";") - attrs = [value for value in attrs if value.strip()] + attrs = instance.data.get("userDefinedAttributes", []) attrs += ["cbId"] attr_prefixes = instance.data.get("attrPrefix", "").split(";") From 80e19b6d430f239b95d8b14e9965ba3821e28867 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 23 Feb 2023 12:22:49 +0000 Subject: [PATCH 046/184] Include animation family --- .../maya/plugins/create/create_animation.py | 4 ++++ .../maya/plugins/publish/collect_animation.py | 16 +++++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/create/create_animation.py b/openpype/hosts/maya/plugins/create/create_animation.py index e54c12315c..a4b6e86598 100644 --- a/openpype/hosts/maya/plugins/create/create_animation.py +++ b/openpype/hosts/maya/plugins/create/create_animation.py @@ -13,6 +13,7 @@ class CreateAnimation(plugin.Creator): icon = "male" write_color_sets = False write_face_sets = False + include_user_defined_attributes = False def __init__(self, *args, **kwargs): super(CreateAnimation, self).__init__(*args, **kwargs) @@ -47,3 +48,6 @@ class CreateAnimation(plugin.Creator): # Default to write normals. self.data["writeNormals"] = True + + value = self.include_user_defined_attributes + self.data["includeUserDefinedAttributes"] = value diff --git a/openpype/hosts/maya/plugins/publish/collect_animation.py b/openpype/hosts/maya/plugins/publish/collect_animation.py index 549098863f..8f523f770b 100644 --- a/openpype/hosts/maya/plugins/publish/collect_animation.py +++ b/openpype/hosts/maya/plugins/publish/collect_animation.py @@ -46,7 +46,6 @@ class CollectAnimationOutputGeometry(pyblish.api.InstancePlugin): hierarchy = members + descendants - # Ignore certain node types (e.g. constraints) ignore = cmds.ls(hierarchy, type=self.ignore_type, long=True) if ignore: @@ -58,3 +57,18 @@ class CollectAnimationOutputGeometry(pyblish.api.InstancePlugin): if instance.data.get("farm"): instance.data["families"].append("publish.farm") + + # Collect user defined attributes. + if not instance.data.get("includeUserDefinedAttributes", False): + return + + user_defined_attributes = set() + for node in hierarchy: + attrs = cmds.listAttr(node, userDefined=True) or list() + shapes = cmds.listRelatives(node, shapes=True) or list() + for shape in shapes: + attrs.extend(cmds.listAttr(shape, userDefined=True) or list()) + + user_defined_attributes.update(attrs) + + instance.data["userDefinedAttributes"] = list(user_defined_attributes) From b12d1e9ff99f26435a167c6f17c9a9f9e2b79cdb Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 23 Feb 2023 12:23:10 +0000 Subject: [PATCH 047/184] Add project settings. --- .../hosts/maya/plugins/create/create_pointcache.py | 4 +++- openpype/settings/defaults/project_settings/maya.json | 2 ++ .../projects_schema/schemas/schema_maya_create.json | 10 ++++++++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/create/create_pointcache.py b/openpype/hosts/maya/plugins/create/create_pointcache.py index 51eab94a1a..1b8d5e6850 100644 --- a/openpype/hosts/maya/plugins/create/create_pointcache.py +++ b/openpype/hosts/maya/plugins/create/create_pointcache.py @@ -15,6 +15,7 @@ class CreatePointCache(plugin.Creator): icon = "gears" write_color_sets = False write_face_sets = False + include_user_defined_attributes = False def __init__(self, *args, **kwargs): super(CreatePointCache, self).__init__(*args, **kwargs) @@ -33,7 +34,8 @@ class CreatePointCache(plugin.Creator): self.data["refresh"] = False # Default to suspend refresh. # Add options for custom attributes - self.data["includeUserDefinedAttributes"] = True + value = self.include_user_defined_attributes + self.data["includeUserDefinedAttributes"] = value self.data["attr"] = "" self.data["attrPrefix"] = "" diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 32b141566b..2559448900 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -147,6 +147,7 @@ "enabled": true, "write_color_sets": false, "write_face_sets": false, + "include_user_defined_attributes": false, "defaults": [ "Main" ] @@ -165,6 +166,7 @@ "enabled": true, "write_color_sets": false, "write_face_sets": false, + "include_user_defined_attributes": false, "defaults": [ "Main" ] diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_create.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_create.json index e1a3082616..77a39f692f 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_create.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_create.json @@ -132,6 +132,11 @@ "key": "write_face_sets", "label": "Write Face Sets" }, + { + "type": "boolean", + "key": "include_user_defined_attributes", + "label": "Include User Defined Attributes" + }, { "type": "list", "key": "defaults", @@ -192,6 +197,11 @@ "key": "write_face_sets", "label": "Write Face Sets" }, + { + "type": "boolean", + "key": "include_user_defined_attributes", + "label": "Include User Defined Attributes" + }, { "type": "list", "key": "defaults", From ba927f068f115521d9a51564e1e3b7cb0f4a52e6 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 23 Feb 2023 15:39:15 +0000 Subject: [PATCH 048/184] BigRoy feedback --- openpype/hosts/maya/plugins/publish/collect_pointcache.py | 5 +---- openpype/hosts/maya/plugins/publish/extract_pointcache.py | 4 +++- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_pointcache.py b/openpype/hosts/maya/plugins/publish/collect_pointcache.py index 72aa37fc11..d0430c5612 100644 --- a/openpype/hosts/maya/plugins/publish/collect_pointcache.py +++ b/openpype/hosts/maya/plugins/publish/collect_pointcache.py @@ -47,11 +47,8 @@ class CollectPointcache(pyblish.api.InstancePlugin): if not instance.data.get("includeUserDefinedAttributes", False): return - all_nodes = ( - instance.data["setMembers"] + instance.data.get("proxy", []) - ) user_defined_attributes = set() - for node in all_nodes: + for node in instance: attrs = cmds.listAttr(node, userDefined=True) or list() shapes = cmds.listRelatives(node, shapes=True) or list() for shape in shapes: diff --git a/openpype/hosts/maya/plugins/publish/extract_pointcache.py b/openpype/hosts/maya/plugins/publish/extract_pointcache.py index 8e794d4e17..e551858d48 100644 --- a/openpype/hosts/maya/plugins/publish/extract_pointcache.py +++ b/openpype/hosts/maya/plugins/publish/extract_pointcache.py @@ -39,7 +39,9 @@ class ExtractAlembic(publish.Extractor): start = float(instance.data.get("frameStartHandle", 1)) end = float(instance.data.get("frameEndHandle", 1)) - attrs = instance.data.get("userDefinedAttributes", []) + attrs = instance.data.get("attr", "").split(";") + attrs = [value for value in attrs if value.strip()] + attrs += instance.data.get("userDefinedAttributes", []) attrs += ["cbId"] attr_prefixes = instance.data.get("attrPrefix", "").split(";") From 421e24055b03d3c3a910ebf74e102ef5b6144f3b Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 23 Feb 2023 17:14:36 +0000 Subject: [PATCH 049/184] Documentation --- website/docs/artist_hosts_maya.md | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/website/docs/artist_hosts_maya.md b/website/docs/artist_hosts_maya.md index 9fab845e62..73bff286b9 100644 --- a/website/docs/artist_hosts_maya.md +++ b/website/docs/artist_hosts_maya.md @@ -314,10 +314,18 @@ Example setup: ![Maya - Point Cache Example](assets/maya-pointcache_setup.png) -:::note Publish on farm -If your studio has Deadline configured, artists could choose to offload potentially long running export of pointache and publish it to the farm. -Only thing that is necessary is to toggle `Farm` property in created pointcache instance to True. -::: +#### Options + +- **Frame Start**: which frame to start the export at. +- **Frame End**: which frame to end the export at. +- **Handle Start**: additional frames to export at frame start. Ei. frame start - handle start = export start. +- **Handle Start**: additional frames to export at frame end. Ei. frame end + handle end = export end. +- **Step**: frequency of sampling the export. For example when dealing with quick movements for motion blur, a step size of less than 1 might be better. +- **Refresh**: refresh the viewport when exporting the pointcache. For performance is best to leave off, but certain situations can require to refresh the viewport, for example using the Bullet plugin. +- **Attr**: specific attributes to publish separated by `;` +- **Include User Defined Attribudes**: include all user defined attributes in the publish. +- **Farm**: if your studio has Deadline configured, artists could choose to offload potentially long running export of pointache and publish it to the farm. Only thing that is necessary is to toggle this attribute in created pointcache instance to True. +- **Priority**: Farm priority. ### Loading Point Caches From aee263d24da72655d48942ee198cf95cd94054fc Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Fri, 24 Feb 2023 08:56:52 +0000 Subject: [PATCH 050/184] Update website/docs/artist_hosts_maya.md --- website/docs/artist_hosts_maya.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/website/docs/artist_hosts_maya.md b/website/docs/artist_hosts_maya.md index 73bff286b9..c2100204b8 100644 --- a/website/docs/artist_hosts_maya.md +++ b/website/docs/artist_hosts_maya.md @@ -322,7 +322,8 @@ Example setup: - **Handle Start**: additional frames to export at frame end. Ei. frame end + handle end = export end. - **Step**: frequency of sampling the export. For example when dealing with quick movements for motion blur, a step size of less than 1 might be better. - **Refresh**: refresh the viewport when exporting the pointcache. For performance is best to leave off, but certain situations can require to refresh the viewport, for example using the Bullet plugin. -- **Attr**: specific attributes to publish separated by `;` +- **Attr**: specific attributes to publish separated by `;`. +- **AttrPrefix**: Prefix filter for determining which geometric attributes to write out, separated by `;`. - **Include User Defined Attribudes**: include all user defined attributes in the publish. - **Farm**: if your studio has Deadline configured, artists could choose to offload potentially long running export of pointache and publish it to the farm. Only thing that is necessary is to toggle this attribute in created pointcache instance to True. - **Priority**: Farm priority. From 30768de8502db34e2d03d2440262733e61002949 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Fri, 24 Feb 2023 10:15:01 +0000 Subject: [PATCH 051/184] Update website/docs/artist_hosts_maya.md Co-authored-by: Roy Nieterau --- website/docs/artist_hosts_maya.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/artist_hosts_maya.md b/website/docs/artist_hosts_maya.md index c2100204b8..07495fbc96 100644 --- a/website/docs/artist_hosts_maya.md +++ b/website/docs/artist_hosts_maya.md @@ -323,7 +323,7 @@ Example setup: - **Step**: frequency of sampling the export. For example when dealing with quick movements for motion blur, a step size of less than 1 might be better. - **Refresh**: refresh the viewport when exporting the pointcache. For performance is best to leave off, but certain situations can require to refresh the viewport, for example using the Bullet plugin. - **Attr**: specific attributes to publish separated by `;`. -- **AttrPrefix**: Prefix filter for determining which geometric attributes to write out, separated by `;`. +- **AttrPrefix**: specific attributes which start with this prefix to publish separated by `;`. - **Include User Defined Attribudes**: include all user defined attributes in the publish. - **Farm**: if your studio has Deadline configured, artists could choose to offload potentially long running export of pointache and publish it to the farm. Only thing that is necessary is to toggle this attribute in created pointcache instance to True. - **Priority**: Farm priority. From 90955e577900ad86072468f33cbf26af3b2a5116 Mon Sep 17 00:00:00 2001 From: Joseff Date: Wed, 25 Jan 2023 11:31:27 +0100 Subject: [PATCH 052/184] Refactored the generation of UE projects, plugin is now being installed in the engine. --- openpype/hosts/unreal/addon.py | 2 +- .../unreal/hooks/pre_workfile_preparation.py | 28 ++- .../UE_4.7/CommandletProject/.gitignore | 6 + .../CommandletProject.uproject | 12 + .../Config/DefaultEditor.ini | 0 .../Config/DefaultEngine.ini | 16 ++ .../CommandletProject/Config/DefaultGame.ini | 4 + .../UE_4.7/{ => OpenPype}/.gitignore | 0 .../Config/DefaultOpenPypeSettings.ini | 0 .../UE_4.7/OpenPype/Config/FilterPlugin.ini | 8 + .../Content/Python/init_unreal.py | 0 .../OpenPype}/OpenPype.uplugin | 4 +- .../UE_4.7/{ => OpenPype}/README.md | 0 .../{ => OpenPype}/Resources/openpype128.png | Bin .../{ => OpenPype}/Resources/openpype40.png | Bin .../{ => OpenPype}/Resources/openpype512.png | Bin .../Source/OpenPype/OpenPype.Build.cs | 5 +- .../OPGenerateProjectCommandlet.cpp | 140 +++++++++++ .../Private/Commandlets/OPActionResult.cpp | 41 ++++ .../OpenPype/Private/Logging/OP_Log.cpp | 1 + .../Source/OpenPype/Private/OpenPype.cpp | 42 ++-- .../Source/OpenPype/Private/OpenPypeLib.cpp | 0 .../Private/OpenPypePublishInstance.cpp | 4 +- .../OpenPypePublishInstanceFactory.cpp | 0 .../OpenPype/Private/OpenPypePythonBridge.cpp | 0 .../OpenPype/Private/OpenPypeSettings.cpp | 3 +- .../Source/OpenPype/Private/OpenPypeStyle.cpp | 3 +- .../OPGenerateProjectCommandlet.h | 60 +++++ .../Public/Commandlets/OPActionResult.h | 83 +++++++ .../Source/OpenPype/Public/Logging/OP_Log.h | 3 + .../Source/OpenPype/Public/OPConstants.h | 12 + .../Source/OpenPype/Public/OpenPype.h | 0 .../Source/OpenPype/Public/OpenPypeLib.h | 0 .../OpenPype/Public/OpenPypePublishInstance.h | 6 +- .../Public/OpenPypePublishInstanceFactory.h | 0 .../OpenPype/Public/OpenPypePythonBridge.h | 0 .../Source/OpenPype/Public/OpenPypeSettings.h | 1 - .../Source/OpenPype/Public/OpenPypeStyle.h | 0 .../OpenPype/Private/AssetContainer.cpp | 115 --------- .../Private/AssetContainerFactory.cpp | 20 -- .../Source/OpenPype/Public/AssetContainer.h | 39 --- .../OpenPype/Public/AssetContainerFactory.h | 21 -- .../UE_5.0/CommandletProject/.gitignore | 6 + .../CommandletProject.uproject | 20 ++ .../Config/DefaultEditor.ini | 0 .../Config/DefaultEngine.ini | 42 ++++ .../CommandletProject/Config/DefaultGame.ini | 4 + .../UE_5.0/{ => OpenPype}/.gitignore | 0 .../Config/DefaultOpenPypeSettings.ini | 0 .../UE_5.0/OpenPype/Config/FilterPlugin.ini | 8 + .../Content/Python/init_unreal.py | 0 .../OpenPype}/OpenPype.uplugin | 1 + .../UE_5.0/{ => OpenPype}/README.md | 0 .../{ => OpenPype}/Resources/openpype128.png | Bin .../{ => OpenPype}/Resources/openpype40.png | Bin .../{ => OpenPype}/Resources/openpype512.png | Bin .../Source/OpenPype/OpenPype.Build.cs | 5 +- .../OPGenerateProjectCommandlet.cpp | 140 +++++++++++ .../Private/Commandlets/OPActionResult.cpp | 41 ++++ .../OpenPype/Private/Logging/OP_Log.cpp | 1 + .../Source/OpenPype/Private/OpenPype.cpp | 0 .../OpenPype/Private/OpenPypeCommands.cpp | 0 .../Source/OpenPype/Private/OpenPypeLib.cpp | 0 .../Private/OpenPypePublishInstance.cpp | 2 +- .../OpenPypePublishInstanceFactory.cpp | 0 .../OpenPype/Private/OpenPypePythonBridge.cpp | 0 .../OpenPype/Private/OpenPypeSettings.cpp | 0 .../Source/OpenPype/Private/OpenPypeStyle.cpp | 0 .../OPGenerateProjectCommandlet.h | 60 +++++ .../Public/Commandlets/OPActionResult.h | 83 +++++++ .../Source/OpenPype/Public/Logging/OP_Log.h | 3 + .../Source/OpenPype/Public/OPConstants.h | 12 + .../Source/OpenPype/Public/OpenPype.h | 0 .../Source/OpenPype/Public/OpenPypeCommands.h | 0 .../Source/OpenPype/Public/OpenPypeLib.h | 0 .../OpenPype/Public/OpenPypePublishInstance.h | 6 +- .../Public/OpenPypePublishInstanceFactory.h | 0 .../OpenPype/Public/OpenPypePythonBridge.h | 0 .../Source/OpenPype/Public/OpenPypeSettings.h | 0 .../Source/OpenPype/Public/OpenPypeStyle.h | 0 .../OpenPype/Private/AssetContainer.cpp | 115 --------- .../Private/AssetContainerFactory.cpp | 20 -- .../Source/OpenPype/Public/AssetContainer.h | 39 --- .../OpenPype/Public/AssetContainerFactory.h | 21 -- openpype/hosts/unreal/lib.py | 230 +++++++++++++----- 85 files changed, 1029 insertions(+), 509 deletions(-) create mode 100644 openpype/hosts/unreal/integration/UE_4.7/CommandletProject/.gitignore create mode 100644 openpype/hosts/unreal/integration/UE_4.7/CommandletProject/CommandletProject.uproject create mode 100644 openpype/hosts/unreal/integration/UE_4.7/CommandletProject/Config/DefaultEditor.ini create mode 100644 openpype/hosts/unreal/integration/UE_4.7/CommandletProject/Config/DefaultEngine.ini create mode 100644 openpype/hosts/unreal/integration/UE_4.7/CommandletProject/Config/DefaultGame.ini rename openpype/hosts/unreal/integration/UE_4.7/{ => OpenPype}/.gitignore (100%) rename openpype/hosts/unreal/integration/UE_4.7/{ => OpenPype}/Config/DefaultOpenPypeSettings.ini (100%) create mode 100644 openpype/hosts/unreal/integration/UE_4.7/OpenPype/Config/FilterPlugin.ini rename openpype/hosts/unreal/integration/UE_4.7/{ => OpenPype}/Content/Python/init_unreal.py (100%) rename openpype/hosts/unreal/integration/{UE_5.0 => UE_4.7/OpenPype}/OpenPype.uplugin (90%) rename openpype/hosts/unreal/integration/UE_4.7/{ => OpenPype}/README.md (100%) rename openpype/hosts/unreal/integration/UE_4.7/{ => OpenPype}/Resources/openpype128.png (100%) rename openpype/hosts/unreal/integration/UE_4.7/{ => OpenPype}/Resources/openpype40.png (100%) rename openpype/hosts/unreal/integration/UE_4.7/{ => OpenPype}/Resources/openpype512.png (100%) rename openpype/hosts/unreal/integration/UE_4.7/{ => OpenPype}/Source/OpenPype/OpenPype.Build.cs (92%) create mode 100644 openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/Commandlets/Implementations/OPGenerateProjectCommandlet.cpp create mode 100644 openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/Commandlets/OPActionResult.cpp create mode 100644 openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/Logging/OP_Log.cpp rename openpype/hosts/unreal/integration/UE_4.7/{ => OpenPype}/Source/OpenPype/Private/OpenPype.cpp (79%) rename openpype/hosts/unreal/integration/UE_4.7/{ => OpenPype}/Source/OpenPype/Private/OpenPypeLib.cpp (100%) rename openpype/hosts/unreal/integration/UE_4.7/{ => OpenPype}/Source/OpenPype/Private/OpenPypePublishInstance.cpp (98%) rename openpype/hosts/unreal/integration/UE_4.7/{ => OpenPype}/Source/OpenPype/Private/OpenPypePublishInstanceFactory.cpp (100%) rename openpype/hosts/unreal/integration/UE_4.7/{ => OpenPype}/Source/OpenPype/Private/OpenPypePythonBridge.cpp (100%) rename openpype/hosts/unreal/integration/UE_4.7/{ => OpenPype}/Source/OpenPype/Private/OpenPypeSettings.cpp (91%) rename openpype/hosts/unreal/integration/UE_4.7/{ => OpenPype}/Source/OpenPype/Private/OpenPypeStyle.cpp (93%) create mode 100644 openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/Commandlets/Implementations/OPGenerateProjectCommandlet.h create mode 100644 openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/Commandlets/OPActionResult.h create mode 100644 openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/Logging/OP_Log.h create mode 100644 openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OPConstants.h rename openpype/hosts/unreal/integration/UE_4.7/{ => OpenPype}/Source/OpenPype/Public/OpenPype.h (100%) rename openpype/hosts/unreal/integration/UE_4.7/{ => OpenPype}/Source/OpenPype/Public/OpenPypeLib.h (100%) rename openpype/hosts/unreal/integration/UE_4.7/{ => OpenPype}/Source/OpenPype/Public/OpenPypePublishInstance.h (94%) rename openpype/hosts/unreal/integration/UE_4.7/{ => OpenPype}/Source/OpenPype/Public/OpenPypePublishInstanceFactory.h (100%) rename openpype/hosts/unreal/integration/UE_4.7/{ => OpenPype}/Source/OpenPype/Public/OpenPypePythonBridge.h (100%) rename openpype/hosts/unreal/integration/UE_4.7/{ => OpenPype}/Source/OpenPype/Public/OpenPypeSettings.h (97%) rename openpype/hosts/unreal/integration/UE_4.7/{ => OpenPype}/Source/OpenPype/Public/OpenPypeStyle.h (100%) delete mode 100644 openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Private/AssetContainer.cpp delete mode 100644 openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Private/AssetContainerFactory.cpp delete mode 100644 openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Public/AssetContainer.h delete mode 100644 openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Public/AssetContainerFactory.h create mode 100644 openpype/hosts/unreal/integration/UE_5.0/CommandletProject/.gitignore create mode 100644 openpype/hosts/unreal/integration/UE_5.0/CommandletProject/CommandletProject.uproject create mode 100644 openpype/hosts/unreal/integration/UE_5.0/CommandletProject/Config/DefaultEditor.ini create mode 100644 openpype/hosts/unreal/integration/UE_5.0/CommandletProject/Config/DefaultEngine.ini create mode 100644 openpype/hosts/unreal/integration/UE_5.0/CommandletProject/Config/DefaultGame.ini rename openpype/hosts/unreal/integration/UE_5.0/{ => OpenPype}/.gitignore (100%) rename openpype/hosts/unreal/integration/UE_5.0/{ => OpenPype}/Config/DefaultOpenPypeSettings.ini (100%) create mode 100644 openpype/hosts/unreal/integration/UE_5.0/OpenPype/Config/FilterPlugin.ini rename openpype/hosts/unreal/integration/UE_5.0/{ => OpenPype}/Content/Python/init_unreal.py (100%) rename openpype/hosts/unreal/integration/{UE_4.7 => UE_5.0/OpenPype}/OpenPype.uplugin (95%) rename openpype/hosts/unreal/integration/UE_5.0/{ => OpenPype}/README.md (100%) rename openpype/hosts/unreal/integration/UE_5.0/{ => OpenPype}/Resources/openpype128.png (100%) rename openpype/hosts/unreal/integration/UE_5.0/{ => OpenPype}/Resources/openpype40.png (100%) rename openpype/hosts/unreal/integration/UE_5.0/{ => OpenPype}/Resources/openpype512.png (100%) rename openpype/hosts/unreal/integration/UE_5.0/{ => OpenPype}/Source/OpenPype/OpenPype.Build.cs (92%) create mode 100644 openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/Commandlets/Implementations/OPGenerateProjectCommandlet.cpp create mode 100644 openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/Commandlets/OPActionResult.cpp create mode 100644 openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/Logging/OP_Log.cpp rename openpype/hosts/unreal/integration/UE_5.0/{ => OpenPype}/Source/OpenPype/Private/OpenPype.cpp (100%) rename openpype/hosts/unreal/integration/UE_5.0/{ => OpenPype}/Source/OpenPype/Private/OpenPypeCommands.cpp (100%) rename openpype/hosts/unreal/integration/UE_5.0/{ => OpenPype}/Source/OpenPype/Private/OpenPypeLib.cpp (100%) rename openpype/hosts/unreal/integration/UE_5.0/{ => OpenPype}/Source/OpenPype/Private/OpenPypePublishInstance.cpp (99%) rename openpype/hosts/unreal/integration/UE_5.0/{ => OpenPype}/Source/OpenPype/Private/OpenPypePublishInstanceFactory.cpp (100%) rename openpype/hosts/unreal/integration/UE_5.0/{ => OpenPype}/Source/OpenPype/Private/OpenPypePythonBridge.cpp (100%) rename openpype/hosts/unreal/integration/UE_5.0/{ => OpenPype}/Source/OpenPype/Private/OpenPypeSettings.cpp (100%) rename openpype/hosts/unreal/integration/UE_5.0/{ => OpenPype}/Source/OpenPype/Private/OpenPypeStyle.cpp (100%) create mode 100644 openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/Commandlets/Implementations/OPGenerateProjectCommandlet.h create mode 100644 openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/Commandlets/OPActionResult.h create mode 100644 openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/Logging/OP_Log.h create mode 100644 openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OPConstants.h rename openpype/hosts/unreal/integration/UE_5.0/{ => OpenPype}/Source/OpenPype/Public/OpenPype.h (100%) rename openpype/hosts/unreal/integration/UE_5.0/{ => OpenPype}/Source/OpenPype/Public/OpenPypeCommands.h (100%) rename openpype/hosts/unreal/integration/UE_5.0/{ => OpenPype}/Source/OpenPype/Public/OpenPypeLib.h (100%) rename openpype/hosts/unreal/integration/UE_5.0/{ => OpenPype}/Source/OpenPype/Public/OpenPypePublishInstance.h (94%) rename openpype/hosts/unreal/integration/UE_5.0/{ => OpenPype}/Source/OpenPype/Public/OpenPypePublishInstanceFactory.h (100%) rename openpype/hosts/unreal/integration/UE_5.0/{ => OpenPype}/Source/OpenPype/Public/OpenPypePythonBridge.h (100%) rename openpype/hosts/unreal/integration/UE_5.0/{ => OpenPype}/Source/OpenPype/Public/OpenPypeSettings.h (100%) rename openpype/hosts/unreal/integration/UE_5.0/{ => OpenPype}/Source/OpenPype/Public/OpenPypeStyle.h (100%) delete mode 100644 openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Private/AssetContainer.cpp delete mode 100644 openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Private/AssetContainerFactory.cpp delete mode 100644 openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Public/AssetContainer.h delete mode 100644 openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Public/AssetContainerFactory.h diff --git a/openpype/hosts/unreal/addon.py b/openpype/hosts/unreal/addon.py index e2c8484651..c92a44870f 100644 --- a/openpype/hosts/unreal/addon.py +++ b/openpype/hosts/unreal/addon.py @@ -17,7 +17,7 @@ class UnrealAddon(OpenPypeModule, IHostAddon): ue_plugin = "UE_5.0" if app.name[:1] == "5" else "UE_4.7" unreal_plugin_path = os.path.join( - UNREAL_ROOT_DIR, "integration", ue_plugin + UNREAL_ROOT_DIR, "integration", ue_plugin, "OpenPype" ) if not env.get("OPENPYPE_UNREAL_PLUGIN"): env["OPENPYPE_UNREAL_PLUGIN"] = unreal_plugin_path diff --git a/openpype/hosts/unreal/hooks/pre_workfile_preparation.py b/openpype/hosts/unreal/hooks/pre_workfile_preparation.py index 2dc6fb9f42..821018ba9d 100644 --- a/openpype/hosts/unreal/hooks/pre_workfile_preparation.py +++ b/openpype/hosts/unreal/hooks/pre_workfile_preparation.py @@ -119,29 +119,33 @@ class UnrealPrelaunchHook(PreLaunchHook): f"detected [ {engine_version} ]" )) - ue_path = unreal_lib.get_editor_executable_path( + ue_path = unreal_lib.get_editor_exe_path( Path(detected[engine_version]), engine_version) self.launch_context.launch_args = [ue_path.as_posix()] project_path.mkdir(parents=True, exist_ok=True) + # Set "OPENPYPE_UNREAL_PLUGIN" to current process environment for + # execution of `create_unreal_project` + if self.launch_context.env.get("OPENPYPE_UNREAL_PLUGIN"): + self.log.info(( + f"{self.signature} using OpenPype plugin from " + f"{self.launch_context.env.get('OPENPYPE_UNREAL_PLUGIN')}" + )) + env_key = "OPENPYPE_UNREAL_PLUGIN" + if self.launch_context.env.get(env_key): + os.environ[env_key] = self.launch_context.env[env_key] + + engine_path = detected[engine_version] + + unreal_lib.try_installing_plugin(Path(engine_path), engine_version) + project_file = project_path / unreal_project_filename if not project_file.is_file(): - engine_path = detected[engine_version] self.log.info(( f"{self.signature} creating unreal " f"project [ {unreal_project_name} ]" )) - # Set "OPENPYPE_UNREAL_PLUGIN" to current process environment for - # execution of `create_unreal_project` - if self.launch_context.env.get("OPENPYPE_UNREAL_PLUGIN"): - self.log.info(( - f"{self.signature} using OpenPype plugin from " - f"{self.launch_context.env.get('OPENPYPE_UNREAL_PLUGIN')}" - )) - env_key = "OPENPYPE_UNREAL_PLUGIN" - if self.launch_context.env.get(env_key): - os.environ[env_key] = self.launch_context.env[env_key] unreal_lib.create_unreal_project( unreal_project_name, diff --git a/openpype/hosts/unreal/integration/UE_4.7/CommandletProject/.gitignore b/openpype/hosts/unreal/integration/UE_4.7/CommandletProject/.gitignore new file mode 100644 index 0000000000..1004610e4f --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_4.7/CommandletProject/.gitignore @@ -0,0 +1,6 @@ +/Saved +/DerivedDataCache +/Intermediate +/Binaries +/.idea +/.vs \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_4.7/CommandletProject/CommandletProject.uproject b/openpype/hosts/unreal/integration/UE_4.7/CommandletProject/CommandletProject.uproject new file mode 100644 index 0000000000..4d75e03bf3 --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_4.7/CommandletProject/CommandletProject.uproject @@ -0,0 +1,12 @@ +{ + "FileVersion": 3, + "EngineAssociation": "4.27", + "Category": "", + "Description": "", + "Plugins": [ + { + "Name": "OpenPype", + "Enabled": true + } + ] +} \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_4.7/CommandletProject/Config/DefaultEditor.ini b/openpype/hosts/unreal/integration/UE_4.7/CommandletProject/Config/DefaultEditor.ini new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openpype/hosts/unreal/integration/UE_4.7/CommandletProject/Config/DefaultEngine.ini b/openpype/hosts/unreal/integration/UE_4.7/CommandletProject/Config/DefaultEngine.ini new file mode 100644 index 0000000000..2845baccca --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_4.7/CommandletProject/Config/DefaultEngine.ini @@ -0,0 +1,16 @@ + + +[/Script/EngineSettings.GameMapsSettings] +GameDefaultMap=/Engine/Maps/Templates/Template_Default.Template_Default + + +[/Script/HardwareTargeting.HardwareTargetingSettings] +TargetedHardwareClass=Desktop +AppliedTargetedHardwareClass=Desktop +DefaultGraphicsPerformance=Maximum +AppliedDefaultGraphicsPerformance=Maximum + +[/Script/Engine.Engine] ++ActiveGameNameRedirects=(OldGameName="TP_BlankBP",NewGameName="/Script/CommandletProject") ++ActiveGameNameRedirects=(OldGameName="/Script/TP_BlankBP",NewGameName="/Script/CommandletProject") + diff --git a/openpype/hosts/unreal/integration/UE_4.7/CommandletProject/Config/DefaultGame.ini b/openpype/hosts/unreal/integration/UE_4.7/CommandletProject/Config/DefaultGame.ini new file mode 100644 index 0000000000..40956de961 --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_4.7/CommandletProject/Config/DefaultGame.ini @@ -0,0 +1,4 @@ + + +[/Script/EngineSettings.GeneralProjectSettings] +ProjectID=95AED0BF45A918DF73ABB3BB27D25356 diff --git a/openpype/hosts/unreal/integration/UE_4.7/.gitignore b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/.gitignore similarity index 100% rename from openpype/hosts/unreal/integration/UE_4.7/.gitignore rename to openpype/hosts/unreal/integration/UE_4.7/OpenPype/.gitignore diff --git a/openpype/hosts/unreal/integration/UE_4.7/Config/DefaultOpenPypeSettings.ini b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Config/DefaultOpenPypeSettings.ini similarity index 100% rename from openpype/hosts/unreal/integration/UE_4.7/Config/DefaultOpenPypeSettings.ini rename to openpype/hosts/unreal/integration/UE_4.7/OpenPype/Config/DefaultOpenPypeSettings.ini diff --git a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Config/FilterPlugin.ini b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Config/FilterPlugin.ini new file mode 100644 index 0000000000..ccebca2f32 --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Config/FilterPlugin.ini @@ -0,0 +1,8 @@ +[FilterPlugin] +; This section lists additional files which will be packaged along with your plugin. Paths should be listed relative to the root plugin directory, and +; may include "...", "*", and "?" wildcards to match directories, files, and individual characters respectively. +; +; Examples: +; /README.txt +; /Extras/... +; /Binaries/ThirdParty/*.dll diff --git a/openpype/hosts/unreal/integration/UE_4.7/Content/Python/init_unreal.py b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Content/Python/init_unreal.py similarity index 100% rename from openpype/hosts/unreal/integration/UE_4.7/Content/Python/init_unreal.py rename to openpype/hosts/unreal/integration/UE_4.7/OpenPype/Content/Python/init_unreal.py diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype.uplugin b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/OpenPype.uplugin similarity index 90% rename from openpype/hosts/unreal/integration/UE_5.0/OpenPype.uplugin rename to openpype/hosts/unreal/integration/UE_4.7/OpenPype/OpenPype.uplugin index 4c7a74403c..23155cb74d 100644 --- a/openpype/hosts/unreal/integration/UE_5.0/OpenPype.uplugin +++ b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/OpenPype.uplugin @@ -10,10 +10,10 @@ "DocsURL": "https://openpype.io/docs/artist_hosts_unreal", "MarketplaceURL": "", "SupportURL": "https://pype.club/", + "EngineVersion": "4.27", "CanContainContent": true, "IsBetaVersion": true, - "IsExperimentalVersion": false, - "Installed": false, + "Installed": true, "Modules": [ { "Name": "OpenPype", diff --git a/openpype/hosts/unreal/integration/UE_4.7/README.md b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/README.md similarity index 100% rename from openpype/hosts/unreal/integration/UE_4.7/README.md rename to openpype/hosts/unreal/integration/UE_4.7/OpenPype/README.md diff --git a/openpype/hosts/unreal/integration/UE_4.7/Resources/openpype128.png b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Resources/openpype128.png similarity index 100% rename from openpype/hosts/unreal/integration/UE_4.7/Resources/openpype128.png rename to openpype/hosts/unreal/integration/UE_4.7/OpenPype/Resources/openpype128.png diff --git a/openpype/hosts/unreal/integration/UE_4.7/Resources/openpype40.png b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Resources/openpype40.png similarity index 100% rename from openpype/hosts/unreal/integration/UE_4.7/Resources/openpype40.png rename to openpype/hosts/unreal/integration/UE_4.7/OpenPype/Resources/openpype40.png diff --git a/openpype/hosts/unreal/integration/UE_4.7/Resources/openpype512.png b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Resources/openpype512.png similarity index 100% rename from openpype/hosts/unreal/integration/UE_4.7/Resources/openpype512.png rename to openpype/hosts/unreal/integration/UE_4.7/OpenPype/Resources/openpype512.png diff --git a/openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/OpenPype.Build.cs b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/OpenPype.Build.cs similarity index 92% rename from openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/OpenPype.Build.cs rename to openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/OpenPype.Build.cs index 46e5dcb2df..13afb11003 100644 --- a/openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/OpenPype.Build.cs +++ b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/OpenPype.Build.cs @@ -6,8 +6,8 @@ public class OpenPype : ModuleRules { public OpenPype(ReadOnlyTargetRules Target) : base(Target) { - PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; - + PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; + PublicIncludePaths.AddRange( new string[] { // ... add public include paths required here ... @@ -34,6 +34,7 @@ public class OpenPype : ModuleRules PrivateDependencyModuleNames.AddRange( new string[] { + "GameProjectGeneration", "Projects", "InputCore", "UnrealEd", diff --git a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/Commandlets/Implementations/OPGenerateProjectCommandlet.cpp b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/Commandlets/Implementations/OPGenerateProjectCommandlet.cpp new file mode 100644 index 0000000000..024a6097b3 --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/Commandlets/Implementations/OPGenerateProjectCommandlet.cpp @@ -0,0 +1,140 @@ +#include "Commandlets/Implementations/OPGenerateProjectCommandlet.h" + +#include "Editor.h" +#include "GameProjectUtils.h" +#include "OPConstants.h" +#include "Commandlets/OPActionResult.h" +#include "ProjectDescriptor.h" + +int32 UOPGenerateProjectCommandlet::Main(const FString& CommandLineParams) +{ + //Parses command line parameters & creates structure FProjectInformation + const FOPGenerateProjectParams ParsedParams = FOPGenerateProjectParams(CommandLineParams); + ProjectInformation = ParsedParams.GenerateUEProjectInformation(); + + //Creates .uproject & other UE files + EVALUATE_OP_ACTION_RESULT(TryCreateProject()); + + //Loads created .uproject + EVALUATE_OP_ACTION_RESULT(TryLoadProjectDescriptor()); + + //Adds needed plugin to .uproject + AttachPluginsToProjectDescriptor(); + + //Saves .uproject + EVALUATE_OP_ACTION_RESULT(TrySave()); + + //When we are here, there should not be problems in generating Unreal Project for OpenPype + return 0; +} + + +FOPGenerateProjectParams::FOPGenerateProjectParams(): FOPGenerateProjectParams("") +{ +} + +FOPGenerateProjectParams::FOPGenerateProjectParams(const FString& CommandLineParams): CommandLineParams( + CommandLineParams) +{ + UCommandlet::ParseCommandLine(*CommandLineParams, Tokens, Switches); +} + +FProjectInformation FOPGenerateProjectParams::GenerateUEProjectInformation() const +{ + FProjectInformation ProjectInformation = FProjectInformation(); + ProjectInformation.ProjectFilename = GetProjectFileName(); + + ProjectInformation.bShouldGenerateCode = IsSwitchPresent("GenerateCode"); + + return ProjectInformation; +} + +FString FOPGenerateProjectParams::TryGetToken(const int32 Index) const +{ + return Tokens.IsValidIndex(Index) ? Tokens[Index] : ""; +} + +FString FOPGenerateProjectParams::GetProjectFileName() const +{ + return TryGetToken(0); +} + +bool FOPGenerateProjectParams::IsSwitchPresent(const FString& Switch) const +{ + return INDEX_NONE != Switches.IndexOfByPredicate([&Switch](const FString& Item) -> bool + { + return Item.Equals(Switch); + } + ); +} + + +UOPGenerateProjectCommandlet::UOPGenerateProjectCommandlet() +{ + LogToConsole = true; +} + +FOP_ActionResult UOPGenerateProjectCommandlet::TryCreateProject() const +{ + FText FailReason; + FText FailLog; + TArray OutCreatedFiles; + + if (!GameProjectUtils::CreateProject(ProjectInformation, FailReason, FailLog, &OutCreatedFiles)) + return FOP_ActionResult(EOP_ActionResult::ProjectNotCreated, FailReason); + return FOP_ActionResult(); +} + +FOP_ActionResult UOPGenerateProjectCommandlet::TryLoadProjectDescriptor() +{ + FText FailReason; + const bool bLoaded = ProjectDescriptor.Load(ProjectInformation.ProjectFilename, FailReason); + + return FOP_ActionResult(bLoaded ? EOP_ActionResult::Ok : EOP_ActionResult::ProjectNotLoaded, FailReason); +} + +void UOPGenerateProjectCommandlet::AttachPluginsToProjectDescriptor() +{ + FPluginReferenceDescriptor OPPluginDescriptor; + OPPluginDescriptor.bEnabled = true; + OPPluginDescriptor.Name = OPConstants::OP_PluginName; + ProjectDescriptor.Plugins.Add(OPPluginDescriptor); + + FPluginReferenceDescriptor PythonPluginDescriptor; + PythonPluginDescriptor.bEnabled = true; + PythonPluginDescriptor.Name = OPConstants::PythonScript_PluginName; + ProjectDescriptor.Plugins.Add(PythonPluginDescriptor); + + FPluginReferenceDescriptor SequencerScriptingPluginDescriptor; + SequencerScriptingPluginDescriptor.bEnabled = true; + SequencerScriptingPluginDescriptor.Name = OPConstants::SequencerScripting_PluginName; + ProjectDescriptor.Plugins.Add(SequencerScriptingPluginDescriptor); + + FPluginReferenceDescriptor MovieRenderPipelinePluginDescriptor; + MovieRenderPipelinePluginDescriptor.bEnabled = true; + MovieRenderPipelinePluginDescriptor.Name = OPConstants::MovieRenderPipeline_PluginName; + ProjectDescriptor.Plugins.Add(MovieRenderPipelinePluginDescriptor); + + FPluginReferenceDescriptor EditorScriptingPluginDescriptor; + EditorScriptingPluginDescriptor.bEnabled = true; + EditorScriptingPluginDescriptor.Name = OPConstants::EditorScriptingUtils_PluginName; + ProjectDescriptor.Plugins.Add(EditorScriptingPluginDescriptor); +} + +FOP_ActionResult UOPGenerateProjectCommandlet::TrySave() +{ + FText FailReason; + const bool bSaved = ProjectDescriptor.Save(ProjectInformation.ProjectFilename, FailReason); + + return FOP_ActionResult(bSaved ? EOP_ActionResult::Ok : EOP_ActionResult::ProjectNotSaved, FailReason); +} + +FOPGenerateProjectParams UOPGenerateProjectCommandlet::ParseParameters(const FString& Params) const +{ + FOPGenerateProjectParams ParamsResult; + + TArray Tokens, Switches; + ParseCommandLine(*Params, Tokens, Switches); + + return ParamsResult; +} diff --git a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/Commandlets/OPActionResult.cpp b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/Commandlets/OPActionResult.cpp new file mode 100644 index 0000000000..9236fbb057 --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/Commandlets/OPActionResult.cpp @@ -0,0 +1,41 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "Commandlets/OPActionResult.h" +#include "Logging/OP_Log.h" + +EOP_ActionResult::Type& FOP_ActionResult::GetStatus() +{ + return Status; +} + +FText& FOP_ActionResult::GetReason() +{ + return Reason; +} + +FOP_ActionResult::FOP_ActionResult():Status(EOP_ActionResult::Type::Ok) +{ + +} + +FOP_ActionResult::FOP_ActionResult(const EOP_ActionResult::Type& InEnum):Status(InEnum) +{ + TryLog(); +} + +FOP_ActionResult::FOP_ActionResult(const EOP_ActionResult::Type& InEnum, const FText& InReason):Status(InEnum), Reason(InReason) +{ + TryLog(); +}; + +bool FOP_ActionResult::IsProblem() const +{ + return Status != EOP_ActionResult::Ok; +} + +void FOP_ActionResult::TryLog() const +{ + if(IsProblem()) + UE_LOG(LogCommandletOPGenerateProject, Error, TEXT("%s"), *Reason.ToString()); +} diff --git a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/Logging/OP_Log.cpp b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/Logging/OP_Log.cpp new file mode 100644 index 0000000000..29b1068c21 --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/Logging/OP_Log.cpp @@ -0,0 +1 @@ +#include "Logging/OP_Log.h" diff --git a/openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Private/OpenPype.cpp b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/OpenPype.cpp similarity index 79% rename from openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Private/OpenPype.cpp rename to openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/OpenPype.cpp index d06a08eb43..a510a5e3bf 100644 --- a/openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Private/OpenPype.cpp +++ b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/OpenPype.cpp @@ -16,32 +16,34 @@ static const FName OpenPypeTabName("OpenPype"); // This function is triggered when the plugin is staring up void FOpenPypeModule::StartupModule() { - FOpenPypeStyle::Initialize(); - FOpenPypeStyle::SetIcon("Logo", "openpype40"); + if (!IsRunningCommandlet()) { + FOpenPypeStyle::Initialize(); + FOpenPypeStyle::SetIcon("Logo", "openpype40"); - // Create the Extender that will add content to the menu - FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked("LevelEditor"); + // Create the Extender that will add content to the menu + FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked("LevelEditor"); - TSharedPtr MenuExtender = MakeShareable(new FExtender()); - TSharedPtr ToolbarExtender = MakeShareable(new FExtender()); + TSharedPtr MenuExtender = MakeShareable(new FExtender()); + TSharedPtr ToolbarExtender = MakeShareable(new FExtender()); - MenuExtender->AddMenuExtension( - "LevelEditor", - EExtensionHook::After, - NULL, - FMenuExtensionDelegate::CreateRaw(this, &FOpenPypeModule::AddMenuEntry) - ); - ToolbarExtender->AddToolBarExtension( - "Settings", - EExtensionHook::After, - NULL, - FToolBarExtensionDelegate::CreateRaw(this, &FOpenPypeModule::AddToobarEntry)); + MenuExtender->AddMenuExtension( + "LevelEditor", + EExtensionHook::After, + NULL, + FMenuExtensionDelegate::CreateRaw(this, &FOpenPypeModule::AddMenuEntry) + ); + ToolbarExtender->AddToolBarExtension( + "Settings", + EExtensionHook::After, + NULL, + FToolBarExtensionDelegate::CreateRaw(this, &FOpenPypeModule::AddToobarEntry)); - LevelEditorModule.GetMenuExtensibilityManager()->AddExtender(MenuExtender); - LevelEditorModule.GetToolBarExtensibilityManager()->AddExtender(ToolbarExtender); + LevelEditorModule.GetMenuExtensibilityManager()->AddExtender(MenuExtender); + LevelEditorModule.GetToolBarExtensibilityManager()->AddExtender(ToolbarExtender); - RegisterSettings(); + RegisterSettings(); + } } void FOpenPypeModule::ShutdownModule() diff --git a/openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Private/OpenPypeLib.cpp b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/OpenPypeLib.cpp similarity index 100% rename from openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Private/OpenPypeLib.cpp rename to openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/OpenPypeLib.cpp diff --git a/openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Private/OpenPypePublishInstance.cpp b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/OpenPypePublishInstance.cpp similarity index 98% rename from openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Private/OpenPypePublishInstance.cpp rename to openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/OpenPypePublishInstance.cpp index 38740f1cbd..424c4ed491 100644 --- a/openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Private/OpenPypePublishInstance.cpp +++ b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/OpenPypePublishInstance.cpp @@ -2,10 +2,10 @@ #include "OpenPypePublishInstance.h" #include "AssetRegistryModule.h" -#include "NotificationManager.h" #include "OpenPypeLib.h" #include "OpenPypeSettings.h" -#include "SNotificationList.h" +#include "Framework/Notifications/NotificationManager.h" +#include "Widgets/Notifications/SNotificationList.h" //Moves all the invalid pointers to the end to prepare them for the shrinking #define REMOVE_INVALID_ENTRIES(VAR) VAR.CompactStable(); \ diff --git a/openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Private/OpenPypePublishInstanceFactory.cpp b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/OpenPypePublishInstanceFactory.cpp similarity index 100% rename from openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Private/OpenPypePublishInstanceFactory.cpp rename to openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/OpenPypePublishInstanceFactory.cpp diff --git a/openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Private/OpenPypePythonBridge.cpp b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/OpenPypePythonBridge.cpp similarity index 100% rename from openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Private/OpenPypePythonBridge.cpp rename to openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/OpenPypePythonBridge.cpp diff --git a/openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Private/OpenPypeSettings.cpp b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/OpenPypeSettings.cpp similarity index 91% rename from openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Private/OpenPypeSettings.cpp rename to openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/OpenPypeSettings.cpp index 7134614d22..951b522308 100644 --- a/openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Private/OpenPypeSettings.cpp +++ b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/OpenPypeSettings.cpp @@ -2,8 +2,7 @@ #include "OpenPypeSettings.h" -#include "IPluginManager.h" -#include "UObjectGlobals.h" +#include "Interfaces/IPluginManager.h" /** * Mainly is used for initializing default values if the DefaultOpenPypeSettings.ini file does not exist in the saved config diff --git a/openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Private/OpenPypeStyle.cpp b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/OpenPypeStyle.cpp similarity index 93% rename from openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Private/OpenPypeStyle.cpp rename to openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/OpenPypeStyle.cpp index a51c2d6aa5..b7abc38156 100644 --- a/openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Private/OpenPypeStyle.cpp +++ b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/OpenPypeStyle.cpp @@ -43,7 +43,7 @@ const FVector2D Icon40x40(40.0f, 40.0f); TUniquePtr< FSlateStyleSet > FOpenPypeStyle::Create() { TUniquePtr< FSlateStyleSet > Style = MakeUnique(GetStyleSetName()); - Style->SetContentRoot(FPaths::ProjectPluginsDir() / TEXT("OpenPype/Resources")); + Style->SetContentRoot(FPaths::EnginePluginsDir() / TEXT("Marketplace/OpenPype/Resources")); return Style; } @@ -66,5 +66,4 @@ const ISlateStyle& FOpenPypeStyle::Get() { check(OpenPypeStyleInstance); return *OpenPypeStyleInstance; - return *OpenPypeStyleInstance; } diff --git a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/Commandlets/Implementations/OPGenerateProjectCommandlet.h b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/Commandlets/Implementations/OPGenerateProjectCommandlet.h new file mode 100644 index 0000000000..8738de6d4a --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/Commandlets/Implementations/OPGenerateProjectCommandlet.h @@ -0,0 +1,60 @@ +#pragma once + + +#include "GameProjectUtils.h" +#include "Commandlets/OPActionResult.h" +#include "ProjectDescriptor.h" +#include "Commandlets/Commandlet.h" +#include "OPGenerateProjectCommandlet.generated.h" + +struct FProjectDescriptor; +struct FProjectInformation; + +/** +* @brief Structure which parses command line parameters and generates FProjectInformation +*/ +USTRUCT() +struct FOPGenerateProjectParams +{ + GENERATED_BODY() + +private: + FString CommandLineParams; + TArray Tokens; + TArray Switches; + +public: + FOPGenerateProjectParams(); + FOPGenerateProjectParams(const FString& CommandLineParams); + + FProjectInformation GenerateUEProjectInformation() const; + +private: + FString TryGetToken(const int32 Index) const; + FString GetProjectFileName() const; + + bool IsSwitchPresent(const FString& Switch) const; +}; + +UCLASS() +class OPENPYPE_API UOPGenerateProjectCommandlet : public UCommandlet +{ + GENERATED_BODY() + +private: + FProjectInformation ProjectInformation; + FProjectDescriptor ProjectDescriptor; + +public: + UOPGenerateProjectCommandlet(); + + virtual int32 Main(const FString& CommandLineParams) override; + +private: + FOPGenerateProjectParams ParseParameters(const FString& Params) const; + FOP_ActionResult TryCreateProject() const; + FOP_ActionResult TryLoadProjectDescriptor(); + void AttachPluginsToProjectDescriptor(); + FOP_ActionResult TrySave(); +}; + diff --git a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/Commandlets/OPActionResult.h b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/Commandlets/OPActionResult.h new file mode 100644 index 0000000000..f46ba9c62a --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/Commandlets/OPActionResult.h @@ -0,0 +1,83 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "OPActionResult.generated.h" + +/** + * @brief This macro returns error code when is problem or does nothing when there is no problem. + * @param ActionResult FOP_ActionResult structure + */ +#define EVALUATE_OP_ACTION_RESULT(ActionResult) \ + if(ActionResult.IsProblem()) \ + return ActionResult.GetStatus(); + +/** +* @brief This enum values are humanly readable mapping of error codes. +* Here should be all error codes to be possible find what went wrong. +* TODO: In the future should exists an web document where is mapped error code & what problem occured & how to repair it... +*/ +UENUM() +namespace EOP_ActionResult +{ + enum Type + { + Ok, + ProjectNotCreated, + ProjectNotLoaded, + ProjectNotSaved, + //....Here insert another values + + //Do not remove! + //Usable for looping through enum values + __Last UMETA(Hidden) + }; +} + + +/** + * @brief This struct holds action result enum and optionally reason of fail + */ +USTRUCT() +struct FOP_ActionResult +{ + GENERATED_BODY() + +public: + /** @brief Default constructor usable when there is no problem */ + FOP_ActionResult(); + + /** + * @brief This constructor initializes variables & attempts to log when is error + * @param InEnum Status + */ + FOP_ActionResult(const EOP_ActionResult::Type& InEnum); + + /** + * @brief This constructor initializes variables & attempts to log when is error + * @param InEnum Status + * @param InReason Reason of potential fail + */ + FOP_ActionResult(const EOP_ActionResult::Type& InEnum, const FText& InReason); + +private: + /** @brief Action status */ + EOP_ActionResult::Type Status; + + /** @brief Optional reason of fail */ + FText Reason; + +public: + /** + * @brief Checks if there is problematic state + * @return true when status is not equal to EOP_ActionResult::Ok + */ + bool IsProblem() const; + EOP_ActionResult::Type& GetStatus(); + FText& GetReason(); + +private: + void TryLog() const; +}; + diff --git a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/Logging/OP_Log.h b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/Logging/OP_Log.h new file mode 100644 index 0000000000..4f8af3e2e6 --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/Logging/OP_Log.h @@ -0,0 +1,3 @@ +#pragma once + +DEFINE_LOG_CATEGORY_STATIC(LogCommandletOPGenerateProject, Log, All); \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OPConstants.h b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OPConstants.h new file mode 100644 index 0000000000..21a033e426 --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OPConstants.h @@ -0,0 +1,12 @@ +#pragma once + +namespace OPConstants +{ + const FString OP_PluginName = "OpenPype"; + const FString PythonScript_PluginName = "PythonScriptPlugin"; + const FString SequencerScripting_PluginName = "SequencerScripting"; + const FString MovieRenderPipeline_PluginName = "MovieRenderPipeline"; + const FString EditorScriptingUtils_PluginName = "EditorScriptingUtilities"; +} + + diff --git a/openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Public/OpenPype.h b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OpenPype.h similarity index 100% rename from openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Public/OpenPype.h rename to openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OpenPype.h diff --git a/openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Public/OpenPypeLib.h b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OpenPypeLib.h similarity index 100% rename from openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Public/OpenPypeLib.h rename to openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OpenPypeLib.h diff --git a/openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Public/OpenPypePublishInstance.h b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OpenPypePublishInstance.h similarity index 94% rename from openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Public/OpenPypePublishInstance.h rename to openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OpenPypePublishInstance.h index cd414fe2cc..16b3194b96 100644 --- a/openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Public/OpenPypePublishInstance.h +++ b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OpenPypePublishInstance.h @@ -16,7 +16,7 @@ public: * * @return - Set of UObjects. Careful! They are returning raw pointers. Seems like an issue in UE5 */ - UFUNCTION(BlueprintCallable, BlueprintPure) + UFUNCTION(BlueprintCallable, BlueprintPure, Category="Python") TSet GetInternalAssets() const { //For some reason it can only return Raw Pointers? Seems like an issue which they haven't fixed. @@ -33,7 +33,7 @@ public: * * @return - TSet of assets (UObjects). Careful! They are returning raw pointers. Seems like an issue in UE5 */ - UFUNCTION(BlueprintCallable, BlueprintPure) + UFUNCTION(BlueprintCallable, BlueprintPure, Category="Python") TSet GetExternalAssets() const { //For some reason it can only return Raw Pointers? Seems like an issue which they haven't fixed. @@ -53,7 +53,7 @@ public: * * @attention If the bAddExternalAssets variable is false, external assets won't be included! */ - UFUNCTION(BlueprintCallable, BlueprintPure) + UFUNCTION(BlueprintCallable, BlueprintPure, Category="Python") TSet GetAllAssets() const { const TSet>& IteratedSet = bAddExternalAssets diff --git a/openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Public/OpenPypePublishInstanceFactory.h b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OpenPypePublishInstanceFactory.h similarity index 100% rename from openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Public/OpenPypePublishInstanceFactory.h rename to openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OpenPypePublishInstanceFactory.h diff --git a/openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Public/OpenPypePythonBridge.h b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OpenPypePythonBridge.h similarity index 100% rename from openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Public/OpenPypePythonBridge.h rename to openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OpenPypePythonBridge.h diff --git a/openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Public/OpenPypeSettings.h b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OpenPypeSettings.h similarity index 97% rename from openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Public/OpenPypeSettings.h rename to openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OpenPypeSettings.h index 2df6c887cf..9bdcfb2399 100644 --- a/openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Public/OpenPypeSettings.h +++ b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OpenPypeSettings.h @@ -3,7 +3,6 @@ #pragma once #include "CoreMinimal.h" -#include "Object.h" #include "OpenPypeSettings.generated.h" #define OPENPYPE_SETTINGS_FILEPATH IPluginManager::Get().FindPlugin("OpenPype")->GetBaseDir() / TEXT("Config") / TEXT("DefaultOpenPypeSettings.ini") diff --git a/openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Public/OpenPypeStyle.h b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OpenPypeStyle.h similarity index 100% rename from openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Public/OpenPypeStyle.h rename to openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OpenPypeStyle.h diff --git a/openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Private/AssetContainer.cpp b/openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Private/AssetContainer.cpp deleted file mode 100644 index c766f87a8e..0000000000 --- a/openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Private/AssetContainer.cpp +++ /dev/null @@ -1,115 +0,0 @@ -// Fill out your copyright notice in the Description page of Project Settings. - -#include "AssetContainer.h" -#include "AssetRegistryModule.h" -#include "Misc/PackageName.h" -#include "Engine.h" -#include "Containers/UnrealString.h" - -UAssetContainer::UAssetContainer(const FObjectInitializer& ObjectInitializer) -: UAssetUserData(ObjectInitializer) -{ - FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); - FString path = UAssetContainer::GetPathName(); - UE_LOG(LogTemp, Warning, TEXT("UAssetContainer %s"), *path); - FARFilter Filter; - Filter.PackagePaths.Add(FName(*path)); - - AssetRegistryModule.Get().OnAssetAdded().AddUObject(this, &UAssetContainer::OnAssetAdded); - AssetRegistryModule.Get().OnAssetRemoved().AddUObject(this, &UAssetContainer::OnAssetRemoved); - AssetRegistryModule.Get().OnAssetRenamed().AddUObject(this, &UAssetContainer::OnAssetRenamed); -} - -void UAssetContainer::OnAssetAdded(const FAssetData& AssetData) -{ - TArray split; - - // get directory of current container - FString selfFullPath = UAssetContainer::GetPathName(); - FString selfDir = FPackageName::GetLongPackagePath(*selfFullPath); - - // get asset path and class - FString assetPath = AssetData.GetFullName(); - FString assetFName = AssetData.AssetClass.ToString(); - - // split path - assetPath.ParseIntoArray(split, TEXT(" "), true); - - FString assetDir = FPackageName::GetLongPackagePath(*split[1]); - - // take interest only in paths starting with path of current container - if (assetDir.StartsWith(*selfDir)) - { - // exclude self - if (assetFName != "AssetContainer") - { - assets.Add(assetPath); - assetsData.Add(AssetData); - UE_LOG(LogTemp, Log, TEXT("%s: asset added to %s"), *selfFullPath, *selfDir); - } - } -} - -void UAssetContainer::OnAssetRemoved(const FAssetData& AssetData) -{ - TArray split; - - // get directory of current container - FString selfFullPath = UAssetContainer::GetPathName(); - FString selfDir = FPackageName::GetLongPackagePath(*selfFullPath); - - // get asset path and class - FString assetPath = AssetData.GetFullName(); - FString assetFName = AssetData.AssetClass.ToString(); - - // split path - assetPath.ParseIntoArray(split, TEXT(" "), true); - - FString assetDir = FPackageName::GetLongPackagePath(*split[1]); - - // take interest only in paths starting with path of current container - FString path = UAssetContainer::GetPathName(); - FString lpp = FPackageName::GetLongPackagePath(*path); - - if (assetDir.StartsWith(*selfDir)) - { - // exclude self - if (assetFName != "AssetContainer") - { - // UE_LOG(LogTemp, Warning, TEXT("%s: asset removed"), *lpp); - assets.Remove(assetPath); - assetsData.Remove(AssetData); - } - } -} - -void UAssetContainer::OnAssetRenamed(const FAssetData& AssetData, const FString& str) -{ - TArray split; - - // get directory of current container - FString selfFullPath = UAssetContainer::GetPathName(); - FString selfDir = FPackageName::GetLongPackagePath(*selfFullPath); - - // get asset path and class - FString assetPath = AssetData.GetFullName(); - FString assetFName = AssetData.AssetClass.ToString(); - - // split path - assetPath.ParseIntoArray(split, TEXT(" "), true); - - FString assetDir = FPackageName::GetLongPackagePath(*split[1]); - if (assetDir.StartsWith(*selfDir)) - { - // exclude self - if (assetFName != "AssetContainer") - { - - assets.Remove(str); - assets.Add(assetPath); - assetsData.Remove(AssetData); - // UE_LOG(LogTemp, Warning, TEXT("%s: asset renamed %s"), *lpp, *str); - } - } -} - diff --git a/openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Private/AssetContainerFactory.cpp b/openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Private/AssetContainerFactory.cpp deleted file mode 100644 index b943150bdd..0000000000 --- a/openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Private/AssetContainerFactory.cpp +++ /dev/null @@ -1,20 +0,0 @@ -#include "AssetContainerFactory.h" -#include "AssetContainer.h" - -UAssetContainerFactory::UAssetContainerFactory(const FObjectInitializer& ObjectInitializer) - : UFactory(ObjectInitializer) -{ - SupportedClass = UAssetContainer::StaticClass(); - bCreateNew = false; - bEditorImport = true; -} - -UObject* UAssetContainerFactory::FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) -{ - UAssetContainer* AssetContainer = NewObject(InParent, Class, Name, Flags); - return AssetContainer; -} - -bool UAssetContainerFactory::ShouldShowInNewMenu() const { - return false; -} diff --git a/openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Public/AssetContainer.h b/openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Public/AssetContainer.h deleted file mode 100644 index 3c2a360c78..0000000000 --- a/openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Public/AssetContainer.h +++ /dev/null @@ -1,39 +0,0 @@ -// Fill out your copyright notice in the Description page of Project Settings. - -#pragma once - -#include "CoreMinimal.h" -#include "UObject/NoExportTypes.h" -#include "Engine/AssetUserData.h" -#include "AssetData.h" -#include "AssetContainer.generated.h" - -/** - * - */ -UCLASS(Blueprintable) -class OPENPYPE_API UAssetContainer : public UAssetUserData -{ - GENERATED_BODY() - -public: - - UAssetContainer(const FObjectInitializer& ObjectInitalizer); - // ~UAssetContainer(); - - UPROPERTY(EditAnywhere, BlueprintReadOnly) - TArray assets; - - // There seems to be no reflection option to expose array of FAssetData - /* - UPROPERTY(Transient, BlueprintReadOnly, Category = "Python", meta=(DisplayName="Assets Data")) - TArray assetsData; - */ -private: - TArray assetsData; - void OnAssetAdded(const FAssetData& AssetData); - void OnAssetRemoved(const FAssetData& AssetData); - void OnAssetRenamed(const FAssetData& AssetData, const FString& str); -}; - - diff --git a/openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Public/AssetContainerFactory.h b/openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Public/AssetContainerFactory.h deleted file mode 100644 index 331ce6bb50..0000000000 --- a/openpype/hosts/unreal/integration/UE_4.7/Source/OpenPype/Public/AssetContainerFactory.h +++ /dev/null @@ -1,21 +0,0 @@ -// Fill out your copyright notice in the Description page of Project Settings. - -#pragma once - -#include "CoreMinimal.h" -#include "Factories/Factory.h" -#include "AssetContainerFactory.generated.h" - -/** - * - */ -UCLASS() -class OPENPYPE_API UAssetContainerFactory : public UFactory -{ - GENERATED_BODY() - -public: - UAssetContainerFactory(const FObjectInitializer& ObjectInitializer); - virtual UObject* FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override; - virtual bool ShouldShowInNewMenu() const override; -}; \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_5.0/CommandletProject/.gitignore b/openpype/hosts/unreal/integration/UE_5.0/CommandletProject/.gitignore new file mode 100644 index 0000000000..1004610e4f --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.0/CommandletProject/.gitignore @@ -0,0 +1,6 @@ +/Saved +/DerivedDataCache +/Intermediate +/Binaries +/.idea +/.vs \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_5.0/CommandletProject/CommandletProject.uproject b/openpype/hosts/unreal/integration/UE_5.0/CommandletProject/CommandletProject.uproject new file mode 100644 index 0000000000..c8dc1c673e --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.0/CommandletProject/CommandletProject.uproject @@ -0,0 +1,20 @@ +{ + "FileVersion": 3, + "EngineAssociation": "5.0", + "Category": "", + "Description": "", + "Plugins": [ + { + "Name": "ModelingToolsEditorMode", + "Enabled": true, + "TargetAllowList": [ + "Editor" + ] + }, + { + "Name": "OpenPype", + "Enabled": true, + "Type": "Editor" + } + ] +} \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_5.0/CommandletProject/Config/DefaultEditor.ini b/openpype/hosts/unreal/integration/UE_5.0/CommandletProject/Config/DefaultEditor.ini new file mode 100644 index 0000000000..e69de29bb2 diff --git a/openpype/hosts/unreal/integration/UE_5.0/CommandletProject/Config/DefaultEngine.ini b/openpype/hosts/unreal/integration/UE_5.0/CommandletProject/Config/DefaultEngine.ini new file mode 100644 index 0000000000..3f5357dac4 --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.0/CommandletProject/Config/DefaultEngine.ini @@ -0,0 +1,42 @@ + + +[/Script/EngineSettings.GameMapsSettings] +GameDefaultMap=/Engine/Maps/Templates/OpenWorld + + +[/Script/HardwareTargeting.HardwareTargetingSettings] +TargetedHardwareClass=Desktop +AppliedTargetedHardwareClass=Desktop +DefaultGraphicsPerformance=Maximum +AppliedDefaultGraphicsPerformance=Maximum + +[/Script/WindowsTargetPlatform.WindowsTargetSettings] +DefaultGraphicsRHI=DefaultGraphicsRHI_DX12 + +[/Script/Engine.RendererSettings] +r.GenerateMeshDistanceFields=True +r.DynamicGlobalIlluminationMethod=1 +r.ReflectionMethod=1 +r.Shadow.Virtual.Enable=1 + +[/Script/WorldPartitionEditor.WorldPartitionEditorSettings] +CommandletClass=Class'/Script/UnrealEd.WorldPartitionConvertCommandlet' + +[/Script/Engine.Engine] ++ActiveGameNameRedirects=(OldGameName="TP_BlankBP",NewGameName="/Script/CommandletProject") ++ActiveGameNameRedirects=(OldGameName="/Script/TP_BlankBP",NewGameName="/Script/CommandletProject") + +[/Script/AndroidFileServerEditor.AndroidFileServerRuntimeSettings] +bEnablePlugin=True +bAllowNetworkConnection=True +SecurityToken=684C16AF4BD96F1D6828A6B067693175 +bIncludeInShipping=False +bAllowExternalStartInShipping=False +bCompileAFSProject=False +bUseCompression=False +bLogFiles=False +bReportStats=False +ConnectionType=USBOnly +bUseManualIPAddress=False +ManualIPAddress= + diff --git a/openpype/hosts/unreal/integration/UE_5.0/CommandletProject/Config/DefaultGame.ini b/openpype/hosts/unreal/integration/UE_5.0/CommandletProject/Config/DefaultGame.ini new file mode 100644 index 0000000000..c661b739ab --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.0/CommandletProject/Config/DefaultGame.ini @@ -0,0 +1,4 @@ + + +[/Script/EngineSettings.GeneralProjectSettings] +ProjectID=D528076140C577E5807BA5BA135366BB diff --git a/openpype/hosts/unreal/integration/UE_5.0/.gitignore b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/.gitignore similarity index 100% rename from openpype/hosts/unreal/integration/UE_5.0/.gitignore rename to openpype/hosts/unreal/integration/UE_5.0/OpenPype/.gitignore diff --git a/openpype/hosts/unreal/integration/UE_5.0/Config/DefaultOpenPypeSettings.ini b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Config/DefaultOpenPypeSettings.ini similarity index 100% rename from openpype/hosts/unreal/integration/UE_5.0/Config/DefaultOpenPypeSettings.ini rename to openpype/hosts/unreal/integration/UE_5.0/OpenPype/Config/DefaultOpenPypeSettings.ini diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Config/FilterPlugin.ini b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Config/FilterPlugin.ini new file mode 100644 index 0000000000..ccebca2f32 --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Config/FilterPlugin.ini @@ -0,0 +1,8 @@ +[FilterPlugin] +; This section lists additional files which will be packaged along with your plugin. Paths should be listed relative to the root plugin directory, and +; may include "...", "*", and "?" wildcards to match directories, files, and individual characters respectively. +; +; Examples: +; /README.txt +; /Extras/... +; /Binaries/ThirdParty/*.dll diff --git a/openpype/hosts/unreal/integration/UE_5.0/Content/Python/init_unreal.py b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Content/Python/init_unreal.py similarity index 100% rename from openpype/hosts/unreal/integration/UE_5.0/Content/Python/init_unreal.py rename to openpype/hosts/unreal/integration/UE_5.0/OpenPype/Content/Python/init_unreal.py diff --git a/openpype/hosts/unreal/integration/UE_4.7/OpenPype.uplugin b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/OpenPype.uplugin similarity index 95% rename from openpype/hosts/unreal/integration/UE_4.7/OpenPype.uplugin rename to openpype/hosts/unreal/integration/UE_5.0/OpenPype/OpenPype.uplugin index 4c7a74403c..b89eb43949 100644 --- a/openpype/hosts/unreal/integration/UE_4.7/OpenPype.uplugin +++ b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/OpenPype.uplugin @@ -11,6 +11,7 @@ "MarketplaceURL": "", "SupportURL": "https://pype.club/", "CanContainContent": true, + "EngineVersion": "5.0", "IsBetaVersion": true, "IsExperimentalVersion": false, "Installed": false, diff --git a/openpype/hosts/unreal/integration/UE_5.0/README.md b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/README.md similarity index 100% rename from openpype/hosts/unreal/integration/UE_5.0/README.md rename to openpype/hosts/unreal/integration/UE_5.0/OpenPype/README.md diff --git a/openpype/hosts/unreal/integration/UE_5.0/Resources/openpype128.png b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Resources/openpype128.png similarity index 100% rename from openpype/hosts/unreal/integration/UE_5.0/Resources/openpype128.png rename to openpype/hosts/unreal/integration/UE_5.0/OpenPype/Resources/openpype128.png diff --git a/openpype/hosts/unreal/integration/UE_5.0/Resources/openpype40.png b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Resources/openpype40.png similarity index 100% rename from openpype/hosts/unreal/integration/UE_5.0/Resources/openpype40.png rename to openpype/hosts/unreal/integration/UE_5.0/OpenPype/Resources/openpype40.png diff --git a/openpype/hosts/unreal/integration/UE_5.0/Resources/openpype512.png b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Resources/openpype512.png similarity index 100% rename from openpype/hosts/unreal/integration/UE_5.0/Resources/openpype512.png rename to openpype/hosts/unreal/integration/UE_5.0/OpenPype/Resources/openpype512.png diff --git a/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/OpenPype.Build.cs b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/OpenPype.Build.cs similarity index 92% rename from openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/OpenPype.Build.cs rename to openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/OpenPype.Build.cs index d853ec028f..99c1c7b306 100644 --- a/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/OpenPype.Build.cs +++ b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/OpenPype.Build.cs @@ -10,7 +10,7 @@ public class OpenPype : ModuleRules bLegacyPublicIncludePaths = false; ShadowVariableWarningLevel = WarningLevel.Error; PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; - IncludeOrderVersion = EngineIncludeOrderVersion.Unreal5_0; + //IncludeOrderVersion = EngineIncludeOrderVersion.Unreal5_0; PublicIncludePaths.AddRange( new string[] { @@ -30,14 +30,15 @@ public class OpenPype : ModuleRules new string[] { "Core", + "CoreUObject" // ... add other public dependencies that you statically link with here ... } ); - PrivateDependencyModuleNames.AddRange( new string[] { + "GameProjectGeneration", "Projects", "InputCore", "EditorFramework", diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/Commandlets/Implementations/OPGenerateProjectCommandlet.cpp b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/Commandlets/Implementations/OPGenerateProjectCommandlet.cpp new file mode 100644 index 0000000000..024a6097b3 --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/Commandlets/Implementations/OPGenerateProjectCommandlet.cpp @@ -0,0 +1,140 @@ +#include "Commandlets/Implementations/OPGenerateProjectCommandlet.h" + +#include "Editor.h" +#include "GameProjectUtils.h" +#include "OPConstants.h" +#include "Commandlets/OPActionResult.h" +#include "ProjectDescriptor.h" + +int32 UOPGenerateProjectCommandlet::Main(const FString& CommandLineParams) +{ + //Parses command line parameters & creates structure FProjectInformation + const FOPGenerateProjectParams ParsedParams = FOPGenerateProjectParams(CommandLineParams); + ProjectInformation = ParsedParams.GenerateUEProjectInformation(); + + //Creates .uproject & other UE files + EVALUATE_OP_ACTION_RESULT(TryCreateProject()); + + //Loads created .uproject + EVALUATE_OP_ACTION_RESULT(TryLoadProjectDescriptor()); + + //Adds needed plugin to .uproject + AttachPluginsToProjectDescriptor(); + + //Saves .uproject + EVALUATE_OP_ACTION_RESULT(TrySave()); + + //When we are here, there should not be problems in generating Unreal Project for OpenPype + return 0; +} + + +FOPGenerateProjectParams::FOPGenerateProjectParams(): FOPGenerateProjectParams("") +{ +} + +FOPGenerateProjectParams::FOPGenerateProjectParams(const FString& CommandLineParams): CommandLineParams( + CommandLineParams) +{ + UCommandlet::ParseCommandLine(*CommandLineParams, Tokens, Switches); +} + +FProjectInformation FOPGenerateProjectParams::GenerateUEProjectInformation() const +{ + FProjectInformation ProjectInformation = FProjectInformation(); + ProjectInformation.ProjectFilename = GetProjectFileName(); + + ProjectInformation.bShouldGenerateCode = IsSwitchPresent("GenerateCode"); + + return ProjectInformation; +} + +FString FOPGenerateProjectParams::TryGetToken(const int32 Index) const +{ + return Tokens.IsValidIndex(Index) ? Tokens[Index] : ""; +} + +FString FOPGenerateProjectParams::GetProjectFileName() const +{ + return TryGetToken(0); +} + +bool FOPGenerateProjectParams::IsSwitchPresent(const FString& Switch) const +{ + return INDEX_NONE != Switches.IndexOfByPredicate([&Switch](const FString& Item) -> bool + { + return Item.Equals(Switch); + } + ); +} + + +UOPGenerateProjectCommandlet::UOPGenerateProjectCommandlet() +{ + LogToConsole = true; +} + +FOP_ActionResult UOPGenerateProjectCommandlet::TryCreateProject() const +{ + FText FailReason; + FText FailLog; + TArray OutCreatedFiles; + + if (!GameProjectUtils::CreateProject(ProjectInformation, FailReason, FailLog, &OutCreatedFiles)) + return FOP_ActionResult(EOP_ActionResult::ProjectNotCreated, FailReason); + return FOP_ActionResult(); +} + +FOP_ActionResult UOPGenerateProjectCommandlet::TryLoadProjectDescriptor() +{ + FText FailReason; + const bool bLoaded = ProjectDescriptor.Load(ProjectInformation.ProjectFilename, FailReason); + + return FOP_ActionResult(bLoaded ? EOP_ActionResult::Ok : EOP_ActionResult::ProjectNotLoaded, FailReason); +} + +void UOPGenerateProjectCommandlet::AttachPluginsToProjectDescriptor() +{ + FPluginReferenceDescriptor OPPluginDescriptor; + OPPluginDescriptor.bEnabled = true; + OPPluginDescriptor.Name = OPConstants::OP_PluginName; + ProjectDescriptor.Plugins.Add(OPPluginDescriptor); + + FPluginReferenceDescriptor PythonPluginDescriptor; + PythonPluginDescriptor.bEnabled = true; + PythonPluginDescriptor.Name = OPConstants::PythonScript_PluginName; + ProjectDescriptor.Plugins.Add(PythonPluginDescriptor); + + FPluginReferenceDescriptor SequencerScriptingPluginDescriptor; + SequencerScriptingPluginDescriptor.bEnabled = true; + SequencerScriptingPluginDescriptor.Name = OPConstants::SequencerScripting_PluginName; + ProjectDescriptor.Plugins.Add(SequencerScriptingPluginDescriptor); + + FPluginReferenceDescriptor MovieRenderPipelinePluginDescriptor; + MovieRenderPipelinePluginDescriptor.bEnabled = true; + MovieRenderPipelinePluginDescriptor.Name = OPConstants::MovieRenderPipeline_PluginName; + ProjectDescriptor.Plugins.Add(MovieRenderPipelinePluginDescriptor); + + FPluginReferenceDescriptor EditorScriptingPluginDescriptor; + EditorScriptingPluginDescriptor.bEnabled = true; + EditorScriptingPluginDescriptor.Name = OPConstants::EditorScriptingUtils_PluginName; + ProjectDescriptor.Plugins.Add(EditorScriptingPluginDescriptor); +} + +FOP_ActionResult UOPGenerateProjectCommandlet::TrySave() +{ + FText FailReason; + const bool bSaved = ProjectDescriptor.Save(ProjectInformation.ProjectFilename, FailReason); + + return FOP_ActionResult(bSaved ? EOP_ActionResult::Ok : EOP_ActionResult::ProjectNotSaved, FailReason); +} + +FOPGenerateProjectParams UOPGenerateProjectCommandlet::ParseParameters(const FString& Params) const +{ + FOPGenerateProjectParams ParamsResult; + + TArray Tokens, Switches; + ParseCommandLine(*Params, Tokens, Switches); + + return ParamsResult; +} diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/Commandlets/OPActionResult.cpp b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/Commandlets/OPActionResult.cpp new file mode 100644 index 0000000000..9236fbb057 --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/Commandlets/OPActionResult.cpp @@ -0,0 +1,41 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "Commandlets/OPActionResult.h" +#include "Logging/OP_Log.h" + +EOP_ActionResult::Type& FOP_ActionResult::GetStatus() +{ + return Status; +} + +FText& FOP_ActionResult::GetReason() +{ + return Reason; +} + +FOP_ActionResult::FOP_ActionResult():Status(EOP_ActionResult::Type::Ok) +{ + +} + +FOP_ActionResult::FOP_ActionResult(const EOP_ActionResult::Type& InEnum):Status(InEnum) +{ + TryLog(); +} + +FOP_ActionResult::FOP_ActionResult(const EOP_ActionResult::Type& InEnum, const FText& InReason):Status(InEnum), Reason(InReason) +{ + TryLog(); +}; + +bool FOP_ActionResult::IsProblem() const +{ + return Status != EOP_ActionResult::Ok; +} + +void FOP_ActionResult::TryLog() const +{ + if(IsProblem()) + UE_LOG(LogCommandletOPGenerateProject, Error, TEXT("%s"), *Reason.ToString()); +} diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/Logging/OP_Log.cpp b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/Logging/OP_Log.cpp new file mode 100644 index 0000000000..29b1068c21 --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/Logging/OP_Log.cpp @@ -0,0 +1 @@ +#include "Logging/OP_Log.h" diff --git a/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Private/OpenPype.cpp b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPype.cpp similarity index 100% rename from openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Private/OpenPype.cpp rename to openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPype.cpp diff --git a/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Private/OpenPypeCommands.cpp b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPypeCommands.cpp similarity index 100% rename from openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Private/OpenPypeCommands.cpp rename to openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPypeCommands.cpp diff --git a/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Private/OpenPypeLib.cpp b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPypeLib.cpp similarity index 100% rename from openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Private/OpenPypeLib.cpp rename to openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPypeLib.cpp diff --git a/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Private/OpenPypePublishInstance.cpp b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPypePublishInstance.cpp similarity index 99% rename from openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Private/OpenPypePublishInstance.cpp rename to openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPypePublishInstance.cpp index 0b56111a49..e6a85002c7 100644 --- a/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Private/OpenPypePublishInstance.cpp +++ b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPypePublishInstance.cpp @@ -55,7 +55,7 @@ void UOpenPypePublishInstance::OnAssetCreated(const FAssetData& InAssetData) if (!IsValid(Asset)) { UE_LOG(LogAssetData, Warning, TEXT("Asset \"%s\" is not valid! Skipping the addition."), - *InAssetData.GetObjectPathString()); + *InAssetData.ObjectPath.ToString()); return; } diff --git a/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Private/OpenPypePublishInstanceFactory.cpp b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPypePublishInstanceFactory.cpp similarity index 100% rename from openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Private/OpenPypePublishInstanceFactory.cpp rename to openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPypePublishInstanceFactory.cpp diff --git a/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Private/OpenPypePythonBridge.cpp b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPypePythonBridge.cpp similarity index 100% rename from openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Private/OpenPypePythonBridge.cpp rename to openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPypePythonBridge.cpp diff --git a/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Private/OpenPypeSettings.cpp b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPypeSettings.cpp similarity index 100% rename from openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Private/OpenPypeSettings.cpp rename to openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPypeSettings.cpp diff --git a/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Private/OpenPypeStyle.cpp b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPypeStyle.cpp similarity index 100% rename from openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Private/OpenPypeStyle.cpp rename to openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPypeStyle.cpp diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/Commandlets/Implementations/OPGenerateProjectCommandlet.h b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/Commandlets/Implementations/OPGenerateProjectCommandlet.h new file mode 100644 index 0000000000..8738de6d4a --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/Commandlets/Implementations/OPGenerateProjectCommandlet.h @@ -0,0 +1,60 @@ +#pragma once + + +#include "GameProjectUtils.h" +#include "Commandlets/OPActionResult.h" +#include "ProjectDescriptor.h" +#include "Commandlets/Commandlet.h" +#include "OPGenerateProjectCommandlet.generated.h" + +struct FProjectDescriptor; +struct FProjectInformation; + +/** +* @brief Structure which parses command line parameters and generates FProjectInformation +*/ +USTRUCT() +struct FOPGenerateProjectParams +{ + GENERATED_BODY() + +private: + FString CommandLineParams; + TArray Tokens; + TArray Switches; + +public: + FOPGenerateProjectParams(); + FOPGenerateProjectParams(const FString& CommandLineParams); + + FProjectInformation GenerateUEProjectInformation() const; + +private: + FString TryGetToken(const int32 Index) const; + FString GetProjectFileName() const; + + bool IsSwitchPresent(const FString& Switch) const; +}; + +UCLASS() +class OPENPYPE_API UOPGenerateProjectCommandlet : public UCommandlet +{ + GENERATED_BODY() + +private: + FProjectInformation ProjectInformation; + FProjectDescriptor ProjectDescriptor; + +public: + UOPGenerateProjectCommandlet(); + + virtual int32 Main(const FString& CommandLineParams) override; + +private: + FOPGenerateProjectParams ParseParameters(const FString& Params) const; + FOP_ActionResult TryCreateProject() const; + FOP_ActionResult TryLoadProjectDescriptor(); + void AttachPluginsToProjectDescriptor(); + FOP_ActionResult TrySave(); +}; + diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/Commandlets/OPActionResult.h b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/Commandlets/OPActionResult.h new file mode 100644 index 0000000000..f46ba9c62a --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/Commandlets/OPActionResult.h @@ -0,0 +1,83 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "OPActionResult.generated.h" + +/** + * @brief This macro returns error code when is problem or does nothing when there is no problem. + * @param ActionResult FOP_ActionResult structure + */ +#define EVALUATE_OP_ACTION_RESULT(ActionResult) \ + if(ActionResult.IsProblem()) \ + return ActionResult.GetStatus(); + +/** +* @brief This enum values are humanly readable mapping of error codes. +* Here should be all error codes to be possible find what went wrong. +* TODO: In the future should exists an web document where is mapped error code & what problem occured & how to repair it... +*/ +UENUM() +namespace EOP_ActionResult +{ + enum Type + { + Ok, + ProjectNotCreated, + ProjectNotLoaded, + ProjectNotSaved, + //....Here insert another values + + //Do not remove! + //Usable for looping through enum values + __Last UMETA(Hidden) + }; +} + + +/** + * @brief This struct holds action result enum and optionally reason of fail + */ +USTRUCT() +struct FOP_ActionResult +{ + GENERATED_BODY() + +public: + /** @brief Default constructor usable when there is no problem */ + FOP_ActionResult(); + + /** + * @brief This constructor initializes variables & attempts to log when is error + * @param InEnum Status + */ + FOP_ActionResult(const EOP_ActionResult::Type& InEnum); + + /** + * @brief This constructor initializes variables & attempts to log when is error + * @param InEnum Status + * @param InReason Reason of potential fail + */ + FOP_ActionResult(const EOP_ActionResult::Type& InEnum, const FText& InReason); + +private: + /** @brief Action status */ + EOP_ActionResult::Type Status; + + /** @brief Optional reason of fail */ + FText Reason; + +public: + /** + * @brief Checks if there is problematic state + * @return true when status is not equal to EOP_ActionResult::Ok + */ + bool IsProblem() const; + EOP_ActionResult::Type& GetStatus(); + FText& GetReason(); + +private: + void TryLog() const; +}; + diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/Logging/OP_Log.h b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/Logging/OP_Log.h new file mode 100644 index 0000000000..4f8af3e2e6 --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/Logging/OP_Log.h @@ -0,0 +1,3 @@ +#pragma once + +DEFINE_LOG_CATEGORY_STATIC(LogCommandletOPGenerateProject, Log, All); \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OPConstants.h b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OPConstants.h new file mode 100644 index 0000000000..21a033e426 --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OPConstants.h @@ -0,0 +1,12 @@ +#pragma once + +namespace OPConstants +{ + const FString OP_PluginName = "OpenPype"; + const FString PythonScript_PluginName = "PythonScriptPlugin"; + const FString SequencerScripting_PluginName = "SequencerScripting"; + const FString MovieRenderPipeline_PluginName = "MovieRenderPipeline"; + const FString EditorScriptingUtils_PluginName = "EditorScriptingUtilities"; +} + + diff --git a/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Public/OpenPype.h b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OpenPype.h similarity index 100% rename from openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Public/OpenPype.h rename to openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OpenPype.h diff --git a/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Public/OpenPypeCommands.h b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OpenPypeCommands.h similarity index 100% rename from openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Public/OpenPypeCommands.h rename to openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OpenPypeCommands.h diff --git a/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Public/OpenPypeLib.h b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OpenPypeLib.h similarity index 100% rename from openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Public/OpenPypeLib.h rename to openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OpenPypeLib.h diff --git a/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Public/OpenPypePublishInstance.h b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OpenPypePublishInstance.h similarity index 94% rename from openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Public/OpenPypePublishInstance.h rename to openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OpenPypePublishInstance.h index 146025bd6d..c221f64135 100644 --- a/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Public/OpenPypePublishInstance.h +++ b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OpenPypePublishInstance.h @@ -17,7 +17,7 @@ public: * * @return - Set of UObjects. Careful! They are returning raw pointers. Seems like an issue in UE5 */ - UFUNCTION(BlueprintCallable, BlueprintPure) + UFUNCTION(BlueprintCallable, BlueprintPure, Category="Python") TSet GetInternalAssets() const { //For some reason it can only return Raw Pointers? Seems like an issue which they haven't fixed. @@ -34,7 +34,7 @@ public: * * @return - TSet of assets (UObjects). Careful! They are returning raw pointers. Seems like an issue in UE5 */ - UFUNCTION(BlueprintCallable, BlueprintPure) + UFUNCTION(BlueprintCallable, BlueprintPure, Category="Python") TSet GetExternalAssets() const { //For some reason it can only return Raw Pointers? Seems like an issue which they haven't fixed. @@ -54,7 +54,7 @@ public: * * @attention If the bAddExternalAssets variable is false, external assets won't be included! */ - UFUNCTION(BlueprintCallable, BlueprintPure) + UFUNCTION(BlueprintCallable, BlueprintPure, Category="Python") TSet GetAllAssets() const { const TSet>& IteratedSet = bAddExternalAssets diff --git a/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Public/OpenPypePublishInstanceFactory.h b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OpenPypePublishInstanceFactory.h similarity index 100% rename from openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Public/OpenPypePublishInstanceFactory.h rename to openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OpenPypePublishInstanceFactory.h diff --git a/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Public/OpenPypePythonBridge.h b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OpenPypePythonBridge.h similarity index 100% rename from openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Public/OpenPypePythonBridge.h rename to openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OpenPypePythonBridge.h diff --git a/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Public/OpenPypeSettings.h b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OpenPypeSettings.h similarity index 100% rename from openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Public/OpenPypeSettings.h rename to openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OpenPypeSettings.h diff --git a/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Public/OpenPypeStyle.h b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OpenPypeStyle.h similarity index 100% rename from openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Public/OpenPypeStyle.h rename to openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OpenPypeStyle.h diff --git a/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Private/AssetContainer.cpp b/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Private/AssetContainer.cpp deleted file mode 100644 index 61e563f729..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Private/AssetContainer.cpp +++ /dev/null @@ -1,115 +0,0 @@ -// Fill out your copyright notice in the Description page of Project Settings. - -#include "AssetContainer.h" -#include "AssetRegistry/AssetRegistryModule.h" -#include "Misc/PackageName.h" -#include "Engine.h" -#include "Containers/UnrealString.h" - -UAssetContainer::UAssetContainer(const FObjectInitializer& ObjectInitializer) -: UAssetUserData(ObjectInitializer) -{ - FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); - FString path = UAssetContainer::GetPathName(); - UE_LOG(LogTemp, Warning, TEXT("UAssetContainer %s"), *path); - FARFilter Filter; - Filter.PackagePaths.Add(FName(*path)); - - AssetRegistryModule.Get().OnAssetAdded().AddUObject(this, &UAssetContainer::OnAssetAdded); - AssetRegistryModule.Get().OnAssetRemoved().AddUObject(this, &UAssetContainer::OnAssetRemoved); - AssetRegistryModule.Get().OnAssetRenamed().AddUObject(this, &UAssetContainer::OnAssetRenamed); -} - -void UAssetContainer::OnAssetAdded(const FAssetData& AssetData) -{ - TArray split; - - // get directory of current container - FString selfFullPath = UAssetContainer::GetPathName(); - FString selfDir = FPackageName::GetLongPackagePath(*selfFullPath); - - // get asset path and class - FString assetPath = AssetData.GetFullName(); - FString assetFName = AssetData.AssetClassPath.ToString(); - UE_LOG(LogTemp, Log, TEXT("asset name %s"), *assetFName); - // split path - assetPath.ParseIntoArray(split, TEXT(" "), true); - - FString assetDir = FPackageName::GetLongPackagePath(*split[1]); - - // take interest only in paths starting with path of current container - if (assetDir.StartsWith(*selfDir)) - { - // exclude self - if (assetFName != "AssetContainer") - { - assets.Add(assetPath); - assetsData.Add(AssetData); - UE_LOG(LogTemp, Log, TEXT("%s: asset added to %s"), *selfFullPath, *selfDir); - } - } -} - -void UAssetContainer::OnAssetRemoved(const FAssetData& AssetData) -{ - TArray split; - - // get directory of current container - FString selfFullPath = UAssetContainer::GetPathName(); - FString selfDir = FPackageName::GetLongPackagePath(*selfFullPath); - - // get asset path and class - FString assetPath = AssetData.GetFullName(); - FString assetFName = AssetData.AssetClassPath.ToString(); - - // split path - assetPath.ParseIntoArray(split, TEXT(" "), true); - - FString assetDir = FPackageName::GetLongPackagePath(*split[1]); - - // take interest only in paths starting with path of current container - FString path = UAssetContainer::GetPathName(); - FString lpp = FPackageName::GetLongPackagePath(*path); - - if (assetDir.StartsWith(*selfDir)) - { - // exclude self - if (assetFName != "AssetContainer") - { - // UE_LOG(LogTemp, Warning, TEXT("%s: asset removed"), *lpp); - assets.Remove(assetPath); - assetsData.Remove(AssetData); - } - } -} - -void UAssetContainer::OnAssetRenamed(const FAssetData& AssetData, const FString& str) -{ - TArray split; - - // get directory of current container - FString selfFullPath = UAssetContainer::GetPathName(); - FString selfDir = FPackageName::GetLongPackagePath(*selfFullPath); - - // get asset path and class - FString assetPath = AssetData.GetFullName(); - FString assetFName = AssetData.AssetClassPath.ToString(); - - // split path - assetPath.ParseIntoArray(split, TEXT(" "), true); - - FString assetDir = FPackageName::GetLongPackagePath(*split[1]); - if (assetDir.StartsWith(*selfDir)) - { - // exclude self - if (assetFName != "AssetContainer") - { - - assets.Remove(str); - assets.Add(assetPath); - assetsData.Remove(AssetData); - // UE_LOG(LogTemp, Warning, TEXT("%s: asset renamed %s"), *lpp, *str); - } - } -} - diff --git a/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Private/AssetContainerFactory.cpp b/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Private/AssetContainerFactory.cpp deleted file mode 100644 index b943150bdd..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Private/AssetContainerFactory.cpp +++ /dev/null @@ -1,20 +0,0 @@ -#include "AssetContainerFactory.h" -#include "AssetContainer.h" - -UAssetContainerFactory::UAssetContainerFactory(const FObjectInitializer& ObjectInitializer) - : UFactory(ObjectInitializer) -{ - SupportedClass = UAssetContainer::StaticClass(); - bCreateNew = false; - bEditorImport = true; -} - -UObject* UAssetContainerFactory::FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) -{ - UAssetContainer* AssetContainer = NewObject(InParent, Class, Name, Flags); - return AssetContainer; -} - -bool UAssetContainerFactory::ShouldShowInNewMenu() const { - return false; -} diff --git a/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Public/AssetContainer.h b/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Public/AssetContainer.h deleted file mode 100644 index 2c06e59d6f..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Public/AssetContainer.h +++ /dev/null @@ -1,39 +0,0 @@ -// Fill out your copyright notice in the Description page of Project Settings. - -#pragma once - -#include "CoreMinimal.h" -#include "UObject/NoExportTypes.h" -#include "Engine/AssetUserData.h" -#include "AssetRegistry/AssetData.h" -#include "AssetContainer.generated.h" - -/** - * - */ -UCLASS(Blueprintable) -class OPENPYPE_API UAssetContainer : public UAssetUserData -{ - GENERATED_BODY() - -public: - - UAssetContainer(const FObjectInitializer& ObjectInitalizer); - // ~UAssetContainer(); - - UPROPERTY(EditAnywhere, BlueprintReadOnly) - TArray assets; - - // There seems to be no reflection option to expose array of FAssetData - /* - UPROPERTY(Transient, BlueprintReadOnly, Category = "Python", meta=(DisplayName="Assets Data")) - TArray assetsData; - */ -private: - TArray assetsData; - void OnAssetAdded(const FAssetData& AssetData); - void OnAssetRemoved(const FAssetData& AssetData); - void OnAssetRenamed(const FAssetData& AssetData, const FString& str); -}; - - diff --git a/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Public/AssetContainerFactory.h b/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Public/AssetContainerFactory.h deleted file mode 100644 index 331ce6bb50..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.0/Source/OpenPype/Public/AssetContainerFactory.h +++ /dev/null @@ -1,21 +0,0 @@ -// Fill out your copyright notice in the Description page of Project Settings. - -#pragma once - -#include "CoreMinimal.h" -#include "Factories/Factory.h" -#include "AssetContainerFactory.generated.h" - -/** - * - */ -UCLASS() -class OPENPYPE_API UAssetContainerFactory : public UFactory -{ - GENERATED_BODY() - -public: - UAssetContainerFactory(const FObjectInitializer& ObjectInitializer); - virtual UObject* FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override; - virtual bool ShouldShowInNewMenu() const override; -}; \ No newline at end of file diff --git a/openpype/hosts/unreal/lib.py b/openpype/hosts/unreal/lib.py index 095f5e414b..5bde65edb6 100644 --- a/openpype/hosts/unreal/lib.py +++ b/openpype/hosts/unreal/lib.py @@ -4,6 +4,10 @@ import os import platform import json + +from typing import List + +import openpype from distutils import dir_util import subprocess import re @@ -73,7 +77,7 @@ def get_engine_versions(env=None): return OrderedDict() -def get_editor_executable_path(engine_path: Path, engine_version: str) -> Path: +def get_editor_exe_path(engine_path: Path, engine_version: str) -> Path: """Get UE Editor executable path.""" ue_path = engine_path / "Engine/Binaries" if platform.system().lower() == "windows": @@ -214,77 +218,58 @@ def create_unreal_project(project_name: str, # created in different UE4 version. When user convert such project # to his UE4 version, Engine ID is replaced in uproject file. If some # other user tries to open it, it will present him with similar error. - ue_modules = Path() - if platform.system().lower() == "windows": - ue_modules_path = engine_path / "Engine/Binaries/Win64" - if ue_version.split(".")[0] == "4": - ue_modules_path /= "UE4Editor.modules" - elif ue_version.split(".")[0] == "5": - ue_modules_path /= "UnrealEditor.modules" - ue_modules = Path(ue_modules_path) - if platform.system().lower() == "linux": - ue_modules = Path(os.path.join(engine_path, "Engine", "Binaries", - "Linux", "UE4Editor.modules")) + # engine_path should be the location of UE_X.X folder - if platform.system().lower() == "darwin": - ue_modules = Path(os.path.join(engine_path, "Engine", "Binaries", - "Mac", "UE4Editor.modules")) + ue_editor_exe_path: Path = get_editor_exe_path(engine_path, ue_version) + cmdlet_project_path = get_path_to_cmdlet_project(ue_version) - if ue_modules.exists(): - print("--- Loading Engine ID from modules file ...") - with open(ue_modules, "r") as mp: - loaded_modules = json.load(mp) + project_file = pr_dir / f"{project_name}.uproject" - if loaded_modules.get("BuildId"): - ue_id = "{" + loaded_modules.get("BuildId") + "}" - - plugins_path = None - if os.path.isdir(env.get("OPENPYPE_UNREAL_PLUGIN", "")): - # copy plugin to correct path under project - plugins_path = pr_dir / "Plugins" - openpype_plugin_path = plugins_path / "OpenPype" - if not openpype_plugin_path.is_dir(): - openpype_plugin_path.mkdir(parents=True, exist_ok=True) - dir_util._path_created = {} - dir_util.copy_tree(os.environ.get("OPENPYPE_UNREAL_PLUGIN"), - openpype_plugin_path.as_posix()) - - if not (openpype_plugin_path / "Binaries").is_dir() \ - or not (openpype_plugin_path / "Intermediate").is_dir(): - dev_mode = True - - # data for project file - data = { - "FileVersion": 3, - "EngineAssociation": ue_id, - "Category": "", - "Description": "", - "Plugins": [ - {"Name": "PythonScriptPlugin", "Enabled": True}, - {"Name": "EditorScriptingUtilities", "Enabled": True}, - {"Name": "SequencerScripting", "Enabled": True}, - {"Name": "MovieRenderPipeline", "Enabled": True}, - {"Name": "OpenPype", "Enabled": True} - ] - } + print("--- Generating a new project ...") + commandlet_cmd = [f'{ue_editor_exe_path.as_posix()}', + f'{cmdlet_project_path.as_posix()}', + f'-run=OPGenerateProject', + f'{project_file.resolve().as_posix()}'] if dev_mode or preset["dev_mode"]: - # this will add the project module and necessary source file to - # make it a C++ project and to (hopefully) make Unreal Editor to - # compile all # sources at start + commandlet_cmd.append('-GenerateCode') - data["Modules"] = [{ - "Name": project_name, - "Type": "Runtime", - "LoadingPhase": "Default", - "AdditionalDependencies": ["Engine"], - }] + subprocess.run(commandlet_cmd) - # write project file - project_file = pr_dir / f"{project_name}.uproject" - with open(project_file, mode="w") as pf: - json.dump(data, pf, indent=4) + with open(project_file, mode="r+") as pf: + pf_json = json.load(pf) + pf_json["EngineAssociation"] = _get_build_id(engine_path, ue_version) + pf.seek(0) + json.dump(pf_json, pf, indent=4) + pf.truncate() + print(f'--- Engine ID has been writen into the project file') + + if dev_mode or preset["dev_mode"]: + u_build_tool = get_path_to_ubt(engine_path, ue_version) + + arch = "Win64" + if platform.system().lower() == "windows": + arch = "Win64" + elif platform.system().lower() == "linux": + arch = "Linux" + elif platform.system().lower() == "darwin": + # we need to test this out + arch = "Mac" + + command1 = [u_build_tool.as_posix(), "-projectfiles", + f"-project={project_file}", "-progress"] + + subprocess.run(command1) + + command2 = [u_build_tool.as_posix(), + f"-ModuleWithSuffix={project_name},3555", arch, + "Development", "-TargetType=Editor", + f'-Project={project_file}', + f'{project_file}', + "-IgnoreJunk"] + + subprocess.run(command2) # ensure we have PySide2 installed in engine python_path = None @@ -307,8 +292,121 @@ def create_unreal_project(project_name: str, subprocess.check_call( [python_path.as_posix(), "-m", "pip", "install", "pyside2"]) - if dev_mode or preset["dev_mode"]: - _prepare_cpp_project(project_file, engine_path, ue_version) + +def get_path_to_uat(engine_path: Path) -> Path: + if platform.system().lower() == "windows": + return engine_path / "Engine/Build/BatchFiles/RunUAT.bat" + + if platform.system().lower() == "linux" or platform.system().lower() == "darwin": + return engine_path / "Engine/Build/BatchFiles/RunUAT.sh" + + +def get_path_to_cmdlet_project(ue_version: str) -> Path: + commandlet_project_path: Path = Path(os.path.dirname(os.path.abspath(openpype.__file__))) + + # For now, only tested on Windows (For Linux and Mac it has to be implemented) + if ue_version.split(".")[0] == "4": + return commandlet_project_path / "hosts/unreal/integration/UE_4.7/CommandletProject/CommandletProject.uproject" + elif ue_version.split(".")[0] == "5": + return commandlet_project_path / "hosts/unreal/integration/UE_5.0/CommandletProject/CommandletProject.uproject" + + +def get_path_to_ubt(engine_path: Path, ue_version: str) -> Path: + u_build_tool_path = engine_path / "Engine/Binaries/DotNET" + + if ue_version.split(".")[0] == "4": + u_build_tool_path /= "UnrealBuildTool.exe" + elif ue_version.split(".")[0] == "5": + u_build_tool_path /= "UnrealBuildTool/UnrealBuildTool.exe" + + return Path(u_build_tool_path) + + +def _get_build_id(engine_path: Path, ue_version: str) -> str: + ue_modules = Path() + if platform.system().lower() == "windows": + ue_modules_path = engine_path / "Engine/Binaries/Win64" + if ue_version.split(".")[0] == "4": + ue_modules_path /= "UE4Editor.modules" + elif ue_version.split(".")[0] == "5": + ue_modules_path /= "UnrealEditor.modules" + ue_modules = Path(ue_modules_path) + + if platform.system().lower() == "linux": + ue_modules = Path(os.path.join(engine_path, "Engine", "Binaries", + "Linux", "UE4Editor.modules")) + + if platform.system().lower() == "darwin": + ue_modules = Path(os.path.join(engine_path, "Engine", "Binaries", + "Mac", "UE4Editor.modules")) + + if ue_modules.exists(): + print("--- Loading Engine ID from modules file ...") + with open(ue_modules, "r") as mp: + loaded_modules = json.load(mp) + + if loaded_modules.get("BuildId"): + return "{" + loaded_modules.get("BuildId") + "}" + + +def try_installing_plugin(engine_path: Path, + ue_version: str, + env: dict = None) -> None: + env = env or os.environ + + integration_plugin_path: Path = Path(env.get("OPENPYPE_UNREAL_PLUGIN", "")) + + if not os.path.isdir(integration_plugin_path): + raise RuntimeError("Path to the integration plugin is null!") + + # Create a path to the plugin in the engine + openpype_plugin_path: Path = engine_path / "Engine/Plugins/Marketplace/OpenPype" + + if not openpype_plugin_path.is_dir(): + print("--- OpenPype Plugin is not present. Creating a new plugin directory ...") + openpype_plugin_path.mkdir(parents=True, exist_ok=True) + + engine_plugin_config_path: Path = openpype_plugin_path / "Config" + engine_plugin_config_path.mkdir(exist_ok=True) + + dir_util._path_created = {} + + if not (openpype_plugin_path / "Binaries").is_dir() \ + or not (openpype_plugin_path / "Intermediate").is_dir(): + print("--- Binaries are not present. Building the plugin ...") + _build_and_move_integration_plugin(engine_path, openpype_plugin_path, env) + + +def _build_and_move_integration_plugin(engine_path: Path, + plugin_build_path: Path, + env: dict = None) -> None: + uat_path: Path = get_path_to_uat(engine_path) + + env = env or os.environ + integration_plugin_path: Path = Path(env.get("OPENPYPE_UNREAL_PLUGIN", "")) + + if uat_path.is_file(): + temp_dir: Path = integration_plugin_path.parent / "Temp" + temp_dir.mkdir(exist_ok=True) + uplugin_path: Path = integration_plugin_path / "OpenPype.uplugin" + + # in order to successfully build the plugin, It must be built outside the Engine directory and then moved + build_plugin_cmd: List[str] = [f'{uat_path.as_posix()}', + 'BuildPlugin', + f'-Plugin={uplugin_path.as_posix()}', + f'-Package={temp_dir.as_posix()}'] + subprocess.run(build_plugin_cmd) + + # Copy the contents of the 'Temp' dir into the 'OpenPype' directory in the engine + dir_util.copy_tree(temp_dir.as_posix(), plugin_build_path.as_posix()) + + # We need to also copy the config folder. The UAT doesn't include the Config folder in the build + plugin_install_config_path: Path = plugin_build_path / "Config" + integration_plugin_config_path = integration_plugin_path / "Config" + + dir_util.copy_tree(integration_plugin_config_path.as_posix(), plugin_install_config_path.as_posix()) + + dir_util.remove_tree(temp_dir.as_posix()) def _prepare_cpp_project( From 222a2a0631293f5c9640af10c99f15fc85372ada Mon Sep 17 00:00:00 2001 From: Joseff Date: Wed, 25 Jan 2023 13:25:57 +0100 Subject: [PATCH 053/184] Cleaning up and refactoring the code. --- openpype/hosts/unreal/lib.py | 210 ++++------------------------------- 1 file changed, 23 insertions(+), 187 deletions(-) diff --git a/openpype/hosts/unreal/lib.py b/openpype/hosts/unreal/lib.py index 5bde65edb6..8c4299be53 100644 --- a/openpype/hosts/unreal/lib.py +++ b/openpype/hosts/unreal/lib.py @@ -221,14 +221,14 @@ def create_unreal_project(project_name: str, # engine_path should be the location of UE_X.X folder - ue_editor_exe_path: Path = get_editor_exe_path(engine_path, ue_version) - cmdlet_project_path = get_path_to_cmdlet_project(ue_version) + ue_editor_exe: Path = get_editor_exe_path(engine_path, ue_version) + cmdlet_project: Path = get_path_to_cmdlet_project(ue_version) project_file = pr_dir / f"{project_name}.uproject" print("--- Generating a new project ...") - commandlet_cmd = [f'{ue_editor_exe_path.as_posix()}', - f'{cmdlet_project_path.as_posix()}', + commandlet_cmd = [f'{ue_editor_exe.as_posix()}', + f'{cmdlet_project.as_posix()}', f'-run=OPGenerateProject', f'{project_file.resolve().as_posix()}'] @@ -297,18 +297,21 @@ def get_path_to_uat(engine_path: Path) -> Path: if platform.system().lower() == "windows": return engine_path / "Engine/Build/BatchFiles/RunUAT.bat" - if platform.system().lower() == "linux" or platform.system().lower() == "darwin": + if platform.system().lower() == "linux" \ + or platform.system().lower() == "darwin": return engine_path / "Engine/Build/BatchFiles/RunUAT.sh" def get_path_to_cmdlet_project(ue_version: str) -> Path: - commandlet_project_path: Path = Path(os.path.dirname(os.path.abspath(openpype.__file__))) + cmdlet_project: Path = Path(os.path.dirname(os.path.abspath(openpype.__file__))) # For now, only tested on Windows (For Linux and Mac it has to be implemented) if ue_version.split(".")[0] == "4": - return commandlet_project_path / "hosts/unreal/integration/UE_4.7/CommandletProject/CommandletProject.uproject" + cmdlet_project /= "hosts/unreal/integration/UE_4.7" elif ue_version.split(".")[0] == "5": - return commandlet_project_path / "hosts/unreal/integration/UE_5.0/CommandletProject/CommandletProject.uproject" + cmdlet_project /= "hosts/unreal/integration/UE_5.0" + + return cmdlet_project / "CommandletProject/CommandletProject.uproject" def get_path_to_ubt(engine_path: Path, ue_version: str) -> Path: @@ -374,12 +377,12 @@ def try_installing_plugin(engine_path: Path, if not (openpype_plugin_path / "Binaries").is_dir() \ or not (openpype_plugin_path / "Intermediate").is_dir(): print("--- Binaries are not present. Building the plugin ...") - _build_and_move_integration_plugin(engine_path, openpype_plugin_path, env) + _build_and_move_plugin(engine_path, openpype_plugin_path, env) -def _build_and_move_integration_plugin(engine_path: Path, - plugin_build_path: Path, - env: dict = None) -> None: +def _build_and_move_plugin(engine_path: Path, + plugin_build_path: Path, + env: dict = None) -> None: uat_path: Path = get_path_to_uat(engine_path) env = env or os.environ @@ -390,191 +393,24 @@ def _build_and_move_integration_plugin(engine_path: Path, temp_dir.mkdir(exist_ok=True) uplugin_path: Path = integration_plugin_path / "OpenPype.uplugin" - # in order to successfully build the plugin, It must be built outside the Engine directory and then moved + # in order to successfully build the plugin, + # It must be built outside the Engine directory and then moved build_plugin_cmd: List[str] = [f'{uat_path.as_posix()}', 'BuildPlugin', f'-Plugin={uplugin_path.as_posix()}', f'-Package={temp_dir.as_posix()}'] subprocess.run(build_plugin_cmd) - # Copy the contents of the 'Temp' dir into the 'OpenPype' directory in the engine + # Copy the contents of the 'Temp' dir into the + # 'OpenPype' directory in the engine dir_util.copy_tree(temp_dir.as_posix(), plugin_build_path.as_posix()) - # We need to also copy the config folder. The UAT doesn't include the Config folder in the build + # We need to also copy the config folder. + # The UAT doesn't include the Config folder in the build plugin_install_config_path: Path = plugin_build_path / "Config" integration_plugin_config_path = integration_plugin_path / "Config" - dir_util.copy_tree(integration_plugin_config_path.as_posix(), plugin_install_config_path.as_posix()) + dir_util.copy_tree(integration_plugin_config_path.as_posix(), + plugin_install_config_path.as_posix()) dir_util.remove_tree(temp_dir.as_posix()) - - -def _prepare_cpp_project( - project_file: Path, engine_path: Path, ue_version: str) -> None: - """Prepare CPP Unreal Project. - - This function will add source files needed for project to be - rebuild along with the OpenPype integration plugin. - - There seems not to be automated way to do it from command line. - But there might be way to create at least those target and build files - by some generator. This needs more research as manually writing - those files is rather hackish. :skull_and_crossbones: - - - Args: - project_file (str): Path to .uproject file. - engine_path (str): Path to unreal engine associated with project. - - """ - project_name = project_file.stem - project_dir = project_file.parent - targets_dir = project_dir / "Source" - sources_dir = targets_dir / project_name - - sources_dir.mkdir(parents=True, exist_ok=True) - (project_dir / "Content").mkdir(parents=True, exist_ok=True) - - module_target = ''' -using UnrealBuildTool; -using System.Collections.Generic; - -public class {0}Target : TargetRules -{{ - public {0}Target( TargetInfo Target) : base(Target) - {{ - Type = TargetType.Game; - ExtraModuleNames.AddRange( new string[] {{ "{0}" }} ); - }} -}} -'''.format(project_name) - - editor_module_target = ''' -using UnrealBuildTool; -using System.Collections.Generic; - -public class {0}EditorTarget : TargetRules -{{ - public {0}EditorTarget( TargetInfo Target) : base(Target) - {{ - Type = TargetType.Editor; - - ExtraModuleNames.AddRange( new string[] {{ "{0}" }} ); - }} -}} -'''.format(project_name) - - module_build = ''' -using UnrealBuildTool; -public class {0} : ModuleRules -{{ - public {0}(ReadOnlyTargetRules Target) : base(Target) - {{ - PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; - PublicDependencyModuleNames.AddRange(new string[] {{ "Core", - "CoreUObject", "Engine", "InputCore" }}); - PrivateDependencyModuleNames.AddRange(new string[] {{ }}); - }} -}} -'''.format(project_name) - - module_cpp = ''' -#include "{0}.h" -#include "Modules/ModuleManager.h" - -IMPLEMENT_PRIMARY_GAME_MODULE( FDefaultGameModuleImpl, {0}, "{0}" ); -'''.format(project_name) - - module_header = ''' -#pragma once -#include "CoreMinimal.h" -''' - - game_mode_cpp = ''' -#include "{0}GameModeBase.h" -'''.format(project_name) - - game_mode_h = ''' -#pragma once - -#include "CoreMinimal.h" -#include "GameFramework/GameModeBase.h" -#include "{0}GameModeBase.generated.h" - -UCLASS() -class {1}_API A{0}GameModeBase : public AGameModeBase -{{ - GENERATED_BODY() -}}; -'''.format(project_name, project_name.upper()) - - with open(targets_dir / f"{project_name}.Target.cs", mode="w") as f: - f.write(module_target) - - with open(targets_dir / f"{project_name}Editor.Target.cs", mode="w") as f: - f.write(editor_module_target) - - with open(sources_dir / f"{project_name}.Build.cs", mode="w") as f: - f.write(module_build) - - with open(sources_dir / f"{project_name}.cpp", mode="w") as f: - f.write(module_cpp) - - with open(sources_dir / f"{project_name}.h", mode="w") as f: - f.write(module_header) - - with open(sources_dir / f"{project_name}GameModeBase.cpp", mode="w") as f: - f.write(game_mode_cpp) - - with open(sources_dir / f"{project_name}GameModeBase.h", mode="w") as f: - f.write(game_mode_h) - - u_build_tool_path = engine_path / "Engine/Binaries/DotNET" - if ue_version.split(".")[0] == "4": - u_build_tool_path /= "UnrealBuildTool.exe" - elif ue_version.split(".")[0] == "5": - u_build_tool_path /= "UnrealBuildTool/UnrealBuildTool.exe" - u_build_tool = Path(u_build_tool_path) - u_header_tool = None - - arch = "Win64" - if platform.system().lower() == "windows": - arch = "Win64" - u_header_tool = Path( - engine_path / "Engine/Binaries/Win64/UnrealHeaderTool.exe") - elif platform.system().lower() == "linux": - arch = "Linux" - u_header_tool = Path( - engine_path / "Engine/Binaries/Linux/UnrealHeaderTool") - elif platform.system().lower() == "darwin": - # we need to test this out - arch = "Mac" - u_header_tool = Path( - engine_path / "Engine/Binaries/Mac/UnrealHeaderTool") - - if not u_header_tool: - raise NotImplementedError("Unsupported platform") - - command1 = [u_build_tool.as_posix(), "-projectfiles", - f"-project={project_file}", "-progress"] - - subprocess.run(command1) - - command2 = [u_build_tool.as_posix(), - f"-ModuleWithSuffix={project_name},3555", arch, - "Development", "-TargetType=Editor", - f'-Project={project_file}', - f'{project_file}', - "-IgnoreJunk"] - - subprocess.run(command2) - - """ - uhtmanifest = os.path.join(os.path.dirname(project_file), - f"{project_name}.uhtmanifest") - - command3 = [u_header_tool, f'"{project_file}"', f'"{uhtmanifest}"', - "-Unattended", "-WarningsAsErrors", "-installed"] - - subprocess.run(command3) - """ From 87d8c912cab99ac4fc9cae7a354de4c24aa5a456 Mon Sep 17 00:00:00 2001 From: Joseff Date: Wed, 25 Jan 2023 13:34:48 +0100 Subject: [PATCH 054/184] Shortening the lines of code --- openpype/hosts/unreal/lib.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/openpype/hosts/unreal/lib.py b/openpype/hosts/unreal/lib.py index 8c4299be53..3b842a112e 100644 --- a/openpype/hosts/unreal/lib.py +++ b/openpype/hosts/unreal/lib.py @@ -303,15 +303,15 @@ def get_path_to_uat(engine_path: Path) -> Path: def get_path_to_cmdlet_project(ue_version: str) -> Path: - cmdlet_project: Path = Path(os.path.dirname(os.path.abspath(openpype.__file__))) + cmd_project: Path = Path(os.path.dirname(os.path.abspath(openpype.__file__))) # For now, only tested on Windows (For Linux and Mac it has to be implemented) if ue_version.split(".")[0] == "4": - cmdlet_project /= "hosts/unreal/integration/UE_4.7" + cmd_project /= "hosts/unreal/integration/UE_4.7" elif ue_version.split(".")[0] == "5": - cmdlet_project /= "hosts/unreal/integration/UE_5.0" + cmd_project /= "hosts/unreal/integration/UE_5.0" - return cmdlet_project / "CommandletProject/CommandletProject.uproject" + return cmd_project / "CommandletProject/CommandletProject.uproject" def get_path_to_ubt(engine_path: Path, ue_version: str) -> Path: @@ -363,21 +363,21 @@ def try_installing_plugin(engine_path: Path, raise RuntimeError("Path to the integration plugin is null!") # Create a path to the plugin in the engine - openpype_plugin_path: Path = engine_path / "Engine/Plugins/Marketplace/OpenPype" + op_plugin_path: Path = engine_path / "Engine/Plugins/Marketplace/OpenPype" - if not openpype_plugin_path.is_dir(): - print("--- OpenPype Plugin is not present. Creating a new plugin directory ...") - openpype_plugin_path.mkdir(parents=True, exist_ok=True) + if not op_plugin_path.is_dir(): + print("--- OpenPype Plugin is not present. Installing ...") + op_plugin_path.mkdir(parents=True, exist_ok=True) - engine_plugin_config_path: Path = openpype_plugin_path / "Config" + engine_plugin_config_path: Path = op_plugin_path / "Config" engine_plugin_config_path.mkdir(exist_ok=True) dir_util._path_created = {} - if not (openpype_plugin_path / "Binaries").is_dir() \ - or not (openpype_plugin_path / "Intermediate").is_dir(): + if not (op_plugin_path / "Binaries").is_dir() \ + or not (op_plugin_path / "Intermediate").is_dir(): print("--- Binaries are not present. Building the plugin ...") - _build_and_move_plugin(engine_path, openpype_plugin_path, env) + _build_and_move_plugin(engine_path, op_plugin_path, env) def _build_and_move_plugin(engine_path: Path, From ae3248b6d9ad95c9c816ba832545bb87c4f04809 Mon Sep 17 00:00:00 2001 From: Joseff Date: Wed, 25 Jan 2023 11:31:27 +0100 Subject: [PATCH 055/184] Refactored the generation of UE projects, plugin is now being installed in the engine. --- openpype/hosts/unreal/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/unreal/lib.py b/openpype/hosts/unreal/lib.py index 3b842a112e..b502737771 100644 --- a/openpype/hosts/unreal/lib.py +++ b/openpype/hosts/unreal/lib.py @@ -303,7 +303,7 @@ def get_path_to_uat(engine_path: Path) -> Path: def get_path_to_cmdlet_project(ue_version: str) -> Path: - cmd_project: Path = Path(os.path.dirname(os.path.abspath(openpype.__file__))) + cmd_project = Path(os.path.dirname(os.path.abspath(openpype.__file__))) # For now, only tested on Windows (For Linux and Mac it has to be implemented) if ue_version.split(".")[0] == "4": From 141214d86c8e1055210e517fd7cd467d2dd93496 Mon Sep 17 00:00:00 2001 From: Joseff Date: Fri, 3 Feb 2023 11:03:10 +0100 Subject: [PATCH 056/184] Added a stdout printing for the project generation command --- openpype/hosts/unreal/lib.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/unreal/lib.py b/openpype/hosts/unreal/lib.py index b502737771..04171f3ac0 100644 --- a/openpype/hosts/unreal/lib.py +++ b/openpype/hosts/unreal/lib.py @@ -235,15 +235,27 @@ def create_unreal_project(project_name: str, if dev_mode or preset["dev_mode"]: commandlet_cmd.append('-GenerateCode') - subprocess.run(commandlet_cmd) + gen_process = subprocess.Popen(commandlet_cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) - with open(project_file, mode="r+") as pf: + for line in gen_process.stdout: + print(line.decode(), end='') + gen_process.stdout.close() + return_code = gen_process.wait() + + if return_code and return_code != 0: + raise RuntimeError(f'Failed to generate \'{project_name}\' project! Exited with return code {return_code}') + + print("--- Project has been generated successfully.") + + with open(project_file.as_posix(), mode="r+") as pf: pf_json = json.load(pf) pf_json["EngineAssociation"] = _get_build_id(engine_path, ue_version) pf.seek(0) json.dump(pf_json, pf, indent=4) pf.truncate() - print(f'--- Engine ID has been writen into the project file') + print(f'--- Engine ID has been written into the project file') if dev_mode or preset["dev_mode"]: u_build_tool = get_path_to_ubt(engine_path, ue_version) From 283a5fb8e4cafc33eb6ad4bd36a82a02e5893222 Mon Sep 17 00:00:00 2001 From: Joseff Date: Fri, 3 Feb 2023 11:07:47 +0100 Subject: [PATCH 057/184] Shortened lines for the error message --- openpype/hosts/unreal/lib.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/unreal/lib.py b/openpype/hosts/unreal/lib.py index 04171f3ac0..2e1f59d439 100644 --- a/openpype/hosts/unreal/lib.py +++ b/openpype/hosts/unreal/lib.py @@ -229,7 +229,7 @@ def create_unreal_project(project_name: str, print("--- Generating a new project ...") commandlet_cmd = [f'{ue_editor_exe.as_posix()}', f'{cmdlet_project.as_posix()}', - f'-run=OPGenerateProject', + f'-run=OPGenerateProjec', f'{project_file.resolve().as_posix()}'] if dev_mode or preset["dev_mode"]: @@ -245,7 +245,8 @@ def create_unreal_project(project_name: str, return_code = gen_process.wait() if return_code and return_code != 0: - raise RuntimeError(f'Failed to generate \'{project_name}\' project! Exited with return code {return_code}') + raise RuntimeError(f'Failed to generate \'{project_name}\' project! ' + f'Exited with return code {return_code}') print("--- Project has been generated successfully.") From 5ad3bfbaf2cd732a08fc0f9e00600017b4ef0e95 Mon Sep 17 00:00:00 2001 From: Joseff Date: Mon, 20 Feb 2023 15:57:45 +0100 Subject: [PATCH 058/184] Fixed the generation of the project, added copyrights notices, removed .ini files. --- openpype/hosts/unreal/addon.py | 3 +- .../unreal/hooks/pre_workfile_preparation.py | 3 +- .../UE_4.7/CommandletProject/.gitignore | 2 + .../Config/DefaultEditor.ini | 0 .../Config/DefaultEngine.ini | 16 ------- .../CommandletProject/Config/DefaultGame.ini | 4 -- .../integration/UE_4.7/OpenPype/.gitignore | 6 +++ .../UE_4.7/OpenPype/OpenPype.uplugin | 1 - .../Source/OpenPype/OpenPype.Build.cs | 2 +- .../OPGenerateProjectCommandlet.cpp | 1 + .../Private/Commandlets/OPActionResult.cpp | 2 +- .../Source/OpenPype/Private/OpenPype.cpp | 1 + .../Source/OpenPype/Private/OpenPypeLib.cpp | 1 + .../Private/OpenPypePublishInstance.cpp | 1 + .../OpenPypePublishInstanceFactory.cpp | 1 + .../OpenPype/Private/OpenPypePythonBridge.cpp | 1 + .../OpenPype/Private/OpenPypeSettings.cpp | 2 +- .../Source/OpenPype/Private/OpenPypeStyle.cpp | 1 + .../OPGenerateProjectCommandlet.h | 2 +- .../Public/Commandlets/OPActionResult.h | 2 +- .../Source/OpenPype/Public/Logging/OP_Log.h | 1 + .../Source/OpenPype/Public/OPConstants.h | 1 + .../Source/OpenPype/Public/OpenPype.h | 2 +- .../Source/OpenPype/Public/OpenPypeLib.h | 1 + .../OpenPype/Public/OpenPypePublishInstance.h | 1 + .../Public/OpenPypePublishInstanceFactory.h | 1 + .../OpenPype/Public/OpenPypePythonBridge.h | 1 + .../Source/OpenPype/Public/OpenPypeSettings.h | 2 +- .../Source/OpenPype/Public/OpenPypeStyle.h | 1 + .../UE_5.0/CommandletProject/.gitignore | 35 ++++++++++++++++ .../Config/DefaultEditor.ini | 0 .../Config/DefaultEngine.ini | 42 ------------------- .../CommandletProject/Config/DefaultGame.ini | 4 -- .../UE_5.0/OpenPype/OpenPype.uplugin | 3 +- .../Source/OpenPype/OpenPype.Build.cs | 2 +- .../OPGenerateProjectCommandlet.cpp | 1 + .../Private/Commandlets/OPActionResult.cpp | 3 +- .../OpenPype/Private/Logging/OP_Log.cpp | 2 + .../Source/OpenPype/Private/OpenPype.cpp | 1 + .../OpenPype/Private/OpenPypeCommands.cpp | 2 +- .../Source/OpenPype/Private/OpenPypeLib.cpp | 1 + .../Private/OpenPypePublishInstance.cpp | 1 + .../OpenPypePublishInstanceFactory.cpp | 1 + .../OpenPype/Private/OpenPypePythonBridge.cpp | 1 + .../OpenPype/Private/OpenPypeSettings.cpp | 2 +- .../Source/OpenPype/Private/OpenPypeStyle.cpp | 2 + .../OPGenerateProjectCommandlet.h | 1 + .../Public/Commandlets/OPActionResult.h | 2 +- .../Source/OpenPype/Public/Logging/OP_Log.h | 1 + .../Source/OpenPype/Public/OPConstants.h | 1 + .../Source/OpenPype/Public/OpenPype.h | 2 +- .../Source/OpenPype/Public/OpenPypeCommands.h | 2 +- .../Source/OpenPype/Public/OpenPypeLib.h | 1 + .../OpenPype/Public/OpenPypePublishInstance.h | 1 + .../Public/OpenPypePublishInstanceFactory.h | 1 + .../OpenPype/Public/OpenPypePythonBridge.h | 1 + .../Source/OpenPype/Public/OpenPypeSettings.h | 2 +- .../Source/OpenPype/Public/OpenPypeStyle.h | 1 + openpype/hosts/unreal/lib.py | 6 +-- 59 files changed, 97 insertions(+), 91 deletions(-) delete mode 100644 openpype/hosts/unreal/integration/UE_4.7/CommandletProject/Config/DefaultEditor.ini delete mode 100644 openpype/hosts/unreal/integration/UE_4.7/CommandletProject/Config/DefaultEngine.ini delete mode 100644 openpype/hosts/unreal/integration/UE_4.7/CommandletProject/Config/DefaultGame.ini delete mode 100644 openpype/hosts/unreal/integration/UE_5.0/CommandletProject/Config/DefaultEditor.ini delete mode 100644 openpype/hosts/unreal/integration/UE_5.0/CommandletProject/Config/DefaultEngine.ini delete mode 100644 openpype/hosts/unreal/integration/UE_5.0/CommandletProject/Config/DefaultGame.ini diff --git a/openpype/hosts/unreal/addon.py b/openpype/hosts/unreal/addon.py index c92a44870f..24e2db975d 100644 --- a/openpype/hosts/unreal/addon.py +++ b/openpype/hosts/unreal/addon.py @@ -19,7 +19,8 @@ class UnrealAddon(OpenPypeModule, IHostAddon): unreal_plugin_path = os.path.join( UNREAL_ROOT_DIR, "integration", ue_plugin, "OpenPype" ) - if not env.get("OPENPYPE_UNREAL_PLUGIN"): + if not env.get("OPENPYPE_UNREAL_PLUGIN") or \ + env.get("OPENPYPE_UNREAL_PLUGIN") != unreal_plugin_path: env["OPENPYPE_UNREAL_PLUGIN"] = unreal_plugin_path # Set default environments if are not set via settings diff --git a/openpype/hosts/unreal/hooks/pre_workfile_preparation.py b/openpype/hosts/unreal/hooks/pre_workfile_preparation.py index 821018ba9d..14285cb78c 100644 --- a/openpype/hosts/unreal/hooks/pre_workfile_preparation.py +++ b/openpype/hosts/unreal/hooks/pre_workfile_preparation.py @@ -127,6 +127,7 @@ class UnrealPrelaunchHook(PreLaunchHook): # Set "OPENPYPE_UNREAL_PLUGIN" to current process environment for # execution of `create_unreal_project` + if self.launch_context.env.get("OPENPYPE_UNREAL_PLUGIN"): self.log.info(( f"{self.signature} using OpenPype plugin from " @@ -138,7 +139,7 @@ class UnrealPrelaunchHook(PreLaunchHook): engine_path = detected[engine_version] - unreal_lib.try_installing_plugin(Path(engine_path), engine_version) + unreal_lib.try_installing_plugin(Path(engine_path), os.environ) project_file = project_path / unreal_project_filename if not project_file.is_file(): diff --git a/openpype/hosts/unreal/integration/UE_4.7/CommandletProject/.gitignore b/openpype/hosts/unreal/integration/UE_4.7/CommandletProject/.gitignore index 1004610e4f..e74e6886b7 100644 --- a/openpype/hosts/unreal/integration/UE_4.7/CommandletProject/.gitignore +++ b/openpype/hosts/unreal/integration/UE_4.7/CommandletProject/.gitignore @@ -1,6 +1,8 @@ /Saved /DerivedDataCache /Intermediate +/Content +/Config /Binaries /.idea /.vs \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_4.7/CommandletProject/Config/DefaultEditor.ini b/openpype/hosts/unreal/integration/UE_4.7/CommandletProject/Config/DefaultEditor.ini deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/openpype/hosts/unreal/integration/UE_4.7/CommandletProject/Config/DefaultEngine.ini b/openpype/hosts/unreal/integration/UE_4.7/CommandletProject/Config/DefaultEngine.ini deleted file mode 100644 index 2845baccca..0000000000 --- a/openpype/hosts/unreal/integration/UE_4.7/CommandletProject/Config/DefaultEngine.ini +++ /dev/null @@ -1,16 +0,0 @@ - - -[/Script/EngineSettings.GameMapsSettings] -GameDefaultMap=/Engine/Maps/Templates/Template_Default.Template_Default - - -[/Script/HardwareTargeting.HardwareTargetingSettings] -TargetedHardwareClass=Desktop -AppliedTargetedHardwareClass=Desktop -DefaultGraphicsPerformance=Maximum -AppliedDefaultGraphicsPerformance=Maximum - -[/Script/Engine.Engine] -+ActiveGameNameRedirects=(OldGameName="TP_BlankBP",NewGameName="/Script/CommandletProject") -+ActiveGameNameRedirects=(OldGameName="/Script/TP_BlankBP",NewGameName="/Script/CommandletProject") - diff --git a/openpype/hosts/unreal/integration/UE_4.7/CommandletProject/Config/DefaultGame.ini b/openpype/hosts/unreal/integration/UE_4.7/CommandletProject/Config/DefaultGame.ini deleted file mode 100644 index 40956de961..0000000000 --- a/openpype/hosts/unreal/integration/UE_4.7/CommandletProject/Config/DefaultGame.ini +++ /dev/null @@ -1,4 +0,0 @@ - - -[/Script/EngineSettings.GeneralProjectSettings] -ProjectID=95AED0BF45A918DF73ABB3BB27D25356 diff --git a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/.gitignore b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/.gitignore index b32a6f55e5..5add07aef8 100644 --- a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/.gitignore +++ b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/.gitignore @@ -33,3 +33,9 @@ /Binaries /Intermediate +/Saved +/DerivedDataCache +/Content +/Config +/.idea +/.vs diff --git a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/OpenPype.uplugin b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/OpenPype.uplugin index 23155cb74d..b2cbe3cff3 100644 --- a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/OpenPype.uplugin +++ b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/OpenPype.uplugin @@ -12,7 +12,6 @@ "SupportURL": "https://pype.club/", "EngineVersion": "4.27", "CanContainContent": true, - "IsBetaVersion": true, "Installed": true, "Modules": [ { diff --git a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/OpenPype.Build.cs b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/OpenPype.Build.cs index 13afb11003..f77c1383eb 100644 --- a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/OpenPype.Build.cs +++ b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/OpenPype.Build.cs @@ -1,4 +1,4 @@ -// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. +// Copyright 2023, Ayon, All rights reserved. using UnrealBuildTool; diff --git a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/Commandlets/Implementations/OPGenerateProjectCommandlet.cpp b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/Commandlets/Implementations/OPGenerateProjectCommandlet.cpp index 024a6097b3..abb1975027 100644 --- a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/Commandlets/Implementations/OPGenerateProjectCommandlet.cpp +++ b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/Commandlets/Implementations/OPGenerateProjectCommandlet.cpp @@ -1,3 +1,4 @@ +// Copyright 2023, Ayon, All rights reserved. #include "Commandlets/Implementations/OPGenerateProjectCommandlet.h" #include "Editor.h" diff --git a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/Commandlets/OPActionResult.cpp b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/Commandlets/OPActionResult.cpp index 9236fbb057..6e50ef2221 100644 --- a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/Commandlets/OPActionResult.cpp +++ b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/Commandlets/OPActionResult.cpp @@ -1,4 +1,4 @@ -// Fill out your copyright notice in the Description page of Project Settings. +// Copyright 2023, Ayon, All rights reserved. #include "Commandlets/OPActionResult.h" diff --git a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/OpenPype.cpp b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/OpenPype.cpp index a510a5e3bf..9bf7b341c5 100644 --- a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/OpenPype.cpp +++ b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/OpenPype.cpp @@ -1,3 +1,4 @@ +// Copyright 2023, Ayon, All rights reserved. #include "OpenPype.h" #include "ISettingsContainer.h" diff --git a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/OpenPypeLib.cpp b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/OpenPypeLib.cpp index a58e921288..008025e816 100644 --- a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/OpenPypeLib.cpp +++ b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/OpenPypeLib.cpp @@ -1,3 +1,4 @@ +// Copyright 2023, Ayon, All rights reserved. #include "OpenPypeLib.h" #include "AssetViewUtils.h" diff --git a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/OpenPypePublishInstance.cpp b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/OpenPypePublishInstance.cpp index 424c4ed491..05638fbd0b 100644 --- a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/OpenPypePublishInstance.cpp +++ b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/OpenPypePublishInstance.cpp @@ -1,3 +1,4 @@ +// Copyright 2023, Ayon, All rights reserved. #pragma once #include "OpenPypePublishInstance.h" diff --git a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/OpenPypePublishInstanceFactory.cpp b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/OpenPypePublishInstanceFactory.cpp index 9b26da7fa4..a32ebe32cb 100644 --- a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/OpenPypePublishInstanceFactory.cpp +++ b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/OpenPypePublishInstanceFactory.cpp @@ -1,3 +1,4 @@ +// Copyright 2023, Ayon, All rights reserved. #include "OpenPypePublishInstanceFactory.h" #include "OpenPypePublishInstance.h" diff --git a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/OpenPypePythonBridge.cpp b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/OpenPypePythonBridge.cpp index 8113231503..6ebfc528f0 100644 --- a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/OpenPypePythonBridge.cpp +++ b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/OpenPypePythonBridge.cpp @@ -1,3 +1,4 @@ +// Copyright 2023, Ayon, All rights reserved. #include "OpenPypePythonBridge.h" UOpenPypePythonBridge* UOpenPypePythonBridge::Get() diff --git a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/OpenPypeSettings.cpp b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/OpenPypeSettings.cpp index 951b522308..dd4228dfd0 100644 --- a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/OpenPypeSettings.cpp +++ b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/OpenPypeSettings.cpp @@ -1,4 +1,4 @@ -// Fill out your copyright notice in the Description page of Project Settings. +// Copyright 2023, Ayon, All rights reserved. #include "OpenPypeSettings.h" diff --git a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/OpenPypeStyle.cpp b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/OpenPypeStyle.cpp index b7abc38156..0cc854c5ef 100644 --- a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/OpenPypeStyle.cpp +++ b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Private/OpenPypeStyle.cpp @@ -1,3 +1,4 @@ +// Copyright 2023, Ayon, All rights reserved. #include "OpenPypeStyle.h" #include "Framework/Application/SlateApplication.h" #include "Styling/SlateStyle.h" diff --git a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/Commandlets/Implementations/OPGenerateProjectCommandlet.h b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/Commandlets/Implementations/OPGenerateProjectCommandlet.h index 8738de6d4a..d1129aa070 100644 --- a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/Commandlets/Implementations/OPGenerateProjectCommandlet.h +++ b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/Commandlets/Implementations/OPGenerateProjectCommandlet.h @@ -1,6 +1,6 @@ +// Copyright 2023, Ayon, All rights reserved. #pragma once - #include "GameProjectUtils.h" #include "Commandlets/OPActionResult.h" #include "ProjectDescriptor.h" diff --git a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/Commandlets/OPActionResult.h b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/Commandlets/OPActionResult.h index f46ba9c62a..c960bbf190 100644 --- a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/Commandlets/OPActionResult.h +++ b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/Commandlets/OPActionResult.h @@ -1,4 +1,4 @@ -// Fill out your copyright notice in the Description page of Project Settings. +// Copyright 2023, Ayon, All rights reserved. #pragma once diff --git a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/Logging/OP_Log.h b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/Logging/OP_Log.h index 4f8af3e2e6..3740c5285a 100644 --- a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/Logging/OP_Log.h +++ b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/Logging/OP_Log.h @@ -1,3 +1,4 @@ +// Copyright 2023, Ayon, All rights reserved. #pragma once DEFINE_LOG_CATEGORY_STATIC(LogCommandletOPGenerateProject, Log, All); \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OPConstants.h b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OPConstants.h index 21a033e426..f4587f7a50 100644 --- a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OPConstants.h +++ b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OPConstants.h @@ -1,3 +1,4 @@ +// Copyright 2023, Ayon, All rights reserved. #pragma once namespace OPConstants diff --git a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OpenPype.h b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OpenPype.h index 9cfa60176c..2454344128 100644 --- a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OpenPype.h +++ b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OpenPype.h @@ -1,4 +1,4 @@ -// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. +// Copyright 2023, Ayon, All rights reserved. #pragma once diff --git a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OpenPypeLib.h b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OpenPypeLib.h index 06425c7c7d..ef4d1027ea 100644 --- a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OpenPypeLib.h +++ b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OpenPypeLib.h @@ -1,3 +1,4 @@ +// Copyright 2023, Ayon, All rights reserved. #pragma once #include "Engine.h" diff --git a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OpenPypePublishInstance.h b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OpenPypePublishInstance.h index 16b3194b96..8cfcd067c0 100644 --- a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OpenPypePublishInstance.h +++ b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OpenPypePublishInstance.h @@ -1,3 +1,4 @@ +// Copyright 2023, Ayon, All rights reserved. #pragma once #include "Engine.h" diff --git a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OpenPypePublishInstanceFactory.h b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OpenPypePublishInstanceFactory.h index 7d2c77fe6e..3fdb984411 100644 --- a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OpenPypePublishInstanceFactory.h +++ b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OpenPypePublishInstanceFactory.h @@ -1,3 +1,4 @@ +// Copyright 2023, Ayon, All rights reserved. #pragma once #include "CoreMinimal.h" diff --git a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OpenPypePythonBridge.h b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OpenPypePythonBridge.h index 692aab2e5e..827f76f56b 100644 --- a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OpenPypePythonBridge.h +++ b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OpenPypePythonBridge.h @@ -1,3 +1,4 @@ +// Copyright 2023, Ayon, All rights reserved. #pragma once #include "Engine.h" #include "OpenPypePythonBridge.generated.h" diff --git a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OpenPypeSettings.h b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OpenPypeSettings.h index 9bdcfb2399..88defaa773 100644 --- a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OpenPypeSettings.h +++ b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OpenPypeSettings.h @@ -1,4 +1,4 @@ -// Fill out your copyright notice in the Description page of Project Settings. +// Copyright 2023, Ayon, All rights reserved. #pragma once diff --git a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OpenPypeStyle.h b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OpenPypeStyle.h index fbc8bcdd5b..0e4af129d0 100644 --- a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OpenPypeStyle.h +++ b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/Source/OpenPype/Public/OpenPypeStyle.h @@ -1,3 +1,4 @@ +// Copyright 2023, Ayon, All rights reserved. #pragma once #include "CoreMinimal.h" diff --git a/openpype/hosts/unreal/integration/UE_5.0/CommandletProject/.gitignore b/openpype/hosts/unreal/integration/UE_5.0/CommandletProject/.gitignore index 1004610e4f..80814ef0a6 100644 --- a/openpype/hosts/unreal/integration/UE_5.0/CommandletProject/.gitignore +++ b/openpype/hosts/unreal/integration/UE_5.0/CommandletProject/.gitignore @@ -1,6 +1,41 @@ +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + /Saved /DerivedDataCache /Intermediate /Binaries +/Content +/Config /.idea /.vs \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_5.0/CommandletProject/Config/DefaultEditor.ini b/openpype/hosts/unreal/integration/UE_5.0/CommandletProject/Config/DefaultEditor.ini deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/openpype/hosts/unreal/integration/UE_5.0/CommandletProject/Config/DefaultEngine.ini b/openpype/hosts/unreal/integration/UE_5.0/CommandletProject/Config/DefaultEngine.ini deleted file mode 100644 index 3f5357dac4..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.0/CommandletProject/Config/DefaultEngine.ini +++ /dev/null @@ -1,42 +0,0 @@ - - -[/Script/EngineSettings.GameMapsSettings] -GameDefaultMap=/Engine/Maps/Templates/OpenWorld - - -[/Script/HardwareTargeting.HardwareTargetingSettings] -TargetedHardwareClass=Desktop -AppliedTargetedHardwareClass=Desktop -DefaultGraphicsPerformance=Maximum -AppliedDefaultGraphicsPerformance=Maximum - -[/Script/WindowsTargetPlatform.WindowsTargetSettings] -DefaultGraphicsRHI=DefaultGraphicsRHI_DX12 - -[/Script/Engine.RendererSettings] -r.GenerateMeshDistanceFields=True -r.DynamicGlobalIlluminationMethod=1 -r.ReflectionMethod=1 -r.Shadow.Virtual.Enable=1 - -[/Script/WorldPartitionEditor.WorldPartitionEditorSettings] -CommandletClass=Class'/Script/UnrealEd.WorldPartitionConvertCommandlet' - -[/Script/Engine.Engine] -+ActiveGameNameRedirects=(OldGameName="TP_BlankBP",NewGameName="/Script/CommandletProject") -+ActiveGameNameRedirects=(OldGameName="/Script/TP_BlankBP",NewGameName="/Script/CommandletProject") - -[/Script/AndroidFileServerEditor.AndroidFileServerRuntimeSettings] -bEnablePlugin=True -bAllowNetworkConnection=True -SecurityToken=684C16AF4BD96F1D6828A6B067693175 -bIncludeInShipping=False -bAllowExternalStartInShipping=False -bCompileAFSProject=False -bUseCompression=False -bLogFiles=False -bReportStats=False -ConnectionType=USBOnly -bUseManualIPAddress=False -ManualIPAddress= - diff --git a/openpype/hosts/unreal/integration/UE_5.0/CommandletProject/Config/DefaultGame.ini b/openpype/hosts/unreal/integration/UE_5.0/CommandletProject/Config/DefaultGame.ini deleted file mode 100644 index c661b739ab..0000000000 --- a/openpype/hosts/unreal/integration/UE_5.0/CommandletProject/Config/DefaultGame.ini +++ /dev/null @@ -1,4 +0,0 @@ - - -[/Script/EngineSettings.GeneralProjectSettings] -ProjectID=D528076140C577E5807BA5BA135366BB diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/OpenPype.uplugin b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/OpenPype.uplugin index b89eb43949..ff08edc13e 100644 --- a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/OpenPype.uplugin +++ b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/OpenPype.uplugin @@ -12,9 +12,8 @@ "SupportURL": "https://pype.club/", "CanContainContent": true, "EngineVersion": "5.0", - "IsBetaVersion": true, "IsExperimentalVersion": false, - "Installed": false, + "Installed": true, "Modules": [ { "Name": "OpenPype", diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/OpenPype.Build.cs b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/OpenPype.Build.cs index 99c1c7b306..e1087fd720 100644 --- a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/OpenPype.Build.cs +++ b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/OpenPype.Build.cs @@ -1,4 +1,4 @@ -// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. +// Copyright 2023, Ayon, All rights reserved. using UnrealBuildTool; diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/Commandlets/Implementations/OPGenerateProjectCommandlet.cpp b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/Commandlets/Implementations/OPGenerateProjectCommandlet.cpp index 024a6097b3..abb1975027 100644 --- a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/Commandlets/Implementations/OPGenerateProjectCommandlet.cpp +++ b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/Commandlets/Implementations/OPGenerateProjectCommandlet.cpp @@ -1,3 +1,4 @@ +// Copyright 2023, Ayon, All rights reserved. #include "Commandlets/Implementations/OPGenerateProjectCommandlet.h" #include "Editor.h" diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/Commandlets/OPActionResult.cpp b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/Commandlets/OPActionResult.cpp index 9236fbb057..23ae2dd329 100644 --- a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/Commandlets/OPActionResult.cpp +++ b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/Commandlets/OPActionResult.cpp @@ -1,5 +1,4 @@ -// Fill out your copyright notice in the Description page of Project Settings. - +// Copyright 2023, Ayon, All rights reserved. #include "Commandlets/OPActionResult.h" #include "Logging/OP_Log.h" diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/Logging/OP_Log.cpp b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/Logging/OP_Log.cpp index 29b1068c21..198fb9df0c 100644 --- a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/Logging/OP_Log.cpp +++ b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/Logging/OP_Log.cpp @@ -1 +1,3 @@ +// Copyright 2023, Ayon, All rights reserved. + #include "Logging/OP_Log.h" diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPype.cpp b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPype.cpp index d23de61102..65da29da35 100644 --- a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPype.cpp +++ b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPype.cpp @@ -1,3 +1,4 @@ +// Copyright 2023, Ayon, All rights reserved. #include "OpenPype.h" #include "ISettingsContainer.h" diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPypeCommands.cpp b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPypeCommands.cpp index 6187bd7c7e..881814e278 100644 --- a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPypeCommands.cpp +++ b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPypeCommands.cpp @@ -1,4 +1,4 @@ -// Copyright Epic Games, Inc. All Rights Reserved. +// Copyright 2023, Ayon, All rights reserved. #include "OpenPypeCommands.h" diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPypeLib.cpp b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPypeLib.cpp index a58e921288..008025e816 100644 --- a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPypeLib.cpp +++ b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPypeLib.cpp @@ -1,3 +1,4 @@ +// Copyright 2023, Ayon, All rights reserved. #include "OpenPypeLib.h" #include "AssetViewUtils.h" diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPypePublishInstance.cpp b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPypePublishInstance.cpp index e6a85002c7..05d5c8a87d 100644 --- a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPypePublishInstance.cpp +++ b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPypePublishInstance.cpp @@ -1,3 +1,4 @@ +// Copyright 2023, Ayon, All rights reserved. #pragma once #include "OpenPypePublishInstance.h" diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPypePublishInstanceFactory.cpp b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPypePublishInstanceFactory.cpp index 9b26da7fa4..a32ebe32cb 100644 --- a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPypePublishInstanceFactory.cpp +++ b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPypePublishInstanceFactory.cpp @@ -1,3 +1,4 @@ +// Copyright 2023, Ayon, All rights reserved. #include "OpenPypePublishInstanceFactory.h" #include "OpenPypePublishInstance.h" diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPypePythonBridge.cpp b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPypePythonBridge.cpp index 8113231503..6ebfc528f0 100644 --- a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPypePythonBridge.cpp +++ b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPypePythonBridge.cpp @@ -1,3 +1,4 @@ +// Copyright 2023, Ayon, All rights reserved. #include "OpenPypePythonBridge.h" UOpenPypePythonBridge* UOpenPypePythonBridge::Get() diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPypeSettings.cpp b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPypeSettings.cpp index a6b9eba749..6562a81138 100644 --- a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPypeSettings.cpp +++ b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPypeSettings.cpp @@ -1,4 +1,4 @@ -// Fill out your copyright notice in the Description page of Project Settings. +// Copyright 2023, Ayon, All rights reserved. #include "OpenPypeSettings.h" diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPypeStyle.cpp b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPypeStyle.cpp index 49e805da4d..a4d75e048e 100644 --- a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPypeStyle.cpp +++ b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/OpenPypeStyle.cpp @@ -1,3 +1,5 @@ +// Copyright 2023, Ayon, All rights reserved. + #include "OpenPypeStyle.h" #include "OpenPype.h" #include "Framework/Application/SlateApplication.h" diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/Commandlets/Implementations/OPGenerateProjectCommandlet.h b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/Commandlets/Implementations/OPGenerateProjectCommandlet.h index 8738de6d4a..6a6c6406e7 100644 --- a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/Commandlets/Implementations/OPGenerateProjectCommandlet.h +++ b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/Commandlets/Implementations/OPGenerateProjectCommandlet.h @@ -1,3 +1,4 @@ +// Copyright 2023, Ayon, All rights reserved. #pragma once diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/Commandlets/OPActionResult.h b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/Commandlets/OPActionResult.h index f46ba9c62a..c960bbf190 100644 --- a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/Commandlets/OPActionResult.h +++ b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/Commandlets/OPActionResult.h @@ -1,4 +1,4 @@ -// Fill out your copyright notice in the Description page of Project Settings. +// Copyright 2023, Ayon, All rights reserved. #pragma once diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/Logging/OP_Log.h b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/Logging/OP_Log.h index 4f8af3e2e6..3740c5285a 100644 --- a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/Logging/OP_Log.h +++ b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/Logging/OP_Log.h @@ -1,3 +1,4 @@ +// Copyright 2023, Ayon, All rights reserved. #pragma once DEFINE_LOG_CATEGORY_STATIC(LogCommandletOPGenerateProject, Log, All); \ No newline at end of file diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OPConstants.h b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OPConstants.h index 21a033e426..f4587f7a50 100644 --- a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OPConstants.h +++ b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OPConstants.h @@ -1,3 +1,4 @@ +// Copyright 2023, Ayon, All rights reserved. #pragma once namespace OPConstants diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OpenPype.h b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OpenPype.h index 4261476da8..b89760099b 100644 --- a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OpenPype.h +++ b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OpenPype.h @@ -1,4 +1,4 @@ -// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. +// Copyright 2023, Ayon, All rights reserved. #pragma once diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OpenPypeCommands.h b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OpenPypeCommands.h index 62ffb8de33..99b0be26f0 100644 --- a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OpenPypeCommands.h +++ b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OpenPypeCommands.h @@ -1,4 +1,4 @@ -// Copyright Epic Games, Inc. All Rights Reserved. +// Copyright 2023, Ayon, All rights reserved. #pragma once diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OpenPypeLib.h b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OpenPypeLib.h index 06425c7c7d..ef4d1027ea 100644 --- a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OpenPypeLib.h +++ b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OpenPypeLib.h @@ -1,3 +1,4 @@ +// Copyright 2023, Ayon, All rights reserved. #pragma once #include "Engine.h" diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OpenPypePublishInstance.h b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OpenPypePublishInstance.h index c221f64135..bce41ef1b1 100644 --- a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OpenPypePublishInstance.h +++ b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OpenPypePublishInstance.h @@ -1,3 +1,4 @@ +// Copyright 2023, Ayon, All rights reserved. #pragma once #include "Engine.h" diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OpenPypePublishInstanceFactory.h b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OpenPypePublishInstanceFactory.h index 7d2c77fe6e..3fdb984411 100644 --- a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OpenPypePublishInstanceFactory.h +++ b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OpenPypePublishInstanceFactory.h @@ -1,3 +1,4 @@ +// Copyright 2023, Ayon, All rights reserved. #pragma once #include "CoreMinimal.h" diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OpenPypePythonBridge.h b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OpenPypePythonBridge.h index 692aab2e5e..827f76f56b 100644 --- a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OpenPypePythonBridge.h +++ b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OpenPypePythonBridge.h @@ -1,3 +1,4 @@ +// Copyright 2023, Ayon, All rights reserved. #pragma once #include "Engine.h" #include "OpenPypePythonBridge.generated.h" diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OpenPypeSettings.h b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OpenPypeSettings.h index aca80946bb..b818fe0e95 100644 --- a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OpenPypeSettings.h +++ b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OpenPypeSettings.h @@ -1,4 +1,4 @@ -// Fill out your copyright notice in the Description page of Project Settings. +// Copyright 2023, Ayon, All rights reserved. #pragma once diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OpenPypeStyle.h b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OpenPypeStyle.h index ae704251e1..039abe96ef 100644 --- a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OpenPypeStyle.h +++ b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/OpenPypeStyle.h @@ -1,3 +1,4 @@ +// Copyright 2023, Ayon, All rights reserved. #pragma once #include "CoreMinimal.h" #include "Styling/SlateStyle.h" diff --git a/openpype/hosts/unreal/lib.py b/openpype/hosts/unreal/lib.py index 2e1f59d439..28a5106042 100644 --- a/openpype/hosts/unreal/lib.py +++ b/openpype/hosts/unreal/lib.py @@ -229,7 +229,7 @@ def create_unreal_project(project_name: str, print("--- Generating a new project ...") commandlet_cmd = [f'{ue_editor_exe.as_posix()}', f'{cmdlet_project.as_posix()}', - f'-run=OPGenerateProjec', + f'-run=OPGenerateProject', f'{project_file.resolve().as_posix()}'] if dev_mode or preset["dev_mode"]: @@ -365,9 +365,7 @@ def _get_build_id(engine_path: Path, ue_version: str) -> str: return "{" + loaded_modules.get("BuildId") + "}" -def try_installing_plugin(engine_path: Path, - ue_version: str, - env: dict = None) -> None: +def try_installing_plugin(engine_path: Path, env: dict = None) -> None: env = env or os.environ integration_plugin_path: Path = Path(env.get("OPENPYPE_UNREAL_PLUGIN", "")) From f91cefa2fd1089ac564848254e16b7a0ba95ea37 Mon Sep 17 00:00:00 2001 From: Joseff Date: Mon, 20 Feb 2023 16:03:03 +0100 Subject: [PATCH 059/184] Updated .gitignore for the OpenPype plugin --- .../hosts/unreal/integration/UE_4.7/OpenPype/.gitignore | 6 ------ 1 file changed, 6 deletions(-) diff --git a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/.gitignore b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/.gitignore index 5add07aef8..b32a6f55e5 100644 --- a/openpype/hosts/unreal/integration/UE_4.7/OpenPype/.gitignore +++ b/openpype/hosts/unreal/integration/UE_4.7/OpenPype/.gitignore @@ -33,9 +33,3 @@ /Binaries /Intermediate -/Saved -/DerivedDataCache -/Content -/Config -/.idea -/.vs From b1fa39439603a19b8884d1613f4a67ca55d33ff6 Mon Sep 17 00:00:00 2001 From: Joseff Date: Tue, 21 Feb 2023 20:23:32 +0100 Subject: [PATCH 060/184] Replaced the warning for exceeding the project name length with an exception. --- openpype/hosts/unreal/hooks/pre_workfile_preparation.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/unreal/hooks/pre_workfile_preparation.py b/openpype/hosts/unreal/hooks/pre_workfile_preparation.py index 14285cb78c..4c9f8258f5 100644 --- a/openpype/hosts/unreal/hooks/pre_workfile_preparation.py +++ b/openpype/hosts/unreal/hooks/pre_workfile_preparation.py @@ -79,9 +79,9 @@ class UnrealPrelaunchHook(PreLaunchHook): unreal_project_name = os.path.splitext(unreal_project_filename)[0] # Unreal is sensitive about project names longer then 20 chars if len(unreal_project_name) > 20: - self.log.warning(( - f"Project name exceed 20 characters ({unreal_project_name})!" - )) + raise ApplicationLaunchFailed( + f"Project name exceeds 20 characters ({unreal_project_name})!" + ) # Unreal doesn't accept non alphabet characters at the start # of the project name. This is because project name is then used From c2685a6c57394a4fb839a1618846f1363f470657 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 24 Feb 2023 17:02:18 +0100 Subject: [PATCH 061/184] Nuke: caching settings and retrieved active farm module --- openpype/hosts/nuke/api/lib_rendersettings.py | 35 ++++++++++++++----- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/nuke/api/lib_rendersettings.py b/openpype/hosts/nuke/api/lib_rendersettings.py index 4d5440fe48..6784890160 100644 --- a/openpype/hosts/nuke/api/lib_rendersettings.py +++ b/openpype/hosts/nuke/api/lib_rendersettings.py @@ -12,25 +12,40 @@ class RenderFarmSettings: log = Logger.get_logger("RenderFarmSettings") _active_farm_module: str = None - _farm_modules: list = [ - "deadline", "muster", "royalrender"] + _farm_modules: list = ["deadline"] _farm_plugins: dict = { "deadline": "NukeSubmitDeadline" } _creator_farm_keys: list = [ "chunk_size", "priority", "concurrent_tasks"] + _cached_project_settings = None + _cached_system_settings = None + def __init__(self, project_settings=None, log=None): """ Get project settings and active farm module """ if log: self.log = log - self._project_settings = ( - project_settings or get_current_project_settings() - ) - # Get active farm module from system settings - self._get_active_farm_module_from_system_settings() + if project_settings: + self._cached_project_settings = project_settings + + @property + def project_settings(self): + """ returning cached project settings or getting new one + """ + if not self._cached_project_settings: + self._cached_project_settings = get_current_project_settings() + return self._cached_project_settings + + @property + def system_settings(self): + """ returning cached project settings or getting new one + """ + if not self._cached_system_settings: + self._cached_system_settings = get_system_settings() + return self._cached_system_settings def _get_active_farm_module_from_system_settings(self): """ Get active farm module from system settings @@ -38,7 +53,7 @@ class RenderFarmSettings: active_modules = [ module_ for module_ in self._farm_modules - if get_system_settings()["modules"][module_]["enabled"] + if self.system_settings["modules"][module_]["enabled"] ] if not active_modules: raise ValueError(( @@ -54,6 +69,10 @@ class RenderFarmSettings: @property def active_farm_module(self): + # cache active farm module + if self._active_farm_module is None: + self._get_active_farm_module_from_system_settings() + return self._active_farm_module def get_rendering_attributes(self): From 61cf3a068d5aea4daab92201540e6b33c9a0fe0a Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 24 Feb 2023 17:07:43 +0100 Subject: [PATCH 062/184] Nuke: missing bits --- openpype/hosts/nuke/api/lib_rendersettings.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/nuke/api/lib_rendersettings.py b/openpype/hosts/nuke/api/lib_rendersettings.py index 6784890160..5c23bcb1bc 100644 --- a/openpype/hosts/nuke/api/lib_rendersettings.py +++ b/openpype/hosts/nuke/api/lib_rendersettings.py @@ -41,7 +41,7 @@ class RenderFarmSettings: @property def system_settings(self): - """ returning cached project settings or getting new one + """ returning cached system settings or getting new one """ if not self._cached_system_settings: self._cached_system_settings = get_system_settings() @@ -91,7 +91,7 @@ class RenderFarmSettings: ).format(farm_plugin)) # Get farm module settings - module_settings = self._project_settings[self.active_farm_module] + module_settings = self.project_settings[self.active_farm_module] # Get farm plugin settings farm_plugin_settings = ( From 9bd3ff7184c14394057e96568fff574a5e0a22cb Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Fri, 24 Feb 2023 17:51:24 +0000 Subject: [PATCH 063/184] Fix broken lib. --- openpype/hosts/maya/api/lib.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index ce80396326..4324d321dc 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -403,9 +403,9 @@ def lsattrs(attrs): """ - dep_fn = om.MFnDependencyNode() - dag_fn = om.MFnDagNode() - selection_list = om.MSelectionList() + dep_fn = OpenMaya.MFnDependencyNode() + dag_fn = OpenMaya.MFnDagNode() + selection_list = OpenMaya.MSelectionList() first_attr = next(iter(attrs)) @@ -419,7 +419,7 @@ def lsattrs(attrs): matches = set() for i in range(selection_list.length()): node = selection_list.getDependNode(i) - if node.hasFn(om.MFn.kDagNode): + if node.hasFn(OpenMaya.MFn.kDagNode): fn_node = dag_fn.setObject(node) full_path_names = [path.fullPathName() for path in fn_node.getAllPaths()] @@ -868,11 +868,11 @@ def maintained_selection_api(): Warning: This is *not* added to the undo stack. """ - original = om.MGlobal.getActiveSelectionList() + original = OpenMaya.MGlobal.getActiveSelectionList() try: yield finally: - om.MGlobal.setActiveSelectionList(original) + OpenMaya.MGlobal.setActiveSelectionList(original) @contextlib.contextmanager @@ -1282,11 +1282,11 @@ def get_id(node): if node is None: return - sel = om.MSelectionList() + sel = OpenMaya.MSelectionList() sel.add(node) api_node = sel.getDependNode(0) - fn = om.MFnDependencyNode(api_node) + fn = OpenMaya.MFnDependencyNode(api_node) if not fn.hasAttribute("cbId"): return @@ -3341,15 +3341,15 @@ def iter_visible_nodes_in_range(nodes, start, end): @memodict def get_visibility_mplug(node): """Return api 2.0 MPlug with cached memoize decorator""" - sel = om.MSelectionList() + sel = OpenMaya.MSelectionList() sel.add(node) dag = sel.getDagPath(0) - return om.MFnDagNode(dag).findPlug("visibility", True) + return OpenMaya.MFnDagNode(dag).findPlug("visibility", True) @contextlib.contextmanager def dgcontext(mtime): """MDGContext context manager""" - context = om.MDGContext(mtime) + context = OpenMaya.MDGContext(mtime) try: previous = context.makeCurrent() yield context @@ -3358,9 +3358,9 @@ def iter_visible_nodes_in_range(nodes, start, end): # We skip the first frame as we already used that frame to check for # overall visibilities. And end+1 to include the end frame. - scene_units = om.MTime.uiUnit() + scene_units = OpenMaya.MTime.uiUnit() for frame in range(start + 1, end + 1): - mtime = om.MTime(frame, unit=scene_units) + mtime = OpenMaya.MTime(frame, unit=scene_units) # Build little cache so we don't query the same MPlug's value # again if it was checked on this frame and also is a dependency From 3ae3d01a200ee0b007d38964ffa22573f441ee62 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 23 Feb 2023 22:02:03 +0100 Subject: [PATCH 064/184] Don't use ObjectId in scene inventory view --- openpype/tools/sceneinventory/view.py | 42 ++++++++++++--------------- 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/openpype/tools/sceneinventory/view.py b/openpype/tools/sceneinventory/view.py index 3c4e03a195..d7c80df692 100644 --- a/openpype/tools/sceneinventory/view.py +++ b/openpype/tools/sceneinventory/view.py @@ -4,7 +4,6 @@ from functools import partial from qtpy import QtWidgets, QtCore import qtawesome -from bson.objectid import ObjectId from openpype.client import ( get_version_by_id, @@ -84,22 +83,20 @@ class SceneInventoryView(QtWidgets.QTreeView): if not items: return - repre_ids = [] - for item in items: - item_id = ObjectId(item["representation"]) - if item_id not in repre_ids: - repre_ids.append(item_id) + repre_ids = { + item["representation"] + for item in items + } project_name = legacy_io.active_project() repre_docs = get_representations( project_name, representation_ids=repre_ids, fields=["parent"] ) - version_ids = [] - for repre_doc in repre_docs: - version_id = repre_doc["parent"] - if version_id not in version_ids: - version_ids.append(version_id) + version_ids = { + repre_doc["parent"] + for repre_doc in repre_docs + } loaded_versions = get_versions( project_name, version_ids=version_ids, hero=True @@ -107,18 +104,17 @@ class SceneInventoryView(QtWidgets.QTreeView): loaded_hero_versions = [] versions_by_parent_id = collections.defaultdict(list) - version_parents = [] + subset_ids = set() for version in loaded_versions: if version["type"] == "hero_version": loaded_hero_versions.append(version) else: parent_id = version["parent"] versions_by_parent_id[parent_id].append(version) - if parent_id not in version_parents: - version_parents.append(parent_id) + subset_ids.add(parent_id) all_versions = get_versions( - project_name, subset_ids=version_parents, hero=True + project_name, subset_ids=subset_ids, hero=True ) hero_versions = [] versions = [] @@ -146,11 +142,11 @@ class SceneInventoryView(QtWidgets.QTreeView): switch_to_versioned = None if has_loaded_hero_versions: def _on_switch_to_versioned(items): - repre_ids = [] + repre_ids = set() for item in items: - item_id = ObjectId(item["representation"]) + item_id = item["representation"] if item_id not in repre_ids: - repre_ids.append(item_id) + repre_ids.add(item_id) repre_docs = get_representations( project_name, @@ -158,13 +154,13 @@ class SceneInventoryView(QtWidgets.QTreeView): fields=["parent"] ) - version_ids = [] + version_ids = set() version_id_by_repre_id = {} for repre_doc in repre_docs: version_id = repre_doc["parent"] - version_id_by_repre_id[repre_doc["_id"]] = version_id - if version_id not in version_ids: - version_ids.append(version_id) + repre_id = str(repre_doc["_id"]) + version_id_by_repre_id[repre_id] = version_id + version_ids.add(version_id) hero_versions = get_hero_versions( project_name, @@ -194,7 +190,7 @@ class SceneInventoryView(QtWidgets.QTreeView): version_doc["name"] for item in items: - repre_id = ObjectId(item["representation"]) + repre_id = item["representation"] version_id = version_id_by_repre_id.get(repre_id) version_name = version_name_by_id.get(version_id) if version_name is not None: From cba1b46765ca2d03b3881b8fdb19fa73b205be30 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 23 Feb 2023 22:02:28 +0100 Subject: [PATCH 065/184] don't use ObjectId in switch dialog --- openpype/tools/sceneinventory/switch_dialog.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/openpype/tools/sceneinventory/switch_dialog.py b/openpype/tools/sceneinventory/switch_dialog.py index 47baeaebea..e9575f254b 100644 --- a/openpype/tools/sceneinventory/switch_dialog.py +++ b/openpype/tools/sceneinventory/switch_dialog.py @@ -2,7 +2,6 @@ import collections import logging from qtpy import QtWidgets, QtCore import qtawesome -from bson.objectid import ObjectId from openpype.client import ( get_asset_by_name, @@ -161,7 +160,7 @@ class SwitchAssetDialog(QtWidgets.QDialog): repre_ids = set() content_loaders = set() for item in self._items: - repre_ids.add(ObjectId(item["representation"])) + repre_ids.add(str(item["representation"])) content_loaders.add(item["loader"]) project_name = self.active_project() @@ -170,7 +169,7 @@ class SwitchAssetDialog(QtWidgets.QDialog): representation_ids=repre_ids, archived=True )) - repres_by_id = {repre["_id"]: repre for repre in repres} + repres_by_id = {str(repre["_id"]): repre for repre in repres} # stash context values, works only for single representation if len(repres) == 1: @@ -181,18 +180,18 @@ class SwitchAssetDialog(QtWidgets.QDialog): content_repres = {} archived_repres = [] missing_repres = [] - version_ids = [] + version_ids = set() for repre_id in repre_ids: if repre_id not in repres_by_id: missing_repres.append(repre_id) elif repres_by_id[repre_id]["type"] == "archived_representation": repre = repres_by_id[repre_id] archived_repres.append(repre) - version_ids.append(repre["parent"]) + version_ids.add(repre["parent"]) else: repre = repres_by_id[repre_id] content_repres[repre_id] = repres_by_id[repre_id] - version_ids.append(repre["parent"]) + version_ids.add(repre["parent"]) versions = get_versions( project_name, @@ -1249,7 +1248,7 @@ class SwitchAssetDialog(QtWidgets.QDialog): repre_docs_by_parent_id_by_name[parent_id][name] = repre_doc for container in self._items: - container_repre_id = ObjectId(container["representation"]) + container_repre_id = container["representation"] container_repre = self.content_repres[container_repre_id] container_repre_name = container_repre["name"] From 81ed872905bb1d43ef1c2340e1890e329cf31867 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 24 Feb 2023 10:31:49 +0100 Subject: [PATCH 066/184] apply suggested changes --- openpype/tools/sceneinventory/switch_dialog.py | 2 +- openpype/tools/sceneinventory/view.py | 15 +++++++-------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/openpype/tools/sceneinventory/switch_dialog.py b/openpype/tools/sceneinventory/switch_dialog.py index e9575f254b..4aaad38bbc 100644 --- a/openpype/tools/sceneinventory/switch_dialog.py +++ b/openpype/tools/sceneinventory/switch_dialog.py @@ -195,7 +195,7 @@ class SwitchAssetDialog(QtWidgets.QDialog): versions = get_versions( project_name, - version_ids=set(version_ids), + version_ids=version_ids, hero=True ) content_versions = {} diff --git a/openpype/tools/sceneinventory/view.py b/openpype/tools/sceneinventory/view.py index d7c80df692..a04171e429 100644 --- a/openpype/tools/sceneinventory/view.py +++ b/openpype/tools/sceneinventory/view.py @@ -142,11 +142,10 @@ class SceneInventoryView(QtWidgets.QTreeView): switch_to_versioned = None if has_loaded_hero_versions: def _on_switch_to_versioned(items): - repre_ids = set() - for item in items: - item_id = item["representation"] - if item_id not in repre_ids: - repre_ids.add(item_id) + repre_ids = { + item["representation"] + for item in items + } repre_docs = get_representations( project_name, @@ -168,10 +167,10 @@ class SceneInventoryView(QtWidgets.QTreeView): fields=["version_id"] ) - version_ids = set() + hero_src_version_ids = set() for hero_version in hero_versions: version_id = hero_version["version_id"] - version_ids.add(version_id) + hero_src_version_ids.add(version_id) hero_version_id = hero_version["_id"] for _repre_id, current_version_id in ( version_id_by_repre_id.items() @@ -181,7 +180,7 @@ class SceneInventoryView(QtWidgets.QTreeView): version_docs = get_versions( project_name, - version_ids=version_ids, + version_ids=hero_src_version_ids, fields=["name"] ) version_name_by_id = {} From eb72d10f934e816f8df966cb200e1b686d366e04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Fri, 24 Feb 2023 21:43:21 +0100 Subject: [PATCH 067/184] global: source template fixed frame duplication (#4503) --- openpype/settings/defaults/project_anatomy/templates.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/settings/defaults/project_anatomy/templates.json b/openpype/settings/defaults/project_anatomy/templates.json index 99a869963b..02c0e35377 100644 --- a/openpype/settings/defaults/project_anatomy/templates.json +++ b/openpype/settings/defaults/project_anatomy/templates.json @@ -55,7 +55,7 @@ }, "source": { "folder": "{root[work]}/{originalDirname}", - "file": "{originalBasename}<.{@frame}><_{udim}>.{ext}", + "file": "{originalBasename}.{ext}", "path": "{@folder}/{@file}" }, "__dynamic_keys_labels__": { From 0e896d981e87068659deda3f4daabcd79ca21490 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 14 Feb 2023 15:48:32 +0000 Subject: [PATCH 068/184] Simplify pointcache proxy integration --- .../plugins/publish/extract_pointcache.py | 30 +++++-------------- 1 file changed, 8 insertions(+), 22 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_pointcache.py b/openpype/hosts/maya/plugins/publish/extract_pointcache.py index e551858d48..153c177043 100644 --- a/openpype/hosts/maya/plugins/publish/extract_pointcache.py +++ b/openpype/hosts/maya/plugins/publish/extract_pointcache.py @@ -1,5 +1,4 @@ import os -import copy from maya import cmds @@ -10,7 +9,6 @@ from openpype.hosts.maya.api.lib import ( maintained_selection, iter_visible_nodes_in_range ) -from openpype.lib import StringTemplate class ExtractAlembic(publish.Extractor): @@ -135,26 +133,14 @@ class ExtractAlembic(publish.Extractor): **options ) - template_data = copy.deepcopy(instance.data["anatomyData"]) - template_data.update({"ext": "abc"}) - templates = instance.context.data["anatomy"].templates["publish"] - published_filename_without_extension = StringTemplate( - templates["file"] - ).format(template_data).replace(".abc", "_proxy") - transfers = [] - destination = os.path.join( - instance.data["resourcesDir"], - filename.replace( - filename.split(".")[0], - published_filename_without_extension - ) - ) - transfers.append((path, destination)) - - for source, destination in transfers: - self.log.debug("Transfer: {} > {}".format(source, destination)) - - instance.data["transfers"] = transfers + representation = { + "name": "proxy", + "ext": "abc", + "files": path, + "stagingDir": dirname, + "outputName": "proxy" + } + instance.data["representations"].append(representation) def get_members_and_roots(self, instance): return instance[:], instance.data.get("setMembers") From 458eaa8d80889a9d2f1e812b8bd56e2aac500dad Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 16 Feb 2023 16:09:15 +0000 Subject: [PATCH 069/184] Fix pointcache proxy publishing --- openpype/hosts/maya/plugins/publish/extract_pointcache.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_pointcache.py b/openpype/hosts/maya/plugins/publish/extract_pointcache.py index 153c177043..892603535c 100644 --- a/openpype/hosts/maya/plugins/publish/extract_pointcache.py +++ b/openpype/hosts/maya/plugins/publish/extract_pointcache.py @@ -136,7 +136,7 @@ class ExtractAlembic(publish.Extractor): representation = { "name": "proxy", "ext": "abc", - "files": path, + "files": os.path.basename(path), "stagingDir": dirname, "outputName": "proxy" } From 2a213ec5618923336cb9abe38192c0aa5f606252 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Fri, 17 Feb 2023 15:18:01 +0000 Subject: [PATCH 070/184] Inform about skipping proxy extraction. --- openpype/hosts/maya/plugins/publish/extract_pointcache.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hosts/maya/plugins/publish/extract_pointcache.py b/openpype/hosts/maya/plugins/publish/extract_pointcache.py index 892603535c..a3b0560099 100644 --- a/openpype/hosts/maya/plugins/publish/extract_pointcache.py +++ b/openpype/hosts/maya/plugins/publish/extract_pointcache.py @@ -114,6 +114,7 @@ class ExtractAlembic(publish.Extractor): # Extract proxy. if not instance.data.get("proxy"): + self.log.info("No proxy nodes found. Skipping proxy extraction.") return path = path.replace(".abc", "_proxy.abc") From 604f66b71e374f7f2f573a01ecf93ee1ff5465b8 Mon Sep 17 00:00:00 2001 From: Ynbot Date: Sat, 25 Feb 2023 03:28:37 +0000 Subject: [PATCH 071/184] [Automated] Bump version --- openpype/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/version.py b/openpype/version.py index bb5171764c..bd1ba5309d 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.15.2-nightly.1" +__version__ = "3.15.2-nightly.2" From d81debdc1050a008597515b1655dfe341566eba5 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 23 Feb 2023 16:18:14 +0100 Subject: [PATCH 072/184] implemented methods to get active site type from sync server module --- .../modules/sync_server/sync_server_module.py | 101 +++++++++++++++++- 1 file changed, 99 insertions(+), 2 deletions(-) diff --git a/openpype/modules/sync_server/sync_server_module.py b/openpype/modules/sync_server/sync_server_module.py index ba0abe7d3b..1c2e820031 100644 --- a/openpype/modules/sync_server/sync_server_module.py +++ b/openpype/modules/sync_server/sync_server_module.py @@ -3,7 +3,6 @@ import sys import time from datetime import datetime import threading -import platform import copy import signal from collections import deque, defaultdict @@ -25,7 +24,11 @@ from openpype.lib import Logger, get_local_site_id from openpype.pipeline import AvalonMongoDB, Anatomy from openpype.settings.lib import ( get_default_anatomy_settings, - get_anatomy_settings + get_anatomy_settings, + get_local_settings, +) +from openpype.settings.constants import ( + DEFAULT_PROJECT_KEY ) from .providers.local_drive import LocalDriveHandler @@ -639,6 +642,100 @@ class SyncServerModule(OpenPypeModule, ITrayModule): return get_local_site_id() return active_site + def get_active_site_type(self, project_name, local_settings=None): + """Active site which is defined by artist. + + Unlike 'get_active_site' is this method also checking local settings + where might be different active site set by user. The output is limited + to "studio" and "local". + + This method is used by Anatomy when is decided which + + Todos: + Check if sync server is enabled for the project. + - To be able to do that the sync settings MUST NOT be cached for + all projects at once. The sync settings preparation for all + projects is reasonable only in sync server loop. + + Args: + project_name (str): Name of project where to look for active site. + local_settings (Optional[dict[str, Any]]): Prepared local settings. + + Returns: + Literal["studio", "local"]: Active site. + """ + + if not self.enabled: + return "studio" + + if local_settings is None: + local_settings = get_local_settings() + + local_project_settings = local_settings.get("projects") + project_settings = get_project_settings(project_name) + sync_server_settings = project_settings["global"]["sync_server"] + if not sync_server_settings["enabled"]: + return "studio" + + project_active_site = sync_server_settings["config"]["active_site"] + if not local_project_settings: + return project_active_site + + project_locals = local_project_settings.get(project_name) or {} + default_locals = local_project_settings.get(DEFAULT_PROJECT_KEY) or {} + active_site = ( + project_locals.get("active_site") + or default_locals.get("active_site") + ) + if active_site: + return active_site + return project_active_site + + def get_local_site_root_overrides( + self, project_name, site_name, local_settings=None + ): + """Get root overrides for project on a site. + + Implemented to be used in 'Anatomy' for other than 'studio' site. + + Args: + project_name (str): Project for which root overrides should be + received. + site_name (str): Name of site for which should be received roots. + local_settings (Optional[dict[str, Any]]): Prepare local settigns + values. + + Returns: + Union[dict[str, Any], None]: Root overrides for this machine. + """ + + if local_settings is None: + local_settings = get_local_settings() + + if not local_settings: + return + + local_project_settings = local_settings.get("projects") or {} + + # Check for roots existence in local settings first + roots_project_locals = ( + local_project_settings + .get(project_name, {}) + ) + roots_default_locals = ( + local_project_settings + .get(DEFAULT_PROJECT_KEY, {}) + ) + + # Skip rest of processing if roots are not set + if not roots_project_locals and not roots_default_locals: + return + + # Combine roots from local settings + roots_locals = roots_default_locals.get(site_name) or {} + roots_locals.update(roots_project_locals.get(site_name) or {}) + return roots_locals + # remote sites def get_remote_sites(self, project_name): """ From ded3933a0c461f734a07c86e3495479fb64f4573 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 23 Feb 2023 16:22:29 +0100 Subject: [PATCH 073/184] Base anatomy expect only root overrides --- openpype/pipeline/anatomy.py | 63 +++++++----------------------------- 1 file changed, 12 insertions(+), 51 deletions(-) diff --git a/openpype/pipeline/anatomy.py b/openpype/pipeline/anatomy.py index 49d86d69d6..0768167885 100644 --- a/openpype/pipeline/anatomy.py +++ b/openpype/pipeline/anatomy.py @@ -57,20 +57,13 @@ class BaseAnatomy(object): root_key_regex = re.compile(r"{(root?[^}]+)}") root_name_regex = re.compile(r"root\[([^]]+)\]") - def __init__(self, project_doc, local_settings, site_name): + def __init__(self, project_doc, root_overrides=None): project_name = project_doc["name"] self.project_name = project_name self.project_code = project_doc["data"]["code"] - if (site_name and - site_name not in ["studio", "local", get_local_site_id()]): - raise RuntimeError("Anatomy could be created only for default " - "local sites not for {}".format(site_name)) - - self._site_name = site_name - self._data = self._prepare_anatomy_data( - project_doc, local_settings, site_name + project_doc, root_overrides ) self._templates_obj = AnatomyTemplates(self) self._roots_obj = Roots(self) @@ -92,28 +85,18 @@ class BaseAnatomy(object): def items(self): return copy.deepcopy(self._data).items() - def _prepare_anatomy_data(self, project_doc, local_settings, site_name): + def _prepare_anatomy_data(self, project_doc, root_overrides): """Prepare anatomy data for further processing. Method added to replace `{task}` with `{task[name]}` in templates. """ - project_name = project_doc["name"] + anatomy_data = self._project_doc_to_anatomy_data(project_doc) - templates_data = anatomy_data.get("templates") - if templates_data: - # Replace `{task}` with `{task[name]}` in templates - value_queue = collections.deque() - value_queue.append(templates_data) - while value_queue: - item = value_queue.popleft() - if not isinstance(item, dict): - continue - - self._apply_local_settings_on_anatomy_data(anatomy_data, - local_settings, - project_name, - site_name) + self._apply_local_settings_on_anatomy_data( + anatomy_data, + root_overrides + ) return anatomy_data @@ -347,7 +330,7 @@ class BaseAnatomy(object): return output def _apply_local_settings_on_anatomy_data( - self, anatomy_data, local_settings, project_name, site_name + self, anatomy_data, root_overrides ): """Apply local settings on anatomy data. @@ -366,40 +349,18 @@ class BaseAnatomy(object): Args: anatomy_data (dict): Data for anatomy. - local_settings (dict): Data of local settings. - project_name (str): Name of project for which anatomy data are. + root_overrides (dict): Data of local settings. """ - if not local_settings: - return - local_project_settings = local_settings.get("projects") or {} - - # Check for roots existence in local settings first - roots_project_locals = ( - local_project_settings - .get(project_name, {}) - ) - roots_default_locals = ( - local_project_settings - .get(DEFAULT_PROJECT_KEY, {}) - ) - - # Skip rest of processing if roots are not set - if not roots_project_locals and not roots_default_locals: - return - - # Combine roots from local settings - roots_locals = roots_default_locals.get(site_name) or {} - roots_locals.update(roots_project_locals.get(site_name) or {}) # Skip processing if roots for current active site are not available in # local settings - if not roots_locals: + if not root_overrides: return current_platform = platform.system().lower() root_data = anatomy_data["roots"] - for root_name, path in roots_locals.items(): + for root_name, path in root_overrides.items(): if root_name not in root_data: continue anatomy_data["roots"][root_name][current_platform] = ( From c55104afdb868a4b5dea6ec69c5a164250cc51be Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 23 Feb 2023 16:22:53 +0100 Subject: [PATCH 074/184] Use CacheItem for keeping track about caches --- openpype/pipeline/anatomy.py | 57 ++++++++++++++++++++++++++++++++++-- 1 file changed, 55 insertions(+), 2 deletions(-) diff --git a/openpype/pipeline/anatomy.py b/openpype/pipeline/anatomy.py index 0768167885..a5cb97b60f 100644 --- a/openpype/pipeline/anatomy.py +++ b/openpype/pipeline/anatomy.py @@ -368,9 +368,62 @@ class BaseAnatomy(object): ) +class CacheItem: + """Helper to cache data. + + Helper does not handle refresh of data and does not mark data as outdated. + Who uses the object should check of outdated state on his own will. + """ + + default_lifetime = 10 + + def __init__(self, lifetime=None): + self._data = None + self._cached = None + self._lifetime = lifetime or self.default_lifetime + + @property + def data(self): + """Cached data/object. + + Returns: + Any: Whatever was cached. + """ + + return self._data + + @property + def is_outdated(self): + """Item has outdated cache. + + Lifetime of cache item expired or was not yet set. + + Returns: + bool: Item is outdated. + """ + + if self._cached is None: + return True + return (time.time() - self._cached) > self._lifetime + + def update_data(self, data): + """Update cache of data. + + Args: + data (Any): Data to cache. + """ + + self._data = data + self._cached = time.time() + + class Anatomy(BaseAnatomy): - _project_cache = {} - _site_cache = {} + _sync_server_addon_cache = CacheItem() + _project_cache = collections.defaultdict(CacheItem) + _default_site_id_cache = collections.defaultdict(CacheItem) + _root_overrides_cache = collections.defaultdict( + lambda: collections.defaultdict(CacheItem) + ) def __init__(self, project_name=None, site_name=None): if not project_name: From 72eac0b31edaed55347f0f047f28eec894417055 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 23 Feb 2023 16:23:28 +0100 Subject: [PATCH 075/184] change how site resolving and it's root overrides work --- openpype/pipeline/anatomy.py | 170 +++++++++++++++++++++++------------ 1 file changed, 114 insertions(+), 56 deletions(-) diff --git a/openpype/pipeline/anatomy.py b/openpype/pipeline/anatomy.py index a5cb97b60f..7f5be18b12 100644 --- a/openpype/pipeline/anatomy.py +++ b/openpype/pipeline/anatomy.py @@ -9,7 +9,6 @@ import six import time from openpype.settings.lib import ( - get_project_settings, get_local_settings, ) from openpype.settings.constants import ( @@ -25,6 +24,7 @@ from openpype.lib.path_templates import ( ) from openpype.lib.log import Logger from openpype.lib import get_local_site_id +from openpype.modules import ModulesManager log = Logger.get_logger(__name__) @@ -436,73 +436,131 @@ class Anatomy(BaseAnatomy): )) project_doc = self.get_project_doc_from_cache(project_name) - local_settings = get_local_settings() - if not site_name: - site_name = self.get_site_name_from_cache( - project_name, local_settings - ) + root_overrides = self._get_site_root_overrides(project_name, site_name) - super(Anatomy, self).__init__( - project_doc, - local_settings, - site_name - ) + super(Anatomy, self).__init__(project_doc, root_overrides) @classmethod def get_project_doc_from_cache(cls, project_name): - project_cache = cls._project_cache.get(project_name) - if project_cache is not None: - if time.time() - project_cache["start"] > 10: - cls._project_cache.pop(project_name) - project_cache = None - - if project_cache is None: - project_cache = { - "project_doc": get_project(project_name), - "start": time.time() - } - cls._project_cache[project_name] = project_cache - - return copy.deepcopy( - cls._project_cache[project_name]["project_doc"] - ) + project_cache = cls._project_cache[project_name] + if project_cache.is_outdated: + project_cache.update_data(get_project(project_name)) + return copy.deepcopy(project_cache.data) @classmethod - def get_site_name_from_cache(cls, project_name, local_settings): - site_cache = cls._site_cache.get(project_name) - if site_cache is not None: - if time.time() - site_cache["start"] > 10: - cls._site_cache.pop(project_name) - site_cache = None + def get_sync_server_addon(cls): + if cls._sync_server_addon_cache.is_outdated: + manager = ModulesManager() + cls._sync_server_addon_cache.update_data( + manager.enabled_modules.get("sync_server") + ) + return cls._sync_server_addon_cache.data - if site_cache: - return site_cache["site_name"] + @classmethod + def _get_studio_roots_overrides(cls, project_name, local_settings=None): + """This would return 'studio' site override by local settings. - local_project_settings = local_settings.get("projects") + Notes: + This logic handles local overrides of studio site which may be + available even when sync server is not enabled. + Handling of 'studio' and 'local' site was separated as preparation + for AYON development where that will be received from + separated sources. + + Args: + project_name (str): Name of project. + local_settings (Optional[dict[str, Any]]): Prepared local settings. + + Returns: + Union[Dict[str, str], None]): Local root overrides. + """ + + if local_settings is None: + local_settings = get_local_settings() + + local_project_settings = local_settings.get("projects") or {} if not local_project_settings: + return None + + # Check for roots existence in local settings first + roots_project_locals = ( + local_project_settings + .get(project_name, {}) + ) + roots_default_locals = ( + local_project_settings + .get(DEFAULT_PROJECT_KEY, {}) + ) + + # Skip rest of processing if roots are not set + if not roots_project_locals and not roots_default_locals: return - project_locals = local_project_settings.get(project_name) or {} - default_locals = local_project_settings.get(DEFAULT_PROJECT_KEY) or {} - active_site = ( - project_locals.get("active_site") - or default_locals.get("active_site") - ) - if not active_site: - project_settings = get_project_settings(project_name) - active_site = ( - project_settings - ["global"] - ["sync_server"] - ["config"] - ["active_site"] - ) + # Combine roots from local settings + roots_locals = roots_default_locals.get("studio") or {} + roots_locals.update_data(roots_project_locals.get("studio") or {}) + return roots_locals - cls._site_cache[project_name] = { - "site_name": active_site, - "start": time.time() - } - return active_site + + @classmethod + def _get_site_root_overrides(cls, project_name, site_name): + """Get root overrides for site. + + Args: + project_name (str): Project name for which root overrides should be + received. + site_name (Union[str, None]): Name of site for which root overrides + should be returned. + """ + + # Local settings may be used more than once or may not be used at all + # - to avoid slowdowns 'get_local_settings' is not called until it's + # really needed + local_settings = None + local_site_id = get_local_site_id() + + # First check if sync server is available and enabled + sync_server = cls.get_sync_server_addon() + if sync_server is None or not sync_server.enabled: + # QUESTION is ok to force 'studio' when site sync is not enabled? + site_name = "studio" + + elif not site_name: + # Use sync server to receive active site name + project_cache = cls._default_site_id_cache[project_name] + if project_cache.is_outdated: + local_settings = get_local_settings() + project_cache.update_data( + sync_server.get_active_site_type( + project_name, local_settings + ) + ) + site_name = project_cache.data + + elif site_name not in ("studio", "local"): + # Validate that site name is valid + if site_name != local_site_id: + raise RuntimeError(( + "Anatomy could be created only for" + " default local sites not for {}" + ).format(site_name)) + site_name = "local" + + site_cache = cls._root_overrides_cache[project_name][site_name] + if site_cache.is_outdated: + if site_name == "studio": + # Handle studio root overrides without sync server + # - studio root overrides can be done even without sync server + roots_overrides = cls._get_studio_roots_overrides( + project_name, local_settings + ) + else: + # Ask sync server to get roots overrides + roots_overrides = sync_server.get_local_site_root_overrides( + project_name, site_name, local_settings + ) + site_cache.update_data(roots_overrides) + return site_cache.data class AnatomyTemplateUnsolved(TemplateUnsolved): From 43179444397f57cfbeca04803caf503a3635e1a5 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 23 Feb 2023 16:32:59 +0100 Subject: [PATCH 076/184] fix modules access --- 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 7f5be18b12..bbc696d7f1 100644 --- a/openpype/pipeline/anatomy.py +++ b/openpype/pipeline/anatomy.py @@ -452,7 +452,7 @@ class Anatomy(BaseAnatomy): if cls._sync_server_addon_cache.is_outdated: manager = ModulesManager() cls._sync_server_addon_cache.update_data( - manager.enabled_modules.get("sync_server") + manager.get_enabled_module("sync_server") ) return cls._sync_server_addon_cache.data From f4c47d41d493050adc40324ab0c32c77efc217ae Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 23 Feb 2023 18:12:34 +0100 Subject: [PATCH 077/184] move validation of site name to sync server --- openpype/modules/sync_server/sync_server_module.py | 10 ++++++++++ openpype/pipeline/anatomy.py | 11 ----------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/openpype/modules/sync_server/sync_server_module.py b/openpype/modules/sync_server/sync_server_module.py index 1c2e820031..c3e28bc4f2 100644 --- a/openpype/modules/sync_server/sync_server_module.py +++ b/openpype/modules/sync_server/sync_server_module.py @@ -709,6 +709,16 @@ class SyncServerModule(OpenPypeModule, ITrayModule): Union[dict[str, Any], None]: Root overrides for this machine. """ + # Validate that site name is valid + if site_name not in ("studio", "local"): + # Considure local site id as 'local' + if site_name != get_local_site_id(): + raise RuntimeError(( + "Anatomy could be created only for" + " default local sites not for {}" + ).format(site_name)) + site_name = "local" + if local_settings is None: local_settings = get_local_settings() diff --git a/openpype/pipeline/anatomy.py b/openpype/pipeline/anatomy.py index bbc696d7f1..bb365c916e 100644 --- a/openpype/pipeline/anatomy.py +++ b/openpype/pipeline/anatomy.py @@ -23,7 +23,6 @@ from openpype.lib.path_templates import ( FormatObject, ) from openpype.lib.log import Logger -from openpype.lib import get_local_site_id from openpype.modules import ModulesManager log = Logger.get_logger(__name__) @@ -517,7 +516,6 @@ class Anatomy(BaseAnatomy): # - to avoid slowdowns 'get_local_settings' is not called until it's # really needed local_settings = None - local_site_id = get_local_site_id() # First check if sync server is available and enabled sync_server = cls.get_sync_server_addon() @@ -537,15 +535,6 @@ class Anatomy(BaseAnatomy): ) site_name = project_cache.data - elif site_name not in ("studio", "local"): - # Validate that site name is valid - if site_name != local_site_id: - raise RuntimeError(( - "Anatomy could be created only for" - " default local sites not for {}" - ).format(site_name)) - site_name = "local" - site_cache = cls._root_overrides_cache[project_name][site_name] if site_cache.is_outdated: if site_name == "studio": From 5de35b48e57ac958eadb6cfd387a146e962bc90d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 23 Feb 2023 18:13:14 +0100 Subject: [PATCH 078/184] change exception type and message --- openpype/modules/sync_server/sync_server_module.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/modules/sync_server/sync_server_module.py b/openpype/modules/sync_server/sync_server_module.py index c3e28bc4f2..7ea8f62d03 100644 --- a/openpype/modules/sync_server/sync_server_module.py +++ b/openpype/modules/sync_server/sync_server_module.py @@ -713,9 +713,9 @@ class SyncServerModule(OpenPypeModule, ITrayModule): if site_name not in ("studio", "local"): # Considure local site id as 'local' if site_name != get_local_site_id(): - raise RuntimeError(( - "Anatomy could be created only for" - " default local sites not for {}" + raise ValueError(( + "Root overrides are available only for" + " default sites not for \"{}\"" ).format(site_name)) site_name = "local" From 1b5d7a1d385b275d84fcc2bb4ee8d3a71b569f3e Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 23 Feb 2023 18:13:37 +0100 Subject: [PATCH 079/184] rename method --- openpype/modules/sync_server/sync_server_module.py | 2 +- openpype/pipeline/anatomy.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/modules/sync_server/sync_server_module.py b/openpype/modules/sync_server/sync_server_module.py index 7ea8f62d03..28863c091a 100644 --- a/openpype/modules/sync_server/sync_server_module.py +++ b/openpype/modules/sync_server/sync_server_module.py @@ -691,7 +691,7 @@ class SyncServerModule(OpenPypeModule, ITrayModule): return active_site return project_active_site - def get_local_site_root_overrides( + def get_site_root_overrides( self, project_name, site_name, local_settings=None ): """Get root overrides for project on a site. diff --git a/openpype/pipeline/anatomy.py b/openpype/pipeline/anatomy.py index bb365c916e..d1bda2cf18 100644 --- a/openpype/pipeline/anatomy.py +++ b/openpype/pipeline/anatomy.py @@ -545,7 +545,7 @@ class Anatomy(BaseAnatomy): ) else: # Ask sync server to get roots overrides - roots_overrides = sync_server.get_local_site_root_overrides( + roots_overrides = sync_server.get_site_root_overrides( project_name, site_name, local_settings ) site_cache.update_data(roots_overrides) From 3408c25f7957805ac5b250c154ef2da464e0662e Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 23 Feb 2023 18:24:35 +0100 Subject: [PATCH 080/184] remove doubled empty line --- openpype/pipeline/anatomy.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/pipeline/anatomy.py b/openpype/pipeline/anatomy.py index d1bda2cf18..ac915b61f0 100644 --- a/openpype/pipeline/anatomy.py +++ b/openpype/pipeline/anatomy.py @@ -500,7 +500,6 @@ class Anatomy(BaseAnatomy): roots_locals.update_data(roots_project_locals.get("studio") or {}) return roots_locals - @classmethod def _get_site_root_overrides(cls, project_name, site_name): """Get root overrides for site. From afec68dcba636ccc345ab080bf049c37ec692068 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 27 Feb 2023 10:56:11 +0100 Subject: [PATCH 081/184] fix dict update --- 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 ac915b61f0..683960f3d8 100644 --- a/openpype/pipeline/anatomy.py +++ b/openpype/pipeline/anatomy.py @@ -497,7 +497,7 @@ class Anatomy(BaseAnatomy): # Combine roots from local settings roots_locals = roots_default_locals.get("studio") or {} - roots_locals.update_data(roots_project_locals.get("studio") or {}) + roots_locals.update(roots_project_locals.get("studio") or {}) return roots_locals @classmethod From 3c215350931b4b65d5eab9a51cdc804ed128478a Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 27 Feb 2023 11:34:30 +0100 Subject: [PATCH 082/184] removed unnecessary try catch --- openpype/modules/sync_server/sync_server.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/openpype/modules/sync_server/sync_server.py b/openpype/modules/sync_server/sync_server.py index aef3623efa..5b873a37cf 100644 --- a/openpype/modules/sync_server/sync_server.py +++ b/openpype/modules/sync_server/sync_server.py @@ -50,13 +50,10 @@ async def upload(module, project_name, file, representation, provider_name, presets=preset) file_path = file.get("path", "") - try: - local_file_path, remote_file_path = resolve_paths( - module, file_path, project_name, - remote_site_name, remote_handler - ) - except Exception as exp: - print(exp) + local_file_path, remote_file_path = resolve_paths( + module, file_path, project_name, + remote_site_name, remote_handler + ) target_folder = os.path.dirname(remote_file_path) folder_id = remote_handler.create_folder(target_folder) From 557ce7c016515f15878d9c37d6a8cf6fb777be43 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 27 Feb 2023 15:08:03 +0100 Subject: [PATCH 083/184] Nuke, Deadline: moving to module plugin centric approach --- openpype/hosts/nuke/api/lib_rendersettings.py | 111 ------------------ .../plugins/create/create_write_prerender.py | 37 +----- .../plugins/create/create_write_render.py | 36 +----- .../plugins/publish/submit_nuke_deadline.py | 81 +++++++++---- 4 files changed, 60 insertions(+), 205 deletions(-) delete mode 100644 openpype/hosts/nuke/api/lib_rendersettings.py diff --git a/openpype/hosts/nuke/api/lib_rendersettings.py b/openpype/hosts/nuke/api/lib_rendersettings.py deleted file mode 100644 index 5c23bcb1bc..0000000000 --- a/openpype/hosts/nuke/api/lib_rendersettings.py +++ /dev/null @@ -1,111 +0,0 @@ - -from openpype.lib import Logger -from openpype.settings import ( - get_current_project_settings, - get_system_settings -) - - -class RenderFarmSettings: - """ Class for getting farm settings from project settings - """ - log = Logger.get_logger("RenderFarmSettings") - - _active_farm_module: str = None - _farm_modules: list = ["deadline"] - _farm_plugins: dict = { - "deadline": "NukeSubmitDeadline" - } - _creator_farm_keys: list = [ - "chunk_size", "priority", "concurrent_tasks"] - - _cached_project_settings = None - _cached_system_settings = None - - def __init__(self, project_settings=None, log=None): - """ Get project settings and active farm module - """ - if log: - self.log = log - - if project_settings: - self._cached_project_settings = project_settings - - @property - def project_settings(self): - """ returning cached project settings or getting new one - """ - if not self._cached_project_settings: - self._cached_project_settings = get_current_project_settings() - return self._cached_project_settings - - @property - def system_settings(self): - """ returning cached system settings or getting new one - """ - if not self._cached_system_settings: - self._cached_system_settings = get_system_settings() - return self._cached_system_settings - - def _get_active_farm_module_from_system_settings(self): - """ Get active farm module from system settings - """ - active_modules = [ - module_ - for module_ in self._farm_modules - if self.system_settings["modules"][module_]["enabled"] - ] - if not active_modules: - raise ValueError(( - "No active farm module found in system settings." - )) - if len(active_modules) > 1: - raise ValueError(( - "Multiple active farm modules " - "found in system settings. {}".format(active_modules) - )) - - self._active_farm_module = active_modules.pop() - - @property - def active_farm_module(self): - # cache active farm module - if self._active_farm_module is None: - self._get_active_farm_module_from_system_settings() - - return self._active_farm_module - - def get_rendering_attributes(self): - ''' Get rendering attributes from project settings - - Returns: - dict: rendering attributes - ''' - return_dict = {} - farm_plugin = self._farm_plugins.get(self.active_farm_module) - self.log.debug("Farm plugin: \"{}\"".format(farm_plugin)) - - if not farm_plugin: - raise ValueError(( - "Farm plugin \"{}\" not found in farm plugins." - ).format(farm_plugin)) - - # Get farm module settings - module_settings = self.project_settings[self.active_farm_module] - - # Get farm plugin settings - farm_plugin_settings = ( - module_settings["publish"][farm_plugin]) - self.log.debug( - "Farm plugin settings: \"{}\"".format(farm_plugin_settings)) - - # Get all keys from farm_plugin_settings - for key in self._creator_farm_keys: - if key not in farm_plugin_settings: - self.log.warning(( - "Key \"{}\" not found in farm plugin \"{}\" settings." - ).format(key, farm_plugin)) - continue - return_dict[key] = farm_plugin_settings[key] - - return return_dict diff --git a/openpype/hosts/nuke/plugins/create/create_write_prerender.py b/openpype/hosts/nuke/plugins/create/create_write_prerender.py index 411a79dbf4..1603bf17e3 100644 --- a/openpype/hosts/nuke/plugins/create/create_write_prerender.py +++ b/openpype/hosts/nuke/plugins/create/create_write_prerender.py @@ -6,13 +6,9 @@ from openpype.pipeline import ( CreatedInstance ) from openpype.lib import ( - BoolDef, - NumberDef, - UISeparatorDef, - UILabelDef + BoolDef ) from openpype.hosts.nuke import api as napi -from openpype.hosts.nuke.api.lib_rendersettings import RenderFarmSettings class CreateWritePrerender(napi.NukeWriteCreator): @@ -50,37 +46,6 @@ class CreateWritePrerender(napi.NukeWriteCreator): self._get_render_target_enum(), self._get_reviewable_bool() ] - if "farm_rendering" in self.instance_attributes: - render_farm_settings = RenderFarmSettings( - log=self.log).get_rendering_attributes() - - - attr_defs.extend([ - UISeparatorDef(), - UILabelDef("Farm rendering attributes"), - BoolDef("suspended_publish", label="Suspended publishing"), - NumberDef( - "farm_priority", - label="Priority", - minimum=1, - maximum=99, - default=render_farm_settings.get("priority", 50) - ), - NumberDef( - "farm_chunk", - label="Chunk size", - minimum=1, - maximum=99, - default=render_farm_settings.get("chunk_size", 10) - ), - NumberDef( - "farm_concurrency", - label="Concurrent tasks", - minimum=1, - maximum=10, - default=render_farm_settings.get("concurrent_tasks", 1) - ) - ]) return attr_defs def create_instance_node(self, subset_name, instance_data): diff --git a/openpype/hosts/nuke/plugins/create/create_write_render.py b/openpype/hosts/nuke/plugins/create/create_write_render.py index a51661425f..72fcb4f232 100644 --- a/openpype/hosts/nuke/plugins/create/create_write_render.py +++ b/openpype/hosts/nuke/plugins/create/create_write_render.py @@ -6,13 +6,9 @@ from openpype.pipeline import ( CreatedInstance ) from openpype.lib import ( - BoolDef, - NumberDef, - UISeparatorDef, - UILabelDef + BoolDef ) from openpype.hosts.nuke import api as napi -from openpype.hosts.nuke.api.lib_rendersettings import RenderFarmSettings class CreateWriteRender(napi.NukeWriteCreator): @@ -47,36 +43,6 @@ class CreateWriteRender(napi.NukeWriteCreator): self._get_render_target_enum(), self._get_reviewable_bool() ] - if "farm_rendering" in self.instance_attributes: - render_farm_settings = RenderFarmSettings( - log=self.log).get_rendering_attributes() - - attr_defs.extend([ - UISeparatorDef(), - UILabelDef("Farm rendering attributes"), - BoolDef("suspended_publish", label="Suspended publishing"), - NumberDef( - "farm_priority", - label="Priority", - minimum=1, - maximum=99, - default=render_farm_settings.get("priority", 50) - ), - NumberDef( - "farm_chunk", - label="Chunk size", - minimum=1, - maximum=99, - default=render_farm_settings.get("chunk_size", 10) - ), - NumberDef( - "farm_concurrency", - label="Concurrent tasks", - minimum=1, - maximum=10, - default=render_farm_settings.get("concurrent_tasks", 1) - ) - ]) return attr_defs def create_instance_node(self, subset_name, instance_data): diff --git a/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py b/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py index b4b59c4c77..51e380dc03 100644 --- a/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py @@ -9,11 +9,19 @@ import pyblish.api import nuke from openpype.pipeline import legacy_io +from openpype.pipeline.publish import ( + OpenPypePyblishPluginMixin +) from openpype.tests.lib import is_in_tests -from openpype.lib import is_running_from_build +from openpype.lib import ( + is_running_from_build, + BoolDef, + NumberDef, + UISeparatorDef +) - -class NukeSubmitDeadline(pyblish.api.InstancePlugin): +class NukeSubmitDeadline(pyblish.api.InstancePlugin, + OpenPypePyblishPluginMixin): """Submit write to Deadline Renders are submitted to a Deadline Web Service as @@ -21,10 +29,10 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): """ - label = "Submit to Deadline" + label = "Submit Nuke to Deadline" order = pyblish.api.IntegratorOrder + 0.1 hosts = ["nuke"] - families = ["render.farm", "prerender.farm"] + families = ["render", "prerender.farm"] optional = True targets = ["local"] @@ -39,7 +47,42 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): env_allowed_keys = [] env_search_replace_values = {} + @classmethod + def get_attribute_defs(cls): + return [ + NumberDef( + "priority", + label="Priority", + default=cls.priority, + decimals=0 + ), + NumberDef( + "chunk", + label="Frames Per Task", + default=cls.chunk_size, + decimals=0, + minimum=1, + maximum=1000 + ), + NumberDef( + "concurrency", + label="Concurency", + default=cls.concurrent_tasks, + decimals=0, + minimum=1, + maximum=10 + ), + BoolDef( + "use_gpu", + default=cls.use_gpu, + label="Use GPU" + ) + ] + def process(self, instance): + instance.data["attributeValues"] = self.get_attr_values_from_data( + instance.data) + instance.data["toBeRenderedOn"] = "deadline" families = instance.data["families"] @@ -161,20 +204,6 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): except OSError: pass - # define chunk and priority - chunk_size = instance.data.get("farm_chunk") - if not chunk_size: - chunk_size = self.chunk_size - - # define chunk and priority - concurrent_tasks = instance.data.get("farm_concurrency") - if not concurrent_tasks: - concurrent_tasks = self.concurrent_tasks - - priority = instance.data.get("farm_priority") - if not priority: - priority = self.priority - # resolve any limit groups limit_groups = self.get_limit_groups() self.log.info("Limit groups: `{}`".format(limit_groups)) @@ -193,9 +222,14 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): # Arbitrary username, for visualisation in Monitor "UserName": self._deadline_user, - "Priority": priority, - "ChunkSize": chunk_size, - "ConcurrentTasks": concurrent_tasks, + "Priority": instance.data["attributeValues"].get( + "priority", self.priority), + "ChunkSize": instance.data["attributeValues"].get( + "chunk", self.chunk_size), + "ConcurrentTasks": instance.data["attributeValues"].get( + "concurrency", + self.concurrent_tasks + ), "Department": self.department, @@ -234,7 +268,8 @@ class NukeSubmitDeadline(pyblish.api.InstancePlugin): "AWSAssetFile0": render_path, # using GPU by default - "UseGpu": self.use_gpu, + "UseGpu": instance.data["attributeValues"].get( + "use_gpu", self.use_gpu), # Only the specific write node is rendered. "WriteNode": exe_node_name From e444ab3f5e3037cc0528a748df4f0554a597fb78 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 27 Feb 2023 15:16:27 +0100 Subject: [PATCH 084/184] hound comments --- .../modules/deadline/plugins/publish/submit_nuke_deadline.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py b/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py index 51e380dc03..aff34c7e4a 100644 --- a/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_nuke_deadline.py @@ -16,10 +16,10 @@ from openpype.tests.lib import is_in_tests from openpype.lib import ( is_running_from_build, BoolDef, - NumberDef, - UISeparatorDef + NumberDef ) + class NukeSubmitDeadline(pyblish.api.InstancePlugin, OpenPypePyblishPluginMixin): """Submit write to Deadline From fa879c9beb1982cb02f12d36925d28c81943589a Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 27 Feb 2023 17:06:14 +0100 Subject: [PATCH 085/184] fix UI definitions for publish plugins (#4534) --- openpype/tools/publisher/control.py | 3 ++ openpype/tools/publisher/widgets/widgets.py | 36 +++++++++++++++++++-- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py index 023a20ca5e..49e7eeb4f7 100644 --- a/openpype/tools/publisher/control.py +++ b/openpype/tools/publisher/control.py @@ -18,6 +18,7 @@ from openpype.client import ( ) from openpype.lib.events import EventSystem from openpype.lib.attribute_definitions import ( + UIDef, serialize_attr_defs, deserialize_attr_defs, ) @@ -1938,6 +1939,8 @@ class PublisherController(BasePublisherController): plugin_values = all_plugin_values[plugin_name] for attr_def in attr_defs: + if isinstance(attr_def, UIDef): + continue if attr_def.key not in plugin_values: plugin_values[attr_def.key] = [] attr_values = plugin_values[attr_def.key] diff --git a/openpype/tools/publisher/widgets/widgets.py b/openpype/tools/publisher/widgets/widgets.py index 8da3886419..86475460aa 100644 --- a/openpype/tools/publisher/widgets/widgets.py +++ b/openpype/tools/publisher/widgets/widgets.py @@ -9,7 +9,7 @@ import collections from qtpy import QtWidgets, QtCore, QtGui import qtawesome -from openpype.lib.attribute_definitions import UnknownDef +from openpype.lib.attribute_definitions import UnknownDef, UIDef from openpype.tools.attribute_defs import create_widget_for_attr_def from openpype.tools import resources from openpype.tools.flickcharm import FlickCharm @@ -1442,7 +1442,16 @@ class PublishPluginAttrsWidget(QtWidgets.QWidget): ) content_widget = QtWidgets.QWidget(self._scroll_area) - content_layout = QtWidgets.QFormLayout(content_widget) + attr_def_widget = QtWidgets.QWidget(content_widget) + attr_def_layout = QtWidgets.QGridLayout(attr_def_widget) + attr_def_layout.setColumnStretch(0, 0) + attr_def_layout.setColumnStretch(1, 1) + + content_layout = QtWidgets.QVBoxLayout(content_widget) + content_layout.addWidget(attr_def_widget, 0) + content_layout.addStretch(1) + + row = 0 for plugin_name, attr_defs, all_plugin_values in result: plugin_values = all_plugin_values[plugin_name] @@ -1459,8 +1468,29 @@ class PublishPluginAttrsWidget(QtWidgets.QWidget): hidden_widget = True if not hidden_widget: + expand_cols = 2 + if attr_def.is_value_def and attr_def.is_label_horizontal: + expand_cols = 1 + + col_num = 2 - expand_cols label = attr_def.label or attr_def.key - content_layout.addRow(label, widget) + if label: + label_widget = QtWidgets.QLabel(label, content_widget) + tooltip = attr_def.tooltip + if tooltip: + label_widget.setToolTip(tooltip) + attr_def_layout.addWidget( + label_widget, row, 0, 1, expand_cols + ) + if not attr_def.is_label_horizontal: + row += 1 + attr_def_layout.addWidget( + widget, row, col_num, 1, expand_cols + ) + row += 1 + + if isinstance(attr_def, UIDef): + continue widget.value_changed.connect(self._input_value_changed) From 2839e775e43eaddaa28d58bb11471c450198b4dd Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 27 Feb 2023 18:55:23 +0100 Subject: [PATCH 086/184] Deadline: Hint to use Python 3 (#4518) * add shebank for python 3 --- .../deadline/repository/custom/plugins/GlobalJobPreLoad.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py b/openpype/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py index e4fc64269a..20a58c9131 100644 --- a/openpype/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py +++ b/openpype/modules/deadline/repository/custom/plugins/GlobalJobPreLoad.py @@ -1,3 +1,4 @@ +# /usr/bin/env python3 # -*- coding: utf-8 -*- import os import tempfile @@ -341,7 +342,7 @@ def inject_openpype_environment(deadlinePlugin): "app": job.GetJobEnvironmentKeyValue("AVALON_APP_NAME"), "envgroup": "farm" } - + if job.GetJobEnvironmentKeyValue('IS_TEST'): args.append("--automatic-tests") From 86f0383f183251cb2b725f06ad33ba887e2305ef Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 28 Feb 2023 11:13:44 +0100 Subject: [PATCH 087/184] Publisher: Prevent access to create tab after publish start (#4528) make sure it is not possible to go to create tab if the tab is not enabled --- openpype/tools/publisher/widgets/overview_widget.py | 3 +++ openpype/tools/publisher/window.py | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/openpype/tools/publisher/widgets/overview_widget.py b/openpype/tools/publisher/widgets/overview_widget.py index 022de2dc34..8706daeda6 100644 --- a/openpype/tools/publisher/widgets/overview_widget.py +++ b/openpype/tools/publisher/widgets/overview_widget.py @@ -146,6 +146,7 @@ class OverviewWidget(QtWidgets.QFrame): self._subset_list_view = subset_list_view self._subset_views_layout = subset_views_layout + self._create_btn = create_btn self._delete_btn = delete_btn self._subset_attributes_widget = subset_attributes_widget @@ -388,11 +389,13 @@ class OverviewWidget(QtWidgets.QFrame): def _on_publish_start(self): """Publish started.""" + self._create_btn.setEnabled(False) self._subset_attributes_wrap.setEnabled(False) def _on_publish_reset(self): """Context in controller has been refreshed.""" + self._create_btn.setEnabled(True) self._subset_attributes_wrap.setEnabled(True) self._subset_content_widget.setEnabled(self._controller.host_is_valid) diff --git a/openpype/tools/publisher/window.py b/openpype/tools/publisher/window.py index 6f7ffdb8ea..74977d65d8 100644 --- a/openpype/tools/publisher/window.py +++ b/openpype/tools/publisher/window.py @@ -561,7 +561,8 @@ class PublisherWindow(QtWidgets.QDialog): return self._tabs_widget.is_current_tab(identifier) def _go_to_create_tab(self): - self._set_current_tab("create") + if self._create_tab.isEnabled(): + self._set_current_tab("create") def _go_to_publish_tab(self): self._set_current_tab("publish") From f83fefd8a91f90133216f819a251594a72fe920c Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Mon, 20 Feb 2023 18:35:21 +0100 Subject: [PATCH 088/184] Added support for extensions filtering --- openpype/pipeline/load/plugins.py | 82 +++++++++++++++++++++++++++---- 1 file changed, 72 insertions(+), 10 deletions(-) diff --git a/openpype/pipeline/load/plugins.py b/openpype/pipeline/load/plugins.py index 9b891a4da3..8372743a23 100644 --- a/openpype/pipeline/load/plugins.py +++ b/openpype/pipeline/load/plugins.py @@ -21,16 +21,18 @@ class LoaderPlugin(list): Arguments: context (dict): avalon-core:context-1.0 - name (str, optional): Use pre-defined name - namespace (str, optional): Use pre-defined namespace .. versionadded:: 4.0 This class was introduced """ - families = list() - representations = list() + families = [] + representations = [] + # Extensions filtering was not available until 20/2/2023 + # - filtering by extensions is not enabled if is set to 'None' + # which is to keep backwards compatibility + extensions = None order = 0 is_multiple_contexts_compatible = False enabled = True @@ -82,20 +84,72 @@ class LoaderPlugin(list): print(" - setting `{}`: `{}`".format(option, value)) setattr(cls, option, value) + @classmethod + def has_valid_extension(cls, repre_doc): + """Has representation document valid extension for loader. + + Args: + repre_doc (dict[str, Any]): Representation document. + + Returns: + bool: Representation has valid extension + """ + + # Empty list of extensions is considered as all representations are + # invalid -> use '["*"]' to support all extensions + valid_extensions = cls.extensions + if not valid_extensions: + return False + + # Get representation main file extension from 'context' + repre_context = repre_doc.get("context") or {} + ext = repre_context.get("ext") + if not ext: + # Legacy way how to get extensions + path = repre_doc["data"].get("path") + if not path: + cls.log.info( + "Representation doesn't have known source of extension" + " information." + ) + return False + + cls.log.info("Using legacy source of extension on representation.") + ext = os.path.splitext(path)[-1] + while ext.startswith("."): + ext = ext[1:] + + # If representation does not have extension then can't be valid + if not ext: + return False + + if "*" in valid_extensions: + return True + + valid_extensions_low = {ext.lower() for ext in valid_extensions} + return ext.lower() in valid_extensions_low + + @classmethod def is_compatible_loader(cls, context): """Return whether a loader is compatible with a context. + On override make sure it is overriden as class or static method. + This checks the version's families and the representation for the given - Loader. + loader plugin. + + Args: + context (dict[str, Any]): Documents of context for which should + be loader used. Returns: - bool + bool: Is loader compatible for context. """ plugin_repre_names = cls.get_representations() plugin_families = cls.families - if not plugin_repre_names or not plugin_families: + if not plugin_repre_names or not plugin_families or not cls.extensions: return False repre_doc = context.get("representation") @@ -109,11 +163,19 @@ class LoaderPlugin(list): ): return False - maj_version, _ = schema.get_schema_version(context["subset"]["schema"]) + if ( + cls.extensions is not None + and not cls.has_valid_extension(repre_doc) + ): + return False + + verison_doc = context["version"] + subset_doc = context["subset"] + maj_version, _ = schema.get_schema_version(subset_doc["schema"]) if maj_version < 3: - families = context["version"]["data"].get("families", []) + families = verison_doc["data"].get("families", []) else: - families = context["subset"]["data"]["families"] + families = subset_doc["families"] plugin_families = set(plugin_families) return ( From 1b7af3e2c6299df7d6aee1f9c3e5f3e12e60db3f Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 23 Feb 2023 18:53:32 +0100 Subject: [PATCH 089/184] fix filtering --- openpype/pipeline/load/plugins.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/openpype/pipeline/load/plugins.py b/openpype/pipeline/load/plugins.py index 8372743a23..4ce7d47fd9 100644 --- a/openpype/pipeline/load/plugins.py +++ b/openpype/pipeline/load/plugins.py @@ -149,7 +149,7 @@ class LoaderPlugin(list): plugin_repre_names = cls.get_representations() plugin_families = cls.families - if not plugin_repre_names or not plugin_families or not cls.extensions: + if not plugin_repre_names or not plugin_families: return False repre_doc = context.get("representation") @@ -173,14 +173,21 @@ class LoaderPlugin(list): subset_doc = context["subset"] maj_version, _ = schema.get_schema_version(subset_doc["schema"]) if maj_version < 3: - families = verison_doc["data"].get("families", []) + families = verison_doc["data"].get("families") else: - families = subset_doc["families"] + families = context["subset"]["data"].get("families") + if families is None: + family = context["subset"]["data"].get("family") + if family: + families = [family] plugin_families = set(plugin_families) return ( "*" in plugin_families - or any(family in plugin_families for family in families) + or any( + family in plugin_families + for family in (families or []) + ) ) @classmethod From 7a8aa123ff8bc159cbc7b4a0c7acedb01847b9e9 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 23 Feb 2023 21:48:32 +0100 Subject: [PATCH 090/184] cleanup changes based on comments --- openpype/pipeline/load/plugins.py | 57 ++++++++++++------------------- 1 file changed, 22 insertions(+), 35 deletions(-) diff --git a/openpype/pipeline/load/plugins.py b/openpype/pipeline/load/plugins.py index 4ce7d47fd9..f5b6e858b4 100644 --- a/openpype/pipeline/load/plugins.py +++ b/openpype/pipeline/load/plugins.py @@ -29,10 +29,7 @@ class LoaderPlugin(list): families = [] representations = [] - # Extensions filtering was not available until 20/2/2023 - # - filtering by extensions is not enabled if is set to 'None' - # which is to keep backwards compatibility - extensions = None + extensions = {"*"} order = 0 is_multiple_contexts_compatible = False enabled = True @@ -95,11 +92,8 @@ class LoaderPlugin(list): bool: Representation has valid extension """ - # Empty list of extensions is considered as all representations are - # invalid -> use '["*"]' to support all extensions - valid_extensions = cls.extensions - if not valid_extensions: - return False + if "*" in cls.extensions: + return True # Get representation main file extension from 'context' repre_context = repre_doc.get("context") or {} @@ -114,22 +108,16 @@ class LoaderPlugin(list): ) return False - cls.log.info("Using legacy source of extension on representation.") - ext = os.path.splitext(path)[-1] - while ext.startswith("."): - ext = ext[1:] + cls.log.info("Using legacy source of extension from path.") + ext = os.path.splitext(path)[-1].lstrip(".") # If representation does not have extension then can't be valid if not ext: return False - if "*" in valid_extensions: - return True - - valid_extensions_low = {ext.lower() for ext in valid_extensions} + valid_extensions_low = {ext.lower() for ext in cls.extensions} return ext.lower() in valid_extensions_low - @classmethod def is_compatible_loader(cls, context): """Return whether a loader is compatible with a context. @@ -149,7 +137,11 @@ class LoaderPlugin(list): plugin_repre_names = cls.get_representations() plugin_families = cls.families - if not plugin_repre_names or not plugin_families: + if ( + not plugin_repre_names + or not plugin_families + or not cls.extensions + ): return False repre_doc = context.get("representation") @@ -163,32 +155,27 @@ class LoaderPlugin(list): ): return False - if ( - cls.extensions is not None - and not cls.has_valid_extension(repre_doc) - ): + if not cls.has_valid_extension(repre_doc): return False - verison_doc = context["version"] + plugin_families = set(plugin_families) + if "*" in plugin_families: + return True + subset_doc = context["subset"] maj_version, _ = schema.get_schema_version(subset_doc["schema"]) if maj_version < 3: - families = verison_doc["data"].get("families") + families = context["version"]["data"].get("families") else: - families = context["subset"]["data"].get("families") + families = subset_doc["data"].get("families") if families is None: - family = context["subset"]["data"].get("family") + family = subset_doc["data"].get("family") if family: families = [family] - plugin_families = set(plugin_families) - return ( - "*" in plugin_families - or any( - family in plugin_families - for family in (families or []) - ) - ) + if not families: + return False + return any(family in plugin_families for family in families) @classmethod def get_representations(cls): From 8180076c08efa2e1ad3e1ece939b1b40899b3ef2 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 24 Feb 2023 10:20:40 +0100 Subject: [PATCH 091/184] safer data access Co-authored-by: Roy Nieterau --- openpype/pipeline/load/plugins.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/load/plugins.py b/openpype/pipeline/load/plugins.py index f5b6e858b4..d35281e823 100644 --- a/openpype/pipeline/load/plugins.py +++ b/openpype/pipeline/load/plugins.py @@ -100,7 +100,7 @@ class LoaderPlugin(list): ext = repre_context.get("ext") if not ext: # Legacy way how to get extensions - path = repre_doc["data"].get("path") + path = repre_doc.get("data", {}).get("path") if not path: cls.log.info( "Representation doesn't have known source of extension" From 9e432f7c5c7fa19dec06123d6e0867c9735be147 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 24 Feb 2023 10:55:30 +0100 Subject: [PATCH 092/184] add data and context to repre document fields --- openpype/tools/loader/widgets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/loader/widgets.py b/openpype/tools/loader/widgets.py index 0c5c9391cf..538925d141 100644 --- a/openpype/tools/loader/widgets.py +++ b/openpype/tools/loader/widgets.py @@ -339,7 +339,7 @@ class SubsetWidget(QtWidgets.QWidget): repre_docs = get_representations( project_name, version_ids=version_ids, - fields=["name", "parent"] + fields=["name", "parent", "data", "context"] ) repre_docs_by_version_id = { From 8a34313b97b7d6dc370aac485f188ffdb705a3f9 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 24 Feb 2023 11:01:24 +0100 Subject: [PATCH 093/184] added 'data' and 'context' to representations widget too --- openpype/tools/loader/widgets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/loader/widgets.py b/openpype/tools/loader/widgets.py index 538925d141..98ac9c871f 100644 --- a/openpype/tools/loader/widgets.py +++ b/openpype/tools/loader/widgets.py @@ -1264,7 +1264,7 @@ class RepresentationWidget(QtWidgets.QWidget): repre_docs = list(get_representations( project_name, representation_ids=repre_ids, - fields=["name", "parent"] + fields=["name", "parent", "data", "context"] )) version_ids = [ From 1bd5f04880611a4461ead821dc2a14cf60d466b3 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 24 Feb 2023 11:16:25 +0100 Subject: [PATCH 094/184] nuke: extension to loaders --- openpype/hosts/nuke/plugins/load/load_clip.py | 28 ++++++++++--------- .../hosts/nuke/plugins/load/load_image.py | 10 +++++-- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/openpype/hosts/nuke/plugins/load/load_clip.py b/openpype/hosts/nuke/plugins/load/load_clip.py index 8f9b463037..18ecaf4d2b 100644 --- a/openpype/hosts/nuke/plugins/load/load_clip.py +++ b/openpype/hosts/nuke/plugins/load/load_clip.py @@ -21,6 +21,10 @@ from openpype.hosts.nuke.api import ( viewer_update_and_undo_stop, colorspace_exists_on_node ) +from openpype.lib.transcoding import ( + VIDEO_EXTENSIONS, + IMAGE_EXTENSIONS +) from openpype.hosts.nuke.api import plugin @@ -38,13 +42,11 @@ class LoadClip(plugin.NukeLoader): "prerender", "review" ] - representations = [ - "exr", - "dpx", - "mov", - "review", - "mp4" - ] + representations = ["*"] + + extensions = set( + ext.lstrip(".") for ext in IMAGE_EXTENSIONS.union(VIDEO_EXTENSIONS) + ) label = "Load Clip" order = -20 @@ -81,17 +83,17 @@ class LoadClip(plugin.NukeLoader): @classmethod def get_representations(cls): - return ( - cls.representations - + cls._representations - + plugin.get_review_presets_config() - ) + return cls._representations or cls.representations def load(self, context, name, namespace, options): + """Load asset via database + """ representation = context["representation"] - # reste container id so it is always unique for each instance + # reset container id so it is always unique for each instance self.reset_container_id() + self.log.warning(self.extensions) + is_sequence = len(representation["files"]) > 1 if is_sequence: diff --git a/openpype/hosts/nuke/plugins/load/load_image.py b/openpype/hosts/nuke/plugins/load/load_image.py index 49dc12f588..f82ee4db88 100644 --- a/openpype/hosts/nuke/plugins/load/load_image.py +++ b/openpype/hosts/nuke/plugins/load/load_image.py @@ -19,6 +19,9 @@ from openpype.hosts.nuke.api import ( update_container, viewer_update_and_undo_stop ) +from openpype.lib.transcoding import ( + IMAGE_EXTENSIONS +) class LoadImage(load.LoaderPlugin): @@ -33,7 +36,10 @@ class LoadImage(load.LoaderPlugin): "review", "image" ] - representations = ["exr", "dpx", "jpg", "jpeg", "png", "psd", "tiff"] + representations = ["*"] + extensions = set( + ext.lstrip(".") for ext in IMAGE_EXTENSIONS + ) label = "Load Image" order = -10 @@ -58,7 +64,7 @@ class LoadImage(load.LoaderPlugin): @classmethod def get_representations(cls): - return cls.representations + cls._representations + return cls._representations or cls.representations def load(self, context, name, namespace, options): self.log.info("__ options: `{}`".format(options)) From f432fb29de1cc9e01ea59a2482a9430086c987fa Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 24 Feb 2023 11:26:46 +0100 Subject: [PATCH 095/184] nuke: adding extension to plugins --- openpype/hosts/nuke/plugins/load/actions.py | 1 + openpype/hosts/nuke/plugins/load/load_backdrop.py | 3 ++- openpype/hosts/nuke/plugins/load/load_camera_abc.py | 3 ++- openpype/hosts/nuke/plugins/load/load_clip.py | 1 - openpype/hosts/nuke/plugins/load/load_effects.py | 3 ++- openpype/hosts/nuke/plugins/load/load_effects_ip.py | 3 ++- openpype/hosts/nuke/plugins/load/load_gizmo.py | 3 ++- openpype/hosts/nuke/plugins/load/load_gizmo_ip.py | 3 ++- openpype/hosts/nuke/plugins/load/load_matchmove.py | 4 +++- openpype/hosts/nuke/plugins/load/load_model.py | 3 ++- openpype/hosts/nuke/plugins/load/load_script_precomp.py | 3 ++- 11 files changed, 20 insertions(+), 10 deletions(-) diff --git a/openpype/hosts/nuke/plugins/load/actions.py b/openpype/hosts/nuke/plugins/load/actions.py index 69f56c7305..e562c74c58 100644 --- a/openpype/hosts/nuke/plugins/load/actions.py +++ b/openpype/hosts/nuke/plugins/load/actions.py @@ -17,6 +17,7 @@ class SetFrameRangeLoader(load.LoaderPlugin): "yeticache", "pointcache"] representations = ["*"] + extension = {"*"} label = "Set frame range" order = 11 diff --git a/openpype/hosts/nuke/plugins/load/load_backdrop.py b/openpype/hosts/nuke/plugins/load/load_backdrop.py index d1fb763500..f227aa161a 100644 --- a/openpype/hosts/nuke/plugins/load/load_backdrop.py +++ b/openpype/hosts/nuke/plugins/load/load_backdrop.py @@ -25,8 +25,9 @@ from openpype.hosts.nuke.api import containerise, update_container class LoadBackdropNodes(load.LoaderPlugin): """Loading Published Backdrop nodes (workfile, nukenodes)""" - representations = ["nk"] families = ["workfile", "nukenodes"] + representations = ["*"] + extension = {"nk"} label = "Import Nuke Nodes" order = 0 diff --git a/openpype/hosts/nuke/plugins/load/load_camera_abc.py b/openpype/hosts/nuke/plugins/load/load_camera_abc.py index 9fef7424c8..11cc63d25c 100644 --- a/openpype/hosts/nuke/plugins/load/load_camera_abc.py +++ b/openpype/hosts/nuke/plugins/load/load_camera_abc.py @@ -25,7 +25,8 @@ class AlembicCameraLoader(load.LoaderPlugin): """ families = ["camera"] - representations = ["abc"] + representations = ["*"] + extension = {"abc"} label = "Load Alembic Camera" icon = "camera" diff --git a/openpype/hosts/nuke/plugins/load/load_clip.py b/openpype/hosts/nuke/plugins/load/load_clip.py index 18ecaf4d2b..d170276add 100644 --- a/openpype/hosts/nuke/plugins/load/load_clip.py +++ b/openpype/hosts/nuke/plugins/load/load_clip.py @@ -43,7 +43,6 @@ class LoadClip(plugin.NukeLoader): "review" ] representations = ["*"] - extensions = set( ext.lstrip(".") for ext in IMAGE_EXTENSIONS.union(VIDEO_EXTENSIONS) ) diff --git a/openpype/hosts/nuke/plugins/load/load_effects.py b/openpype/hosts/nuke/plugins/load/load_effects.py index cef4b0a5fc..d49f87a094 100644 --- a/openpype/hosts/nuke/plugins/load/load_effects.py +++ b/openpype/hosts/nuke/plugins/load/load_effects.py @@ -22,8 +22,9 @@ from openpype.hosts.nuke.api import ( class LoadEffects(load.LoaderPlugin): """Loading colorspace soft effect exported from nukestudio""" - representations = ["effectJson"] families = ["effect"] + representations = ["*"] + extension = {"json"} label = "Load Effects - nodes" order = 0 diff --git a/openpype/hosts/nuke/plugins/load/load_effects_ip.py b/openpype/hosts/nuke/plugins/load/load_effects_ip.py index 9bd40be816..bfe32c1ed9 100644 --- a/openpype/hosts/nuke/plugins/load/load_effects_ip.py +++ b/openpype/hosts/nuke/plugins/load/load_effects_ip.py @@ -23,8 +23,9 @@ from openpype.hosts.nuke.api import ( class LoadEffectsInputProcess(load.LoaderPlugin): """Loading colorspace soft effect exported from nukestudio""" - representations = ["effectJson"] families = ["effect"] + representations = ["*"] + extension = {"json"} label = "Load Effects - Input Process" order = 0 diff --git a/openpype/hosts/nuke/plugins/load/load_gizmo.py b/openpype/hosts/nuke/plugins/load/load_gizmo.py index 9a18eeef5c..2aa7c49723 100644 --- a/openpype/hosts/nuke/plugins/load/load_gizmo.py +++ b/openpype/hosts/nuke/plugins/load/load_gizmo.py @@ -24,8 +24,9 @@ from openpype.hosts.nuke.api import ( class LoadGizmo(load.LoaderPlugin): """Loading nuke Gizmo""" - representations = ["gizmo"] families = ["gizmo"] + representations = ["*"] + extension = {"gizmo"} label = "Load Gizmo" order = 0 diff --git a/openpype/hosts/nuke/plugins/load/load_gizmo_ip.py b/openpype/hosts/nuke/plugins/load/load_gizmo_ip.py index 2890dbfd2c..2514a28299 100644 --- a/openpype/hosts/nuke/plugins/load/load_gizmo_ip.py +++ b/openpype/hosts/nuke/plugins/load/load_gizmo_ip.py @@ -26,8 +26,9 @@ from openpype.hosts.nuke.api import ( class LoadGizmoInputProcess(load.LoaderPlugin): """Loading colorspace soft effect exported from nukestudio""" - representations = ["gizmo"] families = ["gizmo"] + representations = ["*"] + extension = {"gizmo"} label = "Load Gizmo - Input Process" order = 0 diff --git a/openpype/hosts/nuke/plugins/load/load_matchmove.py b/openpype/hosts/nuke/plugins/load/load_matchmove.py index f5a90706c7..a7d124d472 100644 --- a/openpype/hosts/nuke/plugins/load/load_matchmove.py +++ b/openpype/hosts/nuke/plugins/load/load_matchmove.py @@ -8,7 +8,9 @@ class MatchmoveLoader(load.LoaderPlugin): """ families = ["matchmove"] - representations = ["py"] + representations = ["*"] + extension = {"py"} + defaults = ["Camera", "Object"] label = "Run matchmove script" diff --git a/openpype/hosts/nuke/plugins/load/load_model.py b/openpype/hosts/nuke/plugins/load/load_model.py index ad985e83c6..f968da8475 100644 --- a/openpype/hosts/nuke/plugins/load/load_model.py +++ b/openpype/hosts/nuke/plugins/load/load_model.py @@ -23,7 +23,8 @@ class AlembicModelLoader(load.LoaderPlugin): """ families = ["model", "pointcache", "animation"] - representations = ["abc"] + representations = ["*"] + extension = {"abc"} label = "Load Alembic" icon = "cube" diff --git a/openpype/hosts/nuke/plugins/load/load_script_precomp.py b/openpype/hosts/nuke/plugins/load/load_script_precomp.py index f0972f85d2..90581c2f22 100644 --- a/openpype/hosts/nuke/plugins/load/load_script_precomp.py +++ b/openpype/hosts/nuke/plugins/load/load_script_precomp.py @@ -20,8 +20,9 @@ from openpype.hosts.nuke.api import ( class LinkAsGroup(load.LoaderPlugin): """Copy the published file to be pasted at the desired location""" - representations = ["nk"] families = ["workfile", "nukenodes"] + representations = ["*"] + extension = {"nk"} label = "Load Precomp" order = 0 From 1b479f7eb7919c114efa7537f4a9922eb9e01a4b Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 24 Feb 2023 11:52:26 +0100 Subject: [PATCH 096/184] resolve: adding extensions to loader --- openpype/hosts/resolve/plugins/load/load_clip.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/resolve/plugins/load/load_clip.py b/openpype/hosts/resolve/plugins/load/load_clip.py index a0c78c182f..d30a7ea272 100644 --- a/openpype/hosts/resolve/plugins/load/load_clip.py +++ b/openpype/hosts/resolve/plugins/load/load_clip.py @@ -14,7 +14,10 @@ from openpype.hosts.resolve.api.pipeline import ( containerise, update_container, ) - +from openpype.lib.transcoding import ( + VIDEO_EXTENSIONS, + IMAGE_EXTENSIONS +) class LoadClip(plugin.TimelineItemLoader): """Load a subset to timeline as clip @@ -24,7 +27,11 @@ class LoadClip(plugin.TimelineItemLoader): """ families = ["render2d", "source", "plate", "render", "review"] - representations = ["exr", "dpx", "jpg", "jpeg", "png", "h264", "mov"] + + representations = ["*"] + extensions = set( + ext.lstrip(".") for ext in IMAGE_EXTENSIONS.union(VIDEO_EXTENSIONS) + ) label = "Load as clip" order = -10 From 339417c5055cdb8f77ee603a32a9caa630ab695d Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 24 Feb 2023 15:14:31 +0100 Subject: [PATCH 097/184] hiero: adding extension to load plugins also removing representation from settings and ignoring settings for representation on plugin --- .../hosts/hiero/plugins/load/load_clip.py | 41 ++++++++++++++++++- .../hosts/hiero/plugins/load/load_effects.py | 3 +- .../defaults/project_settings/hiero.json | 10 ----- .../projects_schema/schema_project_hiero.json | 8 +--- 4 files changed, 43 insertions(+), 19 deletions(-) diff --git a/openpype/hosts/hiero/plugins/load/load_clip.py b/openpype/hosts/hiero/plugins/load/load_clip.py index 2a7d1af41e..77844d2448 100644 --- a/openpype/hosts/hiero/plugins/load/load_clip.py +++ b/openpype/hosts/hiero/plugins/load/load_clip.py @@ -6,6 +6,10 @@ from openpype.pipeline import ( legacy_io, get_representation_path, ) +from openpype.lib.transcoding import ( + VIDEO_EXTENSIONS, + IMAGE_EXTENSIONS +) import openpype.hosts.hiero.api as phiero @@ -17,7 +21,10 @@ class LoadClip(phiero.SequenceLoader): """ families = ["render2d", "source", "plate", "render", "review"] - representations = ["exr", "dpx", "jpg", "jpeg", "png", "h264"] + representations = ["*"] + extensions = set( + ext.lstrip(".") for ext in IMAGE_EXTENSIONS.union(VIDEO_EXTENSIONS) + ) label = "Load as clip" order = -10 @@ -34,6 +41,38 @@ class LoadClip(phiero.SequenceLoader): clip_name_template = "{asset}_{subset}_{representation}" + def apply_settings(cls, project_settings, system_settings): + + plugin_type_settings = ( + project_settings + .get("hiero", {}) + .get("load", {}) + ) + + if not plugin_type_settings: + return + + plugin_name = cls.__name__ + + plugin_settings = None + # Look for plugin settings in host specific settings + if plugin_name in plugin_type_settings: + plugin_settings = plugin_type_settings[plugin_name] + + if not plugin_settings: + return + + print(">>> We have preset for {}".format(plugin_name)) + for option, value in plugin_settings.items(): + if option == "enabled" and value is False: + print(" - is disabled by preset") + elif option == "representations": + continue + else: + print(" - setting `{}`: `{}`".format(option, value)) + setattr(cls, option, value) + + def load(self, context, name, namespace, options): # add clip name template to options options.update({ diff --git a/openpype/hosts/hiero/plugins/load/load_effects.py b/openpype/hosts/hiero/plugins/load/load_effects.py index a3fcd63b5b..b61cca9731 100644 --- a/openpype/hosts/hiero/plugins/load/load_effects.py +++ b/openpype/hosts/hiero/plugins/load/load_effects.py @@ -19,8 +19,9 @@ from openpype.lib import Logger class LoadEffects(load.LoaderPlugin): """Loading colorspace soft effect exported from nukestudio""" - representations = ["effectJson"] families = ["effect"] + representations = ["*"] + extension = {"json"} label = "Load Effects" order = 0 diff --git a/openpype/settings/defaults/project_settings/hiero.json b/openpype/settings/defaults/project_settings/hiero.json index 0412967eaa..100c1f5b47 100644 --- a/openpype/settings/defaults/project_settings/hiero.json +++ b/openpype/settings/defaults/project_settings/hiero.json @@ -60,16 +60,6 @@ "render", "review" ], - "representations": [ - "exr", - "dpx", - "jpg", - "jpeg", - "png", - "h264", - "mov", - "mp4" - ], "clip_name_template": "{asset}_{subset}_{representation}" } }, diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json b/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json index 03bfb56ad1..f44f92438c 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json @@ -266,12 +266,6 @@ "label": "Families", "object_type": "text" }, - { - "type": "list", - "key": "representations", - "label": "Representations", - "object_type": "text" - }, { "type": "text", "key": "clip_name_template", @@ -334,4 +328,4 @@ "name": "schema_scriptsmenu" } ] -} \ No newline at end of file +} From 6d43fa21902f0b9cfa2beee43bf9e9d8ff362eb0 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 24 Feb 2023 15:19:50 +0100 Subject: [PATCH 098/184] flame: adding extensions to loaders also removing representations from settings and ignoring them in plugin settings loader --- openpype/hosts/flame/api/plugin.py | 31 +++++++++++++++++++ .../hosts/flame/plugins/load/load_clip.py | 9 +++++- .../flame/plugins/load/load_clip_batch.py | 10 ++++-- .../defaults/project_settings/flame.json | 22 ------------- .../projects_schema/schema_project_flame.json | 12 ------- 5 files changed, 47 insertions(+), 37 deletions(-) diff --git a/openpype/hosts/flame/api/plugin.py b/openpype/hosts/flame/api/plugin.py index b1db612671..983d7486b3 100644 --- a/openpype/hosts/flame/api/plugin.py +++ b/openpype/hosts/flame/api/plugin.py @@ -702,6 +702,37 @@ class ClipLoader(LoaderPlugin): _mapping = None + def apply_settings(cls, project_settings, system_settings): + + plugin_type_settings = ( + project_settings + .get("flame", {}) + .get("load", {}) + ) + + if not plugin_type_settings: + return + + plugin_name = cls.__name__ + + plugin_settings = None + # Look for plugin settings in host specific settings + if plugin_name in plugin_type_settings: + plugin_settings = plugin_type_settings[plugin_name] + + if not plugin_settings: + return + + print(">>> We have preset for {}".format(plugin_name)) + for option, value in plugin_settings.items(): + if option == "enabled" and value is False: + print(" - is disabled by preset") + elif option == "representations": + continue + else: + print(" - setting `{}`: `{}`".format(option, value)) + setattr(cls, option, value) + def get_colorspace(self, context): """Get colorspace name diff --git a/openpype/hosts/flame/plugins/load/load_clip.py b/openpype/hosts/flame/plugins/load/load_clip.py index 6f47c23d57..25b31c94a3 100644 --- a/openpype/hosts/flame/plugins/load/load_clip.py +++ b/openpype/hosts/flame/plugins/load/load_clip.py @@ -4,6 +4,10 @@ import flame from pprint import pformat import openpype.hosts.flame.api as opfapi from openpype.lib import StringTemplate +from openpype.lib.transcoding import ( + VIDEO_EXTENSIONS, + IMAGE_EXTENSIONS +) class LoadClip(opfapi.ClipLoader): @@ -14,7 +18,10 @@ class LoadClip(opfapi.ClipLoader): """ families = ["render2d", "source", "plate", "render", "review"] - representations = ["exr", "dpx", "jpg", "jpeg", "png", "h264"] + representations = ["*"] + extensions = set( + ext.lstrip(".") for ext in IMAGE_EXTENSIONS.union(VIDEO_EXTENSIONS) + ) label = "Load as clip" order = -10 diff --git a/openpype/hosts/flame/plugins/load/load_clip_batch.py b/openpype/hosts/flame/plugins/load/load_clip_batch.py index 5975c6e42f..86bc0f8f1e 100644 --- a/openpype/hosts/flame/plugins/load/load_clip_batch.py +++ b/openpype/hosts/flame/plugins/load/load_clip_batch.py @@ -4,7 +4,10 @@ import flame from pprint import pformat import openpype.hosts.flame.api as opfapi from openpype.lib import StringTemplate - +from openpype.lib.transcoding import ( + VIDEO_EXTENSIONS, + IMAGE_EXTENSIONS +) class LoadClipBatch(opfapi.ClipLoader): """Load a subset to timeline as clip @@ -14,7 +17,10 @@ class LoadClipBatch(opfapi.ClipLoader): """ families = ["render2d", "source", "plate", "render", "review"] - representations = ["exr", "dpx", "jpg", "jpeg", "png", "h264"] + representations = ["*"] + extensions = set( + ext.lstrip(".") for ext in IMAGE_EXTENSIONS.union(VIDEO_EXTENSIONS) + ) label = "Load as clip to current batch" order = -10 diff --git a/openpype/settings/defaults/project_settings/flame.json b/openpype/settings/defaults/project_settings/flame.json index 3190bdb3bf..5a13d81384 100644 --- a/openpype/settings/defaults/project_settings/flame.json +++ b/openpype/settings/defaults/project_settings/flame.json @@ -114,17 +114,6 @@ "render", "review" ], - "representations": [ - "exr", - "dpx", - "jpg", - "jpeg", - "png", - "h264", - "mov", - "mp4", - "exr16fpdwaa" - ], "reel_group_name": "OpenPype_Reels", "reel_name": "Loaded", "clip_name_template": "{asset}_{subset}<_{output}>", @@ -143,17 +132,6 @@ "render", "review" ], - "representations": [ - "exr", - "dpx", - "jpg", - "jpeg", - "png", - "h264", - "mov", - "mp4", - "exr16fpdwaa" - ], "reel_name": "OP_LoadedReel", "clip_name_template": "{batch}_{asset}_{subset}<_{output}>", "layer_rename_template": "{asset}_{subset}<_{output}>", diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_flame.json b/openpype/settings/entities/schemas/projects_schema/schema_project_flame.json index 0f20c0efbe..aab8f21d15 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_flame.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_flame.json @@ -494,12 +494,6 @@ "label": "Families", "object_type": "text" }, - { - "type": "list", - "key": "representations", - "label": "Representations", - "object_type": "text" - }, { "type": "separator" }, @@ -552,12 +546,6 @@ "label": "Families", "object_type": "text" }, - { - "type": "list", - "key": "representations", - "label": "Representations", - "object_type": "text" - }, { "type": "separator" }, From a2d7ab24f60609096b58fbb8e6b891e743a1892e Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 24 Feb 2023 15:24:22 +0100 Subject: [PATCH 099/184] fusion: adding extensions to loaders --- openpype/hosts/fusion/plugins/load/actions.py | 1 + openpype/hosts/fusion/plugins/load/load_alembic.py | 3 ++- openpype/hosts/fusion/plugins/load/load_fbx.py | 3 ++- openpype/hosts/fusion/plugins/load/load_sequence.py | 6 ++++++ 4 files changed, 11 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/fusion/plugins/load/actions.py b/openpype/hosts/fusion/plugins/load/actions.py index 819c9272fd..3b14f022e5 100644 --- a/openpype/hosts/fusion/plugins/load/actions.py +++ b/openpype/hosts/fusion/plugins/load/actions.py @@ -15,6 +15,7 @@ class FusionSetFrameRangeLoader(load.LoaderPlugin): "pointcache", "render"] representations = ["*"] + extensions = {"*"} label = "Set frame range" order = 11 diff --git a/openpype/hosts/fusion/plugins/load/load_alembic.py b/openpype/hosts/fusion/plugins/load/load_alembic.py index f8b8c2cb0a..11bf59af12 100644 --- a/openpype/hosts/fusion/plugins/load/load_alembic.py +++ b/openpype/hosts/fusion/plugins/load/load_alembic.py @@ -13,7 +13,8 @@ class FusionLoadAlembicMesh(load.LoaderPlugin): """Load Alembic mesh into Fusion""" families = ["pointcache", "model"] - representations = ["abc"] + representations = ["*"] + extensions = {"abc"} label = "Load alembic mesh" order = -10 diff --git a/openpype/hosts/fusion/plugins/load/load_fbx.py b/openpype/hosts/fusion/plugins/load/load_fbx.py index 70fe82ffef..b8f501ae7e 100644 --- a/openpype/hosts/fusion/plugins/load/load_fbx.py +++ b/openpype/hosts/fusion/plugins/load/load_fbx.py @@ -14,7 +14,8 @@ class FusionLoadFBXMesh(load.LoaderPlugin): """Load FBX mesh into Fusion""" families = ["*"] - representations = ["fbx"] + representations = ["*"] + extensions = {"fbx"} label = "Load FBX mesh" order = -10 diff --git a/openpype/hosts/fusion/plugins/load/load_sequence.py b/openpype/hosts/fusion/plugins/load/load_sequence.py index 6f44c61d1b..465df757b1 100644 --- a/openpype/hosts/fusion/plugins/load/load_sequence.py +++ b/openpype/hosts/fusion/plugins/load/load_sequence.py @@ -12,6 +12,9 @@ from openpype.hosts.fusion.api import ( get_current_comp, comp_lock_and_undo_chunk ) +from openpype.lib.transcoding import ( + IMAGE_EXTENSIONS +) comp = get_current_comp() @@ -129,6 +132,9 @@ class FusionLoadSequence(load.LoaderPlugin): families = ["imagesequence", "review", "render", "plate"] representations = ["*"] + extensions = set( + ext.lstrip(".") for ext in IMAGE_EXTENSIONS + ) label = "Load sequence" order = -10 From 3a02a03efad89fe50850c270a55ee028e3f6ec91 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 24 Feb 2023 16:38:34 +0100 Subject: [PATCH 100/184] fusion: adding video extensions to sequence loader --- openpype/hosts/fusion/plugins/load/load_sequence.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/fusion/plugins/load/load_sequence.py b/openpype/hosts/fusion/plugins/load/load_sequence.py index 465df757b1..542c3c1a51 100644 --- a/openpype/hosts/fusion/plugins/load/load_sequence.py +++ b/openpype/hosts/fusion/plugins/load/load_sequence.py @@ -13,7 +13,8 @@ from openpype.hosts.fusion.api import ( comp_lock_and_undo_chunk ) from openpype.lib.transcoding import ( - IMAGE_EXTENSIONS + IMAGE_EXTENSIONS, + VIDEO_EXTENSIONS ) comp = get_current_comp() @@ -133,7 +134,7 @@ class FusionLoadSequence(load.LoaderPlugin): families = ["imagesequence", "review", "render", "plate"] representations = ["*"] extensions = set( - ext.lstrip(".") for ext in IMAGE_EXTENSIONS + ext.lstrip(".") for ext in IMAGE_EXTENSIONS.union(VIDEO_EXTENSIONS) ) label = "Load sequence" From 42cc7152334bb59485470a6bb3a56d8eb76f8dde Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 24 Feb 2023 16:50:11 +0100 Subject: [PATCH 101/184] changed info to debug --- openpype/pipeline/load/plugins.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/load/plugins.py b/openpype/pipeline/load/plugins.py index d35281e823..e380d65bbe 100644 --- a/openpype/pipeline/load/plugins.py +++ b/openpype/pipeline/load/plugins.py @@ -108,7 +108,7 @@ class LoaderPlugin(list): ) return False - cls.log.info("Using legacy source of extension from path.") + cls.log.debug("Using legacy source of extension from path.") ext = os.path.splitext(path)[-1].lstrip(".") # If representation does not have extension then can't be valid From 956fbb780badbd2bcbf052f4646d3014b5fe9df8 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 28 Feb 2023 11:24:02 +0100 Subject: [PATCH 102/184] change Harmony loader to use extensions --- openpype/hosts/harmony/plugins/load/load_imagesequence.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/harmony/plugins/load/load_imagesequence.py b/openpype/hosts/harmony/plugins/load/load_imagesequence.py index 1b64aff595..7c3185fd75 100644 --- a/openpype/hosts/harmony/plugins/load/load_imagesequence.py +++ b/openpype/hosts/harmony/plugins/load/load_imagesequence.py @@ -20,8 +20,9 @@ class ImageSequenceLoader(load.LoaderPlugin): Stores the imported asset in a container named after the asset. """ - families = ["shot", "render", "image", "plate", "reference"] - representations = ["jpeg", "png", "jpg"] + families = ["shot", "render", "image", "plate", "reference", "review"] + representations = ["*"] + extensions = set(["jpeg", "png", "jpg"]) def load(self, context, name=None, namespace=None, data=None): """Plugin entry point. From 0765602ce3aabfc6751662d1026a7638385d67a8 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 28 Feb 2023 11:27:13 +0100 Subject: [PATCH 103/184] simpler set initialization --- openpype/hosts/harmony/plugins/load/load_imagesequence.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/harmony/plugins/load/load_imagesequence.py b/openpype/hosts/harmony/plugins/load/load_imagesequence.py index 7c3185fd75..b95d25f507 100644 --- a/openpype/hosts/harmony/plugins/load/load_imagesequence.py +++ b/openpype/hosts/harmony/plugins/load/load_imagesequence.py @@ -22,7 +22,7 @@ class ImageSequenceLoader(load.LoaderPlugin): families = ["shot", "render", "image", "plate", "reference", "review"] representations = ["*"] - extensions = set(["jpeg", "png", "jpg"]) + extensions = {"jpeg", "png", "jpg"} def load(self, context, name=None, namespace=None, data=None): """Plugin entry point. From 9229ff9c0b229db2d9d25c8d91b6cb6f7b58de9b Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 28 Feb 2023 13:58:01 +0100 Subject: [PATCH 104/184] hiero: fix effect item node class --- .../hosts/hiero/plugins/publish/collect_clip_effects.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/hiero/plugins/publish/collect_clip_effects.py b/openpype/hosts/hiero/plugins/publish/collect_clip_effects.py index 9489b1c4fb..95e4b09504 100644 --- a/openpype/hosts/hiero/plugins/publish/collect_clip_effects.py +++ b/openpype/hosts/hiero/plugins/publish/collect_clip_effects.py @@ -120,13 +120,10 @@ class CollectClipEffects(pyblish.api.InstancePlugin): track = sitem.parentTrack().name() # node serialization node = sitem.node() - node_serialized = self.node_serialisation(node) + node_serialized = self.node_serialization(node) node_name = sitem.name() + node_class = node.Class() - if "_" in node_name: - node_class = re.sub(r"(?:_)[_0-9]+", "", node_name) # more numbers - else: - node_class = re.sub(r"\d+", "", node_name) # one number # collect timelineIn/Out effect_t_in = int(sitem.timelineIn()) @@ -148,7 +145,7 @@ class CollectClipEffects(pyblish.api.InstancePlugin): "node": node_serialized }} - def node_serialisation(self, node): + def node_serialization(self, node): node_serialized = {} # adding ignoring knob keys From 2bd4e5c3c91b453bca3b21aed3c1cfd6cf19be37 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 28 Feb 2023 14:11:01 +0100 Subject: [PATCH 105/184] hound comments --- openpype/hosts/hiero/plugins/publish/collect_clip_effects.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/hiero/plugins/publish/collect_clip_effects.py b/openpype/hosts/hiero/plugins/publish/collect_clip_effects.py index 95e4b09504..d455ad4a4e 100644 --- a/openpype/hosts/hiero/plugins/publish/collect_clip_effects.py +++ b/openpype/hosts/hiero/plugins/publish/collect_clip_effects.py @@ -124,7 +124,6 @@ class CollectClipEffects(pyblish.api.InstancePlugin): node_name = sitem.name() node_class = node.Class() - # collect timelineIn/Out effect_t_in = int(sitem.timelineIn()) effect_t_out = int(sitem.timelineOut()) From 477d2e24143ef5caebe6801d094541ab012ffe39 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Mon, 27 Feb 2023 18:15:44 +0100 Subject: [PATCH 106/184] :bug: return AssetContainer to allow loading --- .../OpenPype/Private/AssetContainer.cpp | 114 ++++++++++++++++++ .../Private/AssetContainerFactory.cpp | 20 +++ .../Source/OpenPype/Public/AssetContainer.h | 37 ++++++ .../OpenPype/Public/AssetContainerFactory.h | 21 ++++ 4 files changed, 192 insertions(+) create mode 100644 openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/AssetContainer.cpp create mode 100644 openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/AssetContainerFactory.cpp create mode 100644 openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/AssetContainer.h create mode 100644 openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/AssetContainerFactory.h diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/AssetContainer.cpp b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/AssetContainer.cpp new file mode 100644 index 0000000000..0bea9e3d78 --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/AssetContainer.cpp @@ -0,0 +1,114 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#include "AssetContainer.h" +#include "AssetRegistry/AssetRegistryModule.h" +#include "Misc/PackageName.h" +#include "Engine.h" +#include "Containers/UnrealString.h" + +UAssetContainer::UAssetContainer(const FObjectInitializer& ObjectInitializer) +: UAssetUserData(ObjectInitializer) +{ + FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); + FString path = UAssetContainer::GetPathName(); + UE_LOG(LogTemp, Warning, TEXT("UAssetContainer %s"), *path); + FARFilter Filter; + Filter.PackagePaths.Add(FName(*path)); + + AssetRegistryModule.Get().OnAssetAdded().AddUObject(this, &UAssetContainer::OnAssetAdded); + AssetRegistryModule.Get().OnAssetRemoved().AddUObject(this, &UAssetContainer::OnAssetRemoved); + AssetRegistryModule.Get().OnAssetRenamed().AddUObject(this, &UAssetContainer::OnAssetRenamed); +} + +void UAssetContainer::OnAssetAdded(const FAssetData& AssetData) +{ + TArray split; + + // get directory of current container + FString selfFullPath = UAssetContainer::GetPathName(); + FString selfDir = FPackageName::GetLongPackagePath(*selfFullPath); + + // get asset path and class + FString assetPath = AssetData.GetFullName(); + FString assetFName = AssetData.AssetClassPath.ToString(); + UE_LOG(LogTemp, Log, TEXT("asset name %s"), *assetFName); + // split path + assetPath.ParseIntoArray(split, TEXT(" "), true); + + FString assetDir = FPackageName::GetLongPackagePath(*split[1]); + + // take interest only in paths starting with path of current container + if (assetDir.StartsWith(*selfDir)) + { + // exclude self + if (assetFName != "AssetContainer") + { + assets.Add(assetPath); + assetsData.Add(AssetData); + UE_LOG(LogTemp, Log, TEXT("%s: asset added to %s"), *selfFullPath, *selfDir); + } + } +} + +void UAssetContainer::OnAssetRemoved(const FAssetData& AssetData) +{ + TArray split; + + // get directory of current container + FString selfFullPath = UAssetContainer::GetPathName(); + FString selfDir = FPackageName::GetLongPackagePath(*selfFullPath); + + // get asset path and class + FString assetPath = AssetData.GetFullName(); + FString assetFName = AssetData.AssetClassPath.ToString(); + + // split path + assetPath.ParseIntoArray(split, TEXT(" "), true); + + FString assetDir = FPackageName::GetLongPackagePath(*split[1]); + + // take interest only in paths starting with path of current container + FString path = UAssetContainer::GetPathName(); + FString lpp = FPackageName::GetLongPackagePath(*path); + + if (assetDir.StartsWith(*selfDir)) + { + // exclude self + if (assetFName != "AssetContainer") + { + // UE_LOG(LogTemp, Warning, TEXT("%s: asset removed"), *lpp); + assets.Remove(assetPath); + assetsData.Remove(AssetData); + } + } +} + +void UAssetContainer::OnAssetRenamed(const FAssetData& AssetData, const FString& str) +{ + TArray split; + + // get directory of current container + FString selfFullPath = UAssetContainer::GetPathName(); + FString selfDir = FPackageName::GetLongPackagePath(*selfFullPath); + + // get asset path and class + FString assetPath = AssetData.GetFullName(); + FString assetFName = AssetData.AssetClassPath.ToString(); + + // split path + assetPath.ParseIntoArray(split, TEXT(" "), true); + + FString assetDir = FPackageName::GetLongPackagePath(*split[1]); + if (assetDir.StartsWith(*selfDir)) + { + // exclude self + if (assetFName != "AssetContainer") + { + + assets.Remove(str); + assets.Add(assetPath); + assetsData.Remove(AssetData); + // UE_LOG(LogTemp, Warning, TEXT("%s: asset renamed %s"), *lpp, *str); + } + } +} diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/AssetContainerFactory.cpp b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/AssetContainerFactory.cpp new file mode 100644 index 0000000000..b943150bdd --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Private/AssetContainerFactory.cpp @@ -0,0 +1,20 @@ +#include "AssetContainerFactory.h" +#include "AssetContainer.h" + +UAssetContainerFactory::UAssetContainerFactory(const FObjectInitializer& ObjectInitializer) + : UFactory(ObjectInitializer) +{ + SupportedClass = UAssetContainer::StaticClass(); + bCreateNew = false; + bEditorImport = true; +} + +UObject* UAssetContainerFactory::FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) +{ + UAssetContainer* AssetContainer = NewObject(InParent, Class, Name, Flags); + return AssetContainer; +} + +bool UAssetContainerFactory::ShouldShowInNewMenu() const { + return false; +} diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/AssetContainer.h b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/AssetContainer.h new file mode 100644 index 0000000000..9157569c08 --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/AssetContainer.h @@ -0,0 +1,37 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/NoExportTypes.h" +#include "Engine/AssetUserData.h" +#include "AssetRegistry/AssetData.h" +#include "AssetContainer.generated.h" + +/** + * + */ +UCLASS(Blueprintable) +class OPENPYPE_API UAssetContainer : public UAssetUserData +{ + GENERATED_BODY() + +public: + + UAssetContainer(const FObjectInitializer& ObjectInitalizer); + // ~UAssetContainer(); + + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category="Assets") + TArray assets; + + // There seems to be no reflection option to expose array of FAssetData + /* + UPROPERTY(Transient, BlueprintReadOnly, Category = "Python", meta=(DisplayName="Assets Data")) + TArray assetsData; + */ +private: + TArray assetsData; + void OnAssetAdded(const FAssetData& AssetData); + void OnAssetRemoved(const FAssetData& AssetData); + void OnAssetRenamed(const FAssetData& AssetData, const FString& str); +}; diff --git a/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/AssetContainerFactory.h b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/AssetContainerFactory.h new file mode 100644 index 0000000000..9095f8a3d7 --- /dev/null +++ b/openpype/hosts/unreal/integration/UE_5.0/OpenPype/Source/OpenPype/Public/AssetContainerFactory.h @@ -0,0 +1,21 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "Factories/Factory.h" +#include "AssetContainerFactory.generated.h" + +/** + * + */ +UCLASS() +class OPENPYPE_API UAssetContainerFactory : public UFactory +{ + GENERATED_BODY() + +public: + UAssetContainerFactory(const FObjectInitializer& ObjectInitializer); + virtual UObject* FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override; + virtual bool ShouldShowInNewMenu() const override; +}; From ee3e346c8df68515d72221bb2e3fe84ab92e9b0e Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 28 Feb 2023 14:53:49 +0100 Subject: [PATCH 107/184] Global: refactory colormanaged exctractor into plugin mixin --- .../plugins/publish/extract_render_local.py | 3 +- openpype/pipeline/publish/__init__.py | 4 +- openpype/pipeline/publish/publish_plugins.py | 40 ++++++++++--------- .../publish/extract_colorspace_data.py | 3 +- 4 files changed, 28 insertions(+), 22 deletions(-) diff --git a/openpype/hosts/nuke/plugins/publish/extract_render_local.py b/openpype/hosts/nuke/plugins/publish/extract_render_local.py index b99a7a9548..4d7ade9c7a 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_render_local.py +++ b/openpype/hosts/nuke/plugins/publish/extract_render_local.py @@ -9,7 +9,8 @@ from openpype.pipeline import publish from openpype.lib import collect_frames -class NukeRenderLocal(publish.ExtractorColormanaged): +class NukeRenderLocal(publish.Extractor, + publish.ColormanagedPyblishPluginMixin): """Render the current Nuke composition locally. Extract the result of savers by starting a comp render diff --git a/openpype/pipeline/publish/__init__.py b/openpype/pipeline/publish/__init__.py index 05ba1c9c33..36252c9f3d 100644 --- a/openpype/pipeline/publish/__init__.py +++ b/openpype/pipeline/publish/__init__.py @@ -19,7 +19,7 @@ from .publish_plugins import ( RepairContextAction, Extractor, - ExtractorColormanaged, + ColormanagedPyblishPluginMixin ) from .lib import ( @@ -64,7 +64,7 @@ __all__ = ( "RepairContextAction", "Extractor", - "ExtractorColormanaged", + "ColormanagedPyblishPluginMixin", "get_publish_template_name", diff --git a/openpype/pipeline/publish/publish_plugins.py b/openpype/pipeline/publish/publish_plugins.py index e2ae893aa9..0142919e76 100644 --- a/openpype/pipeline/publish/publish_plugins.py +++ b/openpype/pipeline/publish/publish_plugins.py @@ -3,7 +3,7 @@ from abc import ABCMeta from pprint import pformat import pyblish.api from pyblish.plugin import MetaPlugin, ExplicitMetaPlugin - +from openpype.lib.transcoding import VIDEO_EXTENSIONS, IMAGE_EXTENSIONS from openpype.lib import BoolDef from .lib import ( @@ -288,24 +288,25 @@ class Extractor(pyblish.api.InstancePlugin): return get_instance_staging_dir(instance) -class ExtractorColormanaged(Extractor): - """Extractor base for color managed image data. - - Each Extractor intended to export pixel data representation - should inherit from this class to allow color managed data. - Class implements "get_colorspace_settings" and - "set_representation_colorspace" functions used - for injecting colorspace data to representation data for farther - integration into db document. +class ColormanagedPyblishPluginMixin(object): + """Mixin for colormanaged plugins. + This class is used to set colorspace data to a publishing + representation. It contains a static method, + get_colorspace_settings, which returns config and + file rules data for the host context. + It also contains a method, set_representation_colorspace, + which sets colorspace data to the representation. + The allowed file extensions are listed in the allowed_ext variable. + he method first checks if the file extension is in + the list of allowed extensions. If it is, it then gets the + colorspace settings from the host context and gets a + matching colorspace from rules. Finally, it infuses this + data into the representation. """ - - allowed_ext = [ - "cin", "dpx", "avi", "dv", "gif", "flv", "mkv", "mov", "mpg", "mpeg", - "mp4", "m4v", "mxf", "iff", "z", "ifl", "jpeg", "jpg", "jfif", "lut", - "1dl", "exr", "pic", "png", "ppm", "pnm", "pgm", "pbm", "rla", "rpf", - "sgi", "rgba", "rgb", "bw", "tga", "tiff", "tif", "img" - ] + allowed_ext = set( + ext.lstrip(".") for ext in IMAGE_EXTENSIONS.union(VIDEO_EXTENSIONS) + ) @staticmethod def get_colorspace_settings(context): @@ -375,7 +376,10 @@ class ExtractorColormanaged(Extractor): ext = representation["ext"] # check extension self.log.debug("__ ext: `{}`".format(ext)) - if ext.lower() not in self.allowed_ext: + + # check if ext in lower case is in self.allowed_ext + if ext.lstrip(".").lower() not in self.allowed_ext: + self.log.debug("Extension is not in allowed extensions.") return if colorspace_settings is None: diff --git a/openpype/plugins/publish/extract_colorspace_data.py b/openpype/plugins/publish/extract_colorspace_data.py index 611fb91cbb..363df28fb5 100644 --- a/openpype/plugins/publish/extract_colorspace_data.py +++ b/openpype/plugins/publish/extract_colorspace_data.py @@ -2,7 +2,8 @@ import pyblish.api from openpype.pipeline import publish -class ExtractColorspaceData(publish.ExtractorColormanaged): +class ExtractColorspaceData(publish.Extractor, + publish.ColormanagedPyblishPluginMixin): """ Inject Colorspace data to available representations. Input data: From 76f312a3ff026d04feda901b98bc0ad523e3b00b Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 28 Feb 2023 15:04:54 +0100 Subject: [PATCH 108/184] Nuke: adding colorspace to representation when rendered mode --- .../hosts/nuke/plugins/publish/collect_writes.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/nuke/plugins/publish/collect_writes.py b/openpype/hosts/nuke/plugins/publish/collect_writes.py index 3054e5a30c..2b741426e6 100644 --- a/openpype/hosts/nuke/plugins/publish/collect_writes.py +++ b/openpype/hosts/nuke/plugins/publish/collect_writes.py @@ -3,9 +3,10 @@ from pprint import pformat import nuke import pyblish.api from openpype.hosts.nuke import api as napi +from openpype.pipeline import publish - -class CollectNukeWrites(pyblish.api.InstancePlugin): +class CollectNukeWrites(pyblish.api.InstancePlugin, + publish.ColormanagedPyblishPluginMixin): """Collect all write nodes.""" order = pyblish.api.CollectorOrder - 0.48 @@ -128,6 +129,12 @@ class CollectNukeWrites(pyblish.api.InstancePlugin): else: representation['files'] = collected_frames + # inject colorspace data + self.set_representation_colorspace( + representation, instance.context, + colorspace=colorspace + ) + instance.data["representations"].append(representation) self.log.info("Publishing rendered frames ...") @@ -147,6 +154,8 @@ class CollectNukeWrites(pyblish.api.InstancePlugin): # get colorspace and add to version data colorspace = napi.get_colorspace_from_node(write_node) + + # TODO: remove this when we have proper colorspace support version_data = { "colorspace": colorspace } From 9bb36864be3911b25c37c873d68ae4871fdcf57a Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Tue, 28 Feb 2023 15:05:16 +0100 Subject: [PATCH 109/184] Nuke: colorspace from node unified --- openpype/hosts/nuke/plugins/publish/extract_render_local.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/nuke/plugins/publish/extract_render_local.py b/openpype/hosts/nuke/plugins/publish/extract_render_local.py index 4d7ade9c7a..e5feda4cd8 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_render_local.py +++ b/openpype/hosts/nuke/plugins/publish/extract_render_local.py @@ -4,7 +4,7 @@ import shutil import pyblish.api import clique import nuke - +from openpype.hosts.nuke import api as napi from openpype.pipeline import publish from openpype.lib import collect_frames @@ -86,7 +86,7 @@ class NukeRenderLocal(publish.Extractor, ) ext = node["file_type"].value() - colorspace = node["colorspace"].value() + colorspace = napi.get_colorspace_from_node(node) if "representations" not in instance.data: instance.data["representations"] = [] From d68cc23d32aabb7773b40ff2896fb8ea6390cae7 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 28 Feb 2023 14:37:41 +0100 Subject: [PATCH 110/184] hardcode Poetry to 1.3.2 temporarily New 1.4.0 breaks sphinxcontrib-applehelp. Change in poetry.lock would need new minor version. --- tools/create_env.ps1 | 2 +- tools/create_env.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/create_env.ps1 b/tools/create_env.ps1 index f79bc2a076..7d1f6635d0 100644 --- a/tools/create_env.ps1 +++ b/tools/create_env.ps1 @@ -68,7 +68,7 @@ function Install-Poetry() { } $env:POETRY_HOME="$openpype_root\.poetry" - # $env:POETRY_VERSION="1.1.15" + $env:POETRY_VERSION="1.3.2" (Invoke-WebRequest -Uri https://install.python-poetry.org/ -UseBasicParsing).Content | & $($python) - } diff --git a/tools/create_env.sh b/tools/create_env.sh index fbae69e56d..6915d3f000 100755 --- a/tools/create_env.sh +++ b/tools/create_env.sh @@ -109,7 +109,7 @@ detect_python () { install_poetry () { echo -e "${BIGreen}>>>${RST} Installing Poetry ..." export POETRY_HOME="$openpype_root/.poetry" - # export POETRY_VERSION="1.1.15" + export POETRY_VERSION="1.3.2" command -v curl >/dev/null 2>&1 || { echo -e "${BIRed}!!!${RST}${BIYellow} Missing ${RST}${BIBlue}curl${BIYellow} command.${RST}"; return 1; } curl -sSL https://install.python-poetry.org/ | python - } From 9f35ed2a63ce38f0d7c6b1c29fec305116896a04 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 28 Feb 2023 16:33:18 +0100 Subject: [PATCH 111/184] Fix - store target_colorspace as new colorspace (#4544) When transcoding into new colorspace, representation must carry this information instead original color space. --- openpype/plugins/publish/extract_color_transcode.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/plugins/publish/extract_color_transcode.py b/openpype/plugins/publish/extract_color_transcode.py index a6fa710425..58e0350a2e 100644 --- a/openpype/plugins/publish/extract_color_transcode.py +++ b/openpype/plugins/publish/extract_color_transcode.py @@ -129,11 +129,14 @@ class ExtractOIIOTranscode(publish.Extractor): colorspace_data.get("display")) # both could be already collected by DCC, - # but could be overwritten + # but could be overwritten when transcoding if view: new_repre["colorspaceData"]["view"] = view if display: new_repre["colorspaceData"]["display"] = display + if target_colorspace: + new_repre["colorspaceData"]["colorspace"] = \ + target_colorspace additional_command_args = (output_def["oiiotool_args"] ["additional_command_args"]) From b29b53af2024a4b849a4e96f3e77e24f5fba7be7 Mon Sep 17 00:00:00 2001 From: Ynbot Date: Wed, 1 Mar 2023 03:32:10 +0000 Subject: [PATCH 112/184] [Automated] Bump version --- openpype/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/version.py b/openpype/version.py index bd1ba5309d..4d6f3d43e4 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.15.2-nightly.2" +__version__ = "3.15.2-nightly.3" From f4d22e6ac671245dab4c4f6831ea6f057ca088c6 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Fri, 24 Feb 2023 11:44:41 +0000 Subject: [PATCH 113/184] Options for VrayProxy output. --- .../maya/plugins/create/create_vrayproxy.py | 6 ++++ .../maya/plugins/publish/collect_vrayproxy.py | 8 ++++- .../plugins/publish/extract_pointcache.py | 2 +- .../maya/plugins/publish/extract_vrayproxy.py | 2 +- .../plugins/publish/validate_vrayproxy.py | 32 ++++++++++------- .../defaults/project_settings/maya.json | 16 +++++---- .../schemas/schema_maya_create.json | 34 ++++++++++++++++--- 7 files changed, 73 insertions(+), 27 deletions(-) diff --git a/openpype/hosts/maya/plugins/create/create_vrayproxy.py b/openpype/hosts/maya/plugins/create/create_vrayproxy.py index 5c0365b495..d135073e82 100644 --- a/openpype/hosts/maya/plugins/create/create_vrayproxy.py +++ b/openpype/hosts/maya/plugins/create/create_vrayproxy.py @@ -9,6 +9,9 @@ class CreateVrayProxy(plugin.Creator): family = "vrayproxy" icon = "gears" + vrmesh = True + alembic = True + def __init__(self, *args, **kwargs): super(CreateVrayProxy, self).__init__(*args, **kwargs) @@ -18,3 +21,6 @@ class CreateVrayProxy(plugin.Creator): # Write vertex colors self.data["vertexColors"] = False + + self.data["vrmesh"] = self.vrmesh + self.data["alembic"] = self.alembic diff --git a/openpype/hosts/maya/plugins/publish/collect_vrayproxy.py b/openpype/hosts/maya/plugins/publish/collect_vrayproxy.py index 236797ca3c..24521a2f09 100644 --- a/openpype/hosts/maya/plugins/publish/collect_vrayproxy.py +++ b/openpype/hosts/maya/plugins/publish/collect_vrayproxy.py @@ -9,10 +9,16 @@ class CollectVrayProxy(pyblish.api.InstancePlugin): Add `pointcache` family for it. """ order = pyblish.api.CollectorOrder + 0.01 - label = 'Collect Vray Proxy' + label = "Collect Vray Proxy" families = ["vrayproxy"] def process(self, instance): """Collector entry point.""" if not instance.data.get('families'): instance.data["families"] = [] + + if instance.data.get("vrmesh"): + instance.data["families"].append("vrayproxy.vrmesh") + + if instance.data.get("alembic"): + instance.data["families"].append("vrayproxy.alembic") diff --git a/openpype/hosts/maya/plugins/publish/extract_pointcache.py b/openpype/hosts/maya/plugins/publish/extract_pointcache.py index a3b0560099..f44c13767c 100644 --- a/openpype/hosts/maya/plugins/publish/extract_pointcache.py +++ b/openpype/hosts/maya/plugins/publish/extract_pointcache.py @@ -23,7 +23,7 @@ class ExtractAlembic(publish.Extractor): label = "Extract Pointcache (Alembic)" hosts = ["maya"] - families = ["pointcache", "model", "vrayproxy"] + families = ["pointcache", "model", "vrayproxy.alembic"] targets = ["local", "remote"] def process(self, instance): diff --git a/openpype/hosts/maya/plugins/publish/extract_vrayproxy.py b/openpype/hosts/maya/plugins/publish/extract_vrayproxy.py index 38bf02245a..9b10d2737d 100644 --- a/openpype/hosts/maya/plugins/publish/extract_vrayproxy.py +++ b/openpype/hosts/maya/plugins/publish/extract_vrayproxy.py @@ -16,7 +16,7 @@ class ExtractVRayProxy(publish.Extractor): label = "VRay Proxy (.vrmesh)" hosts = ["maya"] - families = ["vrayproxy"] + families = ["vrayproxy.vrmesh"] def process(self, instance): diff --git a/openpype/hosts/maya/plugins/publish/validate_vrayproxy.py b/openpype/hosts/maya/plugins/publish/validate_vrayproxy.py index 3eceace76d..075eedc378 100644 --- a/openpype/hosts/maya/plugins/publish/validate_vrayproxy.py +++ b/openpype/hosts/maya/plugins/publish/validate_vrayproxy.py @@ -1,27 +1,33 @@ import pyblish.api +from openpype.pipeline import KnownPublishError + class ValidateVrayProxy(pyblish.api.InstancePlugin): order = pyblish.api.ValidatorOrder - label = 'VRay Proxy Settings' - hosts = ['maya'] - families = ['studio.vrayproxy'] + label = "VRay Proxy Settings" + hosts = ["maya"] + families = ["vrayproxy"] def process(self, instance): - - invalid = self.get_invalid(instance) - if invalid: - raise RuntimeError("'%s' has invalid settings for VRay Proxy " - "export!" % instance.name) - - @classmethod - def get_invalid(cls, instance): data = instance.data + self.log.info(data["family"]) + self.log.info(data["families"]) if not data["setMembers"]: - cls.log.error("'%s' is empty! This is a bug" % instance.name) + raise KnownPublishError( + "'%s' is empty! This is a bug" % instance.name + ) if data["animation"]: if data["frameEnd"] < data["frameStart"]: - cls.log.error("End frame is smaller than start frame") + raise KnownPublishError( + "End frame is smaller than start frame" + ) + + if not data["vrmesh"] and not data["alembic"]: + raise KnownPublishError( + "Both vrmesh and alembic are off. Needs at least one to" + " publish." + ) diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 2559448900..90334a6644 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -199,6 +199,14 @@ "maskColor_manager": false, "maskOperator": false }, + "CreateVrayProxy": { + "enabled": true, + "vrmesh": true, + "alembic": true, + "defaults": [ + "Main" + ] + }, "CreateMultiverseUsd": { "enabled": true, "defaults": [ @@ -268,12 +276,6 @@ "Anim" ] }, - "CreateVrayProxy": { - "enabled": true, - "defaults": [ - "Main" - ] - }, "CreateVRayScene": { "enabled": true, "defaults": [ @@ -676,7 +678,7 @@ "families": [ "pointcache", "model", - "vrayproxy" + "vrayproxy.alembic" ] }, "ExtractObj": { diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_create.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_create.json index 77a39f692f..49503cce83 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_create.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_create.json @@ -332,6 +332,36 @@ } ] }, + { + "type": "dict", + "collapsible": true, + "key": "CreateVrayProxy", + "label": "Create VRay Proxy", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "boolean", + "key": "vrmesh", + "label": "VrMesh" + }, + { + "type": "boolean", + "key": "alembic", + "label": "Alembic" + }, + { + "type": "list", + "key": "defaults", + "label": "Default Subsets", + "object_type": "text" + } + ] + }, { "type": "schema_template", "name": "template_create_plugin", @@ -380,10 +410,6 @@ "key": "CreateSetDress", "label": "Create Set Dress" }, - { - "key": "CreateVrayProxy", - "label": "Create VRay Proxy" - }, { "key": "CreateVRayScene", "label": "Create VRay Scene" From 65e0273f1c93dee1bd359d740d3206b4c4472548 Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Tue, 28 Feb 2023 07:22:09 +0000 Subject: [PATCH 114/184] Update openpype/hosts/maya/plugins/publish/validate_vrayproxy.py --- openpype/hosts/maya/plugins/publish/validate_vrayproxy.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_vrayproxy.py b/openpype/hosts/maya/plugins/publish/validate_vrayproxy.py index 075eedc378..a106b970b4 100644 --- a/openpype/hosts/maya/plugins/publish/validate_vrayproxy.py +++ b/openpype/hosts/maya/plugins/publish/validate_vrayproxy.py @@ -12,8 +12,6 @@ class ValidateVrayProxy(pyblish.api.InstancePlugin): def process(self, instance): data = instance.data - self.log.info(data["family"]) - self.log.info(data["families"]) if not data["setMembers"]: raise KnownPublishError( From 4f94a4454ab254018c189c8aa53c21fb12b1392d Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Wed, 1 Mar 2023 16:02:12 +0000 Subject: [PATCH 115/184] Only run Maya specific code in Maya. --- .../plugins/publish/submit_publish_job.py | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 5325715e38..29f6f406df 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -933,15 +933,18 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): self.log.info(data.get("expectedFiles")) - additional_data = { - "renderProducts": instance.data["renderProducts"], - "colorspaceConfig": instance.data["colorspaceConfig"], - "display": instance.data["colorspaceDisplay"], - "view": instance.data["colorspaceView"], - "colorspaceTemplate": instance.data["colorspaceConfig"].replace( - str(context.data["anatomy"].roots["work"]), "{root[work]}" - ) - } + additional_data = {} + if pyblish.api.current_host() == "maya": + config = instance.data["colorspaceConfig"] + additional_data = { + "renderProducts": instance.data["renderProducts"], + "colorspaceConfig": instance.data["colorspaceConfig"], + "display": instance.data["colorspaceDisplay"], + "view": instance.data["colorspaceView"], + "colorspaceTemplate": config.replace( + str(context.data["anatomy"].roots["work"]), "{root[work]}" + ) + } if isinstance(data.get("expectedFiles")[0], dict): # we cannot attach AOVs to other subsets as we consider every From a3508b14122d6ab884a4303d636bdf37b35ca973 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 2 Mar 2023 08:18:47 +0000 Subject: [PATCH 116/184] Fix _get_representations --- openpype/modules/deadline/plugins/publish/submit_publish_job.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 29f6f406df..adfbcbded8 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -588,7 +588,7 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): self.log.debug("instances:{}".format(instances)) return instances - def _get_representations(self, instance, exp_files, additional_data): + def _get_representations(self, instance, exp_files): """Create representations for file sequences. This will return representations of expected files if they are not From 5bb204cacbfd0f9769f2f4112e50f6e65b4a7f6e Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 2 Mar 2023 11:30:42 +0100 Subject: [PATCH 117/184] nuke flip order --- openpype/hosts/nuke/plugins/publish/collect_writes.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/nuke/plugins/publish/collect_writes.py b/openpype/hosts/nuke/plugins/publish/collect_writes.py index 2b741426e6..f6acd24f99 100644 --- a/openpype/hosts/nuke/plugins/publish/collect_writes.py +++ b/openpype/hosts/nuke/plugins/publish/collect_writes.py @@ -67,6 +67,9 @@ class CollectNukeWrites(pyblish.api.InstancePlugin, write_file_path = nuke.filename(write_node) output_dir = os.path.dirname(write_file_path) + # get colorspace and add to version data + colorspace = napi.get_colorspace_from_node(write_node) + self.log.debug('output dir: {}'.format(output_dir)) if render_target == "frames": @@ -152,9 +155,6 @@ class CollectNukeWrites(pyblish.api.InstancePlugin, instance.data["farm"] = True self.log.info("Farm rendering ON ...") - # get colorspace and add to version data - colorspace = napi.get_colorspace_from_node(write_node) - # TODO: remove this when we have proper colorspace support version_data = { "colorspace": colorspace From f0997710818d3ca2f5ece87aed242ddf4c139a6c Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 2 Mar 2023 11:36:17 +0100 Subject: [PATCH 118/184] hound --- openpype/hosts/nuke/plugins/publish/collect_writes.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/hosts/nuke/plugins/publish/collect_writes.py b/openpype/hosts/nuke/plugins/publish/collect_writes.py index f6acd24f99..858fa79a4b 100644 --- a/openpype/hosts/nuke/plugins/publish/collect_writes.py +++ b/openpype/hosts/nuke/plugins/publish/collect_writes.py @@ -5,6 +5,7 @@ import pyblish.api from openpype.hosts.nuke import api as napi from openpype.pipeline import publish + class CollectNukeWrites(pyblish.api.InstancePlugin, publish.ColormanagedPyblishPluginMixin): """Collect all write nodes.""" From 91685e3d1fd3b43677fc33a537c3d93a5e8920cb Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 2 Mar 2023 11:06:47 +0000 Subject: [PATCH 119/184] Move AOV code to host agnostic. --- .../deadline/plugins/publish/submit_publish_job.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index adfbcbded8..31df4746ba 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -933,8 +933,10 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): self.log.info(data.get("expectedFiles")) - additional_data = {} - if pyblish.api.current_host() == "maya": + if isinstance(data.get("expectedFiles")[0], dict): + # we cannot attach AOVs to other subsets as we consider every + # AOV subset of its own. + config = instance.data["colorspaceConfig"] additional_data = { "renderProducts": instance.data["renderProducts"], @@ -946,10 +948,6 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): ) } - if isinstance(data.get("expectedFiles")[0], dict): - # we cannot attach AOVs to other subsets as we consider every - # AOV subset of its own. - if len(data.get("attachTo")) > 0: assert len(data.get("expectedFiles")[0].keys()) == 1, ( "attaching multiple AOVs or renderable cameras to " From 185623ff702a3ddc58038a4368e69e5b3ce4cc94 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Fri, 24 Feb 2023 16:21:00 +0000 Subject: [PATCH 120/184] Set frame range with handles on review instance. --- openpype/hosts/maya/api/lib.py | 32 +++++++++++++------ .../maya/plugins/create/create_review.py | 6 ++-- 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 4324d321dc..0d9733fcf7 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -4,7 +4,6 @@ import os import sys import platform import uuid -import math import re import json @@ -2064,13 +2063,8 @@ def set_scene_resolution(width, height, pixelAspect): cmds.setAttr("%s.pixelAspect" % control_node, pixelAspect) -def reset_frame_range(): - """Set frame range to current asset""" - - fps = convert_to_maya_fps( - float(legacy_io.Session.get("AVALON_FPS", 25)) - ) - set_scene_fps(fps) +def get_frame_range(): + """Get the current assets frame range and handles.""" # Set frame start/end project_name = legacy_io.active_project() @@ -2097,8 +2091,26 @@ def reset_frame_range(): if handle_end is None: handle_end = handles - frame_start -= int(handle_start) - frame_end += int(handle_end) + return { + "frameStart": frame_start, + "frameEnd": frame_end, + "handleStart": handle_start, + "handleEnd": handle_end + } + + +def reset_frame_range(): + """Set frame range to current asset""" + + fps = convert_to_maya_fps( + float(legacy_io.Session.get("AVALON_FPS", 25)) + ) + set_scene_fps(fps) + + frame_range = get_frame_range() + + frame_start = frame_range["frameStart"] - int(frame_range["handleStart"]) + frame_end = frame_range["frameEnd"] + int(frame_range["handleEnd"]) cmds.playbackOptions(minTime=frame_start) cmds.playbackOptions(maxTime=frame_end) diff --git a/openpype/hosts/maya/plugins/create/create_review.py b/openpype/hosts/maya/plugins/create/create_review.py index ba51ffa009..6e0bd2e4c3 100644 --- a/openpype/hosts/maya/plugins/create/create_review.py +++ b/openpype/hosts/maya/plugins/create/create_review.py @@ -28,13 +28,13 @@ class CreateReview(plugin.Creator): def __init__(self, *args, **kwargs): super(CreateReview, self).__init__(*args, **kwargs) + data = OrderedDict(**self.data) # get basic animation data : start / end / handles / steps - data = OrderedDict(**self.data) - animation_data = lib.collect_animation_data(fps=True) - for key, value in animation_data.items(): + for key, value in lib.get_frame_range().items(): data[key] = value + data["fps"] = lib.collect_animation_data(fps=True)["fps"] data["review_width"] = self.Width data["review_height"] = self.Height data["isolate"] = self.isolate From db0a3554b62afcfc03a8a33563e20a9a935bef22 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Fri, 24 Feb 2023 16:42:01 +0000 Subject: [PATCH 121/184] Validate frame range on instance to asset. - frame start - frame end - handle start - handle end --- .../plugins/publish/validate_frame_range.py | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_frame_range.py b/openpype/hosts/maya/plugins/publish/validate_frame_range.py index d86925184e..dbf856a30a 100644 --- a/openpype/hosts/maya/plugins/publish/validate_frame_range.py +++ b/openpype/hosts/maya/plugins/publish/validate_frame_range.py @@ -57,6 +57,10 @@ class ValidateFrameRange(pyblish.api.InstancePlugin): inst_start = int(instance.data.get("frameStartHandle")) inst_end = int(instance.data.get("frameEndHandle")) + inst_frame_start = int(instance.data.get("frameStart")) + inst_frame_end = int(instance.data.get("frameEnd")) + inst_handle_start = int(instance.data.get("handleStart")) + inst_handle_end = int(instance.data.get("handleEnd")) # basic sanity checks assert frame_start_handle <= frame_end_handle, ( @@ -69,7 +73,7 @@ class ValidateFrameRange(pyblish.api.InstancePlugin): if [ef for ef in self.exclude_families if instance.data["family"] in ef]: return - if(inst_start != frame_start_handle): + if (inst_start != frame_start_handle): errors.append("Instance start frame [ {} ] doesn't " "match the one set on instance [ {} ]: " "{}/{}/{}/{} (handle/start/end/handle)".format( @@ -78,7 +82,7 @@ class ValidateFrameRange(pyblish.api.InstancePlugin): handle_start, frame_start, frame_end, handle_end )) - if(inst_end != frame_end_handle): + if (inst_end != frame_end_handle): errors.append("Instance end frame [ {} ] doesn't " "match the one set on instance [ {} ]: " "{}/{}/{}/{} (handle/start/end/handle)".format( @@ -87,6 +91,19 @@ class ValidateFrameRange(pyblish.api.InstancePlugin): handle_start, frame_start, frame_end, handle_end )) + checks = { + "frame start": (frame_start, inst_frame_start), + "frame end": (frame_end, inst_frame_end), + "handle start": (handle_start, inst_handle_start), + "handle end": (handle_end, inst_handle_end) + } + for label, values in checks.items(): + if values[0] != values[1]: + errors.append( + "{} on instance ({}) does not match with the asset " + "({}).".format(label.title(), values[1], values[0]) + ) + for e in errors: self.log.error(e) From d33aa1cc7df5eb7ae73320693b3a9b00183b3d70 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 28 Feb 2023 07:26:50 +0000 Subject: [PATCH 122/184] Better error reports. --- openpype/hosts/maya/plugins/publish/validate_frame_range.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_frame_range.py b/openpype/hosts/maya/plugins/publish/validate_frame_range.py index dbf856a30a..59b06874b3 100644 --- a/openpype/hosts/maya/plugins/publish/validate_frame_range.py +++ b/openpype/hosts/maya/plugins/publish/validate_frame_range.py @@ -75,7 +75,7 @@ class ValidateFrameRange(pyblish.api.InstancePlugin): return if (inst_start != frame_start_handle): errors.append("Instance start frame [ {} ] doesn't " - "match the one set on instance [ {} ]: " + "match the one set on asset [ {} ]: " "{}/{}/{}/{} (handle/start/end/handle)".format( inst_start, frame_start_handle, @@ -84,7 +84,7 @@ class ValidateFrameRange(pyblish.api.InstancePlugin): if (inst_end != frame_end_handle): errors.append("Instance end frame [ {} ] doesn't " - "match the one set on instance [ {} ]: " + "match the one set on asset [ {} ]: " "{}/{}/{}/{} (handle/start/end/handle)".format( inst_end, frame_end_handle, From 7bbf5608711c865f3f50c82f1fbd34ddc679982a Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 28 Feb 2023 07:42:58 +0000 Subject: [PATCH 123/184] Backwards compatibility --- .../maya/plugins/create/create_review.py | 8 +++-- .../defaults/project_settings/maya.json | 13 +++++---- .../schemas/schema_maya_create.json | 29 ++++++++++++++++--- 3 files changed, 38 insertions(+), 12 deletions(-) diff --git a/openpype/hosts/maya/plugins/create/create_review.py b/openpype/hosts/maya/plugins/create/create_review.py index 6e0bd2e4c3..f1b626c06b 100644 --- a/openpype/hosts/maya/plugins/create/create_review.py +++ b/openpype/hosts/maya/plugins/create/create_review.py @@ -25,13 +25,17 @@ class CreateReview(plugin.Creator): "depth peeling", "alpha cut" ] + useMayaTimeline = True def __init__(self, *args, **kwargs): super(CreateReview, self).__init__(*args, **kwargs) data = OrderedDict(**self.data) - # get basic animation data : start / end / handles / steps - for key, value in lib.get_frame_range().items(): + # Option for using Maya or asset frame range in settings. + frame_range = lib.get_frame_range() + if self.useMayaTimeline: + frame_range = lib.collect_animation_data(fps=True) + for key, value in frame_range.items(): data[key] = value data["fps"] = lib.collect_animation_data(fps=True)["fps"] diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 90334a6644..dca0b95293 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -179,6 +179,13 @@ "Main" ] }, + "CreateReview": { + "enabled": true, + "defaults": [ + "Main" + ], + "useMayaTimeline": true + }, "CreateAss": { "enabled": true, "defaults": [ @@ -255,12 +262,6 @@ "Main" ] }, - "CreateReview": { - "enabled": true, - "defaults": [ - "Main" - ] - }, "CreateRig": { "enabled": true, "defaults": [ diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_create.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_create.json index 49503cce83..1598f90643 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_create.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_create.json @@ -240,6 +240,31 @@ } ] }, + { + "type": "dict", + "collapsible": true, + "key": "CreateReview", + "label": "Create Review", + "checkbox_key": "enabled", + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, + { + "type": "list", + "key": "defaults", + "label": "Default Subsets", + "object_type": "text" + }, + { + "type": "boolean", + "key": "useMayaTimeline", + "label": "Use Maya Timeline for Frame Range." + } + ] + }, { "type": "dict", "collapsible": true, @@ -398,10 +423,6 @@ "key": "CreateRenderSetup", "label": "Create Render Setup" }, - { - "key": "CreateReview", - "label": "Create Review" - }, { "key": "CreateRig", "label": "Create Rig" From db98f65b43517c1930ab3be11f56f0fa672e5c8d Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 23 Feb 2023 16:23:21 +0000 Subject: [PATCH 124/184] Fix publish pool and secondary. --- .../modules/deadline/plugins/publish/submit_publish_job.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index 31df4746ba..53c09ad22f 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -284,6 +284,9 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): args.append("--automatic-tests") # Generate the payload for Deadline submission + secondary_pool = ( + self.deadline_pool_secondary or instance.data.get("secondaryPool") + ) payload = { "JobInfo": { "Plugin": self.deadline_plugin, @@ -297,8 +300,8 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "Priority": priority, "Group": self.deadline_group, - "Pool": instance.data.get("primaryPool"), - "SecondaryPool": instance.data.get("secondaryPool"), + "Pool": self.deadline_pool or instance.data.get("primaryPool"), + "SecondaryPool": secondary_pool, # ensure the outputdirectory with correct slashes "OutputDirectory0": output_dir.replace("\\", "/") }, From d827ffa8fbabd6fb02dc03e123e22d69c5b87c24 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 23 Feb 2023 16:23:38 +0000 Subject: [PATCH 125/184] Use publish pool for tile jobs. --- .../deadline/plugins/publish/submit_maya_deadline.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 22b5c02296..15025e47f2 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -419,8 +419,13 @@ class MayaSubmitDeadline(abstract_submit_deadline.AbstractSubmitDeadline): assembly_job_info.Name += " - Tile Assembly Job" assembly_job_info.Frames = 1 assembly_job_info.MachineLimit = 1 - assembly_job_info.Priority = instance.data.get("tile_priority", - self.tile_priority) + assembly_job_info.Priority = instance.data.get( + "tile_priority", self.tile_priority + ) + + pool = instance.context.data["project_settings"]["deadline"] + pool = pool["publish"]["ProcessSubmittedJobOnFarm"]["deadline_pool"] + assembly_job_info.Pool = pool or instance.data.get("primaryPool", "") assembly_plugin_info = { "CleanupTiles": 1, From 9117a0d6329c5cf47aee68bf4fd2e57dac5fff6a Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 23 Feb 2023 16:39:10 +0000 Subject: [PATCH 126/184] Documentation --- website/docs/module_deadline.md | 34 +++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/website/docs/module_deadline.md b/website/docs/module_deadline.md index c96da91909..c4b179b98d 100644 --- a/website/docs/module_deadline.md +++ b/website/docs/module_deadline.md @@ -28,16 +28,16 @@ For [AWS Thinkbox Deadline](https://www.awsthinkbox.com/deadline) support you ne OpenPype integration for Deadline consists of two parts: - The `OpenPype` Deadline Plug-in -- A `GlobalJobPreLoad` Deadline Script (this gets triggered for each deadline job) +- A `GlobalJobPreLoad` Deadline Script (this gets triggered for each deadline job) The `GlobalJobPreLoad` handles populating render and publish jobs with proper environment variables using settings from the `OpenPype` Deadline Plug-in. -The `OpenPype` Deadline Plug-in must be configured to point to a valid OpenPype executable location. The executable need to be installed to +The `OpenPype` Deadline Plug-in must be configured to point to a valid OpenPype executable location. The executable need to be installed to destinations accessible by DL process. Check permissions (must be executable and accessible by Deadline process) - Enable `Tools > Super User Mode` in Deadline Monitor -- Go to `Tools > Configure Plugins...`, find `OpenPype` in the list on the left side, find location of OpenPype +- Go to `Tools > Configure Plugins...`, find `OpenPype` in the list on the left side, find location of OpenPype executable. It is recommended to use the `openpype_console` executable as it provides a bit more logging. - In case of multi OS farms, provide multiple locations, each Deadline Worker goes through the list and tries to find the first accessible @@ -45,12 +45,22 @@ executable. It is recommended to use the `openpype_console` executable as it pro ![Configure plugin](assets/deadline_configure_plugin.png) +### Pools + +The main pools can be configured at `project_settings/deadline/publish/CollectDeadlinePools/primary_pool`, which is applied to the rendering jobs. + +The dependent publishing job's pool uses `project_settings/deadline/publish/ProcessSubmittedJobOnFarm/deadline_pool`. If nothing is specified the pool will fallback to the main pool above. + +:::note maya tile rendering +The logic for publishing job pool assignment applies to tiling jobs. +::: + ## Troubleshooting #### Publishing jobs fail directly in DCCs - Double check that all previously described steps were finished -- Check that `deadlinewebservice` is running on DL server +- Check that `deadlinewebservice` is running on DL server - Check that user's machine has access to deadline server on configured port #### Jobs are failing on DL side @@ -61,40 +71,40 @@ Each publishing from OpenPype consists of 2 jobs, first one is rendering, second - Jobs are failing with `OpenPype executable was not found` error - Check if OpenPype is installed on the Worker handling this job and ensure `OpenPype` Deadline Plug-in is properly [configured](#configuration) + Check if OpenPype is installed on the Worker handling this job and ensure `OpenPype` Deadline Plug-in is properly [configured](#configuration) - Publishing job is failing with `ffmpeg not installed` error - + OpenPype executable has to have access to `ffmpeg` executable, check OpenPype `Setting > General` ![FFmpeg setting](assets/ffmpeg_path.png) - Both jobs finished successfully, but there is no review on Ftrack - Make sure that you correctly set published family to be send to Ftrack. + Make sure that you correctly set published family to be send to Ftrack. ![Ftrack Family](assets/ftrack/ftrack-collect-main.png) Example: I want send to Ftrack review of rendered images from Harmony : - `Host names`: "harmony" - - `Families`: "render" + - `Families`: "render" - `Add Ftrack Family` to "Enabled" - + Make sure that you actually configured to create review for published subset in `project_settings/ftrack/publish/CollectFtrackFamily` ![Ftrack Family](assets/deadline_review.png) - Example: I want to create review for all reviewable subsets in Harmony : + Example: I want to create review for all reviewable subsets in Harmony : - Add "harmony" as a new key an ".*" as a value. - Rendering jobs are stuck in 'Queued' state or failing Make sure that your Deadline is not limiting specific jobs to be run only on specific machines. (Eg. only some machines have installed particular application.) - + Check `project_settings/deadline` - + ![Deadline group](assets/deadline_group.png) Example: I have separated machines with "Harmony" installed into "harmony" group on Deadline. I want rendering jobs published from Harmony to run only on those machines. From 6f5ba9ce2a98aed1777df57731ae02409cc474a2 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 23 Feb 2023 16:42:02 +0000 Subject: [PATCH 127/184] Docs --- website/docs/module_deadline.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/module_deadline.md b/website/docs/module_deadline.md index c4b179b98d..ab1016788d 100644 --- a/website/docs/module_deadline.md +++ b/website/docs/module_deadline.md @@ -49,7 +49,7 @@ executable. It is recommended to use the `openpype_console` executable as it pro The main pools can be configured at `project_settings/deadline/publish/CollectDeadlinePools/primary_pool`, which is applied to the rendering jobs. -The dependent publishing job's pool uses `project_settings/deadline/publish/ProcessSubmittedJobOnFarm/deadline_pool`. If nothing is specified the pool will fallback to the main pool above. +The dependent publishing job's pool uses `project_settings/deadline/publish/ProcessSubmittedJobOnFarm/deadline_pool`. If nothing is specified the pool will fallback to the primary pool above. :::note maya tile rendering The logic for publishing job pool assignment applies to tiling jobs. From b61f1ed1df5a5a6dcab162ffe320a4093b5efd12 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 28 Feb 2023 08:17:29 +0000 Subject: [PATCH 128/184] Validate against zero polygon mesh. --- .../plugins/publish/validate_mesh_empty.py | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 openpype/hosts/maya/plugins/publish/validate_mesh_empty.py diff --git a/openpype/hosts/maya/plugins/publish/validate_mesh_empty.py b/openpype/hosts/maya/plugins/publish/validate_mesh_empty.py new file mode 100644 index 0000000000..848d66c4ae --- /dev/null +++ b/openpype/hosts/maya/plugins/publish/validate_mesh_empty.py @@ -0,0 +1,54 @@ +from maya import cmds + +import pyblish.api +import openpype.hosts.maya.api.action +from openpype.pipeline.publish import ( + RepairAction, + ValidateMeshOrder +) + + +class ValidateMeshEmpty(pyblish.api.InstancePlugin): + """Validate meshes have some vertices. + + Its possible to have meshes without any vertices. To replicate + this issue, delete all faces/polygons then all edges. + """ + + order = ValidateMeshOrder + hosts = ["maya"] + families = ["model"] + label = "Mesh Empty" + actions = [ + openpype.hosts.maya.api.action.SelectInvalidAction, RepairAction + ] + + @classmethod + def repair(cls, instance): + invalid = cls.get_invalid(instance) + for node in invalid: + cmds.delete(node) + + @classmethod + def get_invalid(cls, instance): + invalid = [] + + meshes = cmds.ls(instance, type="mesh", long=True) + for mesh in meshes: + num_vertices = cmds.polyEvaluate(mesh, vertex=True) + + if num_vertices == 0: + cls.log.warning( + "\"{}\" does not have any vertices.".format(mesh) + ) + invalid.append(mesh) + + return invalid + + def process(self, instance): + + invalid = self.get_invalid(instance) + if invalid: + raise RuntimeError( + "Meshes found in instance without any vertices: %s" % invalid + ) From 206bf55a4909e603e845d0ca5b3fc46896f1ca90 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 28 Feb 2023 08:18:16 +0000 Subject: [PATCH 129/184] Refactor `len_flattened` --- openpype/hosts/maya/api/lib.py | 31 +++++++++++++++++ .../plugins/publish/validate_mesh_has_uv.py | 32 +---------------- .../validate_mesh_vertices_have_edges.py | 34 +------------------ 3 files changed, 33 insertions(+), 64 deletions(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 0d9733fcf7..954576f02e 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -3574,3 +3574,34 @@ def get_color_management_output_transform(): if preferences["output_transform_enabled"]: colorspace = preferences["output_transform"] return colorspace + + +def len_flattened(components): + """Return the length of the list as if it was flattened. + + Maya will return consecutive components as a single entry + when requesting with `maya.cmds.ls` without the `flatten` + flag. Though enabling `flatten` on a large list (e.g. millions) + will result in a slow result. This command will return the amount + of entries in a non-flattened list by parsing the result with + regex. + + Args: + components (list): The non-flattened components. + + Returns: + int: The amount of entries. + + """ + assert isinstance(components, (list, tuple)) + n = 0 + + pattern = re.compile(r"\[(\d+):(\d+)\]") + for c in components: + match = pattern.search(c) + if match: + start, end = match.groups() + n += int(end) - int(start) + 1 + else: + n += 1 + return n diff --git a/openpype/hosts/maya/plugins/publish/validate_mesh_has_uv.py b/openpype/hosts/maya/plugins/publish/validate_mesh_has_uv.py index 0eece1014e..1775bd84c6 100644 --- a/openpype/hosts/maya/plugins/publish/validate_mesh_has_uv.py +++ b/openpype/hosts/maya/plugins/publish/validate_mesh_has_uv.py @@ -1,39 +1,9 @@ -import re - from maya import cmds import pyblish.api import openpype.hosts.maya.api.action from openpype.pipeline.publish import ValidateMeshOrder - - -def len_flattened(components): - """Return the length of the list as if it was flattened. - - Maya will return consecutive components as a single entry - when requesting with `maya.cmds.ls` without the `flatten` - flag. Though enabling `flatten` on a large list (e.g. millions) - will result in a slow result. This command will return the amount - of entries in a non-flattened list by parsing the result with - regex. - - Args: - components (list): The non-flattened components. - - Returns: - int: The amount of entries. - - """ - assert isinstance(components, (list, tuple)) - n = 0 - for c in components: - match = re.search("\[([0-9]+):([0-9]+)\]", c) - if match: - start, end = match.groups() - n += int(end) - int(start) + 1 - else: - n += 1 - return n +from openpype.hosts.maya.api.lib import len_flattened class ValidateMeshHasUVs(pyblish.api.InstancePlugin): diff --git a/openpype/hosts/maya/plugins/publish/validate_mesh_vertices_have_edges.py b/openpype/hosts/maya/plugins/publish/validate_mesh_vertices_have_edges.py index 9ac7735501..51e1ddfc7f 100644 --- a/openpype/hosts/maya/plugins/publish/validate_mesh_vertices_have_edges.py +++ b/openpype/hosts/maya/plugins/publish/validate_mesh_vertices_have_edges.py @@ -1,5 +1,3 @@ -import re - from maya import cmds import pyblish.api @@ -8,37 +6,7 @@ from openpype.pipeline.publish import ( RepairAction, ValidateMeshOrder, ) - - -def len_flattened(components): - """Return the length of the list as if it was flattened. - - Maya will return consecutive components as a single entry - when requesting with `maya.cmds.ls` without the `flatten` - flag. Though enabling `flatten` on a large list (e.g. millions) - will result in a slow result. This command will return the amount - of entries in a non-flattened list by parsing the result with - regex. - - Args: - components (list): The non-flattened components. - - Returns: - int: The amount of entries. - - """ - assert isinstance(components, (list, tuple)) - n = 0 - - pattern = re.compile(r"\[(\d+):(\d+)\]") - for c in components: - match = pattern.search(c) - if match: - start, end = match.groups() - n += int(end) - int(start) + 1 - else: - n += 1 - return n +from openpype.hosts.maya.api.lib import len_flattened class ValidateMeshVerticesHaveEdges(pyblish.api.InstancePlugin): From 63177ca7e935cd87e716229831eaf1f456825e66 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 28 Feb 2023 08:18:50 +0000 Subject: [PATCH 130/184] Only mesh empty validator should fail. --- .../plugins/publish/validate_mesh_has_uv.py | 9 +++++++++ .../publish/validate_mesh_non_zero_edge.py | 20 +++++++++++++++++-- .../validate_mesh_vertices_have_edges.py | 7 +++++++ 3 files changed, 34 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_mesh_has_uv.py b/openpype/hosts/maya/plugins/publish/validate_mesh_has_uv.py index 1775bd84c6..b7836b3e92 100644 --- a/openpype/hosts/maya/plugins/publish/validate_mesh_has_uv.py +++ b/openpype/hosts/maya/plugins/publish/validate_mesh_has_uv.py @@ -27,6 +27,15 @@ class ValidateMeshHasUVs(pyblish.api.InstancePlugin): invalid = [] for node in cmds.ls(instance, type='mesh'): + num_vertices = cmds.polyEvaluate(node, vertex=True) + + if num_vertices == 0: + cls.log.warning( + "Skipping \"{}\", cause it does not have any " + "vertices.".format(node) + ) + continue + uv = cmds.polyEvaluate(node, uv=True) if uv == 0: diff --git a/openpype/hosts/maya/plugins/publish/validate_mesh_non_zero_edge.py b/openpype/hosts/maya/plugins/publish/validate_mesh_non_zero_edge.py index 78e844d201..b49ba85648 100644 --- a/openpype/hosts/maya/plugins/publish/validate_mesh_non_zero_edge.py +++ b/openpype/hosts/maya/plugins/publish/validate_mesh_non_zero_edge.py @@ -28,7 +28,10 @@ class ValidateMeshNonZeroEdgeLength(pyblish.api.InstancePlugin): @classmethod def get_invalid(cls, instance): """Return the invalid edges. - Also see: http://help.autodesk.com/view/MAYAUL/2015/ENU/?guid=Mesh__Cleanup + + Also see: + + http://help.autodesk.com/view/MAYAUL/2015/ENU/?guid=Mesh__Cleanup """ @@ -36,8 +39,21 @@ class ValidateMeshNonZeroEdgeLength(pyblish.api.InstancePlugin): if not meshes: return list() + valid_meshes = [] + for mesh in meshes: + num_vertices = cmds.polyEvaluate(mesh, vertex=True) + + if num_vertices == 0: + cls.log.warning( + "Skipping \"{}\", cause it does not have any " + "vertices.".format(mesh) + ) + continue + + valid_meshes.append(mesh) + # Get all edges - edges = ['{0}.e[*]'.format(node) for node in meshes] + edges = ['{0}.e[*]'.format(node) for node in valid_meshes] # Filter by constraint on edge length invalid = lib.polyConstraint(edges, diff --git a/openpype/hosts/maya/plugins/publish/validate_mesh_vertices_have_edges.py b/openpype/hosts/maya/plugins/publish/validate_mesh_vertices_have_edges.py index 51e1ddfc7f..d885158004 100644 --- a/openpype/hosts/maya/plugins/publish/validate_mesh_vertices_have_edges.py +++ b/openpype/hosts/maya/plugins/publish/validate_mesh_vertices_have_edges.py @@ -55,6 +55,13 @@ class ValidateMeshVerticesHaveEdges(pyblish.api.InstancePlugin): for mesh in meshes: num_vertices = cmds.polyEvaluate(mesh, vertex=True) + if num_vertices == 0: + cls.log.warning( + "Skipping \"{}\", cause it does not have any " + "vertices.".format(mesh) + ) + continue + # Vertices from all edges edges = "%s.e[*]" % mesh vertices = cmds.polyListComponentConversion(edges, toVertex=True) From ec7392433cc0386feaccca6c0c3adea47281f67c Mon Sep 17 00:00:00 2001 From: Toke Jepsen Date: Thu, 2 Mar 2023 17:24:41 +0000 Subject: [PATCH 131/184] Maya: Validate missing instance attributes (#4559) * Validate missing instance attributes. * Plugins docs. --- .../maya/plugins/publish/collect_instances.py | 1 + .../publish/validate_instance_attributes.py | 60 +++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 openpype/hosts/maya/plugins/publish/validate_instance_attributes.py diff --git a/openpype/hosts/maya/plugins/publish/collect_instances.py b/openpype/hosts/maya/plugins/publish/collect_instances.py index 6c6819f0a2..c594626569 100644 --- a/openpype/hosts/maya/plugins/publish/collect_instances.py +++ b/openpype/hosts/maya/plugins/publish/collect_instances.py @@ -137,6 +137,7 @@ class CollectInstances(pyblish.api.ContextPlugin): # Create the instance instance = context.create_instance(objset) instance[:] = members_hierarchy + instance.data["objset"] = objset # Store the exact members of the object set instance.data["setMembers"] = members diff --git a/openpype/hosts/maya/plugins/publish/validate_instance_attributes.py b/openpype/hosts/maya/plugins/publish/validate_instance_attributes.py new file mode 100644 index 0000000000..f870c9f8c4 --- /dev/null +++ b/openpype/hosts/maya/plugins/publish/validate_instance_attributes.py @@ -0,0 +1,60 @@ +from maya import cmds + +import pyblish.api +from openpype.pipeline.publish import ( + ValidateContentsOrder, PublishValidationError, RepairAction +) +from openpype.pipeline import discover_legacy_creator_plugins +from openpype.hosts.maya.api.lib import imprint + + +class ValidateInstanceAttributes(pyblish.api.InstancePlugin): + """Validate Instance Attributes. + + New attributes can be introduced as new features come in. Old instances + will need to be updated with these attributes for the documentation to make + sense, and users do not have to recreate the instances. + """ + + order = ValidateContentsOrder + hosts = ["maya"] + families = ["*"] + label = "Instance Attributes" + plugins_by_family = { + p.family: p for p in discover_legacy_creator_plugins() + } + actions = [RepairAction] + + @classmethod + def get_missing_attributes(self, instance): + plugin = self.plugins_by_family[instance.data["family"]] + subset = instance.data["subset"] + asset = instance.data["asset"] + objset = instance.data["objset"] + + missing_attributes = {} + for key, value in plugin(subset, asset).data.items(): + if not cmds.objExists("{}.{}".format(objset, key)): + missing_attributes[key] = value + + return missing_attributes + + def process(self, instance): + objset = instance.data.get("objset") + if objset is None: + self.log.debug( + "Skipping {} because no objectset found.".format(instance) + ) + return + + missing_attributes = self.get_missing_attributes(instance) + if missing_attributes: + raise PublishValidationError( + "Missing attributes on {}:\n{}".format( + objset, missing_attributes + ) + ) + + @classmethod + def repair(cls, instance): + imprint(instance.data["objset"], cls.get_missing_attributes(instance)) From f3baace6682bebfbbbae273b54c4aaf38477f4a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Fri, 3 Mar 2023 13:17:46 +0100 Subject: [PATCH 132/184] Update openpype/pipeline/publish/publish_plugins.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fabià Serra Arrizabalaga --- openpype/pipeline/publish/publish_plugins.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/publish/publish_plugins.py b/openpype/pipeline/publish/publish_plugins.py index 0142919e76..7da61fec5e 100644 --- a/openpype/pipeline/publish/publish_plugins.py +++ b/openpype/pipeline/publish/publish_plugins.py @@ -298,7 +298,7 @@ class ColormanagedPyblishPluginMixin(object): It also contains a method, set_representation_colorspace, which sets colorspace data to the representation. The allowed file extensions are listed in the allowed_ext variable. - he method first checks if the file extension is in + The method first checks if the file extension is in the list of allowed extensions. If it is, it then gets the colorspace settings from the host context and gets a matching colorspace from rules. Finally, it infuses this From 0c517a12a618076fdd3fc043fb1ce80d7e1f3327 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Fri, 3 Mar 2023 14:07:20 +0100 Subject: [PATCH 133/184] Nuke: fix the order of plugin to be after anatomy data collector also convert anatomy data with deepcopy --- openpype/hosts/nuke/plugins/publish/collect_writes.py | 2 +- openpype/pipeline/publish/publish_plugins.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/nuke/plugins/publish/collect_writes.py b/openpype/hosts/nuke/plugins/publish/collect_writes.py index 858fa79a4b..304b3d8f32 100644 --- a/openpype/hosts/nuke/plugins/publish/collect_writes.py +++ b/openpype/hosts/nuke/plugins/publish/collect_writes.py @@ -10,7 +10,7 @@ class CollectNukeWrites(pyblish.api.InstancePlugin, publish.ColormanagedPyblishPluginMixin): """Collect all write nodes.""" - order = pyblish.api.CollectorOrder - 0.48 + order = pyblish.api.CollectorOrder + 0.0021 label = "Collect Writes" hosts = ["nuke", "nukeassist"] families = ["render", "prerender", "image"] diff --git a/openpype/pipeline/publish/publish_plugins.py b/openpype/pipeline/publish/publish_plugins.py index 7da61fec5e..2df98221ba 100644 --- a/openpype/pipeline/publish/publish_plugins.py +++ b/openpype/pipeline/publish/publish_plugins.py @@ -1,3 +1,4 @@ +from copy import deepcopy import inspect from abc import ABCMeta from pprint import pformat @@ -323,7 +324,7 @@ class ColormanagedPyblishPluginMixin(object): project_name = context.data["projectName"] host_name = context.data["hostName"] - anatomy_data = context.data["anatomyData"] + anatomy_data = deepcopy(context.data["anatomyData"]) project_settings_ = context.data["project_settings"] config_data = get_imageio_config( From f2311c686638dd1e9d646333b5dd482129024869 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Tue, 28 Feb 2023 09:12:51 +0000 Subject: [PATCH 134/184] Fixes - missing platform extraction from settings - map function should be list comprehension - code cosmetics --- .../maya/plugins/publish/validate_model_name.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/validate_model_name.py b/openpype/hosts/maya/plugins/publish/validate_model_name.py index 2dec9ba267..0e7adc640f 100644 --- a/openpype/hosts/maya/plugins/publish/validate_model_name.py +++ b/openpype/hosts/maya/plugins/publish/validate_model_name.py @@ -2,9 +2,11 @@ """Validate model nodes names.""" import os import re -from maya import cmds -import pyblish.api +import platform +from maya import cmds + +import pyblish.api from openpype.pipeline import legacy_io from openpype.pipeline.publish import ValidateContentsOrder import openpype.hosts.maya.api.action @@ -44,7 +46,7 @@ class ValidateModelName(pyblish.api.InstancePlugin): if not cmds.ls(child, transforms=True): return False return True - except: + except Exception: return False invalid = [] @@ -94,9 +96,10 @@ class ValidateModelName(pyblish.api.InstancePlugin): # load shader list file as utf-8 shaders = [] if not use_db: - if cls.material_file: - if os.path.isfile(cls.material_file): - shader_file = open(cls.material_file, "r") + material_file = cls.material_file[platform.system().lower()] + if material_file: + if os.path.isfile(material_file): + shader_file = open(material_file, "r") shaders = shader_file.readlines() shader_file.close() else: @@ -113,7 +116,7 @@ class ValidateModelName(pyblish.api.InstancePlugin): shader_file.close() # strip line endings from list - shaders = map(lambda s: s.rstrip(), shaders) + shaders = [s.rstrip() for s in shaders if s.rstrip()] # compile regex for testing names regex = cls.regex From 6f0cd7fc2f04f8ff13e7a756f932ab7068c29629 Mon Sep 17 00:00:00 2001 From: Jacob Danell Date: Tue, 31 Jan 2023 17:30:27 +0100 Subject: [PATCH 135/184] add visualParent None to Shots and Assets Without this, tray-publisher throws an error and stops working. --- openpype/modules/kitsu/utils/update_op_with_zou.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index 2d14b38bc4..5af3a61e81 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -389,6 +389,7 @@ def sync_project_from_kitsu(dbcon: AvalonMongoDB, project: dict): "data": { "root_of": r, "tasks": {}, + "visualParent": None, }, } for r in ["Assets", "Shots"] From 76273b7376c5b65423beb3a195c4a6376f0453be Mon Sep 17 00:00:00 2001 From: Jacob Danell Date: Wed, 1 Feb 2023 13:38:22 +0100 Subject: [PATCH 136/184] lowercase URL path to match in Kitsu Without this fix the menu-dropdown wouldn't show the correct current page in Kitsu --- openpype/modules/kitsu/actions/launcher_show_in_kitsu.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/kitsu/actions/launcher_show_in_kitsu.py b/openpype/modules/kitsu/actions/launcher_show_in_kitsu.py index c95079e042..4793d60fc3 100644 --- a/openpype/modules/kitsu/actions/launcher_show_in_kitsu.py +++ b/openpype/modules/kitsu/actions/launcher_show_in_kitsu.py @@ -100,7 +100,7 @@ class ShowInKitsu(LauncherAction): kitsu_url = kitsu_url[:-len("/api")] sub_url = f"/productions/{project_id}" - asset_type_url = "Shots" if asset_type in shots_url else "Assets" + asset_type_url = "shots" if asset_type in shots_url else "assets" if task_id: # Go to task page From 9795e7c9105777f0ae0c4d20376d5204b23b1b00 Mon Sep 17 00:00:00 2001 From: Jacob Danell Date: Wed, 1 Feb 2023 15:52:56 +0100 Subject: [PATCH 137/184] Populate items with correct data from Kitsu or project defaults If data doesn't exist in Kitsu, use Projects default data. --- .../modules/kitsu/utils/update_op_with_zou.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index 5af3a61e81..554d90f7a9 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -69,6 +69,7 @@ def set_op_project(dbcon: AvalonMongoDB, project_id: str): def update_op_assets( dbcon: AvalonMongoDB, + gazu_project: dict, project_doc: dict, entities_list: List[dict], asset_doc_ids: Dict[str, dict], @@ -119,21 +120,20 @@ def update_op_assets( # because of zou's legacy design frames_duration = int(item.get("nb_frames", 0)) except (TypeError, ValueError): - frames_duration = 0 + frames_duration = None # Frame out, fallback on frame_in + duration or project's value or 1001 frame_out = item_data.pop("frame_out", None) if not frame_out: - frame_out = frame_in + frames_duration - try: - frame_out = int(frame_out) - except (TypeError, ValueError): - frame_out = 1001 + if frames_duration: + frame_out = frame_in + frames_duration + else: + frame_out = project_doc["data"].get("frameEnd", 1001) item_data["frameEnd"] = frame_out # Fps, fallback to project's value or default value (25.0) try: - fps = float(item_data.get("fps", project_doc["data"].get("fps"))) + fps = float(item_data.get("fps")) except (TypeError, ValueError): - fps = 25.0 + fps = float(gazu_project.get("fps", project_doc["data"].get("fps", 25))) item_data["fps"] = fps # Tasks @@ -424,7 +424,7 @@ def sync_project_from_kitsu(dbcon: AvalonMongoDB, project: dict): [ UpdateOne({"_id": id}, update) for id, update in update_op_assets( - dbcon, project_doc, all_entities, zou_ids_and_asset_docs + dbcon, project, project_doc, all_entities, zou_ids_and_asset_docs ) ] ) From a005d108292821e9a062da8f3cef32f113ad8eca Mon Sep 17 00:00:00 2001 From: Jacob Danell Date: Wed, 1 Feb 2023 16:07:45 +0100 Subject: [PATCH 138/184] Add Resolution and Pixel Aspect to each item Without this info eg. Fusion would throw an error and stop working. --- openpype/modules/kitsu/utils/update_op_with_zou.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index 554d90f7a9..cbf7afb413 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -135,6 +135,17 @@ def update_op_assets( except (TypeError, ValueError): fps = float(gazu_project.get("fps", project_doc["data"].get("fps", 25))) item_data["fps"] = fps + # Resolution, fall back to project default + match_res = re.match(r"(\d+)x(\d+)", item_data.get("resolution", gazu_project.get("resolution"))) + if match_res: + item_data["resolutionWidth"] = int(match_res.group(1)) + item_data["resolutionHeight"] = int(match_res.group(2)) + else: + item_data["resolutionWidth"] = project_doc["data"].get("resolutionWidth") + item_data["resolutionHeight"] = project_doc["data"].get("resolutionHeight") + # Properties that doesn't fully exist in Kitsu. Guessing the property name + # Pixel Aspect Ratio + item_data["pixelAspect"] = item_data.get("pixel_aspect", project_doc["data"].get("pixelAspect")) # Tasks tasks_list = [] From 3b4cdb13db5c3ae89b5c3e3bd6a1b0e73731fe3d Mon Sep 17 00:00:00 2001 From: Jacob Danell Date: Wed, 1 Feb 2023 20:13:00 +0100 Subject: [PATCH 139/184] Add parents key to assets and shots Without this you can't open the project in Tray Publisher --- openpype/modules/kitsu/utils/update_op_with_zou.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index cbf7afb413..1b191ccb1e 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -401,6 +401,7 @@ def sync_project_from_kitsu(dbcon: AvalonMongoDB, project: dict): "root_of": r, "tasks": {}, "visualParent": None, + "parents": [], }, } for r in ["Assets", "Shots"] From 5dee2b8ff6b8eedbaaf68af164b316874f413059 Mon Sep 17 00:00:00 2001 From: Jacob Danell Date: Thu, 2 Feb 2023 17:15:55 +0100 Subject: [PATCH 140/184] Add missing attributes to each asset Without them, Tray Publisher errors out. --- openpype/modules/kitsu/utils/update_op_with_zou.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index 1b191ccb1e..d88198eace 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -146,6 +146,14 @@ def update_op_assets( # Properties that doesn't fully exist in Kitsu. Guessing the property name # Pixel Aspect Ratio item_data["pixelAspect"] = item_data.get("pixel_aspect", project_doc["data"].get("pixelAspect")) + # Handle Start + item_data["handleStart"] = item_data.get("handle_start", project_doc["data"].get("handleStart")) + # Handle End + item_data["handleEnd"] = item_data.get("handle_end", project_doc["data"].get("handleEnd")) + # Clip In + item_data["clipIn"] = item_data.get("clip_in", project_doc["data"].get("clipIn")) + # Clip Out + item_data["clipOut"] = item_data.get("clip_out", project_doc["data"].get("clipOut")) # Tasks tasks_list = [] From 785825751a0e5dacdc6e7bf2825e8360456d906c Mon Sep 17 00:00:00 2001 From: Jacob Danell Date: Thu, 2 Feb 2023 17:18:16 +0100 Subject: [PATCH 141/184] Add render to families to make sure a task exists to process --- openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py | 2 +- .../modules/kitsu/plugins/publish/integrate_kitsu_review.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py index ea98e0b7cc..b801e0e4d4 100644 --- a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py +++ b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py @@ -8,7 +8,7 @@ class IntegrateKitsuNote(pyblish.api.ContextPlugin): order = pyblish.api.IntegratorOrder label = "Kitsu Note and Status" - # families = ["kitsu"] + families = ["render", "kitsu"] set_status_note = False note_status_shortname = "wfa" diff --git a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py index e5e6439439..9e9eaadc27 100644 --- a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py +++ b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py @@ -8,7 +8,7 @@ class IntegrateKitsuReview(pyblish.api.InstancePlugin): order = pyblish.api.IntegratorOrder + 0.01 label = "Kitsu Review" - # families = ["kitsu"] + families = ["render", "kitsu"] optional = True def process(self, instance): From 27c8a1f36099347f777c538d72c67eb5dcb9dc80 Mon Sep 17 00:00:00 2001 From: Jacob Danell Date: Thu, 2 Feb 2023 19:31:06 +0100 Subject: [PATCH 142/184] Fixed so correct review file gets uploaded to Kitsu --- .../modules/kitsu/plugins/publish/integrate_kitsu_review.py | 2 +- .../projects_schema/schemas/schema_representation_tags.json | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py index 9e9eaadc27..94897b2553 100644 --- a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py +++ b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py @@ -27,7 +27,7 @@ class IntegrateKitsuReview(pyblish.api.InstancePlugin): # Add review representations as preview of comment for representation in instance.data.get("representations", []): # Skip if not tagged as review - if "review" not in representation.get("tags", []): + if "kitsureview" not in representation.get("tags", []): continue review_path = representation.get("published_path") diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_representation_tags.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_representation_tags.json index a4b28f47bc..7046952eef 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_representation_tags.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_representation_tags.json @@ -16,6 +16,9 @@ { "shotgridreview": "Add review to Shotgrid" }, + { + "kitsureview": "Add review to Kitsu" + }, { "delete": "Delete output" }, From 72270005edc887e8e708a8d078f8a3d7ac1ec287 Mon Sep 17 00:00:00 2001 From: Jacob Danell Date: Thu, 2 Feb 2023 23:33:57 +0100 Subject: [PATCH 143/184] Update Kitsu plugin to work without assetEntry --- .../plugins/publish/collect_kitsu_entities.py | 83 +++++++++++-------- 1 file changed, 49 insertions(+), 34 deletions(-) diff --git a/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py b/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py index c9e78b59eb..38c67898ef 100644 --- a/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py +++ b/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py @@ -4,6 +4,11 @@ import os import gazu import pyblish.api +from openpype.client import ( + get_projects, + get_project, + get_assets, +) class CollectKitsuEntities(pyblish.api.ContextPlugin): """Collect Kitsu entities according to the current context""" @@ -12,20 +17,34 @@ class CollectKitsuEntities(pyblish.api.ContextPlugin): label = "Kitsu entities" def process(self, context): + + # Get all needed names + project_name = context.data.get("projectName") + asset_name = context.data.get("asset") + task_name = context.data.get("task") + # If asset and task name doesn't exist in context, look in instance + for instance in context: + if not asset_name: + asset_name = instance.data.get("asset") + if not task_name: + task_name = instance.data.get("task") - asset_data = context.data["assetEntity"]["data"] - zou_asset_data = asset_data.get("zou") + # Get all assets of the local project + asset_docs = { + asset_doc["name"]: asset_doc + for asset_doc in get_assets(project_name) + } + + # Get asset object + asset = asset_docs.get(asset_name) + if not asset: + raise AssertionError("{} not found in DB".format(asset_name)) + + zou_asset_data = asset["data"].get("zou") if not zou_asset_data: raise AssertionError("Zou asset data not found in OpenPype!") self.log.debug("Collected zou asset data: {}".format(zou_asset_data)) - zou_task_data = asset_data["tasks"][os.environ["AVALON_TASK"]].get( - "zou" - ) - if not zou_task_data: - self.log.warning("Zou task data not found in OpenPype!") - self.log.debug("Collected zou task data: {}".format(zou_task_data)) - kitsu_project = gazu.project.get_project(zou_asset_data["project_id"]) if not kitsu_project: raise AssertionError("Project not found in kitsu!") @@ -37,37 +56,33 @@ class CollectKitsuEntities(pyblish.api.ContextPlugin): kitsu_entity = gazu.shot.get_shot(zou_asset_data["id"]) else: kitsu_entity = gazu.asset.get_asset(zou_asset_data["id"]) - if not kitsu_entity: raise AssertionError("{} not found in kitsu!".format(entity_type)) - context.data["kitsu_entity"] = kitsu_entity self.log.debug( "Collect kitsu {}: {}".format(entity_type, kitsu_entity) ) - if zou_task_data: - kitsu_task = gazu.task.get_task(zou_task_data["id"]) - if not kitsu_task: - raise AssertionError("Task not found in kitsu!") - context.data["kitsu_task"] = kitsu_task - self.log.debug("Collect kitsu task: {}".format(kitsu_task)) - - else: - kitsu_task_type = gazu.task.get_task_type_by_name( - os.environ["AVALON_TASK"] - ) - if not kitsu_task_type: - raise AssertionError( - "Task type {} not found in Kitsu!".format( - os.environ["AVALON_TASK"] + if task_name: + zou_task_data = asset["data"]["tasks"][task_name].get("zou") + self.log.debug("Collected zou task data: {}".format(zou_task_data)) + if zou_task_data: + kitsu_task = gazu.task.get_task(zou_task_data["id"]) + if not kitsu_task: + raise AssertionError("Task not found in kitsu!") + context.data["kitsu_task"] = kitsu_task + self.log.debug("Collect kitsu task: {}".format(kitsu_task)) + else: + kitsu_task_type = gazu.task.get_task_type_by_name(task_name) + if not kitsu_task_type: + raise AssertionError( + "Task type {} not found in Kitsu!".format(task_name) ) - ) - kitsu_task = gazu.task.get_task_by_name( - kitsu_entity, kitsu_task_type - ) - if not kitsu_task: - raise AssertionError("Task not found in kitsu!") - context.data["kitsu_task"] = kitsu_task - self.log.debug("Collect kitsu task: {}".format(kitsu_task)) + kitsu_task = gazu.task.get_task_by_name( + kitsu_entity, kitsu_task_type + ) + if not kitsu_task: + raise AssertionError("Task not found in kitsu!") + context.data["kitsu_task"] = kitsu_task + self.log.debug("Collect kitsu task: {}".format(kitsu_task)) \ No newline at end of file From 62c111e9486e532255d213d0508f00281f40e86c Mon Sep 17 00:00:00 2001 From: Jacob Danell Date: Fri, 3 Feb 2023 00:55:01 +0100 Subject: [PATCH 144/184] Add kitsureview to default burnin tags --- openpype/settings/defaults/project_settings/global.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index a5e2d25a88..aad17d54da 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -139,7 +139,8 @@ "ext": "mp4", "tags": [ "burnin", - "ftrackreview" + "ftrackreview", + "kitsureview" ], "burnins": [], "ffmpeg_args": { From dc2b519fd5689389a482c4772289af8ba9154176 Mon Sep 17 00:00:00 2001 From: Jacob Danell Date: Fri, 3 Feb 2023 01:07:48 +0100 Subject: [PATCH 145/184] Fixed hound-bots comments --- .../plugins/publish/collect_kitsu_entities.py | 25 ++++++++--------- .../modules/kitsu/utils/update_op_with_zou.py | 27 ++++++++++++------- 2 files changed, 29 insertions(+), 23 deletions(-) diff --git a/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py b/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py index 38c67898ef..92c8c1823d 100644 --- a/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py +++ b/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py @@ -5,11 +5,10 @@ import gazu import pyblish.api from openpype.client import ( - get_projects, - get_project, get_assets, ) + class CollectKitsuEntities(pyblish.api.ContextPlugin): """Collect Kitsu entities according to the current context""" @@ -17,7 +16,7 @@ class CollectKitsuEntities(pyblish.api.ContextPlugin): label = "Kitsu entities" def process(self, context): - + # Get all needed names project_name = context.data.get("projectName") asset_name = context.data.get("asset") @@ -38,18 +37,18 @@ class CollectKitsuEntities(pyblish.api.ContextPlugin): # Get asset object asset = asset_docs.get(asset_name) if not asset: - raise AssertionError("{} not found in DB".format(asset_name)) + raise AssertionError(f"{asset_name} not found in DB") zou_asset_data = asset["data"].get("zou") if not zou_asset_data: raise AssertionError("Zou asset data not found in OpenPype!") - self.log.debug("Collected zou asset data: {}".format(zou_asset_data)) + self.log.debug(f"Collected zou asset data: {zou_asset_data}") kitsu_project = gazu.project.get_project(zou_asset_data["project_id"]) if not kitsu_project: raise AssertionError("Project not found in kitsu!") context.data["kitsu_project"] = kitsu_project - self.log.debug("Collect kitsu project: {}".format(kitsu_project)) + self.log.debug(f"Collect kitsu project: {kitsu_project}") entity_type = zou_asset_data["type"] if entity_type == "Shot": @@ -57,26 +56,24 @@ class CollectKitsuEntities(pyblish.api.ContextPlugin): else: kitsu_entity = gazu.asset.get_asset(zou_asset_data["id"]) if not kitsu_entity: - raise AssertionError("{} not found in kitsu!".format(entity_type)) + raise AssertionError(f"{entity_type} not found in kitsu!") context.data["kitsu_entity"] = kitsu_entity - self.log.debug( - "Collect kitsu {}: {}".format(entity_type, kitsu_entity) - ) + self.log.debug(f"Collect kitsu {entity_type}: {kitsu_entity}") if task_name: zou_task_data = asset["data"]["tasks"][task_name].get("zou") - self.log.debug("Collected zou task data: {}".format(zou_task_data)) + self.log.debug(f"Collected zou task data: {zou_task_data}") if zou_task_data: kitsu_task = gazu.task.get_task(zou_task_data["id"]) if not kitsu_task: raise AssertionError("Task not found in kitsu!") context.data["kitsu_task"] = kitsu_task - self.log.debug("Collect kitsu task: {}".format(kitsu_task)) + self.log.debug(f"Collect kitsu task: {kitsu_task}") else: kitsu_task_type = gazu.task.get_task_type_by_name(task_name) if not kitsu_task_type: raise AssertionError( - "Task type {} not found in Kitsu!".format(task_name) + f"Task type {task_name} not found in Kitsu!" ) kitsu_task = gazu.task.get_task_by_name( @@ -85,4 +82,4 @@ class CollectKitsuEntities(pyblish.api.ContextPlugin): if not kitsu_task: raise AssertionError("Task not found in kitsu!") context.data["kitsu_task"] = kitsu_task - self.log.debug("Collect kitsu task: {}".format(kitsu_task)) \ No newline at end of file + self.log.debug(f"Collect kitsu task: {kitsu_task}") diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index d88198eace..a079fd5529 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -133,27 +133,36 @@ def update_op_assets( try: fps = float(item_data.get("fps")) except (TypeError, ValueError): - fps = float(gazu_project.get("fps", project_doc["data"].get("fps", 25))) + fps = float(gazu_project.get( + "fps", project_doc["data"].get("fps", 25))) item_data["fps"] = fps # Resolution, fall back to project default - match_res = re.match(r"(\d+)x(\d+)", item_data.get("resolution", gazu_project.get("resolution"))) + match_res = re.match( + r"(\d+)x(\d+)", item_data.get("resolution", gazu_project.get("resolution"))) if match_res: item_data["resolutionWidth"] = int(match_res.group(1)) item_data["resolutionHeight"] = int(match_res.group(2)) else: - item_data["resolutionWidth"] = project_doc["data"].get("resolutionWidth") - item_data["resolutionHeight"] = project_doc["data"].get("resolutionHeight") + item_data["resolutionWidth"] = project_doc["data"].get( + "resolutionWidth") + item_data["resolutionHeight"] = project_doc["data"].get( + "resolutionHeight") # Properties that doesn't fully exist in Kitsu. Guessing the property name # Pixel Aspect Ratio - item_data["pixelAspect"] = item_data.get("pixel_aspect", project_doc["data"].get("pixelAspect")) + item_data["pixelAspect"] = item_data.get( + "pixel_aspect", project_doc["data"].get("pixelAspect")) # Handle Start - item_data["handleStart"] = item_data.get("handle_start", project_doc["data"].get("handleStart")) + item_data["handleStart"] = item_data.get( + "handle_start", project_doc["data"].get("handleStart")) # Handle End - item_data["handleEnd"] = item_data.get("handle_end", project_doc["data"].get("handleEnd")) + item_data["handleEnd"] = item_data.get( + "handle_end", project_doc["data"].get("handleEnd")) # Clip In - item_data["clipIn"] = item_data.get("clip_in", project_doc["data"].get("clipIn")) + item_data["clipIn"] = item_data.get( + "clip_in", project_doc["data"].get("clipIn")) # Clip Out - item_data["clipOut"] = item_data.get("clip_out", project_doc["data"].get("clipOut")) + item_data["clipOut"] = item_data.get( + "clip_out", project_doc["data"].get("clipOut")) # Tasks tasks_list = [] From bb61d43c27286bda8b319d5e49da8166c1472ae8 Mon Sep 17 00:00:00 2001 From: Jacob Danell Date: Fri, 3 Feb 2023 11:52:37 +0100 Subject: [PATCH 146/184] Shortened length of line 141 --- openpype/modules/kitsu/utils/update_op_with_zou.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index a079fd5529..15e88947a1 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -138,7 +138,9 @@ def update_op_assets( item_data["fps"] = fps # Resolution, fall back to project default match_res = re.match( - r"(\d+)x(\d+)", item_data.get("resolution", gazu_project.get("resolution"))) + r"(\d+)x(\d+)", + item_data.get("resolution", gazu_project.get("resolution")) + ) if match_res: item_data["resolutionWidth"] = int(match_res.group(1)) item_data["resolutionHeight"] = int(match_res.group(2)) From 47caa760305dcb46639d94ae7c345d8682e724fc Mon Sep 17 00:00:00 2001 From: Jacob Danell Date: Fri, 10 Feb 2023 13:32:03 +0100 Subject: [PATCH 147/184] Updated Kitsu Sync module to fully work with all events Manually tried all events, added logging for events and cleaned up the code some. --- openpype/modules/kitsu/utils/sync_service.py | 318 +++++++++++++----- .../modules/kitsu/utils/update_op_with_zou.py | 17 +- 2 files changed, 248 insertions(+), 87 deletions(-) diff --git a/openpype/modules/kitsu/utils/sync_service.py b/openpype/modules/kitsu/utils/sync_service.py index 237746bea0..e371c1a9bb 100644 --- a/openpype/modules/kitsu/utils/sync_service.py +++ b/openpype/modules/kitsu/utils/sync_service.py @@ -1,3 +1,15 @@ +""" +Bugs: + * Error when adding task type to anything that isn't Shot or Assets + * Assets don't get added under an episode if TV show + * Assets added under Main Pack throws error. Can't get the name of Main Pack? + +Features ToDo: + * Select in settings what types you wish to sync + * Print what's updated on entity-update + * Add listener for Edits +""" + import os import threading @@ -5,6 +17,7 @@ import gazu from openpype.client import get_project, get_assets, get_asset_by_name from openpype.pipeline import AvalonMongoDB +from openpype.lib import Logger from .credentials import validate_credentials from .update_op_with_zou import ( create_op_asset, @@ -14,6 +27,8 @@ from .update_op_with_zou import ( update_op_assets, ) +log = Logger.get_logger(__name__) + class Listener: """Host Kitsu listener.""" @@ -33,7 +48,7 @@ class Listener: self.dbcon = AvalonMongoDB() self.dbcon.install() - gazu.client.set_host(os.environ["KITSU_SERVER"]) + gazu.client.set_host(os.environ['KITSU_SERVER']) # Authenticate if not validate_credentials(login, password): @@ -42,7 +57,7 @@ class Listener: ) gazu.set_event_host( - os.environ["KITSU_SERVER"].replace("api", "socket.io") + os.environ['KITSU_SERVER'].replace("api", "socket.io") ) self.event_client = gazu.events.init() @@ -103,6 +118,8 @@ class Listener: ) def start(self): + """Start listening for events.""" + log.info("Listening to Kitsu events...") gazu.events.run_client(self.event_client) # == Project == @@ -112,36 +129,49 @@ class Listener: # Use update process to avoid duplicating code self._update_project(data) + # Print message + ## Happens in write_project_to_op() + def _update_project(self, data): """Update project into OP DB.""" # Get project entity - project = gazu.project.get_project(data["project_id"]) - project_name = project["name"] + project = gazu.project.get_project(data['project_id']) update_project = write_project_to_op(project, self.dbcon) # Write into DB if update_project: - self.dbcon.Session["AVALON_PROJECT"] = project_name + self.dbcon.Session['AVALON_PROJECT'] = get_kitsu_project_name( + data['project_id']) self.dbcon.bulk_write([update_project]) def _delete_project(self, data): """Delete project.""" - project_name = get_kitsu_project_name(data["project_id"]) + collections = self.dbcon.database.list_collection_names() + project_name = None + for collection in collections: + post = self.dbcon.database[collection].find_one( + {"data.zou_id": data['project_id']}) + if post: + project_name = post['name'] + break - # Delete project collection - self.dbcon.database[project_name].drop() + if project_name: + # Delete project collection + self.dbcon.database[project_name].drop() + + # Print message + log.info(f"Project deleted: {project_name}") # == Asset == - def _new_asset(self, data): """Create new asset into OP DB.""" # Get project entity - set_op_project(self.dbcon, data["project_id"]) + set_op_project(self.dbcon, data['project_id']) - # Get gazu entity - asset = gazu.asset.get_asset(data["asset_id"]) + # Get asset entity + asset = gazu.asset.get_asset(data['asset_id']) # Insert doc in DB self.dbcon.insert_one(create_op_asset(asset)) @@ -149,27 +179,43 @@ class Listener: # Update self._update_asset(data) + # Print message + episode = None + ep_id = asset['episode_id'] + if ep_id and ep_id != "": + episode = gazu.asset.get_episode(ep_id) + + msg = "Asset created: " + msg = msg + f"{asset['project_name']} - " + if episode is not None: + msg = msg + f"{episode['name']}_" + msg = msg + f"{asset['asset_type_name']}_" + msg = msg + f"{asset['name']}" + log.info(msg) + def _update_asset(self, data): """Update asset into OP DB.""" - set_op_project(self.dbcon, data["project_id"]) + set_op_project(self.dbcon, data['project_id']) project_name = self.dbcon.active_project() project_doc = get_project(project_name) # Get gazu entity - asset = gazu.asset.get_asset(data["asset_id"]) + asset = gazu.asset.get_asset(data['asset_id']) # Find asset doc # Query all assets of the local project zou_ids_and_asset_docs = { - asset_doc["data"]["zou"]["id"]: asset_doc + asset_doc['data']['zou']['id']: asset_doc for asset_doc in get_assets(project_name) - if asset_doc["data"].get("zou", {}).get("id") + if asset_doc['data'].get("zou", {}).get("id") } - zou_ids_and_asset_docs[asset["project_id"]] = project_doc + zou_ids_and_asset_docs[asset['project_id']] = project_doc + gazu_project = gazu.project.get_project(asset['project_id']) # Update update_op_result = update_op_assets( - self.dbcon, project_doc, [asset], zou_ids_and_asset_docs + self.dbcon, gazu_project, project_doc, + [asset], zou_ids_and_asset_docs ) if update_op_result: asset_doc_id, asset_update = update_op_result[0] @@ -177,21 +223,37 @@ class Listener: def _delete_asset(self, data): """Delete asset of OP DB.""" - set_op_project(self.dbcon, data["project_id"]) + set_op_project(self.dbcon, data['project_id']) - # Delete - self.dbcon.delete_one( - {"type": "asset", "data.zou.id": data["asset_id"]} - ) + asset = self.dbcon.find_one({"data.zou.id": data['asset_id']}) + if asset: + # Delete + self.dbcon.delete_one( + {"type": "asset", "data.zou.id": data['asset_id']} + ) + + # Print message + episode = None + ep_id = asset['data']['zou']['episode_id'] + if ep_id and ep_id != "": + episode = gazu.asset.get_episode(ep_id) + + msg = "Asset deleted: " + msg = msg + f"{asset['data']['zou']['project_name']} - " + if episode is not None: + msg = msg + f"{episode['name']}_" + msg = msg + f"{asset['data']['zou']['asset_type_name']}_" + msg = msg + f"'{asset['name']}" + log.info(msg) # == Episode == def _new_episode(self, data): """Create new episode into OP DB.""" # Get project entity - set_op_project(self.dbcon, data["project_id"]) + set_op_project(self.dbcon, data['project_id']) # Get gazu entity - episode = gazu.shot.get_episode(data["episode_id"]) + episode = gazu.shot.get_episode(data['episode_id']) # Insert doc in DB self.dbcon.insert_one(create_op_asset(episode)) @@ -199,27 +261,34 @@ class Listener: # Update self._update_episode(data) + # Print message + msg = "Episode created: " + msg = msg + f"{episode['project_name']} - " + msg = msg + f"{episode['name']}" + def _update_episode(self, data): """Update episode into OP DB.""" - set_op_project(self.dbcon, data["project_id"]) + set_op_project(self.dbcon, data['project_id']) project_name = self.dbcon.active_project() project_doc = get_project(project_name) # Get gazu entity - episode = gazu.shot.get_episode(data["episode_id"]) + episode = gazu.shot.get_episode(data['episode_id']) # Find asset doc # Query all assets of the local project zou_ids_and_asset_docs = { - asset_doc["data"]["zou"]["id"]: asset_doc + asset_doc['data']['zou']['id']: asset_doc for asset_doc in get_assets(project_name) - if asset_doc["data"].get("zou", {}).get("id") + if asset_doc['data'].get("zou", {}).get("id") } - zou_ids_and_asset_docs[episode["project_id"]] = project_doc + zou_ids_and_asset_docs[episode['project_id']] = project_doc + gazu_project = gazu.project.get_project(episode['project_id']) # Update update_op_result = update_op_assets( - self.dbcon, project_doc, [episode], zou_ids_and_asset_docs + self.dbcon, gazu_project, project_doc, [ + episode], zou_ids_and_asset_docs ) if update_op_result: asset_doc_id, asset_update = update_op_result[0] @@ -227,22 +296,31 @@ class Listener: def _delete_episode(self, data): """Delete shot of OP DB.""" - set_op_project(self.dbcon, data["project_id"]) - print("delete episode") # TODO check bugfix + set_op_project(self.dbcon, data['project_id']) - # Delete - self.dbcon.delete_one( - {"type": "asset", "data.zou.id": data["episode_id"]} - ) + episode = self.dbcon.find_one({"data.zou.id": data['episode_id']}) + if episode: + # Delete + self.dbcon.delete_one( + {"type": "asset", "data.zou.id": data['episode_id']} + ) + + # Print message + project = gazu.project.get_project( + episode['data']['zou']['project_id']) + + msg = "Episode deleted: " + msg = msg + f"{project['name']} - " + msg = msg + f"{episode['name']}" # == Sequence == def _new_sequence(self, data): """Create new sequnce into OP DB.""" # Get project entity - set_op_project(self.dbcon, data["project_id"]) + set_op_project(self.dbcon, data['project_id']) # Get gazu entity - sequence = gazu.shot.get_sequence(data["sequence_id"]) + sequence = gazu.shot.get_sequence(data['sequence_id']) # Insert doc in DB self.dbcon.insert_one(create_op_asset(sequence)) @@ -250,27 +328,43 @@ class Listener: # Update self._update_sequence(data) + # Print message + + episode = None + ep_id = sequence['episode_id'] + if ep_id and ep_id != "": + episode = gazu.asset.get_episode(ep_id) + + msg = "Sequence created: " + msg = msg + f"{sequence['project_name']} - " + if episode is not None: + msg = msg + f"{episode['name']}_" + msg = msg + f"{sequence['name']}" + log.info(msg) + def _update_sequence(self, data): """Update sequence into OP DB.""" - set_op_project(self.dbcon, data["project_id"]) + set_op_project(self.dbcon, data['project_id']) project_name = self.dbcon.active_project() project_doc = get_project(project_name) # Get gazu entity - sequence = gazu.shot.get_sequence(data["sequence_id"]) + sequence = gazu.shot.get_sequence(data['sequence_id']) # Find asset doc # Query all assets of the local project zou_ids_and_asset_docs = { - asset_doc["data"]["zou"]["id"]: asset_doc + asset_doc['data']['zou']['id']: asset_doc for asset_doc in get_assets(project_name) - if asset_doc["data"].get("zou", {}).get("id") + if asset_doc['data'].get("zou", {}).get("id") } - zou_ids_and_asset_docs[sequence["project_id"]] = project_doc + zou_ids_and_asset_docs[sequence['project_id']] = project_doc + gazu_project = gazu.project.get_project(sequence['project_id']) # Update update_op_result = update_op_assets( - self.dbcon, project_doc, [sequence], zou_ids_and_asset_docs + self.dbcon, gazu_project, project_doc, + [sequence], zou_ids_and_asset_docs ) if update_op_result: asset_doc_id, asset_update = update_op_result[0] @@ -278,22 +372,30 @@ class Listener: def _delete_sequence(self, data): """Delete sequence of OP DB.""" - set_op_project(self.dbcon, data["project_id"]) - print("delete sequence") # TODO check bugfix + set_op_project(self.dbcon, data['project_id']) + sequence = self.dbcon.find_one({"data.zou.id": data['sequence_id']}) + if sequence: + # Delete + self.dbcon.delete_one( + {"type": "asset", "data.zou.id": data['sequence_id']} + ) - # Delete - self.dbcon.delete_one( - {"type": "asset", "data.zou.id": data["sequence_id"]} - ) + # Print message + gazu_project = gazu.project.get_project( + sequence['data']['zou']['project_id']) + msg = f"Sequence deleted: " + msg = msg + f"{gazu_project['name']} - " + msg = msg + f"{sequence['name']}" + log.info(msg) # == Shot == def _new_shot(self, data): """Create new shot into OP DB.""" # Get project entity - set_op_project(self.dbcon, data["project_id"]) + set_op_project(self.dbcon, data['project_id']) # Get gazu entity - shot = gazu.shot.get_shot(data["shot_id"]) + shot = gazu.shot.get_shot(data['shot_id']) # Insert doc in DB self.dbcon.insert_one(create_op_asset(shot)) @@ -301,89 +403,151 @@ class Listener: # Update self._update_shot(data) + # Print message + episode = None + if shot['episode_id'] and shot['episode_id'] != "": + episode = gazu.asset.get_episode(shot['episode_id']) + + msg = "Shot created: " + msg = msg + f"{shot['project_name']} - " + if episode is not None: + msg = msg + f"{episode['name']}_" + msg = msg + f"{shot['sequence_name']}_" + msg = msg + f"{shot['name']}" + log.info(msg) + def _update_shot(self, data): """Update shot into OP DB.""" - set_op_project(self.dbcon, data["project_id"]) + set_op_project(self.dbcon, data['project_id']) project_name = self.dbcon.active_project() project_doc = get_project(project_name) # Get gazu entity - shot = gazu.shot.get_shot(data["shot_id"]) + shot = gazu.shot.get_shot(data['shot_id']) # Find asset doc # Query all assets of the local project zou_ids_and_asset_docs = { - asset_doc["data"]["zou"]["id"]: asset_doc + asset_doc['data']['zou']['id']: asset_doc for asset_doc in get_assets(project_name) - if asset_doc["data"].get("zou", {}).get("id") + if asset_doc['data'].get("zou", {}).get("id") } - zou_ids_and_asset_docs[shot["project_id"]] = project_doc + zou_ids_and_asset_docs[shot['project_id']] = project_doc + gazu_project = gazu.project.get_project(shot['project_id']) # Update update_op_result = update_op_assets( - self.dbcon, project_doc, [shot], zou_ids_and_asset_docs + self.dbcon, gazu_project, project_doc, + [shot], zou_ids_and_asset_docs ) + if update_op_result: asset_doc_id, asset_update = update_op_result[0] self.dbcon.update_one({"_id": asset_doc_id}, asset_update) def _delete_shot(self, data): """Delete shot of OP DB.""" - set_op_project(self.dbcon, data["project_id"]) + set_op_project(self.dbcon, data['project_id']) + shot = self.dbcon.find_one({"data.zou.id": data['shot_id']}) - # Delete - self.dbcon.delete_one( - {"type": "asset", "data.zou.id": data["shot_id"]} - ) + if shot: + # Delete + self.dbcon.delete_one( + {"type": "asset", "data.zou.id": data['shot_id']} + ) + + # Print message + gazu_project = gazu.project.get_project( + shot['data']['zou']['project_id']) + + msg = "Shot deleted: " + msg = msg + f"{gazu_project['name']} - " + msg = msg + f"{shot['name']}" + log.info(msg) # == Task == def _new_task(self, data): """Create new task into OP DB.""" # Get project entity - set_op_project(self.dbcon, data["project_id"]) + set_op_project(self.dbcon, data['project_id']) project_name = self.dbcon.active_project() # Get gazu entity - task = gazu.task.get_task(data["task_id"]) + task = gazu.task.get_task(data['task_id']) # Find asset doc - parent_name = task["entity"]["name"] + episode = None + ep_id = task['episode_id'] + if ep_id and ep_id != "": + episode = gazu.asset.get_episode(ep_id) + + parent_name = "" + if episode is not None: + parent_name = episode['name'] + "_" + parent_name = parent_name + \ + task['sequence']['name'] + "_" + task['entity']['name'] asset_doc = get_asset_by_name(project_name, parent_name) # Update asset tasks with new one - asset_tasks = asset_doc["data"].get("tasks") - task_type_name = task["task_type"]["name"] + asset_tasks = asset_doc['data'].get("tasks") + task_type_name = task['task_type']['name'] asset_tasks[task_type_name] = {"type": task_type_name, "zou": task} self.dbcon.update_one( - {"_id": asset_doc["_id"]}, {"$set": {"data.tasks": asset_tasks}} + {"_id": asset_doc['_id']}, {"$set": {"data.tasks": asset_tasks}} ) + # Print message + msg = "Task created: " + msg = msg + f"{task['project']['name']} - " + if episode is not None: + msg = msg + f"{episode['name']}_" + msg = msg + f"{task['sequence']['name']}_" + msg = msg + f"{task['entity']['name']} - " + msg = msg + f"{task['task_type']['name']}" + log.info(msg) + def _update_task(self, data): """Update task into OP DB.""" # TODO is it necessary? - pass def _delete_task(self, data): """Delete task of OP DB.""" - set_op_project(self.dbcon, data["project_id"]) + set_op_project(self.dbcon, data['project_id']) project_name = self.dbcon.active_project() # Find asset doc asset_docs = list(get_assets(project_name)) for doc in asset_docs: # Match task - for name, task in doc["data"]["tasks"].items(): - if task.get("zou") and data["task_id"] == task["zou"]["id"]: + for name, task in doc['data']['tasks'].items(): + if task.get("zou") and data['task_id'] == task['zou']['id']: # Pop task - asset_tasks = doc["data"].get("tasks", {}) + asset_tasks = doc['data'].get("tasks", {}) asset_tasks.pop(name) # Delete task in DB self.dbcon.update_one( - {"_id": doc["_id"]}, + {"_id": doc['_id']}, {"$set": {"data.tasks": asset_tasks}}, ) + + # Print message + shot = gazu.shot.get_shot(task['zou']['entity_id']) + + episode = None + ep_id = shot['episode_id'] + if ep_id and ep_id != "": + episode = gazu.asset.get_episode(ep_id) + + msg = "Task deleted: " + msg = msg + f"{shot['project_name']} - " + if episode is not None: + msg = msg + f"{episode['name']}_" + msg = msg + f"{shot['sequence_name']}_" + msg = msg + f"{shot['name']} - " + msg = msg + f"{task['type']}" + log.info(msg) return @@ -396,7 +560,7 @@ def start_listeners(login: str, password: str): """ # Refresh token every week def refresh_token_every_week(): - print("Refreshing token...") + log.info("Refreshing token...") gazu.refresh_token() threading.Timer(7 * 3600 * 24, refresh_token_every_week).start() diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index 15e88947a1..0a59724393 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -5,10 +5,6 @@ from typing import Dict, List from pymongo import DeleteOne, UpdateOne import gazu -from gazu.task import ( - all_tasks_for_asset, - all_tasks_for_shot, -) from openpype.client import ( get_project, @@ -18,7 +14,6 @@ from openpype.client import ( create_project, ) from openpype.pipeline import AvalonMongoDB -from openpype.settings import get_project_settings from openpype.modules.kitsu.utils.credentials import validate_credentials from openpype.lib import Logger @@ -85,8 +80,10 @@ def update_op_assets( Returns: List[Dict[str, dict]]: List of (doc_id, update_dict) tuples """ + if not project_doc: + return + project_name = project_doc["name"] - project_module_settings = get_project_settings(project_name)["kitsu"] assets_with_update = [] for item in entities_list: @@ -170,9 +167,9 @@ def update_op_assets( tasks_list = [] item_type = item["type"] if item_type == "Asset": - tasks_list = all_tasks_for_asset(item) + tasks_list = gazu.task.all_tasks_for_asset(item) elif item_type == "Shot": - tasks_list = all_tasks_for_shot(item) + tasks_list = gazu.task.all_tasks_for_shot(item) item_data["tasks"] = { t["task_type_name"]: {"type": t["task_type_name"], "zou": t} for t in tasks_list @@ -207,7 +204,7 @@ def update_op_assets( # Root parent folder if exist visual_parent_doc_id = ( - asset_doc_ids[parent_zou_id]["_id"] if parent_zou_id else None + asset_doc_ids[parent_zou_id].get("_id") if parent_zou_id else None ) if visual_parent_doc_id is None: # Find root folder doc ("Assets" or "Shots") @@ -282,7 +279,7 @@ def write_project_to_op(project: dict, dbcon: AvalonMongoDB) -> UpdateOne: project_name = project["name"] project_doc = get_project(project_name) if not project_doc: - log.info(f"Creating project '{project_name}'") + log.info(f"Project created: {project_name}") project_doc = create_project(project_name, project_name) # Project data and tasks From c44c5c9ddbada1264691a32c78cf1765b0b9bce1 Mon Sep 17 00:00:00 2001 From: Jacob Danell Date: Fri, 10 Feb 2023 14:08:33 +0100 Subject: [PATCH 148/184] Get asset and task names from instance only --- .../kitsu/plugins/publish/collect_kitsu_entities.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py b/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py index 92c8c1823d..5499b1782a 100644 --- a/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py +++ b/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py @@ -19,14 +19,12 @@ class CollectKitsuEntities(pyblish.api.ContextPlugin): # Get all needed names project_name = context.data.get("projectName") - asset_name = context.data.get("asset") - task_name = context.data.get("task") + asset_name = None + task_name = None # If asset and task name doesn't exist in context, look in instance for instance in context: - if not asset_name: - asset_name = instance.data.get("asset") - if not task_name: - task_name = instance.data.get("task") + asset_name = instance.data.get("asset") + task_name = instance.data.get("task") # Get all assets of the local project asset_docs = { From 4bc67437b61326540a2a62f141af5169296ee051 Mon Sep 17 00:00:00 2001 From: Jacob Danell Date: Fri, 10 Feb 2023 14:36:17 +0100 Subject: [PATCH 149/184] Fixed line too long and too many '#' for comments --- openpype/modules/kitsu/utils/sync_service.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/modules/kitsu/utils/sync_service.py b/openpype/modules/kitsu/utils/sync_service.py index e371c1a9bb..00c8c4eafa 100644 --- a/openpype/modules/kitsu/utils/sync_service.py +++ b/openpype/modules/kitsu/utils/sync_service.py @@ -2,7 +2,7 @@ Bugs: * Error when adding task type to anything that isn't Shot or Assets * Assets don't get added under an episode if TV show - * Assets added under Main Pack throws error. Can't get the name of Main Pack? + * Assets added under Main Pack throws error. No Main Pack name in dict Features ToDo: * Select in settings what types you wish to sync @@ -130,7 +130,7 @@ class Listener: self._update_project(data) # Print message - ## Happens in write_project_to_op() + # - Happens in write_project_to_op() def _update_project(self, data): """Update project into OP DB.""" From 8652dab47898e30cbdeb9c3ed9ced2f841c4ec4c Mon Sep 17 00:00:00 2001 From: Jacob Danell Date: Thu, 16 Feb 2023 13:14:14 +0100 Subject: [PATCH 150/184] Fixed 'instance' code --- .../plugins/publish/collect_kitsu_entities.py | 104 ++++++++---------- .../plugins/publish/integrate_kitsu_note.py | 48 ++++---- .../plugins/publish/integrate_kitsu_review.py | 6 +- 3 files changed, 73 insertions(+), 85 deletions(-) diff --git a/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py b/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py index 5499b1782a..fe6854218d 100644 --- a/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py +++ b/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py @@ -1,13 +1,7 @@ # -*- coding: utf-8 -*- -import os - import gazu import pyblish.api -from openpype.client import ( - get_assets, -) - class CollectKitsuEntities(pyblish.api.ContextPlugin): """Collect Kitsu entities according to the current context""" @@ -17,67 +11,61 @@ class CollectKitsuEntities(pyblish.api.ContextPlugin): def process(self, context): - # Get all needed names - project_name = context.data.get("projectName") - asset_name = None - task_name = None - # If asset and task name doesn't exist in context, look in instance + kitsu_project = None + + kitsu_entities_by_id = {} for instance in context: - asset_name = instance.data.get("asset") + asset_doc = instance.data.get("assetEntity") task_name = instance.data.get("task") + if not asset_doc: + continue - # Get all assets of the local project - asset_docs = { - asset_doc["name"]: asset_doc - for asset_doc in get_assets(project_name) - } + zou_asset_data = asset_doc["data"].get("zou") + if not zou_asset_data: + raise AssertionError("Zou asset data not found in OpenPype!") - # Get asset object - asset = asset_docs.get(asset_name) - if not asset: - raise AssertionError(f"{asset_name} not found in DB") + if kitsu_project is None: + kitsu_project = gazu.project.get_project( + zou_asset_data["project_id"]) + if not kitsu_project: + raise AssertionError("Project not found in kitsu!") - zou_asset_data = asset["data"].get("zou") - if not zou_asset_data: - raise AssertionError("Zou asset data not found in OpenPype!") - self.log.debug(f"Collected zou asset data: {zou_asset_data}") + entity_type = zou_asset_data["type"] + kitsu_id = zou_asset_data["id"] + kitsu_entity = kitsu_entities_by_id.get(kitsu_id) + if not kitsu_entity: + if entity_type == "Shot": + kitsu_entity = gazu.shot.get_shot(kitsu_id) + else: + kitsu_entity = gazu.asset.get_asset(kitsu_id) + kitsu_entities_by_id[kitsu_id] = kitsu_entity - kitsu_project = gazu.project.get_project(zou_asset_data["project_id"]) - if not kitsu_project: - raise AssertionError("Project not found in kitsu!") - context.data["kitsu_project"] = kitsu_project - self.log.debug(f"Collect kitsu project: {kitsu_project}") + if not kitsu_entity: + raise AssertionError( + "{} not found in kitsu!".format(entity_type)) + instance.data["kitsu_entity"] = kitsu_entity - entity_type = zou_asset_data["type"] - if entity_type == "Shot": - kitsu_entity = gazu.shot.get_shot(zou_asset_data["id"]) - else: - kitsu_entity = gazu.asset.get_asset(zou_asset_data["id"]) - if not kitsu_entity: - raise AssertionError(f"{entity_type} not found in kitsu!") - context.data["kitsu_entity"] = kitsu_entity - self.log.debug(f"Collect kitsu {entity_type}: {kitsu_entity}") - - if task_name: - zou_task_data = asset["data"]["tasks"][task_name].get("zou") - self.log.debug(f"Collected zou task data: {zou_task_data}") - if zou_task_data: - kitsu_task = gazu.task.get_task(zou_task_data["id"]) - if not kitsu_task: - raise AssertionError("Task not found in kitsu!") - context.data["kitsu_task"] = kitsu_task - self.log.debug(f"Collect kitsu task: {kitsu_task}") - else: + if not task_name: + continue + zou_task_data = asset_doc["data"]["tasks"][task_name].get("zou") + self.log.debug( + "Collected zou task data: {}".format(zou_task_data)) + if not zou_task_data: kitsu_task_type = gazu.task.get_task_type_by_name(task_name) if not kitsu_task_type: raise AssertionError( - f"Task type {task_name} not found in Kitsu!" + "Task type {} not found in Kitsu!".format(task_name) ) + continue + kitsu_task_id = zou_task_data["id"] + kitsu_task = kitsu_entities_by_id.get(kitsu_task_id) + if not kitsu_task: + kitsu_task = gazu.task.get_task(zou_task_data["id"]) + kitsu_entities_by_id[kitsu_task_id] = kitsu_task - kitsu_task = gazu.task.get_task_by_name( - kitsu_entity, kitsu_task_type - ) - if not kitsu_task: - raise AssertionError("Task not found in kitsu!") - context.data["kitsu_task"] = kitsu_task - self.log.debug(f"Collect kitsu task: {kitsu_task}") + if not kitsu_task: + raise AssertionError("Task not found in kitsu!") + instance.data["kitsu_task"] = kitsu_task + self.log.debug("Collect kitsu task: {}".format(kitsu_task)) + + context.data["kitsu_project"] = kitsu_project diff --git a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py index b801e0e4d4..aeec2481e0 100644 --- a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py +++ b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py @@ -21,30 +21,32 @@ class IntegrateKitsuNote(pyblish.api.ContextPlugin): self.log.debug("Comment is `{}`".format(publish_comment)) - # Get note status, by default uses the task status for the note - # if it is not specified in the configuration - note_status = context.data["kitsu_task"]["task_status_id"] - if self.set_status_note: - kitsu_status = gazu.task.get_task_status_by_short_name( - self.note_status_shortname - ) - if kitsu_status: - note_status = kitsu_status - self.log.info("Note Kitsu status: {}".format(note_status)) - else: - self.log.info( - "Cannot find {} status. The status will not be " - "changed!".format(self.note_status_shortname) + for instance in context: + + # Get note status, by default uses the task status for the note + # if it is not specified in the configuration + note_status = instance.data["kitsu_task"]["task_status"]["id"] + + if self.set_status_note: + kitsu_status = gazu.task.get_task_status_by_short_name( + self.note_status_shortname ) + if kitsu_status: + note_status = kitsu_status + self.log.info("Note Kitsu status: {}".format(note_status)) + else: + self.log.info( + "Cannot find {} status. The status will not be " + "changed!".format(self.note_status_shortname) + ) - # Add comment to kitsu task - self.log.debug( - "Add new note in taks id {}".format( - context.data["kitsu_task"]["id"] + # Add comment to kitsu task + task = instance.data["kitsu_task"]["id"] + self.log.debug( + "Add new note in taks id {}".format(task) + ) + kitsu_comment = gazu.task.add_comment( + task, note_status, comment=publish_comment ) - ) - kitsu_comment = gazu.task.add_comment( - context.data["kitsu_task"], note_status, comment=publish_comment - ) - context.data["kitsu_comment"] = kitsu_comment + instance.data["kitsu_comment"] = kitsu_comment diff --git a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py index 94897b2553..d8f6cb7ac8 100644 --- a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py +++ b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py @@ -13,9 +13,8 @@ class IntegrateKitsuReview(pyblish.api.InstancePlugin): def process(self, instance): - context = instance.context - task = context.data["kitsu_task"] - comment = context.data.get("kitsu_comment") + task = instance.data["kitsu_task"]["id"] + comment = instance.data["kitsu_comment"]["id"] # Check comment has been created if not comment: @@ -29,7 +28,6 @@ class IntegrateKitsuReview(pyblish.api.InstancePlugin): # Skip if not tagged as review if "kitsureview" not in representation.get("tags", []): continue - review_path = representation.get("published_path") self.log.debug("Found review at: {}".format(review_path)) From 290709705e2811c1a87a5f18b859b5880bd09772 Mon Sep 17 00:00:00 2001 From: Jacob Danell Date: Thu, 16 Feb 2023 15:02:31 +0100 Subject: [PATCH 151/184] Changed logging from f-string to .format() --- .../modules/kitsu/actions/launcher_show_in_kitsu.py | 12 ++++++------ openpype/modules/kitsu/utils/credentials.py | 2 +- openpype/modules/kitsu/utils/sync_service.py | 2 +- openpype/modules/kitsu/utils/update_op_with_zou.py | 4 ++-- openpype/modules/kitsu/utils/update_zou_with_op.py | 4 ++-- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/openpype/modules/kitsu/actions/launcher_show_in_kitsu.py b/openpype/modules/kitsu/actions/launcher_show_in_kitsu.py index 4793d60fc3..e3676afc4c 100644 --- a/openpype/modules/kitsu/actions/launcher_show_in_kitsu.py +++ b/openpype/modules/kitsu/actions/launcher_show_in_kitsu.py @@ -32,12 +32,12 @@ class ShowInKitsu(LauncherAction): project = get_project(project_name=project_name, fields=["data.zou_id"]) if not project: - raise RuntimeError(f"Project {project_name} not found.") + raise RuntimeError("Project {} not found.".format(project_name)) project_zou_id = project["data"].get("zou_id") if not project_zou_id: - raise RuntimeError(f"Project {project_name} has no " - f"connected kitsu id.") + raise RuntimeError( + "Project {} has no connected kitsu id.".format(project_name)) asset_zou_name = None asset_zou_id = None @@ -48,7 +48,7 @@ class ShowInKitsu(LauncherAction): asset_zou_name = asset_name asset_fields = ["data.zou.id", "data.zou.type"] if task_name: - asset_fields.append(f"data.tasks.{task_name}.zou.id") + asset_fields.append("data.tasks.{}.zou.id".format(task_name)) asset = get_asset_by_name(project_name, asset_name=asset_name, @@ -67,7 +67,7 @@ class ShowInKitsu(LauncherAction): task_data = asset["data"]["tasks"][task_name] task_zou_data = task_data.get("zou", {}) if not task_zou_data: - self.log.debug(f"No zou task data for task: {task_name}") + self.log.debug("No zou task data for task: {}".format(task_name)) task_zou_id = task_zou_data["id"] # Define URL @@ -78,7 +78,7 @@ class ShowInKitsu(LauncherAction): task_id=task_zou_id) # Open URL in webbrowser - self.log.info(f"Opening URL: {url}") + self.log.info("Opening URL: {}".format(url)) webbrowser.open(url, # Try in new tab new=2) diff --git a/openpype/modules/kitsu/utils/credentials.py b/openpype/modules/kitsu/utils/credentials.py index adcfb07cd5..1731e1ca4f 100644 --- a/openpype/modules/kitsu/utils/credentials.py +++ b/openpype/modules/kitsu/utils/credentials.py @@ -54,7 +54,7 @@ def validate_host(kitsu_url: str) -> bool: if gazu.client.host_is_valid(): return True else: - raise gazu.exception.HostException(f"Host '{kitsu_url}' is invalid.") + raise gazu.exception.HostException("Host '{}' is invalid.".format(kitsu_url)) def clear_credentials(): diff --git a/openpype/modules/kitsu/utils/sync_service.py b/openpype/modules/kitsu/utils/sync_service.py index 00c8c4eafa..d4ef5ce63a 100644 --- a/openpype/modules/kitsu/utils/sync_service.py +++ b/openpype/modules/kitsu/utils/sync_service.py @@ -162,7 +162,7 @@ class Listener: self.dbcon.database[project_name].drop() # Print message - log.info(f"Project deleted: {project_name}") + log.info("Project deleted: {}".format(project_name)) # == Asset == def _new_asset(self, data): diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index 0a59724393..15e6dd70d9 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -279,7 +279,7 @@ def write_project_to_op(project: dict, dbcon: AvalonMongoDB) -> UpdateOne: project_name = project["name"] project_doc = get_project(project_name) if not project_doc: - log.info(f"Project created: {project_name}") + log.info("Project created: {}".format(project_name)) project_doc = create_project(project_name, project_name) # Project data and tasks @@ -373,7 +373,7 @@ def sync_project_from_kitsu(dbcon: AvalonMongoDB, project: dict): if not project: project = gazu.project.get_project_by_name(project["name"]) - log.info(f"Synchronizing {project['name']}...") + log.info("Synchronizing {}...".format(project['name'])) # Get all assets from zou all_assets = gazu.asset.all_assets_for_project(project) diff --git a/openpype/modules/kitsu/utils/update_zou_with_op.py b/openpype/modules/kitsu/utils/update_zou_with_op.py index 39baf31b93..b13c2dd4c6 100644 --- a/openpype/modules/kitsu/utils/update_zou_with_op.py +++ b/openpype/modules/kitsu/utils/update_zou_with_op.py @@ -61,7 +61,7 @@ def sync_zou_from_op_project( project_doc = get_project(project_name) # Get all entities from zou - print(f"Synchronizing {project_name}...") + print("Synchronizing {}...".format(project_name)) zou_project = gazu.project.get_project_by_name(project_name) # Create project @@ -258,7 +258,7 @@ def sync_zou_from_op_project( for asset_doc in asset_docs.values() } for entity_id in deleted_entities: - gazu.raw.delete(f"data/entities/{entity_id}") + gazu.raw.delete("data/entities/{}".format(entity_id)) # Write into DB if bulk_writes: From 793af30caae9782ab9cb328f7b546231f0c727df Mon Sep 17 00:00:00 2001 From: Jacob Danell Date: Thu, 16 Feb 2023 15:02:53 +0100 Subject: [PATCH 152/184] Changed AssertionError to ValueError --- .../kitsu/plugins/publish/collect_kitsu_entities.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py b/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py index fe6854218d..dc7048cf2a 100644 --- a/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py +++ b/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py @@ -22,13 +22,13 @@ class CollectKitsuEntities(pyblish.api.ContextPlugin): zou_asset_data = asset_doc["data"].get("zou") if not zou_asset_data: - raise AssertionError("Zou asset data not found in OpenPype!") + raise ValueError("Zou asset data not found in OpenPype!") if kitsu_project is None: kitsu_project = gazu.project.get_project( zou_asset_data["project_id"]) if not kitsu_project: - raise AssertionError("Project not found in kitsu!") + raise ValueError("Project not found in kitsu!") entity_type = zou_asset_data["type"] kitsu_id = zou_asset_data["id"] @@ -41,7 +41,7 @@ class CollectKitsuEntities(pyblish.api.ContextPlugin): kitsu_entities_by_id[kitsu_id] = kitsu_entity if not kitsu_entity: - raise AssertionError( + raise ValueError( "{} not found in kitsu!".format(entity_type)) instance.data["kitsu_entity"] = kitsu_entity @@ -53,7 +53,7 @@ class CollectKitsuEntities(pyblish.api.ContextPlugin): if not zou_task_data: kitsu_task_type = gazu.task.get_task_type_by_name(task_name) if not kitsu_task_type: - raise AssertionError( + raise ValueError( "Task type {} not found in Kitsu!".format(task_name) ) continue @@ -64,7 +64,7 @@ class CollectKitsuEntities(pyblish.api.ContextPlugin): kitsu_entities_by_id[kitsu_task_id] = kitsu_task if not kitsu_task: - raise AssertionError("Task not found in kitsu!") + raise ValueError("Task not found in kitsu!") instance.data["kitsu_task"] = kitsu_task self.log.debug("Collect kitsu task: {}".format(kitsu_task)) From 7e6c47967f42f26fd6d0f56c33a14a0e08e77906 Mon Sep 17 00:00:00 2001 From: Jacob Danell Date: Thu, 16 Feb 2023 15:04:05 +0100 Subject: [PATCH 153/184] Get episode_id using get() as ep_id might not always exist --- openpype/modules/kitsu/utils/sync_service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/kitsu/utils/sync_service.py b/openpype/modules/kitsu/utils/sync_service.py index d4ef5ce63a..a449bf8c06 100644 --- a/openpype/modules/kitsu/utils/sync_service.py +++ b/openpype/modules/kitsu/utils/sync_service.py @@ -477,7 +477,7 @@ class Listener: # Find asset doc episode = None - ep_id = task['episode_id'] + ep_id = task.get('episode_id') if ep_id and ep_id != "": episode = gazu.asset.get_episode(ep_id) From 1045ee0c1ed5bb10c3c3d8f4e17f399a5f33c754 Mon Sep 17 00:00:00 2001 From: Jacob Danell Date: Thu, 16 Feb 2023 15:04:55 +0100 Subject: [PATCH 154/184] Check if asset_doc exist before processing it --- openpype/modules/kitsu/utils/sync_service.py | 34 ++++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/openpype/modules/kitsu/utils/sync_service.py b/openpype/modules/kitsu/utils/sync_service.py index a449bf8c06..eed259cda6 100644 --- a/openpype/modules/kitsu/utils/sync_service.py +++ b/openpype/modules/kitsu/utils/sync_service.py @@ -487,25 +487,25 @@ class Listener: parent_name = parent_name + \ task['sequence']['name'] + "_" + task['entity']['name'] - asset_doc = get_asset_by_name(project_name, parent_name) - # Update asset tasks with new one - asset_tasks = asset_doc['data'].get("tasks") - task_type_name = task['task_type']['name'] - asset_tasks[task_type_name] = {"type": task_type_name, "zou": task} - self.dbcon.update_one( - {"_id": asset_doc['_id']}, {"$set": {"data.tasks": asset_tasks}} - ) + asset_doc = get_asset_by_name(project_name, parent_name) + if asset_doc: + asset_tasks = asset_doc['data'].get("tasks") + task_type_name = task['task_type']['name'] + asset_tasks[task_type_name] = {"type": task_type_name, "zou": task} + self.dbcon.update_one( + {"_id": asset_doc['_id']}, {"$set": {"data.tasks": asset_tasks}} + ) - # Print message - msg = "Task created: " - msg = msg + f"{task['project']['name']} - " - if episode is not None: - msg = msg + f"{episode['name']}_" - msg = msg + f"{task['sequence']['name']}_" - msg = msg + f"{task['entity']['name']} - " - msg = msg + f"{task['task_type']['name']}" - log.info(msg) + # Print message + msg = "Task created: " + msg = msg + f"{task['project']['name']} - " + if episode is not None: + msg = msg + f"{episode['name']}_" + msg = msg + f"{task['sequence']['name']}_" + msg = msg + f"{task['entity']['name']} - " + msg = msg + f"{task['task_type']['name']}" + log.info(msg) def _update_task(self, data): """Update task into OP DB.""" From ef698b4aface5ceff0ba1016a242a318853eaa2a Mon Sep 17 00:00:00 2001 From: Jacob Danell Date: Thu, 16 Feb 2023 15:11:07 +0100 Subject: [PATCH 155/184] Fixed 1 extra frame at frameEnd Same as #4466 --- openpype/modules/kitsu/utils/update_op_with_zou.py | 6 +++--- openpype/modules/kitsu/utils/update_zou_with_op.py | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index 15e6dd70d9..93d0d5e3fb 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -111,18 +111,18 @@ def update_op_assets( except (TypeError, ValueError): frame_in = 1001 item_data["frameStart"] = frame_in - # Frames duration, fallback on 0 + # Frames duration, fallback on 1 try: # NOTE nb_frames is stored directly in item # because of zou's legacy design - frames_duration = int(item.get("nb_frames", 0)) + frames_duration = int(item.get("nb_frames", 1)) except (TypeError, ValueError): frames_duration = None # Frame out, fallback on frame_in + duration or project's value or 1001 frame_out = item_data.pop("frame_out", None) if not frame_out: if frames_duration: - frame_out = frame_in + frames_duration + frame_out = frame_in + frames_duration - 1 else: frame_out = project_doc["data"].get("frameEnd", 1001) item_data["frameEnd"] = frame_out diff --git a/openpype/modules/kitsu/utils/update_zou_with_op.py b/openpype/modules/kitsu/utils/update_zou_with_op.py index b13c2dd4c6..b1a9b8b82c 100644 --- a/openpype/modules/kitsu/utils/update_zou_with_op.py +++ b/openpype/modules/kitsu/utils/update_zou_with_op.py @@ -174,7 +174,8 @@ def sync_zou_from_op_project( doc["name"], frame_in=doc["data"]["frameStart"], frame_out=doc["data"]["frameEnd"], - nb_frames=doc["data"]["frameEnd"] - doc["data"]["frameStart"], + nb_frames=( + doc["data"]["frameEnd"] - doc["data"]["frameStart"] + 1), ) elif match.group(2): # Sequence @@ -229,7 +230,7 @@ def sync_zou_from_op_project( "frame_in": frame_in, "frame_out": frame_out, }, - "nb_frames": frame_out - frame_in, + "nb_frames": frame_out - frame_in + 1, } ) entity = gazu.raw.update("entities", zou_id, entity_data) From c4f1a1f452d8e0f70d6a629db7925642d3acbc8d Mon Sep 17 00:00:00 2001 From: Jacob Danell Date: Thu, 16 Feb 2023 15:13:51 +0100 Subject: [PATCH 156/184] Fixed hound's "line too long" comments --- openpype/modules/kitsu/actions/launcher_show_in_kitsu.py | 3 ++- openpype/modules/kitsu/utils/credentials.py | 3 ++- openpype/modules/kitsu/utils/sync_service.py | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/openpype/modules/kitsu/actions/launcher_show_in_kitsu.py b/openpype/modules/kitsu/actions/launcher_show_in_kitsu.py index e3676afc4c..11224f6e52 100644 --- a/openpype/modules/kitsu/actions/launcher_show_in_kitsu.py +++ b/openpype/modules/kitsu/actions/launcher_show_in_kitsu.py @@ -67,7 +67,8 @@ class ShowInKitsu(LauncherAction): task_data = asset["data"]["tasks"][task_name] task_zou_data = task_data.get("zou", {}) if not task_zou_data: - self.log.debug("No zou task data for task: {}".format(task_name)) + self.log.debug( + "No zou task data for task: {}".format(task_name)) task_zou_id = task_zou_data["id"] # Define URL diff --git a/openpype/modules/kitsu/utils/credentials.py b/openpype/modules/kitsu/utils/credentials.py index 1731e1ca4f..941343cc8d 100644 --- a/openpype/modules/kitsu/utils/credentials.py +++ b/openpype/modules/kitsu/utils/credentials.py @@ -54,7 +54,8 @@ def validate_host(kitsu_url: str) -> bool: if gazu.client.host_is_valid(): return True else: - raise gazu.exception.HostException("Host '{}' is invalid.".format(kitsu_url)) + raise gazu.exception.HostException( + "Host '{}' is invalid.".format(kitsu_url)) def clear_credentials(): diff --git a/openpype/modules/kitsu/utils/sync_service.py b/openpype/modules/kitsu/utils/sync_service.py index eed259cda6..498c8de71e 100644 --- a/openpype/modules/kitsu/utils/sync_service.py +++ b/openpype/modules/kitsu/utils/sync_service.py @@ -494,7 +494,8 @@ class Listener: task_type_name = task['task_type']['name'] asset_tasks[task_type_name] = {"type": task_type_name, "zou": task} self.dbcon.update_one( - {"_id": asset_doc['_id']}, {"$set": {"data.tasks": asset_tasks}} + {"_id": asset_doc['_id']}, + {"$set": {"data.tasks": asset_tasks}} ) # Print message From 07ac3d8d4db54b111fd0117bed4a5cbc5c9b19d7 Mon Sep 17 00:00:00 2001 From: Jacob Danell Date: Thu, 16 Feb 2023 15:58:49 +0100 Subject: [PATCH 157/184] If no task in instance, continue fix fore https://github.com/ynput/OpenPype/pull/4425#discussion_r1108582918 --- .../modules/kitsu/plugins/publish/integrate_kitsu_note.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py index aeec2481e0..54fb6a4678 100644 --- a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py +++ b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py @@ -22,10 +22,13 @@ class IntegrateKitsuNote(pyblish.api.ContextPlugin): self.log.debug("Comment is `{}`".format(publish_comment)) for instance in context: + kitsu_task = instance.data.get("kitsu_task") + if kitsu_task is None: + continue # Get note status, by default uses the task status for the note # if it is not specified in the configuration - note_status = instance.data["kitsu_task"]["task_status"]["id"] + note_status = kitsu_task["task_status"]["id"] if self.set_status_note: kitsu_status = gazu.task.get_task_status_by_short_name( @@ -41,7 +44,7 @@ class IntegrateKitsuNote(pyblish.api.ContextPlugin): ) # Add comment to kitsu task - task = instance.data["kitsu_task"]["id"] + task = kitsu_task["id"] self.log.debug( "Add new note in taks id {}".format(task) ) From 915d11040493e1e59d2389e9d5f86f678ef4b9ca Mon Sep 17 00:00:00 2001 From: Jacob Danell Date: Thu, 16 Feb 2023 16:02:53 +0100 Subject: [PATCH 158/184] Set frame_out to frame_in if no duration exists fix for https://github.com/ynput/OpenPype/pull/4425#discussion_r1108593566 --- openpype/modules/kitsu/utils/update_op_with_zou.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index 93d0d5e3fb..265c3638cd 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -124,7 +124,7 @@ def update_op_assets( if frames_duration: frame_out = frame_in + frames_duration - 1 else: - frame_out = project_doc["data"].get("frameEnd", 1001) + frame_out = project_doc["data"].get("frameEnd", frame_in) item_data["frameEnd"] = frame_out # Fps, fallback to project's value or default value (25.0) try: From 4bcef4406803f0210ed3a741ad99de8aa56c80a7 Mon Sep 17 00:00:00 2001 From: Jacob Danell Date: Sat, 18 Feb 2023 20:52:20 +0100 Subject: [PATCH 159/184] Fixed hound's max-length note --- openpype/modules/kitsu/utils/update_op_with_zou.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index 265c3638cd..898cf076c8 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -146,7 +146,8 @@ def update_op_assets( "resolutionWidth") item_data["resolutionHeight"] = project_doc["data"].get( "resolutionHeight") - # Properties that doesn't fully exist in Kitsu. Guessing the property name + # Properties that doesn't fully exist in Kitsu. + # Guessing those property names below: # Pixel Aspect Ratio item_data["pixelAspect"] = item_data.get( "pixel_aspect", project_doc["data"].get("pixelAspect")) @@ -452,7 +453,8 @@ def sync_project_from_kitsu(dbcon: AvalonMongoDB, project: dict): [ UpdateOne({"_id": id}, update) for id, update in update_op_assets( - dbcon, project, project_doc, all_entities, zou_ids_and_asset_docs + dbcon, project, project_doc, + all_entities, zou_ids_and_asset_docs ) ] ) From 3fc2180e51357cc74a82a76f2fcd21fa11752ecc Mon Sep 17 00:00:00 2001 From: Jacob Danell Date: Tue, 28 Feb 2023 23:06:52 +0100 Subject: [PATCH 160/184] Fixed all quotes types so they now match --- openpype/modules/kitsu/utils/sync_service.py | 133 ++++++++++--------- 1 file changed, 67 insertions(+), 66 deletions(-) diff --git a/openpype/modules/kitsu/utils/sync_service.py b/openpype/modules/kitsu/utils/sync_service.py index 498c8de71e..1af0b6edc4 100644 --- a/openpype/modules/kitsu/utils/sync_service.py +++ b/openpype/modules/kitsu/utils/sync_service.py @@ -48,16 +48,16 @@ class Listener: self.dbcon = AvalonMongoDB() self.dbcon.install() - gazu.client.set_host(os.environ['KITSU_SERVER']) + gazu.client.set_host(os.environ["KITSU_SERVER"]) # Authenticate if not validate_credentials(login, password): raise gazu.exception.AuthFailedException( - f"Kitsu authentication failed for login: '{login}'..." + 'Kitsu authentication failed for login: "{}"...'.format(login) ) gazu.set_event_host( - os.environ['KITSU_SERVER'].replace("api", "socket.io") + os.environ["KITSU_SERVER"].replace("api", "socket.io") ) self.event_client = gazu.events.init() @@ -135,14 +135,14 @@ class Listener: def _update_project(self, data): """Update project into OP DB.""" # Get project entity - project = gazu.project.get_project(data['project_id']) + project = gazu.project.get_project(data["project_id"]) update_project = write_project_to_op(project, self.dbcon) # Write into DB if update_project: - self.dbcon.Session['AVALON_PROJECT'] = get_kitsu_project_name( - data['project_id']) + self.dbcon.Session["AVALON_PROJECT"] = get_kitsu_project_name( + data["project_id"]) self.dbcon.bulk_write([update_project]) def _delete_project(self, data): @@ -168,10 +168,10 @@ class Listener: def _new_asset(self, data): """Create new asset into OP DB.""" # Get project entity - set_op_project(self.dbcon, data['project_id']) + set_op_project(self.dbcon, data["project_id"]) # Get asset entity - asset = gazu.asset.get_asset(data['asset_id']) + asset = gazu.asset.get_asset(data["asset_id"]) # Insert doc in DB self.dbcon.insert_one(create_op_asset(asset)) @@ -181,7 +181,7 @@ class Listener: # Print message episode = None - ep_id = asset['episode_id'] + ep_id = asset.get("episode_id") if ep_id and ep_id != "": episode = gazu.asset.get_episode(ep_id) @@ -195,22 +195,22 @@ class Listener: def _update_asset(self, data): """Update asset into OP DB.""" - set_op_project(self.dbcon, data['project_id']) + set_op_project(self.dbcon, data["project_id"]) project_name = self.dbcon.active_project() project_doc = get_project(project_name) # Get gazu entity - asset = gazu.asset.get_asset(data['asset_id']) + asset = gazu.asset.get_asset(data["asset_id"]) # Find asset doc # Query all assets of the local project zou_ids_and_asset_docs = { - asset_doc['data']['zou']['id']: asset_doc + asset_doc["data"]["zou"]["id"]: asset_doc for asset_doc in get_assets(project_name) - if asset_doc['data'].get("zou", {}).get("id") + if asset_doc["data"].get("zou", {}).get("id") } - zou_ids_and_asset_docs[asset['project_id']] = project_doc - gazu_project = gazu.project.get_project(asset['project_id']) + zou_ids_and_asset_docs[asset["project_id"]] = project_doc + gazu_project = gazu.project.get_project(asset["project_id"]) # Update update_op_result = update_op_assets( @@ -223,18 +223,18 @@ class Listener: def _delete_asset(self, data): """Delete asset of OP DB.""" - set_op_project(self.dbcon, data['project_id']) + set_op_project(self.dbcon, data["project_id"]) - asset = self.dbcon.find_one({"data.zou.id": data['asset_id']}) + asset = self.dbcon.find_one({"data.zou.id": data["asset_id"]}) if asset: # Delete self.dbcon.delete_one( - {"type": "asset", "data.zou.id": data['asset_id']} + {"type": "asset", "data.zou.id": data["asset_id"]} ) # Print message episode = None - ep_id = asset['data']['zou']['episode_id'] + ep_id = asset["data"]["zou"].get("episode_id") if ep_id and ep_id != "": episode = gazu.asset.get_episode(ep_id) @@ -250,10 +250,10 @@ class Listener: def _new_episode(self, data): """Create new episode into OP DB.""" # Get project entity - set_op_project(self.dbcon, data['project_id']) + set_op_project(self.dbcon, data["project_id"]) # Get gazu entity - episode = gazu.shot.get_episode(data['episode_id']) + ep = gazu.shot.get_episode(data["episode_id"]) # Insert doc in DB self.dbcon.insert_one(create_op_asset(episode)) @@ -268,22 +268,22 @@ class Listener: def _update_episode(self, data): """Update episode into OP DB.""" - set_op_project(self.dbcon, data['project_id']) + set_op_project(self.dbcon, data["project_id"]) project_name = self.dbcon.active_project() project_doc = get_project(project_name) # Get gazu entity - episode = gazu.shot.get_episode(data['episode_id']) + ep = gazu.shot.get_episode(data["episode_id"]) # Find asset doc # Query all assets of the local project zou_ids_and_asset_docs = { - asset_doc['data']['zou']['id']: asset_doc + asset_doc["data"]["zou"]["id"]: asset_doc for asset_doc in get_assets(project_name) - if asset_doc['data'].get("zou", {}).get("id") + if asset_doc["data"].get("zou", {}).get("id") } - zou_ids_and_asset_docs[episode['project_id']] = project_doc - gazu_project = gazu.project.get_project(episode['project_id']) + zou_ids_and_asset_docs[ep["project_id"]] = project_doc + gazu_project = gazu.project.get_project(ep["project_id"]) # Update update_op_result = update_op_assets( @@ -296,7 +296,7 @@ class Listener: def _delete_episode(self, data): """Delete shot of OP DB.""" - set_op_project(self.dbcon, data['project_id']) + set_op_project(self.dbcon, data["project_id"]) episode = self.dbcon.find_one({"data.zou.id": data['episode_id']}) if episode: @@ -317,10 +317,10 @@ class Listener: def _new_sequence(self, data): """Create new sequnce into OP DB.""" # Get project entity - set_op_project(self.dbcon, data['project_id']) + set_op_project(self.dbcon, data["project_id"]) # Get gazu entity - sequence = gazu.shot.get_sequence(data['sequence_id']) + sequence = gazu.shot.get_sequence(data["sequence_id"]) # Insert doc in DB self.dbcon.insert_one(create_op_asset(sequence)) @@ -331,7 +331,7 @@ class Listener: # Print message episode = None - ep_id = sequence['episode_id'] + ep_id = sequence.get("episode_id") if ep_id and ep_id != "": episode = gazu.asset.get_episode(ep_id) @@ -344,22 +344,22 @@ class Listener: def _update_sequence(self, data): """Update sequence into OP DB.""" - set_op_project(self.dbcon, data['project_id']) + set_op_project(self.dbcon, data["project_id"]) project_name = self.dbcon.active_project() project_doc = get_project(project_name) # Get gazu entity - sequence = gazu.shot.get_sequence(data['sequence_id']) + sequence = gazu.shot.get_sequence(data["sequence_id"]) # Find asset doc # Query all assets of the local project zou_ids_and_asset_docs = { - asset_doc['data']['zou']['id']: asset_doc + asset_doc["data"]["zou"]["id"]: asset_doc for asset_doc in get_assets(project_name) - if asset_doc['data'].get("zou", {}).get("id") + if asset_doc["data"].get("zou", {}).get("id") } - zou_ids_and_asset_docs[sequence['project_id']] = project_doc - gazu_project = gazu.project.get_project(sequence['project_id']) + zou_ids_and_asset_docs[sequence["project_id"]] = project_doc + gazu_project = gazu.project.get_project(sequence["project_id"]) # Update update_op_result = update_op_assets( @@ -372,15 +372,16 @@ class Listener: def _delete_sequence(self, data): """Delete sequence of OP DB.""" - set_op_project(self.dbcon, data['project_id']) - sequence = self.dbcon.find_one({"data.zou.id": data['sequence_id']}) + set_op_project(self.dbcon, data["project_id"]) + sequence = self.dbcon.find_one({"data.zou.id": data["sequence_id"]}) if sequence: # Delete self.dbcon.delete_one( - {"type": "asset", "data.zou.id": data['sequence_id']} + {"type": "asset", "data.zou.id": data["sequence_id"]} ) # Print message + ep_id = sequence["data"]["zou"].get("episode_id") gazu_project = gazu.project.get_project( sequence['data']['zou']['project_id']) msg = f"Sequence deleted: " @@ -392,10 +393,10 @@ class Listener: def _new_shot(self, data): """Create new shot into OP DB.""" # Get project entity - set_op_project(self.dbcon, data['project_id']) + set_op_project(self.dbcon, data["project_id"]) # Get gazu entity - shot = gazu.shot.get_shot(data['shot_id']) + shot = gazu.shot.get_shot(data["shot_id"]) # Insert doc in DB self.dbcon.insert_one(create_op_asset(shot)) @@ -405,7 +406,7 @@ class Listener: # Print message episode = None - if shot['episode_id'] and shot['episode_id'] != "": + if shot["episode_id"] and shot["episode_id"] != "": episode = gazu.asset.get_episode(shot['episode_id']) msg = "Shot created: " @@ -418,21 +419,21 @@ class Listener: def _update_shot(self, data): """Update shot into OP DB.""" - set_op_project(self.dbcon, data['project_id']) + set_op_project(self.dbcon, data["project_id"]) project_name = self.dbcon.active_project() project_doc = get_project(project_name) # Get gazu entity - shot = gazu.shot.get_shot(data['shot_id']) + shot = gazu.shot.get_shot(data["shot_id"]) # Find asset doc # Query all assets of the local project zou_ids_and_asset_docs = { - asset_doc['data']['zou']['id']: asset_doc + asset_doc["data"]["zou"]["id"]: asset_doc for asset_doc in get_assets(project_name) - if asset_doc['data'].get("zou", {}).get("id") - } - zou_ids_and_asset_docs[shot['project_id']] = project_doc + if asset_doc["data"].get("zou", {}).get("id")} + zou_ids_and_asset_docs[shot["project_id"]] = project_doc + gazu_project = gazu.project.get_project(shot["project_id"]) gazu_project = gazu.project.get_project(shot['project_id']) # Update @@ -447,18 +448,18 @@ class Listener: def _delete_shot(self, data): """Delete shot of OP DB.""" - set_op_project(self.dbcon, data['project_id']) - shot = self.dbcon.find_one({"data.zou.id": data['shot_id']}) + set_op_project(self.dbcon, data["project_id"]) + shot = self.dbcon.find_one({"data.zou.id": data["shot_id"]}) if shot: # Delete self.dbcon.delete_one( - {"type": "asset", "data.zou.id": data['shot_id']} + {"type": "asset", "data.zou.id": data["shot_id"]} ) # Print message gazu_project = gazu.project.get_project( - shot['data']['zou']['project_id']) + ep_id = shot["data"]["zou"].get("episode_id") msg = "Shot deleted: " msg = msg + f"{gazu_project['name']} - " @@ -469,15 +470,15 @@ class Listener: def _new_task(self, data): """Create new task into OP DB.""" # Get project entity - set_op_project(self.dbcon, data['project_id']) + set_op_project(self.dbcon, data["project_id"]) project_name = self.dbcon.active_project() # Get gazu entity - task = gazu.task.get_task(data['task_id']) + task = gazu.task.get_task(data["task_id"]) # Find asset doc episode = None - ep_id = task.get('episode_id') + ep_id = task.get("episode_id") if ep_id and ep_id != "": episode = gazu.asset.get_episode(ep_id) @@ -490,11 +491,11 @@ class Listener: # Update asset tasks with new one asset_doc = get_asset_by_name(project_name, parent_name) if asset_doc: - asset_tasks = asset_doc['data'].get("tasks") - task_type_name = task['task_type']['name'] + asset_tasks = asset_doc["data"].get("tasks") + task_type_name = task["task_type"]["name"] asset_tasks[task_type_name] = {"type": task_type_name, "zou": task} self.dbcon.update_one( - {"_id": asset_doc['_id']}, + {"_id": asset_doc["_id"]}, {"$set": {"data.tasks": asset_tasks}} ) @@ -515,29 +516,29 @@ class Listener: def _delete_task(self, data): """Delete task of OP DB.""" - set_op_project(self.dbcon, data['project_id']) + set_op_project(self.dbcon, data["project_id"]) project_name = self.dbcon.active_project() # Find asset doc asset_docs = list(get_assets(project_name)) for doc in asset_docs: # Match task - for name, task in doc['data']['tasks'].items(): - if task.get("zou") and data['task_id'] == task['zou']['id']: + for name, task in doc["data"]["tasks"].items(): + if task.get("zou") and data["task_id"] == task["zou"]["id"]: # Pop task - asset_tasks = doc['data'].get("tasks", {}) + asset_tasks = doc["data"].get("tasks", {}) asset_tasks.pop(name) # Delete task in DB self.dbcon.update_one( - {"_id": doc['_id']}, + {"_id": doc["_id"]}, {"$set": {"data.tasks": asset_tasks}}, ) # Print message - shot = gazu.shot.get_shot(task['zou']['entity_id']) + entity = gazu.entity.get_entity(task["zou"]["entity_id"]) episode = None - ep_id = shot['episode_id'] + ep_id = entity.get("episode_id") if ep_id and ep_id != "": episode = gazu.asset.get_episode(ep_id) From f9137bdb041690f2ef6cc0a3d26878ee70b321bf Mon Sep 17 00:00:00 2001 From: Jacob Danell Date: Tue, 28 Feb 2023 23:08:40 +0100 Subject: [PATCH 161/184] Added docstring and changed `doc` to `dict` in var-names --- .../modules/kitsu/utils/update_op_with_zou.py | 78 +++++++++++-------- 1 file changed, 44 insertions(+), 34 deletions(-) diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index 898cf076c8..9368848532 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -65,30 +65,32 @@ def set_op_project(dbcon: AvalonMongoDB, project_id: str): def update_op_assets( dbcon: AvalonMongoDB, gazu_project: dict, - project_doc: dict, + project_dict: dict, entities_list: List[dict], - asset_doc_ids: Dict[str, dict], + asset_dict_ids: Dict[str, dict], ) -> List[Dict[str, dict]]: """Update OpenPype assets. Set 'data' and 'parent' fields. Args: dbcon (AvalonMongoDB): Connection to DB + gazu_project dict): Dict of gazu, + project_dict dict): Dict of project, entities_list (List[dict]): List of zou entities to update - asset_doc_ids (Dict[str, dict]): Dicts of [{zou_id: asset_doc}, ...] + asset_dict_ids (Dict[str, dict]): Dicts of [{zou_id: asset_doc}, ...] Returns: List[Dict[str, dict]]: List of (doc_id, update_dict) tuples """ - if not project_doc: + if not project_dict: return - project_name = project_doc["name"] + project_name = project_dict["name"] assets_with_update = [] for item in entities_list: # Check asset exists - item_doc = asset_doc_ids.get(item["id"]) + item_doc = asset_dict_ids.get(item["id"]) if not item_doc: # Create asset op_asset = create_op_asset(item) insert_result = dbcon.insert_one(op_asset) @@ -105,7 +107,7 @@ def update_op_assets( try: frame_in = int( item_data.pop( - "frame_in", project_doc["data"].get("frameStart") + "frame_in", project_dict["data"].get("frameStart") ) ) except (TypeError, ValueError): @@ -124,14 +126,14 @@ def update_op_assets( if frames_duration: frame_out = frame_in + frames_duration - 1 else: - frame_out = project_doc["data"].get("frameEnd", frame_in) + frame_out = project_dict["data"].get("frameEnd", frame_in) item_data["frameEnd"] = frame_out # Fps, fallback to project's value or default value (25.0) try: fps = float(item_data.get("fps")) except (TypeError, ValueError): fps = float(gazu_project.get( - "fps", project_doc["data"].get("fps", 25))) + "fps", project_dict["data"].get("fps", 25))) item_data["fps"] = fps # Resolution, fall back to project default match_res = re.match( @@ -142,27 +144,27 @@ def update_op_assets( item_data["resolutionWidth"] = int(match_res.group(1)) item_data["resolutionHeight"] = int(match_res.group(2)) else: - item_data["resolutionWidth"] = project_doc["data"].get( + item_data["resolutionWidth"] = project_dict["data"].get( "resolutionWidth") - item_data["resolutionHeight"] = project_doc["data"].get( + item_data["resolutionHeight"] = project_dict["data"].get( "resolutionHeight") # Properties that doesn't fully exist in Kitsu. # Guessing those property names below: # Pixel Aspect Ratio item_data["pixelAspect"] = item_data.get( - "pixel_aspect", project_doc["data"].get("pixelAspect")) + "pixel_aspect", project_dict["data"].get("pixelAspect")) # Handle Start item_data["handleStart"] = item_data.get( - "handle_start", project_doc["data"].get("handleStart")) + "handle_start", project_dict["data"].get("handleStart")) # Handle End item_data["handleEnd"] = item_data.get( - "handle_end", project_doc["data"].get("handleEnd")) + "handle_end", project_dict["data"].get("handleEnd")) # Clip In item_data["clipIn"] = item_data.get( - "clip_in", project_doc["data"].get("clipIn")) + "clip_in", project_dict["data"].get("clipIn")) # Clip Out item_data["clipOut"] = item_data.get( - "clip_out", project_doc["data"].get("clipOut")) + "clip_out", project_dict["data"].get("clipOut")) # Tasks tasks_list = [] @@ -204,9 +206,14 @@ def update_op_assets( entity_root_asset_name = "Shots" # Root parent folder if exist - visual_parent_doc_id = ( - asset_doc_ids[parent_zou_id].get("_id") if parent_zou_id else None - ) + visual_parent_doc_id = None + if parent_zou_id is not None: + parent_zou_id_dict = asset_dict_ids.get(parent_zou_id) + if parent_zou_id_dict is not None: + visual_parent_doc_id = ( + parent_zou_id_dict.get("_id") + if parent_zou_id_dict else None) + if visual_parent_doc_id is None: # Find root folder doc ("Assets" or "Shots") root_folder_doc = get_asset_by_name( @@ -225,12 +232,15 @@ def update_op_assets( item_data["parents"] = [] ancestor_id = parent_zou_id while ancestor_id is not None: - parent_doc = asset_doc_ids[ancestor_id] - item_data["parents"].insert(0, parent_doc["name"]) + parent_doc = asset_dict_ids.get(ancestor_id) + if parent_doc is not None: + item_data["parents"].insert(0, parent_doc["name"]) - # Get parent entity - parent_entity = parent_doc["data"]["zou"] - ancestor_id = parent_entity.get("parent_id") + # Get parent entity + parent_entity = parent_doc["data"]["zou"] + ancestor_id = parent_entity.get("parent_id") + else: + ancestor_id = None # Build OpenPype compatible name if item_type in ["Shot", "Sequence"] and parent_zou_id is not None: @@ -239,7 +249,7 @@ def update_op_assets( item_name = f"{item_data['parents'][-1]}_{item['name']}" # Update doc name - asset_doc_ids[item["id"]]["name"] = item_name + asset_dict_ids[item["id"]]["name"] = item_name else: item_name = item["name"] @@ -258,7 +268,7 @@ def update_op_assets( "$set": { "name": item_name, "data": item_data, - "parent": project_doc["_id"], + "parent": project_dict["_id"], } }, ) @@ -278,13 +288,13 @@ def write_project_to_op(project: dict, dbcon: AvalonMongoDB) -> UpdateOne: UpdateOne: Update instance for the project """ project_name = project["name"] - project_doc = get_project(project_name) - if not project_doc: + project_dict = get_project(project_name) + if not project_dict: log.info("Project created: {}".format(project_name)) - project_doc = create_project(project_name, project_name) + project_dict = create_project(project_name, project_name) # Project data and tasks - project_data = project_doc["data"] or {} + project_data = project_dict["data"] or {} # Build project code and update Kitsu project_code = project.get("code") @@ -315,7 +325,7 @@ def write_project_to_op(project: dict, dbcon: AvalonMongoDB) -> UpdateOne: ) return UpdateOne( - {"_id": project_doc["_id"]}, + {"_id": project_dict["_id"]}, { "$set": { "config.tasks": { @@ -398,7 +408,7 @@ def sync_project_from_kitsu(dbcon: AvalonMongoDB, project: dict): # Try to find project document project_name = project["name"] dbcon.Session["AVALON_PROJECT"] = project_name - project_doc = get_project(project_name) + project_dict = get_project(project_name) # Query all assets of the local project zou_ids_and_asset_docs = { @@ -406,7 +416,7 @@ def sync_project_from_kitsu(dbcon: AvalonMongoDB, project: dict): for asset_doc in get_assets(project_name) if asset_doc["data"].get("zou", {}).get("id") } - zou_ids_and_asset_docs[project["id"]] = project_doc + zou_ids_and_asset_docs[project["id"]] = project_dict # Create entities root folders to_insert = [ @@ -453,7 +463,7 @@ def sync_project_from_kitsu(dbcon: AvalonMongoDB, project: dict): [ UpdateOne({"_id": id}, update) for id, update in update_op_assets( - dbcon, project, project_doc, + dbcon, project, project_dict, all_entities, zou_ids_and_asset_docs ) ] From 0f76d3a44e4974b5c0ec81f166a19289d9cb4fd6 Mon Sep 17 00:00:00 2001 From: Jacob Danell Date: Tue, 28 Feb 2023 23:13:33 +0100 Subject: [PATCH 162/184] Cleaned up the fetching of the entity_id Also changed the name kitsu_id to entity_id and kitsu_entity to just entity as that's what it is. --- .../plugins/publish/collect_kitsu_entities.py | 34 ++++++++----------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py b/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py index dc7048cf2a..1531c80e04 100644 --- a/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py +++ b/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py @@ -16,7 +16,6 @@ class CollectKitsuEntities(pyblish.api.ContextPlugin): kitsu_entities_by_id = {} for instance in context: asset_doc = instance.data.get("assetEntity") - task_name = instance.data.get("task") if not asset_doc: continue @@ -24,27 +23,24 @@ class CollectKitsuEntities(pyblish.api.ContextPlugin): if not zou_asset_data: raise ValueError("Zou asset data not found in OpenPype!") - if kitsu_project is None: - kitsu_project = gazu.project.get_project( - zou_asset_data["project_id"]) - if not kitsu_project: - raise ValueError("Project not found in kitsu!") + kitsu_project = gazu.project.get_project( + zou_asset_data["project_id"]) + if not kitsu_project: + raise ValueError("Project not found in kitsu!") - entity_type = zou_asset_data["type"] - kitsu_id = zou_asset_data["id"] - kitsu_entity = kitsu_entities_by_id.get(kitsu_id) - if not kitsu_entity: - if entity_type == "Shot": - kitsu_entity = gazu.shot.get_shot(kitsu_id) - else: - kitsu_entity = gazu.asset.get_asset(kitsu_id) - kitsu_entities_by_id[kitsu_id] = kitsu_entity + entity_id = zou_asset_data["id"] + entity = kitsu_entities_by_id.get(entity_id) + if not entity: + entity = gazu.entity.get_entity(entity_id) + if not entity: + raise ValueError( + "{} was not found in kitsu!".format( + zou_asset_data["name"])) - if not kitsu_entity: - raise ValueError( - "{} not found in kitsu!".format(entity_type)) - instance.data["kitsu_entity"] = kitsu_entity + kitsu_entities_by_id[entity_id] = entity + instance.data["entity"] = entity + task_name = instance.data.get("task") if not task_name: continue zou_task_data = asset_doc["data"]["tasks"][task_name].get("zou") From 13a4c7201e5fd410e3546b4cd898ab8f0068b4ab Mon Sep 17 00:00:00 2001 From: Jacob Danell Date: Tue, 28 Feb 2023 23:18:43 +0100 Subject: [PATCH 163/184] change task to task_id --- .../modules/kitsu/plugins/publish/integrate_kitsu_note.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py index 54fb6a4678..006f0bc6d0 100644 --- a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py +++ b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py @@ -44,12 +44,12 @@ class IntegrateKitsuNote(pyblish.api.ContextPlugin): ) # Add comment to kitsu task - task = kitsu_task["id"] + task_id = kitsu_task["id"] self.log.debug( - "Add new note in taks id {}".format(task) + "Add new note in taks id {}".format(task_id) ) kitsu_comment = gazu.task.add_comment( - task, note_status, comment=publish_comment + task_id, note_status, comment=publish_comment ) instance.data["kitsu_comment"] = kitsu_comment From bcea2c70a269559a2fa370185393d6c4b675a38b Mon Sep 17 00:00:00 2001 From: Jacob Danell Date: Tue, 28 Feb 2023 23:20:32 +0100 Subject: [PATCH 164/184] Cleaned up project deletion code --- openpype/modules/kitsu/utils/sync_service.py | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/openpype/modules/kitsu/utils/sync_service.py b/openpype/modules/kitsu/utils/sync_service.py index 1af0b6edc4..6155b396aa 100644 --- a/openpype/modules/kitsu/utils/sync_service.py +++ b/openpype/modules/kitsu/utils/sync_service.py @@ -149,20 +149,16 @@ class Listener: """Delete project.""" collections = self.dbcon.database.list_collection_names() - project_name = None for collection in collections: - post = self.dbcon.database[collection].find_one( - {"data.zou_id": data['project_id']}) - if post: - project_name = post['name'] - break + project = self.dbcon.database[collection].find_one( + {"data.zou_id": data["project_id"]}) + if project: + # Delete project collection + self.dbcon.database[project["name"]].drop() - if project_name: - # Delete project collection - self.dbcon.database[project_name].drop() - - # Print message - log.info("Project deleted: {}".format(project_name)) + # Print message + log.info("Project deleted: {}".format(project["name"])) + return # == Asset == def _new_asset(self, data): From d9ac1ee95255c2d663cffaa00e63ad99a74188af Mon Sep 17 00:00:00 2001 From: Jacob Danell Date: Tue, 28 Feb 2023 23:24:55 +0100 Subject: [PATCH 165/184] Cleaned up log.info() message creation --- openpype/modules/kitsu/utils/sync_service.py | 171 +++++++++++-------- 1 file changed, 96 insertions(+), 75 deletions(-) diff --git a/openpype/modules/kitsu/utils/sync_service.py b/openpype/modules/kitsu/utils/sync_service.py index 6155b396aa..b389d25c4f 100644 --- a/openpype/modules/kitsu/utils/sync_service.py +++ b/openpype/modules/kitsu/utils/sync_service.py @@ -176,17 +176,18 @@ class Listener: self._update_asset(data) # Print message - episode = None + ep = None ep_id = asset.get("episode_id") if ep_id and ep_id != "": - episode = gazu.asset.get_episode(ep_id) + ep = gazu.asset.get_episode(ep_id) - msg = "Asset created: " - msg = msg + f"{asset['project_name']} - " - if episode is not None: - msg = msg + f"{episode['name']}_" - msg = msg + f"{asset['asset_type_name']}_" - msg = msg + f"{asset['name']}" + msg = "Asset created: {proj_name} - {ep_name}" \ + "{asset_type_name} - {asset_name}".format( + proj_name=asset["project_name"], + ep_name=ep["name"] + " - " if ep is not None else "", + asset_type_name=asset["asset_type_name"], + asset_name=asset["name"] + ) log.info(msg) def _update_asset(self, data): @@ -229,17 +230,18 @@ class Listener: ) # Print message - episode = None + ep = None ep_id = asset["data"]["zou"].get("episode_id") if ep_id and ep_id != "": - episode = gazu.asset.get_episode(ep_id) + ep = gazu.asset.get_episode(ep_id) - msg = "Asset deleted: " - msg = msg + f"{asset['data']['zou']['project_name']} - " - if episode is not None: - msg = msg + f"{episode['name']}_" - msg = msg + f"{asset['data']['zou']['asset_type_name']}_" - msg = msg + f"'{asset['name']}" + msg = "Asset deleted: {proj_name} - {ep_name}" \ + "{asset_type_name} - {asset_name}".format( + proj_name=asset["data"]["zou"]["project_name"], + ep_name=ep["name"] + " - " if ep is not None else "", + asset_type_name=asset["data"]["zou"]["asset_type_name"], + asset_name=asset["name"] + ) log.info(msg) # == Episode == @@ -252,15 +254,17 @@ class Listener: ep = gazu.shot.get_episode(data["episode_id"]) # Insert doc in DB - self.dbcon.insert_one(create_op_asset(episode)) + self.dbcon.insert_one(create_op_asset(ep)) # Update self._update_episode(data) # Print message - msg = "Episode created: " - msg = msg + f"{episode['project_name']} - " - msg = msg + f"{episode['name']}" + msg = "Episode created: {proj_name} - {ep_name}".format( + proj_name=ep["project_name"], + ep_name=ep["name"] + ) + log.info(msg) def _update_episode(self, data): """Update episode into OP DB.""" @@ -283,8 +287,8 @@ class Listener: # Update update_op_result = update_op_assets( - self.dbcon, gazu_project, project_doc, [ - episode], zou_ids_and_asset_docs + self.dbcon, gazu_project, project_doc, + [ep], zou_ids_and_asset_docs ) if update_op_result: asset_doc_id, asset_update = update_op_result[0] @@ -294,20 +298,22 @@ class Listener: """Delete shot of OP DB.""" set_op_project(self.dbcon, data["project_id"]) - episode = self.dbcon.find_one({"data.zou.id": data['episode_id']}) - if episode: + ep = self.dbcon.find_one({"data.zou.id": data["episode_id"]}) + if ep: # Delete self.dbcon.delete_one( - {"type": "asset", "data.zou.id": data['episode_id']} + {"type": "asset", "data.zou.id": data["episode_id"]} ) # Print message project = gazu.project.get_project( - episode['data']['zou']['project_id']) + ep["data"]["zou"]["project_id"]) - msg = "Episode deleted: " - msg = msg + f"{project['name']} - " - msg = msg + f"{episode['name']}" + msg = "Episode deleted: {proj_name} - {ep_name}".format( + proj_name=project["name"], + ep_name=ep["name"] + ) + log.info(msg) # == Sequence == def _new_sequence(self, data): @@ -325,17 +331,17 @@ class Listener: self._update_sequence(data) # Print message - - episode = None + ep = None ep_id = sequence.get("episode_id") if ep_id and ep_id != "": - episode = gazu.asset.get_episode(ep_id) + ep = gazu.asset.get_episode(ep_id) - msg = "Sequence created: " - msg = msg + f"{sequence['project_name']} - " - if episode is not None: - msg = msg + f"{episode['name']}_" - msg = msg + f"{sequence['name']}" + msg = "Sequence created: {proj_name} - {ep_name}" \ + "{sequence_name}".format( + proj_name=sequence["project_name"], + ep_name=ep["name"] + " - " if ep is not None else "", + sequence_name=sequence["name"] + ) log.info(msg) def _update_sequence(self, data): @@ -377,12 +383,20 @@ class Listener: ) # Print message + ep = None ep_id = sequence["data"]["zou"].get("episode_id") + if ep_id and ep_id != "": + ep = gazu.asset.get_episode(ep_id) + gazu_project = gazu.project.get_project( - sequence['data']['zou']['project_id']) - msg = f"Sequence deleted: " - msg = msg + f"{gazu_project['name']} - " - msg = msg + f"{sequence['name']}" + sequence["data"]["zou"]["project_id"]) + + msg = "Sequence created: {proj_name} - {ep_name}" \ + "{sequence_name}".format( + proj_name=gazu_project["name"], + ep_name=ep["name"] + " - " if ep is not None else "", + sequence_name=sequence["name"] + ) log.info(msg) # == Shot == @@ -401,16 +415,17 @@ class Listener: self._update_shot(data) # Print message - episode = None + ep = None if shot["episode_id"] and shot["episode_id"] != "": - episode = gazu.asset.get_episode(shot['episode_id']) + ep = gazu.asset.get_episode(shot["episode_id"]) - msg = "Shot created: " - msg = msg + f"{shot['project_name']} - " - if episode is not None: - msg = msg + f"{episode['name']}_" - msg = msg + f"{shot['sequence_name']}_" - msg = msg + f"{shot['name']}" + msg = "Shot created: {proj_name} - {ep_name}" \ + "{sequence_name} - {shot_name}".format( + proj_name=shot["project_name"], + ep_name=ep["name"] + " - " if ep is not None else "", + sequence_name=shot["sequence_name"], + shot_name=shot["name"] + ) log.info(msg) def _update_shot(self, data): @@ -430,7 +445,6 @@ class Listener: if asset_doc["data"].get("zou", {}).get("id")} zou_ids_and_asset_docs[shot["project_id"]] = project_doc gazu_project = gazu.project.get_project(shot["project_id"]) - gazu_project = gazu.project.get_project(shot['project_id']) # Update update_op_result = update_op_assets( @@ -454,12 +468,18 @@ class Listener: ) # Print message - gazu_project = gazu.project.get_project( + ep = None ep_id = shot["data"]["zou"].get("episode_id") + if ep_id and ep_id != "": + ep = gazu.asset.get_episode(ep_id) - msg = "Shot deleted: " - msg = msg + f"{gazu_project['name']} - " - msg = msg + f"{shot['name']}" + msg = "Shot deleted: {proj_name} - {ep_name}" \ + "{sequence_name} - {shot_name}".format( + proj_name=shot["data"]["zou"]["project_name"], + ep_name=ep["name"] + " - " if ep is not None else "", + sequence_name=shot["data"]["zou"]["sequence_name"], + shot_name=shot["name"] + ) log.info(msg) # == Task == @@ -472,14 +492,14 @@ class Listener: # Get gazu entity task = gazu.task.get_task(data["task_id"]) - # Find asset doc - episode = None + # Print message + ep = None ep_id = task.get("episode_id") if ep_id and ep_id != "": - episode = gazu.asset.get_episode(ep_id) + ep = gazu.asset.get_episode(ep_id) - parent_name = "" - if episode is not None: + parent_name = None + entity_type = None parent_name = episode['name'] + "_" parent_name = parent_name + \ task['sequence']['name'] + "_" + task['entity']['name'] @@ -496,13 +516,13 @@ class Listener: ) # Print message - msg = "Task created: " - msg = msg + f"{task['project']['name']} - " - if episode is not None: - msg = msg + f"{episode['name']}_" - msg = msg + f"{task['sequence']['name']}_" - msg = msg + f"{task['entity']['name']} - " - msg = msg + f"{task['task_type']['name']}" + msg = "Task created: {proj_name} - {entity_type}{parent_name}" \ + " - {task_name}".format( + proj_name=task["project"]["name"], + entity_type=entity_type + " - " if entity_type is not None else "", + parent_name=parent_name, + task_name=task["task_type"]["name"] + ) log.info(msg) def _update_task(self, data): @@ -533,19 +553,20 @@ class Listener: # Print message entity = gazu.entity.get_entity(task["zou"]["entity_id"]) - episode = None + ep = None ep_id = entity.get("episode_id") if ep_id and ep_id != "": - episode = gazu.asset.get_episode(ep_id) + ep = gazu.asset.get_episode(ep_id) - msg = "Task deleted: " - msg = msg + f"{shot['project_name']} - " - if episode is not None: - msg = msg + f"{episode['name']}_" - msg = msg + f"{shot['sequence_name']}_" - msg = msg + f"{shot['name']} - " - msg = msg + f"{task['type']}" + msg = "Task deleted: {proj_name} - {entity_type}{parent_name}" \ + " - {task_name}".format( + proj_name=task["zou"]["project"]["name"], + entity_type=entity_type + " - " if entity_type is not None else "", + parent_name=parent_name, + task_name=task["type"] + ) log.info(msg) + return From d153e6c224a6d2ea21b11a1c33e88b2c19049123 Mon Sep 17 00:00:00 2001 From: Jacob Danell Date: Tue, 28 Feb 2023 23:26:10 +0100 Subject: [PATCH 166/184] Split up the difference in name and type Assets/Shots generate Before it was only working for shots. Now it also works for Assets. I'm ding an elif as Kitsu now also have tasks for sequences, edits and other things. Will try and add those in at a later stage. --- openpype/modules/kitsu/utils/sync_service.py | 24 +++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/openpype/modules/kitsu/utils/sync_service.py b/openpype/modules/kitsu/utils/sync_service.py index b389d25c4f..7e7f3f557c 100644 --- a/openpype/modules/kitsu/utils/sync_service.py +++ b/openpype/modules/kitsu/utils/sync_service.py @@ -500,9 +500,15 @@ class Listener: parent_name = None entity_type = None - parent_name = episode['name'] + "_" - parent_name = parent_name + \ - task['sequence']['name'] + "_" + task['entity']['name'] + if task["task_type"]["for_entity"] == "Asset": + parent_name = task["entity"]["name"] + entity_type = task["entity_type"]["name"] + elif task["task_type"]["for_entity"] == "Shot": + parent_name = "{ep_name}{sequence_name} - {shot_name}".format( + ep_name=ep["name"] + " - " if ep is not None else "", + sequence_name=task["sequence"]["name"], + shot_name=task["entity"]["name"] + ) # Update asset tasks with new one asset_doc = get_asset_by_name(project_name, parent_name) @@ -558,6 +564,18 @@ class Listener: if ep_id and ep_id != "": ep = gazu.asset.get_episode(ep_id) + parent_name = None + entity_type = None + if task["task_type"]["for_entity"] == "Asset": + parent_name = task["entity"]["name"] + entity_type = task["entity_type"]["name"] + elif task["task_type"]["for_entity"] == "Shot": + parent_name = "{ep_name}{sequence_name} - {shot_name}".format( + ep_name=ep["name"] + " - " if ep is not None else "", + sequence_name=task["sequence"]["name"], + shot_name=task["entity"]["name"] + ) + msg = "Task deleted: {proj_name} - {entity_type}{parent_name}" \ " - {task_name}".format( proj_name=task["zou"]["project"]["name"], From 7965b91dafd59e0a75b498961d94aa8fdaa14467 Mon Sep 17 00:00:00 2001 From: Jacob Danell Date: Wed, 1 Mar 2023 11:02:25 +0100 Subject: [PATCH 167/184] Moved kitsu_project out of context loop --- .../plugins/publish/collect_kitsu_entities.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py b/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py index 1531c80e04..f68226a4a5 100644 --- a/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py +++ b/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py @@ -11,7 +11,13 @@ class CollectKitsuEntities(pyblish.api.ContextPlugin): def process(self, context): - kitsu_project = None + kitsu_project = gazu.project.get_project_by_name( + context.data["projectName"]) + if not kitsu_project: + raise ValueError("Project not found in kitsu!") + + context.data["kitsu_project"] = kitsu_project + self.log.debug("Collect kitsu project: {}".format(kitsu_project)) kitsu_entities_by_id = {} for instance in context: @@ -23,10 +29,10 @@ class CollectKitsuEntities(pyblish.api.ContextPlugin): if not zou_asset_data: raise ValueError("Zou asset data not found in OpenPype!") - kitsu_project = gazu.project.get_project( - zou_asset_data["project_id"]) - if not kitsu_project: - raise ValueError("Project not found in kitsu!") + task_name = instance.data.get("task") + if not task_name: + continue + entity_id = zou_asset_data["id"] entity = kitsu_entities_by_id.get(entity_id) @@ -63,5 +69,3 @@ class CollectKitsuEntities(pyblish.api.ContextPlugin): raise ValueError("Task not found in kitsu!") instance.data["kitsu_task"] = kitsu_task self.log.debug("Collect kitsu task: {}".format(kitsu_task)) - - context.data["kitsu_project"] = kitsu_project From 962d0783b06d804cacf1ce628d17e1d0836951b2 Mon Sep 17 00:00:00 2001 From: Jacob Danell Date: Wed, 1 Mar 2023 11:05:38 +0100 Subject: [PATCH 168/184] Fixed fetching of kitsu_task + moved data checks to the top of loop --- .../plugins/publish/collect_kitsu_entities.py | 30 +++++++++++-------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py b/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py index f68226a4a5..9b34bd15a9 100644 --- a/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py +++ b/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py @@ -33,6 +33,9 @@ class CollectKitsuEntities(pyblish.api.ContextPlugin): if not task_name: continue + zou_task_data = asset_doc["data"]["tasks"][task_name].get("zou") + self.log.debug( + "Collected zou task data: {}".format(zou_task_data)) entity_id = zou_asset_data["id"] entity = kitsu_entities_by_id.get(entity_id) @@ -45,25 +48,26 @@ class CollectKitsuEntities(pyblish.api.ContextPlugin): kitsu_entities_by_id[entity_id] = entity instance.data["entity"] = entity - - task_name = instance.data.get("task") - if not task_name: - continue - zou_task_data = asset_doc["data"]["tasks"][task_name].get("zou") self.log.debug( - "Collected zou task data: {}".format(zou_task_data)) - if not zou_task_data: + "Collect kitsu {}: {}".format(zou_asset_data["type"], entity) + ) + + if zou_task_data: + kitsu_task_id = zou_task_data["id"] + kitsu_task = kitsu_entities_by_id.get(kitsu_task_id) + if not kitsu_task: + kitsu_task = gazu.task.get_task(zou_task_data["id"]) + kitsu_entities_by_id[kitsu_task_id] = kitsu_task + else: kitsu_task_type = gazu.task.get_task_type_by_name(task_name) if not kitsu_task_type: raise ValueError( "Task type {} not found in Kitsu!".format(task_name) ) - continue - kitsu_task_id = zou_task_data["id"] - kitsu_task = kitsu_entities_by_id.get(kitsu_task_id) - if not kitsu_task: - kitsu_task = gazu.task.get_task(zou_task_data["id"]) - kitsu_entities_by_id[kitsu_task_id] = kitsu_task + + kitsu_task = gazu.task.get_task_by_name( + entity, kitsu_task_type + ) if not kitsu_task: raise ValueError("Task not found in kitsu!") From 8bf970e8b96b80c2cae601530d076b27d3a6d8f7 Mon Sep 17 00:00:00 2001 From: Jacob Danell Date: Wed, 1 Mar 2023 11:16:05 +0100 Subject: [PATCH 169/184] fixed hound's comments --- .../kitsu/plugins/publish/collect_kitsu_entities.py | 2 +- openpype/modules/kitsu/utils/sync_service.py | 9 ++++++--- openpype/modules/kitsu/utils/update_op_with_zou.py | 6 ++++-- openpype/modules/kitsu/utils/update_zou_with_op.py | 3 ++- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py b/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py index 9b34bd15a9..71ed563580 100644 --- a/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py +++ b/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py @@ -64,7 +64,7 @@ class CollectKitsuEntities(pyblish.api.ContextPlugin): raise ValueError( "Task type {} not found in Kitsu!".format(task_name) ) - + kitsu_task = gazu.task.get_task_by_name( entity, kitsu_task_type ) diff --git a/openpype/modules/kitsu/utils/sync_service.py b/openpype/modules/kitsu/utils/sync_service.py index 7e7f3f557c..9c5c9e24ec 100644 --- a/openpype/modules/kitsu/utils/sync_service.py +++ b/openpype/modules/kitsu/utils/sync_service.py @@ -101,7 +101,8 @@ class Listener: self.event_client, "sequence:delete", self._delete_sequence ) - gazu.events.add_listener(self.event_client, "shot:new", self._new_shot) + gazu.events.add_listener( + self.event_client, "shot:new", self._new_shot) gazu.events.add_listener( self.event_client, "shot:update", self._update_shot ) @@ -109,7 +110,8 @@ class Listener: self.event_client, "shot:delete", self._delete_shot ) - gazu.events.add_listener(self.event_client, "task:new", self._new_task) + gazu.events.add_listener( + self.event_client, "task:new", self._new_task) gazu.events.add_listener( self.event_client, "task:update", self._update_task ) @@ -515,7 +517,8 @@ class Listener: if asset_doc: asset_tasks = asset_doc["data"].get("tasks") task_type_name = task["task_type"]["name"] - asset_tasks[task_type_name] = {"type": task_type_name, "zou": task} + asset_tasks[task_type_name] = { + "type": task_type_name, "zou": task} self.dbcon.update_one( {"_id": asset_doc["_id"]}, {"$set": {"data.tasks": asset_tasks}} diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index 9368848532..6590d05a82 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -94,7 +94,8 @@ def update_op_assets( if not item_doc: # Create asset op_asset = create_op_asset(item) insert_result = dbcon.insert_one(op_asset) - item_doc = get_asset_by_id(project_name, insert_result.inserted_id) + item_doc = get_asset_by_id( + project_name, insert_result.inserted_id) # Update asset item_data = deepcopy(item_doc["data"]) @@ -339,7 +340,8 @@ def write_project_to_op(project: dict, dbcon: AvalonMongoDB) -> UpdateOne: ) -def sync_all_projects(login: str, password: str, ignore_projects: list = None): +def sync_all_projects( + login: str, password: str, ignore_projects: list = None): """Update all OP projects in DB with Zou data. Args: diff --git a/openpype/modules/kitsu/utils/update_zou_with_op.py b/openpype/modules/kitsu/utils/update_zou_with_op.py index b1a9b8b82c..617f037c1e 100644 --- a/openpype/modules/kitsu/utils/update_zou_with_op.py +++ b/openpype/modules/kitsu/utils/update_zou_with_op.py @@ -82,7 +82,8 @@ def sync_zou_from_op_project( f"x{project_doc['data']['resolutionHeight']}", } ) - gazu.project.update_project_data(zou_project, data=project_doc["data"]) + gazu.project.update_project_data( + zou_project, data=project_doc["data"]) gazu.project.update_project(zou_project) asset_types = gazu.asset.all_asset_types() From 0d981a61291d78f25c53425de4161d3f109f1505 Mon Sep 17 00:00:00 2001 From: Jacob Danell Date: Wed, 1 Mar 2023 11:42:18 +0100 Subject: [PATCH 170/184] Fixed hound's comments --- openpype/modules/kitsu/utils/sync_service.py | 41 ++++++++++---------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/openpype/modules/kitsu/utils/sync_service.py b/openpype/modules/kitsu/utils/sync_service.py index 9c5c9e24ec..da81a23495 100644 --- a/openpype/modules/kitsu/utils/sync_service.py +++ b/openpype/modules/kitsu/utils/sync_service.py @@ -501,10 +501,10 @@ class Listener: ep = gazu.asset.get_episode(ep_id) parent_name = None - entity_type = None + ent_type = None if task["task_type"]["for_entity"] == "Asset": parent_name = task["entity"]["name"] - entity_type = task["entity_type"]["name"] + ent_type = task["entity_type"]["name"] elif task["task_type"]["for_entity"] == "Shot": parent_name = "{ep_name}{sequence_name} - {shot_name}".format( ep_name=ep["name"] + " - " if ep is not None else "", @@ -525,12 +525,12 @@ class Listener: ) # Print message - msg = "Task created: {proj_name} - {entity_type}{parent_name}" \ - " - {task_name}".format( - proj_name=task["project"]["name"], - entity_type=entity_type + " - " if entity_type is not None else "", - parent_name=parent_name, - task_name=task["task_type"]["name"] + msg = "Task created: {proj} - {ent_type}{parent}" \ + " - {task}".format( + proj=task["project"]["name"], + ent_type=ent_type + " - " if ent_type is not None else "", + parent=parent_name, + task=task["task_type"]["name"] ) log.info(msg) @@ -568,23 +568,24 @@ class Listener: ep = gazu.asset.get_episode(ep_id) parent_name = None - entity_type = None + ent_type = None if task["task_type"]["for_entity"] == "Asset": parent_name = task["entity"]["name"] - entity_type = task["entity_type"]["name"] + ent_type = task["entity_type"]["name"] elif task["task_type"]["for_entity"] == "Shot": - parent_name = "{ep_name}{sequence_name} - {shot_name}".format( - ep_name=ep["name"] + " - " if ep is not None else "", - sequence_name=task["sequence"]["name"], - shot_name=task["entity"]["name"] + parent_name = "{ep}{sequence} - {shot}".format( + ep=ep["name"] + " - " if ep is not None else "", + sequence=task["sequence"]["name"], + shot=task["entity"]["name"] ) - msg = "Task deleted: {proj_name} - {entity_type}{parent_name}" \ - " - {task_name}".format( - proj_name=task["zou"]["project"]["name"], - entity_type=entity_type + " - " if entity_type is not None else "", - parent_name=parent_name, - task_name=task["type"] + ent_type=ent_type + " - " if ent_type is not None else "", + msg = "Task deleted: {proj} - {ent_type}{parent}" \ + " - {task}".format( + proj=task["zou"]["project"]["name"], + ent_type=ent_type, + parent=parent_name, + task=task["type"] ) log.info(msg) From 0beec8c3a710e7c3e950678a4c0b6606f4f079d7 Mon Sep 17 00:00:00 2001 From: Jacob Danell Date: Wed, 1 Mar 2023 11:44:55 +0100 Subject: [PATCH 171/184] Fixed hound's comments --- openpype/modules/kitsu/utils/sync_service.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/kitsu/utils/sync_service.py b/openpype/modules/kitsu/utils/sync_service.py index da81a23495..91ce84637d 100644 --- a/openpype/modules/kitsu/utils/sync_service.py +++ b/openpype/modules/kitsu/utils/sync_service.py @@ -579,7 +579,7 @@ class Listener: shot=task["entity"]["name"] ) - ent_type=ent_type + " - " if ent_type is not None else "", + ent_type=ent_type + " - " if ent_type is not None else "" msg = "Task deleted: {proj} - {ent_type}{parent}" \ " - {task}".format( proj=task["zou"]["project"]["name"], From 247778575f2e5c03cf055da288b444034bca4475 Mon Sep 17 00:00:00 2001 From: Jacob Danell Date: Thu, 2 Mar 2023 12:30:19 +0100 Subject: [PATCH 172/184] Project creation logs happens outside of write_project_to_op() function --- openpype/modules/kitsu/utils/sync_service.py | 12 ++++++------ openpype/modules/kitsu/utils/update_op_with_zou.py | 9 ++++++--- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/openpype/modules/kitsu/utils/sync_service.py b/openpype/modules/kitsu/utils/sync_service.py index 91ce84637d..172f7555ac 100644 --- a/openpype/modules/kitsu/utils/sync_service.py +++ b/openpype/modules/kitsu/utils/sync_service.py @@ -129,12 +129,9 @@ class Listener: """Create new project into OP DB.""" # Use update process to avoid duplicating code - self._update_project(data) + self._update_project(data, new_project=True) - # Print message - # - Happens in write_project_to_op() - - def _update_project(self, data): + def _update_project(self, data, new_project=False): """Update project into OP DB.""" # Get project entity project = gazu.project.get_project(data["project_id"]) @@ -147,6 +144,9 @@ class Listener: data["project_id"]) self.dbcon.bulk_write([update_project]) + if new_project: + log.info("Project created: {}".format(project["name"])) + def _delete_project(self, data): """Delete project.""" @@ -579,7 +579,7 @@ class Listener: shot=task["entity"]["name"] ) - ent_type=ent_type + " - " if ent_type is not None else "" + ent_type = ent_type + " - " if ent_type is not None else "" msg = "Task deleted: {proj} - {ent_type}{parent}" \ " - {task}".format( proj=task["zou"]["project"]["name"], diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index 6590d05a82..a559d8a19f 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -291,7 +291,6 @@ def write_project_to_op(project: dict, dbcon: AvalonMongoDB) -> UpdateOne: project_name = project["name"] project_dict = get_project(project_name) if not project_dict: - log.info("Project created: {}".format(project_name)) project_dict = create_project(project_name, project_name) # Project data and tasks @@ -405,12 +404,16 @@ def sync_project_from_kitsu(dbcon: AvalonMongoDB, project: dict): ] # Sync project. Create if doesn't exist + project_name = project["name"] + project_dict = get_project(project_name) + if not project_dict: + log.info("Project created: {}".format(project_name)) bulk_writes.append(write_project_to_op(project, dbcon)) # Try to find project document - project_name = project["name"] + if not project_dict: + project_dict = get_project(project_name) dbcon.Session["AVALON_PROJECT"] = project_name - project_dict = get_project(project_name) # Query all assets of the local project zou_ids_and_asset_docs = { From 5a9ea0d130ba6009804ac479e3ebe6ef4ff46906 Mon Sep 17 00:00:00 2001 From: Jacob Danell Date: Thu, 2 Mar 2023 12:36:57 +0100 Subject: [PATCH 173/184] Changed back from dict to doc for var names --- .../modules/kitsu/utils/update_op_with_zou.py | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index a559d8a19f..c215126dac 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -65,32 +65,32 @@ def set_op_project(dbcon: AvalonMongoDB, project_id: str): def update_op_assets( dbcon: AvalonMongoDB, gazu_project: dict, - project_dict: dict, + project_doc: dict, entities_list: List[dict], - asset_dict_ids: Dict[str, dict], + asset_doc_ids: Dict[str, dict], ) -> List[Dict[str, dict]]: """Update OpenPype assets. Set 'data' and 'parent' fields. Args: dbcon (AvalonMongoDB): Connection to DB - gazu_project dict): Dict of gazu, - project_dict dict): Dict of project, + gazu_project (dict): Dict of gazu, + project_doc (dict): Dict of project, entities_list (List[dict]): List of zou entities to update - asset_dict_ids (Dict[str, dict]): Dicts of [{zou_id: asset_doc}, ...] + asset_doc_ids (Dict[str, dict]): Dicts of [{zou_id: asset_doc}, ...] Returns: List[Dict[str, dict]]: List of (doc_id, update_dict) tuples """ - if not project_dict: + if not project_doc: return - project_name = project_dict["name"] + project_name = project_doc["name"] assets_with_update = [] for item in entities_list: # Check asset exists - item_doc = asset_dict_ids.get(item["id"]) + item_doc = asset_doc_ids.get(item["id"]) if not item_doc: # Create asset op_asset = create_op_asset(item) insert_result = dbcon.insert_one(op_asset) @@ -108,7 +108,7 @@ def update_op_assets( try: frame_in = int( item_data.pop( - "frame_in", project_dict["data"].get("frameStart") + "frame_in", project_doc["data"].get("frameStart") ) ) except (TypeError, ValueError): @@ -127,14 +127,14 @@ def update_op_assets( if frames_duration: frame_out = frame_in + frames_duration - 1 else: - frame_out = project_dict["data"].get("frameEnd", frame_in) + frame_out = project_doc["data"].get("frameEnd", frame_in) item_data["frameEnd"] = frame_out # Fps, fallback to project's value or default value (25.0) try: fps = float(item_data.get("fps")) except (TypeError, ValueError): fps = float(gazu_project.get( - "fps", project_dict["data"].get("fps", 25))) + "fps", project_doc["data"].get("fps", 25))) item_data["fps"] = fps # Resolution, fall back to project default match_res = re.match( @@ -145,27 +145,27 @@ def update_op_assets( item_data["resolutionWidth"] = int(match_res.group(1)) item_data["resolutionHeight"] = int(match_res.group(2)) else: - item_data["resolutionWidth"] = project_dict["data"].get( + item_data["resolutionWidth"] = project_doc["data"].get( "resolutionWidth") - item_data["resolutionHeight"] = project_dict["data"].get( + item_data["resolutionHeight"] = project_doc["data"].get( "resolutionHeight") # Properties that doesn't fully exist in Kitsu. # Guessing those property names below: # Pixel Aspect Ratio item_data["pixelAspect"] = item_data.get( - "pixel_aspect", project_dict["data"].get("pixelAspect")) + "pixel_aspect", project_doc["data"].get("pixelAspect")) # Handle Start item_data["handleStart"] = item_data.get( - "handle_start", project_dict["data"].get("handleStart")) + "handle_start", project_doc["data"].get("handleStart")) # Handle End item_data["handleEnd"] = item_data.get( - "handle_end", project_dict["data"].get("handleEnd")) + "handle_end", project_doc["data"].get("handleEnd")) # Clip In item_data["clipIn"] = item_data.get( - "clip_in", project_dict["data"].get("clipIn")) + "clip_in", project_doc["data"].get("clipIn")) # Clip Out item_data["clipOut"] = item_data.get( - "clip_out", project_dict["data"].get("clipOut")) + "clip_out", project_doc["data"].get("clipOut")) # Tasks tasks_list = [] @@ -209,7 +209,7 @@ def update_op_assets( # Root parent folder if exist visual_parent_doc_id = None if parent_zou_id is not None: - parent_zou_id_dict = asset_dict_ids.get(parent_zou_id) + parent_zou_id_dict = asset_doc_ids.get(parent_zou_id) if parent_zou_id_dict is not None: visual_parent_doc_id = ( parent_zou_id_dict.get("_id") @@ -233,7 +233,7 @@ def update_op_assets( item_data["parents"] = [] ancestor_id = parent_zou_id while ancestor_id is not None: - parent_doc = asset_dict_ids.get(ancestor_id) + parent_doc = asset_doc_ids.get(ancestor_id) if parent_doc is not None: item_data["parents"].insert(0, parent_doc["name"]) @@ -250,7 +250,7 @@ def update_op_assets( item_name = f"{item_data['parents'][-1]}_{item['name']}" # Update doc name - asset_dict_ids[item["id"]]["name"] = item_name + asset_doc_ids[item["id"]]["name"] = item_name else: item_name = item["name"] @@ -269,7 +269,7 @@ def update_op_assets( "$set": { "name": item_name, "data": item_data, - "parent": project_dict["_id"], + "parent": project_doc["_id"], } }, ) From 93eb9fce8609b0d6543a1f91ddb84907f89b63a3 Mon Sep 17 00:00:00 2001 From: Ember Light <49758407+EmberLightVFX@users.noreply.github.com> Date: Thu, 2 Mar 2023 12:38:37 +0100 Subject: [PATCH 174/184] Update openpype/modules/kitsu/utils/sync_service.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Félix David --- openpype/modules/kitsu/utils/sync_service.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/modules/kitsu/utils/sync_service.py b/openpype/modules/kitsu/utils/sync_service.py index 172f7555ac..1efebb2d47 100644 --- a/openpype/modules/kitsu/utils/sync_service.py +++ b/openpype/modules/kitsu/utils/sync_service.py @@ -178,10 +178,11 @@ class Listener: self._update_asset(data) # Print message - ep = None ep_id = asset.get("episode_id") if ep_id and ep_id != "": ep = gazu.asset.get_episode(ep_id) + else: + ep = None msg = "Asset created: {proj_name} - {ep_name}" \ "{asset_type_name} - {asset_name}".format( From 95e1f95bc1896547bde026f5c2a2517103e9e8e8 Mon Sep 17 00:00:00 2001 From: Jacob Danell Date: Thu, 2 Mar 2023 14:05:36 +0100 Subject: [PATCH 175/184] Moved all ep_dict code into one function --- openpype/modules/kitsu/utils/sync_service.py | 35 ++++++++------------ 1 file changed, 13 insertions(+), 22 deletions(-) diff --git a/openpype/modules/kitsu/utils/sync_service.py b/openpype/modules/kitsu/utils/sync_service.py index 1efebb2d47..d6bdb5391e 100644 --- a/openpype/modules/kitsu/utils/sync_service.py +++ b/openpype/modules/kitsu/utils/sync_service.py @@ -124,6 +124,11 @@ class Listener: log.info("Listening to Kitsu events...") gazu.events.run_client(self.event_client) + def get_ep_dict(self, ep_id): + if ep_id and ep_id != "": + return gazu.entity.get_entity(ep_id) + return + # == Project == def _new_project(self, data): """Create new project into OP DB.""" @@ -179,10 +184,7 @@ class Listener: # Print message ep_id = asset.get("episode_id") - if ep_id and ep_id != "": - ep = gazu.asset.get_episode(ep_id) - else: - ep = None + ep = self.get_ep_dict(ep_id) msg = "Asset created: {proj_name} - {ep_name}" \ "{asset_type_name} - {asset_name}".format( @@ -233,10 +235,8 @@ class Listener: ) # Print message - ep = None ep_id = asset["data"]["zou"].get("episode_id") - if ep_id and ep_id != "": - ep = gazu.asset.get_episode(ep_id) + ep = self.get_ep_dict(ep_id) msg = "Asset deleted: {proj_name} - {ep_name}" \ "{asset_type_name} - {asset_name}".format( @@ -334,10 +334,8 @@ class Listener: self._update_sequence(data) # Print message - ep = None ep_id = sequence.get("episode_id") - if ep_id and ep_id != "": - ep = gazu.asset.get_episode(ep_id) + ep = self.get_ep_dict(ep_id) msg = "Sequence created: {proj_name} - {ep_name}" \ "{sequence_name}".format( @@ -386,10 +384,8 @@ class Listener: ) # Print message - ep = None ep_id = sequence["data"]["zou"].get("episode_id") - if ep_id and ep_id != "": - ep = gazu.asset.get_episode(ep_id) + ep = self.get_ep_dict(ep_id) gazu_project = gazu.project.get_project( sequence["data"]["zou"]["project_id"]) @@ -418,9 +414,8 @@ class Listener: self._update_shot(data) # Print message - ep = None - if shot["episode_id"] and shot["episode_id"] != "": - ep = gazu.asset.get_episode(shot["episode_id"]) + ep_id = shot["episode_id"] + ep = self.get_ep_dict(ep_id) msg = "Shot created: {proj_name} - {ep_name}" \ "{sequence_name} - {shot_name}".format( @@ -471,10 +466,8 @@ class Listener: ) # Print message - ep = None ep_id = shot["data"]["zou"].get("episode_id") - if ep_id and ep_id != "": - ep = gazu.asset.get_episode(ep_id) + ep = self.get_ep_dict(ep_id) msg = "Shot deleted: {proj_name} - {ep_name}" \ "{sequence_name} - {shot_name}".format( @@ -496,10 +489,8 @@ class Listener: task = gazu.task.get_task(data["task_id"]) # Print message - ep = None ep_id = task.get("episode_id") - if ep_id and ep_id != "": - ep = gazu.asset.get_episode(ep_id) + ep = self.get_ep_dict(ep_id) parent_name = None ent_type = None From 966ba0166e349b2882cb5db1f686b6235abbd44b Mon Sep 17 00:00:00 2001 From: Jacob Danell Date: Thu, 2 Mar 2023 14:07:14 +0100 Subject: [PATCH 176/184] Fixed delete_task msg creation to work with assets and episodes --- openpype/modules/kitsu/utils/sync_service.py | 41 ++++++++++---------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/openpype/modules/kitsu/utils/sync_service.py b/openpype/modules/kitsu/utils/sync_service.py index d6bdb5391e..893d6a8b5e 100644 --- a/openpype/modules/kitsu/utils/sync_service.py +++ b/openpype/modules/kitsu/utils/sync_service.py @@ -554,31 +554,32 @@ class Listener: # Print message entity = gazu.entity.get_entity(task["zou"]["entity_id"]) - ep = None - ep_id = entity.get("episode_id") - if ep_id and ep_id != "": - ep = gazu.asset.get_episode(ep_id) + if entity["type"] == "Asset": + ep = self.get_ep_dict(entity["source_id"]) + + parent_name = "{ep}{entity_type} - {entity}".format( + ep=ep["name"] + " - " if ep is not None else "", + entity_type=task["zou"]["entity_type_name"], + entity=task["zou"]["entity_name"] + ) + elif entity["type"] == "Shot": + shot_dict = gazu.entity.get_entity( + task["zou"]["entity_id"]) + seq_dict = gazu.entity.get_entity( + shot_dict["parent_id"]) + ep = self.get_ep_dict(seq_dict["parent_id"]) - parent_name = None - ent_type = None - if task["task_type"]["for_entity"] == "Asset": - parent_name = task["entity"]["name"] - ent_type = task["entity_type"]["name"] - elif task["task_type"]["for_entity"] == "Shot": parent_name = "{ep}{sequence} - {shot}".format( ep=ep["name"] + " - " if ep is not None else "", - sequence=task["sequence"]["name"], - shot=task["entity"]["name"] + sequence=seq_dict["name"], + shot=shot_dict["name"] ) - ent_type = ent_type + " - " if ent_type is not None else "" - msg = "Task deleted: {proj} - {ent_type}{parent}" \ - " - {task}".format( - proj=task["zou"]["project"]["name"], - ent_type=ent_type, - parent=parent_name, - task=task["type"] - ) + msg = "Task deleted: {proj} - {parent} - {task}".format( + proj=task["zou"]["project_name"], + parent=parent_name, + task=task["zou"]["task_type_name"] + ) log.info(msg) return From 804cdcafd6268d84a3c7d2e887c9e05e5798dec4 Mon Sep 17 00:00:00 2001 From: Jacob Danell Date: Thu, 2 Mar 2023 19:33:11 +0100 Subject: [PATCH 177/184] Store the gazu asset data in OPs DB as sync_service does This isn't the most optimal way to do it but it makes sure the data is consistent through out the code until we can revision sync_service to only use the ID from the dict. --- openpype/modules/kitsu/utils/update_op_with_zou.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index c215126dac..6797df6344 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -175,7 +175,7 @@ def update_op_assets( elif item_type == "Shot": tasks_list = gazu.task.all_tasks_for_shot(item) item_data["tasks"] = { - t["task_type_name"]: {"type": t["task_type_name"], "zou": t} + t["task_type_name"]: {"type": t["task_type_name"], "zou": gazu.task.get_task(t["id"])} for t in tasks_list } From 7176be9f92e71ee6942971e25ffc72199c1eecf8 Mon Sep 17 00:00:00 2001 From: Jacob Danell Date: Thu, 2 Mar 2023 19:35:07 +0100 Subject: [PATCH 178/184] Log msg for new_task now work for both shot and assets --- openpype/modules/kitsu/utils/sync_service.py | 37 ++++++++++---------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/openpype/modules/kitsu/utils/sync_service.py b/openpype/modules/kitsu/utils/sync_service.py index 893d6a8b5e..1f12217d44 100644 --- a/openpype/modules/kitsu/utils/sync_service.py +++ b/openpype/modules/kitsu/utils/sync_service.py @@ -239,10 +239,10 @@ class Listener: ep = self.get_ep_dict(ep_id) msg = "Asset deleted: {proj_name} - {ep_name}" \ - "{asset_type_name} - {asset_name}".format( + "{type_name} - {asset_name}".format( proj_name=asset["data"]["zou"]["project_name"], ep_name=ep["name"] + " - " if ep is not None else "", - asset_type_name=asset["data"]["zou"]["asset_type_name"], + type_name=asset["data"]["zou"]["asset_type_name"], asset_name=asset["name"] ) log.info(msg) @@ -390,7 +390,7 @@ class Listener: gazu_project = gazu.project.get_project( sequence["data"]["zou"]["project_id"]) - msg = "Sequence created: {proj_name} - {ep_name}" \ + msg = "Sequence deleted: {proj_name} - {ep_name}" \ "{sequence_name}".format( proj_name=gazu_project["name"], ep_name=ep["name"] + " - " if ep is not None else "", @@ -493,9 +493,12 @@ class Listener: ep = self.get_ep_dict(ep_id) parent_name = None + asset_name = None ent_type = None + if task["task_type"]["for_entity"] == "Asset": parent_name = task["entity"]["name"] + asset_name = task["entity"]["name"] ent_type = task["entity_type"]["name"] elif task["task_type"]["for_entity"] == "Shot": parent_name = "{ep_name}{sequence_name} - {shot_name}".format( @@ -503,9 +506,14 @@ class Listener: sequence_name=task["sequence"]["name"], shot_name=task["entity"]["name"] ) + asset_name = "{ep_name}{sequence_name}_{shot_name}".format( + ep_name=ep["name"] + "_" if ep is not None else "", + sequence_name=task["sequence"]["name"], + shot_name=task["entity"]["name"] + ) # Update asset tasks with new one - asset_doc = get_asset_by_name(project_name, parent_name) + asset_doc = get_asset_by_name(project_name, asset_name) if asset_doc: asset_tasks = asset_doc["data"].get("tasks") task_type_name = task["task_type"]["name"] @@ -553,32 +561,25 @@ class Listener: # Print message entity = gazu.entity.get_entity(task["zou"]["entity_id"]) + ep = self.get_ep_dict(entity["source_id"]) if entity["type"] == "Asset": - ep = self.get_ep_dict(entity["source_id"]) - parent_name = "{ep}{entity_type} - {entity}".format( ep=ep["name"] + " - " if ep is not None else "", - entity_type=task["zou"]["entity_type_name"], - entity=task["zou"]["entity_name"] + entity_type=task["zou"]["entity_type"]["name"], + entity=task["zou"]["entity"]["name"] ) elif entity["type"] == "Shot": - shot_dict = gazu.entity.get_entity( - task["zou"]["entity_id"]) - seq_dict = gazu.entity.get_entity( - shot_dict["parent_id"]) - ep = self.get_ep_dict(seq_dict["parent_id"]) - parent_name = "{ep}{sequence} - {shot}".format( ep=ep["name"] + " - " if ep is not None else "", - sequence=seq_dict["name"], - shot=shot_dict["name"] + sequence=task["zou"]["sequence"]["name"], + shot=task["zou"]["entity"]["name"] ) msg = "Task deleted: {proj} - {parent} - {task}".format( - proj=task["zou"]["project_name"], + proj=task["zou"]["project"]["name"], parent=parent_name, - task=task["zou"]["task_type_name"] + task=name ) log.info(msg) From c50678bcb8c0a1cb2696fbb526d61cbe4261a361 Mon Sep 17 00:00:00 2001 From: Ember Light <49758407+EmberLightVFX@users.noreply.github.com> Date: Fri, 3 Mar 2023 10:34:17 +0100 Subject: [PATCH 179/184] Update openpype/modules/kitsu/utils/update_op_with_zou.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Félix David --- openpype/modules/kitsu/utils/update_op_with_zou.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index 6797df6344..73b7a4249d 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -175,7 +175,12 @@ def update_op_assets( elif item_type == "Shot": tasks_list = gazu.task.all_tasks_for_shot(item) item_data["tasks"] = { - t["task_type_name"]: {"type": t["task_type_name"], "zou": gazu.task.get_task(t["id"])} + item_data["tasks"] = { + t["task_type_name"]: { + "type": t["task_type_name"], + "zou": gazu.task.get_task(t["id"]), + } + } for t in tasks_list } From 8fc6978ea2f56778d794e213c541f89888b24795 Mon Sep 17 00:00:00 2001 From: Jacob Danell Date: Fri, 3 Mar 2023 11:10:12 +0100 Subject: [PATCH 180/184] Formatted with Black --- .../kitsu/actions/launcher_show_in_kitsu.py | 65 +++++----- .../publish/collect_kitsu_credential.py | 1 - .../plugins/publish/collect_kitsu_entities.py | 11 +- .../plugins/publish/integrate_kitsu_note.py | 5 +- .../plugins/publish/integrate_kitsu_review.py | 1 - openpype/modules/kitsu/utils/sync_service.py | 114 ++++++++++++------ .../modules/kitsu/utils/update_zou_with_op.py | 9 +- 7 files changed, 123 insertions(+), 83 deletions(-) diff --git a/openpype/modules/kitsu/actions/launcher_show_in_kitsu.py b/openpype/modules/kitsu/actions/launcher_show_in_kitsu.py index 11224f6e52..81d98cfffb 100644 --- a/openpype/modules/kitsu/actions/launcher_show_in_kitsu.py +++ b/openpype/modules/kitsu/actions/launcher_show_in_kitsu.py @@ -23,36 +23,37 @@ class ShowInKitsu(LauncherAction): return True def process(self, session, **kwargs): - # Context inputs project_name = session["AVALON_PROJECT"] asset_name = session.get("AVALON_ASSET", None) task_name = session.get("AVALON_TASK", None) - project = get_project(project_name=project_name, - fields=["data.zou_id"]) + project = get_project( + project_name=project_name, fields=["data.zou_id"] + ) if not project: raise RuntimeError("Project {} not found.".format(project_name)) project_zou_id = project["data"].get("zou_id") if not project_zou_id: raise RuntimeError( - "Project {} has no connected kitsu id.".format(project_name)) + "Project {} has no connected kitsu id.".format(project_name) + ) asset_zou_name = None asset_zou_id = None - asset_zou_type = 'Assets' + asset_zou_type = "Assets" task_zou_id = None - zou_sub_type = ['AssetType', 'Sequence'] + zou_sub_type = ["AssetType", "Sequence"] if asset_name: asset_zou_name = asset_name asset_fields = ["data.zou.id", "data.zou.type"] if task_name: asset_fields.append("data.tasks.{}.zou.id".format(task_name)) - asset = get_asset_by_name(project_name, - asset_name=asset_name, - fields=asset_fields) + asset = get_asset_by_name( + project_name, asset_name=asset_name, fields=asset_fields + ) asset_zou_data = asset["data"].get("zou") @@ -68,37 +69,43 @@ class ShowInKitsu(LauncherAction): task_zou_data = task_data.get("zou", {}) if not task_zou_data: self.log.debug( - "No zou task data for task: {}".format(task_name)) + "No zou task data for task: {}".format(task_name) + ) task_zou_id = task_zou_data["id"] # Define URL - url = self.get_url(project_id=project_zou_id, - asset_name=asset_zou_name, - asset_id=asset_zou_id, - asset_type=asset_zou_type, - task_id=task_zou_id) + url = self.get_url( + project_id=project_zou_id, + asset_name=asset_zou_name, + asset_id=asset_zou_id, + asset_type=asset_zou_type, + task_id=task_zou_id, + ) # Open URL in webbrowser self.log.info("Opening URL: {}".format(url)) - webbrowser.open(url, - # Try in new tab - new=2) + webbrowser.open( + url, + # Try in new tab + new=2, + ) - def get_url(self, - project_id, - asset_name=None, - asset_id=None, - asset_type=None, - task_id=None): - - shots_url = {'Shots', 'Sequence', 'Shot'} - sub_type = {'AssetType', 'Sequence'} + def get_url( + self, + project_id, + asset_name=None, + asset_id=None, + asset_type=None, + task_id=None, + ): + shots_url = {"Shots", "Sequence", "Shot"} + sub_type = {"AssetType", "Sequence"} kitsu_module = self.get_kitsu_module() # Get kitsu url with /api stripped kitsu_url = kitsu_module.server_url if kitsu_url.endswith("/api"): - kitsu_url = kitsu_url[:-len("/api")] + kitsu_url = kitsu_url[: -len("/api")] sub_url = f"/productions/{project_id}" asset_type_url = "shots" if asset_type in shots_url else "assets" @@ -121,6 +128,6 @@ class ShowInKitsu(LauncherAction): # Add search method if is a sub_type sub_url += f"/{asset_type_url}" if asset_type in sub_type: - sub_url += f'?search={asset_name}' + sub_url += f"?search={asset_name}" return f"{kitsu_url}{sub_url}" diff --git a/openpype/modules/kitsu/plugins/publish/collect_kitsu_credential.py b/openpype/modules/kitsu/plugins/publish/collect_kitsu_credential.py index b7f6f67a40..ac501dd47d 100644 --- a/openpype/modules/kitsu/plugins/publish/collect_kitsu_credential.py +++ b/openpype/modules/kitsu/plugins/publish/collect_kitsu_credential.py @@ -13,6 +13,5 @@ class CollectKitsuSession(pyblish.api.ContextPlugin): # rename log in # families = ["kitsu"] def process(self, context): - gazu.client.set_host(os.environ["KITSU_SERVER"]) gazu.log_in(os.environ["KITSU_LOGIN"], os.environ["KITSU_PWD"]) diff --git a/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py b/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py index 71ed563580..a0bd2b305b 100644 --- a/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py +++ b/openpype/modules/kitsu/plugins/publish/collect_kitsu_entities.py @@ -10,9 +10,9 @@ class CollectKitsuEntities(pyblish.api.ContextPlugin): label = "Kitsu entities" def process(self, context): - kitsu_project = gazu.project.get_project_by_name( - context.data["projectName"]) + context.data["projectName"] + ) if not kitsu_project: raise ValueError("Project not found in kitsu!") @@ -35,7 +35,8 @@ class CollectKitsuEntities(pyblish.api.ContextPlugin): zou_task_data = asset_doc["data"]["tasks"][task_name].get("zou") self.log.debug( - "Collected zou task data: {}".format(zou_task_data)) + "Collected zou task data: {}".format(zou_task_data) + ) entity_id = zou_asset_data["id"] entity = kitsu_entities_by_id.get(entity_id) @@ -44,7 +45,9 @@ class CollectKitsuEntities(pyblish.api.ContextPlugin): if not entity: raise ValueError( "{} was not found in kitsu!".format( - zou_asset_data["name"])) + zou_asset_data["name"] + ) + ) kitsu_entities_by_id[entity_id] = entity instance.data["entity"] = entity diff --git a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py index 006f0bc6d0..6702cbe7aa 100644 --- a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py +++ b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_note.py @@ -13,7 +13,6 @@ class IntegrateKitsuNote(pyblish.api.ContextPlugin): note_status_shortname = "wfa" def process(self, context): - # Get comment text body publish_comment = context.data.get("comment") if not publish_comment: @@ -45,9 +44,7 @@ class IntegrateKitsuNote(pyblish.api.ContextPlugin): # Add comment to kitsu task task_id = kitsu_task["id"] - self.log.debug( - "Add new note in taks id {}".format(task_id) - ) + self.log.debug("Add new note in taks id {}".format(task_id)) kitsu_comment = gazu.task.add_comment( task_id, note_status, comment=publish_comment ) diff --git a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py index d8f6cb7ac8..12482b5657 100644 --- a/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py +++ b/openpype/modules/kitsu/plugins/publish/integrate_kitsu_review.py @@ -12,7 +12,6 @@ class IntegrateKitsuReview(pyblish.api.InstancePlugin): optional = True def process(self, instance): - task = instance.data["kitsu_task"]["id"] comment = instance.data["kitsu_comment"]["id"] diff --git a/openpype/modules/kitsu/utils/sync_service.py b/openpype/modules/kitsu/utils/sync_service.py index 1f12217d44..34714fa4b3 100644 --- a/openpype/modules/kitsu/utils/sync_service.py +++ b/openpype/modules/kitsu/utils/sync_service.py @@ -102,7 +102,8 @@ class Listener: ) gazu.events.add_listener( - self.event_client, "shot:new", self._new_shot) + self.event_client, "shot:new", self._new_shot + ) gazu.events.add_listener( self.event_client, "shot:update", self._update_shot ) @@ -111,7 +112,8 @@ class Listener: ) gazu.events.add_listener( - self.event_client, "task:new", self._new_task) + self.event_client, "task:new", self._new_task + ) gazu.events.add_listener( self.event_client, "task:update", self._update_task ) @@ -146,7 +148,8 @@ class Listener: # Write into DB if update_project: self.dbcon.Session["AVALON_PROJECT"] = get_kitsu_project_name( - data["project_id"]) + data["project_id"] + ) self.dbcon.bulk_write([update_project]) if new_project: @@ -158,7 +161,8 @@ class Listener: collections = self.dbcon.database.list_collection_names() for collection in collections: project = self.dbcon.database[collection].find_one( - {"data.zou_id": data["project_id"]}) + {"data.zou_id": data["project_id"]} + ) if project: # Delete project collection self.dbcon.database[project["name"]].drop() @@ -186,13 +190,15 @@ class Listener: ep_id = asset.get("episode_id") ep = self.get_ep_dict(ep_id) - msg = "Asset created: {proj_name} - {ep_name}" \ + msg = ( + "Asset created: {proj_name} - {ep_name}" "{asset_type_name} - {asset_name}".format( proj_name=asset["project_name"], ep_name=ep["name"] + " - " if ep is not None else "", asset_type_name=asset["asset_type_name"], - asset_name=asset["name"] + asset_name=asset["name"], ) + ) log.info(msg) def _update_asset(self, data): @@ -216,8 +222,11 @@ class Listener: # Update update_op_result = update_op_assets( - self.dbcon, gazu_project, project_doc, - [asset], zou_ids_and_asset_docs + self.dbcon, + gazu_project, + project_doc, + [asset], + zou_ids_and_asset_docs, ) if update_op_result: asset_doc_id, asset_update = update_op_result[0] @@ -238,13 +247,15 @@ class Listener: ep_id = asset["data"]["zou"].get("episode_id") ep = self.get_ep_dict(ep_id) - msg = "Asset deleted: {proj_name} - {ep_name}" \ + msg = ( + "Asset deleted: {proj_name} - {ep_name}" "{type_name} - {asset_name}".format( proj_name=asset["data"]["zou"]["project_name"], ep_name=ep["name"] + " - " if ep is not None else "", type_name=asset["data"]["zou"]["asset_type_name"], - asset_name=asset["name"] + asset_name=asset["name"], ) + ) log.info(msg) # == Episode == @@ -264,8 +275,7 @@ class Listener: # Print message msg = "Episode created: {proj_name} - {ep_name}".format( - proj_name=ep["project_name"], - ep_name=ep["name"] + proj_name=ep["project_name"], ep_name=ep["name"] ) log.info(msg) @@ -290,8 +300,11 @@ class Listener: # Update update_op_result = update_op_assets( - self.dbcon, gazu_project, project_doc, - [ep], zou_ids_and_asset_docs + self.dbcon, + gazu_project, + project_doc, + [ep], + zou_ids_and_asset_docs, ) if update_op_result: asset_doc_id, asset_update = update_op_result[0] @@ -310,11 +323,11 @@ class Listener: # Print message project = gazu.project.get_project( - ep["data"]["zou"]["project_id"]) + ep["data"]["zou"]["project_id"] + ) msg = "Episode deleted: {proj_name} - {ep_name}".format( - proj_name=project["name"], - ep_name=ep["name"] + proj_name=project["name"], ep_name=ep["name"] ) log.info(msg) @@ -337,12 +350,14 @@ class Listener: ep_id = sequence.get("episode_id") ep = self.get_ep_dict(ep_id) - msg = "Sequence created: {proj_name} - {ep_name}" \ + msg = ( + "Sequence created: {proj_name} - {ep_name}" "{sequence_name}".format( proj_name=sequence["project_name"], ep_name=ep["name"] + " - " if ep is not None else "", - sequence_name=sequence["name"] + sequence_name=sequence["name"], ) + ) log.info(msg) def _update_sequence(self, data): @@ -366,8 +381,11 @@ class Listener: # Update update_op_result = update_op_assets( - self.dbcon, gazu_project, project_doc, - [sequence], zou_ids_and_asset_docs + self.dbcon, + gazu_project, + project_doc, + [sequence], + zou_ids_and_asset_docs, ) if update_op_result: asset_doc_id, asset_update = update_op_result[0] @@ -388,14 +406,17 @@ class Listener: ep = self.get_ep_dict(ep_id) gazu_project = gazu.project.get_project( - sequence["data"]["zou"]["project_id"]) + sequence["data"]["zou"]["project_id"] + ) - msg = "Sequence deleted: {proj_name} - {ep_name}" \ + msg = ( + "Sequence deleted: {proj_name} - {ep_name}" "{sequence_name}".format( proj_name=gazu_project["name"], ep_name=ep["name"] + " - " if ep is not None else "", - sequence_name=sequence["name"] + sequence_name=sequence["name"], ) + ) log.info(msg) # == Shot == @@ -417,13 +438,15 @@ class Listener: ep_id = shot["episode_id"] ep = self.get_ep_dict(ep_id) - msg = "Shot created: {proj_name} - {ep_name}" \ + msg = ( + "Shot created: {proj_name} - {ep_name}" "{sequence_name} - {shot_name}".format( proj_name=shot["project_name"], ep_name=ep["name"] + " - " if ep is not None else "", sequence_name=shot["sequence_name"], - shot_name=shot["name"] + shot_name=shot["name"], ) + ) log.info(msg) def _update_shot(self, data): @@ -440,14 +463,18 @@ class Listener: zou_ids_and_asset_docs = { asset_doc["data"]["zou"]["id"]: asset_doc for asset_doc in get_assets(project_name) - if asset_doc["data"].get("zou", {}).get("id")} + if asset_doc["data"].get("zou", {}).get("id") + } zou_ids_and_asset_docs[shot["project_id"]] = project_doc gazu_project = gazu.project.get_project(shot["project_id"]) # Update update_op_result = update_op_assets( - self.dbcon, gazu_project, project_doc, - [shot], zou_ids_and_asset_docs + self.dbcon, + gazu_project, + project_doc, + [shot], + zou_ids_and_asset_docs, ) if update_op_result: @@ -469,13 +496,15 @@ class Listener: ep_id = shot["data"]["zou"].get("episode_id") ep = self.get_ep_dict(ep_id) - msg = "Shot deleted: {proj_name} - {ep_name}" \ + msg = ( + "Shot deleted: {proj_name} - {ep_name}" "{sequence_name} - {shot_name}".format( proj_name=shot["data"]["zou"]["project_name"], ep_name=ep["name"] + " - " if ep is not None else "", sequence_name=shot["data"]["zou"]["sequence_name"], - shot_name=shot["name"] + shot_name=shot["name"], ) + ) log.info(msg) # == Task == @@ -504,12 +533,12 @@ class Listener: parent_name = "{ep_name}{sequence_name} - {shot_name}".format( ep_name=ep["name"] + " - " if ep is not None else "", sequence_name=task["sequence"]["name"], - shot_name=task["entity"]["name"] + shot_name=task["entity"]["name"], ) asset_name = "{ep_name}{sequence_name}_{shot_name}".format( ep_name=ep["name"] + "_" if ep is not None else "", sequence_name=task["sequence"]["name"], - shot_name=task["entity"]["name"] + shot_name=task["entity"]["name"], ) # Update asset tasks with new one @@ -518,20 +547,24 @@ class Listener: asset_tasks = asset_doc["data"].get("tasks") task_type_name = task["task_type"]["name"] asset_tasks[task_type_name] = { - "type": task_type_name, "zou": task} + "type": task_type_name, + "zou": task, + } self.dbcon.update_one( {"_id": asset_doc["_id"]}, - {"$set": {"data.tasks": asset_tasks}} + {"$set": {"data.tasks": asset_tasks}}, ) # Print message - msg = "Task created: {proj} - {ent_type}{parent}" \ + msg = ( + "Task created: {proj} - {ent_type}{parent}" " - {task}".format( proj=task["project"]["name"], ent_type=ent_type + " - " if ent_type is not None else "", parent=parent_name, - task=task["task_type"]["name"] + task=task["task_type"]["name"], ) + ) log.info(msg) def _update_task(self, data): @@ -567,19 +600,19 @@ class Listener: parent_name = "{ep}{entity_type} - {entity}".format( ep=ep["name"] + " - " if ep is not None else "", entity_type=task["zou"]["entity_type"]["name"], - entity=task["zou"]["entity"]["name"] + entity=task["zou"]["entity"]["name"], ) elif entity["type"] == "Shot": parent_name = "{ep}{sequence} - {shot}".format( ep=ep["name"] + " - " if ep is not None else "", sequence=task["zou"]["sequence"]["name"], - shot=task["zou"]["entity"]["name"] + shot=task["zou"]["entity"]["name"], ) msg = "Task deleted: {proj} - {parent} - {task}".format( proj=task["zou"]["project"]["name"], parent=parent_name, - task=name + task=name, ) log.info(msg) @@ -593,6 +626,7 @@ def start_listeners(login: str, password: str): login (str): Kitsu user login password (str): Kitsu user password """ + # Refresh token every week def refresh_token_every_week(): log.info("Refreshing token...") diff --git a/openpype/modules/kitsu/utils/update_zou_with_op.py b/openpype/modules/kitsu/utils/update_zou_with_op.py index 617f037c1e..be931af233 100644 --- a/openpype/modules/kitsu/utils/update_zou_with_op.py +++ b/openpype/modules/kitsu/utils/update_zou_with_op.py @@ -83,7 +83,8 @@ def sync_zou_from_op_project( } ) gazu.project.update_project_data( - zou_project, data=project_doc["data"]) + zou_project, data=project_doc["data"] + ) gazu.project.update_project(zou_project) asset_types = gazu.asset.all_asset_types() @@ -99,8 +100,7 @@ def sync_zou_from_op_project( project_module_settings = get_project_settings(project_name)["kitsu"] dbcon.Session["AVALON_PROJECT"] = project_name asset_docs = { - asset_doc["_id"]: asset_doc - for asset_doc in get_assets(project_name) + asset_doc["_id"]: asset_doc for asset_doc in get_assets(project_name) } # Create new assets @@ -176,7 +176,8 @@ def sync_zou_from_op_project( frame_in=doc["data"]["frameStart"], frame_out=doc["data"]["frameEnd"], nb_frames=( - doc["data"]["frameEnd"] - doc["data"]["frameStart"] + 1), + doc["data"]["frameEnd"] - doc["data"]["frameStart"] + 1 + ), ) elif match.group(2): # Sequence From 67bc287321fd03287aedf222e6d9c7ebf25e3332 Mon Sep 17 00:00:00 2001 From: Jacob Danell Date: Fri, 3 Mar 2023 11:12:17 +0100 Subject: [PATCH 181/184] Fix hound comments --- openpype/modules/kitsu/utils/update_op_with_zou.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/openpype/modules/kitsu/utils/update_op_with_zou.py b/openpype/modules/kitsu/utils/update_op_with_zou.py index 73b7a4249d..053e803ff3 100644 --- a/openpype/modules/kitsu/utils/update_op_with_zou.py +++ b/openpype/modules/kitsu/utils/update_op_with_zou.py @@ -175,12 +175,12 @@ def update_op_assets( elif item_type == "Shot": tasks_list = gazu.task.all_tasks_for_shot(item) item_data["tasks"] = { - item_data["tasks"] = { - t["task_type_name"]: { - "type": t["task_type_name"], - "zou": gazu.task.get_task(t["id"]), + item_data["tasks"] = { + t["task_type_name"]: { + "type": t["task_type_name"], + "zou": gazu.task.get_task(t["id"]), + } } - } for t in tasks_list } From 67e8f59935a7a1824aceb71cdc32e354c8a33a98 Mon Sep 17 00:00:00 2001 From: Ynbot Date: Sat, 4 Mar 2023 03:27:48 +0000 Subject: [PATCH 182/184] [Automated] Bump version --- openpype/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/version.py b/openpype/version.py index 4d6f3d43e4..2939ddbbac 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.15.2-nightly.3" +__version__ = "3.15.2-nightly.4" From 5efc9e0ff0cdf0f410b7a0b92b27cc2ed03256e2 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 6 Mar 2023 11:00:02 +0100 Subject: [PATCH 183/184] Editorial: Fix tasks removal (#4558) Fix tasks removal in editorial --- .../publish/extract_hierarchy_avalon.py | 52 +++++++++++++------ 1 file changed, 36 insertions(+), 16 deletions(-) diff --git a/openpype/plugins/publish/extract_hierarchy_avalon.py b/openpype/plugins/publish/extract_hierarchy_avalon.py index b2a6adc210..493780645c 100644 --- a/openpype/plugins/publish/extract_hierarchy_avalon.py +++ b/openpype/plugins/publish/extract_hierarchy_avalon.py @@ -135,6 +135,38 @@ class ExtractHierarchyToAvalon(pyblish.api.ContextPlugin): ) return project_doc + def _prepare_new_tasks(self, asset_doc, entity_data): + new_tasks = entity_data.get("tasks") or {} + if not asset_doc: + return new_tasks + + old_tasks = asset_doc.get("data", {}).get("tasks") + # Just use new tasks if old are not available + if not old_tasks: + return new_tasks + + output = deepcopy(old_tasks) + # Create mapping of lowered task names from old tasks + cur_task_low_mapping = { + task_name.lower(): task_name + for task_name in old_tasks + } + # Add/update tasks from new entity data + for task_name, task_info in new_tasks.items(): + task_info = deepcopy(task_info) + task_name_low = task_name.lower() + # Add new task + if task_name_low not in cur_task_low_mapping: + output[task_name] = task_info + continue + + # Update existing task with new info + mapped_task_name = cur_task_low_mapping.pop(task_name_low) + src_task_info = output.pop(mapped_task_name) + src_task_info.update(task_info) + output[task_name] = src_task_info + return output + def sync_asset( self, asset_name, @@ -170,11 +202,12 @@ class ExtractHierarchyToAvalon(pyblish.api.ContextPlugin): data["parents"] = parents asset_doc = asset_docs_by_name.get(asset_name) + + # Tasks + data["tasks"] = self._prepare_new_tasks(asset_doc, entity_data) + # --- Create/Unarchive asset and end --- if not asset_doc: - # Just use tasks from entity data as they are - # - this is different from the case when tasks are updated - data["tasks"] = entity_data.get("tasks") or {} archived_asset_doc = None for archived_entity in archived_asset_docs_by_name[asset_name]: archived_parents = ( @@ -201,19 +234,6 @@ class ExtractHierarchyToAvalon(pyblish.api.ContextPlugin): if "data" not in asset_doc: asset_doc["data"] = {} cur_entity_data = asset_doc["data"] - cur_entity_tasks = cur_entity_data.get("tasks") or {} - - # Tasks - data["tasks"] = {} - new_tasks = entity_data.get("tasks") or {} - for task_name, task_info in new_tasks.items(): - task_info = deepcopy(task_info) - if task_name in cur_entity_tasks: - src_task_info = deepcopy(cur_entity_tasks[task_name]) - src_task_info.update(task_info) - task_info = src_task_info - - data["tasks"][task_name] = task_info changes = {} for key, value in data.items(): From 08c71380709cf672e4b930b351a0671331521610 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 6 Mar 2023 11:13:57 +0100 Subject: [PATCH 184/184] Nuke: moving deepcopy to abstraction --- openpype/pipeline/colorspace.py | 31 ++++++++++---------- openpype/pipeline/publish/publish_plugins.py | 5 ++-- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/openpype/pipeline/colorspace.py b/openpype/pipeline/colorspace.py index 6f68bdc5bf..2085e2d37f 100644 --- a/openpype/pipeline/colorspace.py +++ b/openpype/pipeline/colorspace.py @@ -335,9 +335,10 @@ def get_imageio_config( get_template_data_from_session) anatomy_data = get_template_data_from_session() + formatting_data = deepcopy(anatomy_data) # add project roots to anatomy data - anatomy_data["root"] = anatomy.roots - anatomy_data["platform"] = platform.system().lower() + formatting_data["root"] = anatomy.roots + formatting_data["platform"] = platform.system().lower() # get colorspace settings imageio_global, imageio_host = _get_imageio_settings( @@ -347,7 +348,7 @@ def get_imageio_config( if config_host.get("enabled"): config_data = _get_config_data( - config_host["filepath"], anatomy_data + config_host["filepath"], formatting_data ) else: config_data = None @@ -356,7 +357,7 @@ def get_imageio_config( # get config path from either global or host_name config_global = imageio_global["ocio_config"] config_data = _get_config_data( - config_global["filepath"], anatomy_data + config_global["filepath"], formatting_data ) if not config_data: @@ -372,12 +373,12 @@ def _get_config_data(path_list, anatomy_data): """Return first existing path in path list. If template is used in path inputs, - then it is formated by anatomy data + then it is formatted by anatomy data and environment variables Args: path_list (list[str]): list of abs paths - anatomy_data (dict): formating data + anatomy_data (dict): formatting data Returns: dict: config data @@ -389,30 +390,30 @@ def _get_config_data(path_list, anatomy_data): # first try host config paths for path_ in path_list: - formated_path = _format_path(path_, formatting_data) + formatted_path = _format_path(path_, formatting_data) - if not os.path.exists(formated_path): + if not os.path.exists(formatted_path): continue return { - "path": os.path.normpath(formated_path), + "path": os.path.normpath(formatted_path), "template": path_ } -def _format_path(tempate_path, formatting_data): - """Single template path formating. +def _format_path(template_path, formatting_data): + """Single template path formatting. Args: - tempate_path (str): template string + template_path (str): template string formatting_data (dict): data to be used for - template formating + template formatting Returns: - str: absolute formated path + str: absolute formatted path """ # format path for anatomy keys - formatted_path = StringTemplate(tempate_path).format( + formatted_path = StringTemplate(template_path).format( formatting_data) return os.path.abspath(formatted_path) diff --git a/openpype/pipeline/publish/publish_plugins.py b/openpype/pipeline/publish/publish_plugins.py index 2df98221ba..331235fadc 100644 --- a/openpype/pipeline/publish/publish_plugins.py +++ b/openpype/pipeline/publish/publish_plugins.py @@ -1,4 +1,3 @@ -from copy import deepcopy import inspect from abc import ABCMeta from pprint import pformat @@ -311,7 +310,7 @@ class ColormanagedPyblishPluginMixin(object): @staticmethod def get_colorspace_settings(context): - """Retuns solved settings for the host context. + """Returns solved settings for the host context. Args: context (publish.Context): publishing context @@ -324,7 +323,7 @@ class ColormanagedPyblishPluginMixin(object): project_name = context.data["projectName"] host_name = context.data["hostName"] - anatomy_data = deepcopy(context.data["anatomyData"]) + anatomy_data = context.data["anatomyData"] project_settings_ = context.data["project_settings"] config_data = get_imageio_config(