From 58808104280eba59fed182af0849a66e366450f3 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 16 Jan 2023 13:44:57 +0100 Subject: [PATCH 01/82] 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 02/82] 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 03/82] 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 04/82] 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 05/82] 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 06/82] 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 07/82] 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 08/82] 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 09/82] 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 10/82] 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 11/82] 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 12/82] 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 13/82] 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 14/82] 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 15/82] 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 16/82] 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 17/82] 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 18/82] 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 19/82] 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 20/82] 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 21/82] 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 22/82] 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 23/82] 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 24/82] 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 25/82] 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 26/82] 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 27/82] 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 28/82] 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 29/82] 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 30/82] 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 31/82] 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 32/82] 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 33/82] 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 34/82] 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 35/82] 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 36/82] 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 37/82] 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 38/82] 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 59c3510a0219fe726e52816e4d1ff20015455794 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 22 Feb 2023 11:26:06 +0100 Subject: [PATCH 39/82] don't add opacity to layers info --- openpype/hosts/tvpaint/api/lib.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/tvpaint/api/lib.py b/openpype/hosts/tvpaint/api/lib.py index 312a211d49..a0cf761870 100644 --- a/openpype/hosts/tvpaint/api/lib.py +++ b/openpype/hosts/tvpaint/api/lib.py @@ -50,7 +50,8 @@ def parse_layers_data(data): "group_id": int(group_id), "visible": visible == "ON", "position": int(position), - "opacity": int(opacity), + # Opacity from 'tv_layerinfo' is always set to '0' so it's unusable + # "opacity": int(opacity), "name": name, "type": layer_type, "frame_start": int(frame_start), From 0f8a9c58a758fda407d777232112b7723d0e4332 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 22 Feb 2023 11:26:22 +0100 Subject: [PATCH 40/82] added is_current information to layers info --- openpype/hosts/tvpaint/api/lib.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/tvpaint/api/lib.py b/openpype/hosts/tvpaint/api/lib.py index a0cf761870..49846d7f29 100644 --- a/openpype/hosts/tvpaint/api/lib.py +++ b/openpype/hosts/tvpaint/api/lib.py @@ -43,7 +43,7 @@ def parse_layers_data(data): layer_id, group_id, visible, position, opacity, name, layer_type, frame_start, frame_end, prelighttable, postlighttable, - selected, editable, sencil_state + selected, editable, sencil_state, is_current ) = layer_raw.split("|") layer = { "layer_id": int(layer_id), @@ -60,7 +60,8 @@ def parse_layers_data(data): "postlighttable": postlighttable == "1", "selected": selected == "1", "editable": editable == "1", - "sencil_state": sencil_state + "sencil_state": sencil_state, + "is_current": is_current == "1" } layers.append(layer) return layers @@ -88,15 +89,17 @@ def get_layers_data_george_script(output_filepath, layer_ids=None): " selected editable sencilState" ), # Check if layer ID match `tv_LayerCurrentID` + "is_current=0", "IF CMP(current_layer_id, layer_id)==1", # - mark layer as selected if layer id match to current layer id + "is_current=1", "selected=1", "END", # Prepare line with data separated by "|" ( "line = layer_id'|'group_id'|'visible'|'position'|'opacity'|'" "name'|'type'|'startFrame'|'endFrame'|'prelighttable'|'" - "postlighttable'|'selected'|'editable'|'sencilState" + "postlighttable'|'selected'|'editable'|'sencilState'|'is_current" ), # Write data to output file "tv_writetextfile \"strict\" \"append\" '\"'output_path'\"' line", From 80151ca8800ca3fdcbcdebc092f27f6277201aff Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 22 Feb 2023 11:39:50 +0100 Subject: [PATCH 41/82] disable review on render layer/pass by default --- openpype/settings/defaults/project_settings/tvpaint.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/settings/defaults/project_settings/tvpaint.json b/openpype/settings/defaults/project_settings/tvpaint.json index e06a67a254..9173a8c3d5 100644 --- a/openpype/settings/defaults/project_settings/tvpaint.json +++ b/openpype/settings/defaults/project_settings/tvpaint.json @@ -31,13 +31,13 @@ "default_variants": [] }, "create_render_layer": { - "mark_for_review": true, + "mark_for_review": false, "default_pass_name": "beauty", "default_variant": "Main", "default_variants": [] }, "create_render_pass": { - "mark_for_review": true, + "mark_for_review": false, "default_variant": "Main", "default_variants": [] }, From abc36c8357e0773e35b5c312663d80ac3afbbfb1 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Wed, 22 Feb 2023 12:08:08 +0100 Subject: [PATCH 42/82] fix detailed descriptions --- openpype/hosts/tvpaint/plugins/create/create_render.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/tvpaint/plugins/create/create_render.py b/openpype/hosts/tvpaint/plugins/create/create_render.py index 7e85977b11..d996c06818 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_render.py +++ b/openpype/hosts/tvpaint/plugins/create/create_render.py @@ -66,7 +66,7 @@ RENDER_LAYER_DETAILED_DESCRIPTIONS = ( Be aware Render Layer is not TVPaint layer. All TVPaint layers in the scene with the color group id are rendered in the -beauty pass. To create sub passes use Render Layer creator which is +beauty pass. To create sub passes use Render Pass creator which is dependent on existence of render layer instance. The group can represent an asset (tree) or different part of scene that consist @@ -82,8 +82,8 @@ could be Render Layer which has 'Arm', 'Head' and 'Body' as Render Passes. RENDER_PASS_DETAILED_DESCRIPTIONS = ( """Render Pass is sub part of Render Layer. -Render Pass can consist of one or more TVPaint layers. Render Layers must -belong to a Render Layer. Marker TVPaint layers will change it's group color +Render Pass can consist of one or more TVPaint layers. Render Pass must +belong to a Render Layer. Marked TVPaint layers will change it's group color to match group color of Render Layer. """ ) From 79a40902e0a16518097240dd8bffd19c90fecbdd Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 23 Feb 2023 14:49:02 +0100 Subject: [PATCH 43/82] Better error message --- openpype/hosts/tvpaint/plugins/create/create_render.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/tvpaint/plugins/create/create_render.py b/openpype/hosts/tvpaint/plugins/create/create_render.py index d996c06818..563d16f847 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_render.py +++ b/openpype/hosts/tvpaint/plugins/create/create_render.py @@ -461,7 +461,10 @@ class CreateRenderPass(TVPaintCreator): "render_layer_instance_id" ) if not render_layer_instance_id: - raise CreatorError("Missing RenderLayer instance") + raise CreatorError(( + "You cannot create a Render Pass without a Render Layer." + " Please select one first" + )) render_layer_instance = self.create_context.instances_by_id.get( render_layer_instance_id From ba792f648b6aeea6299a5137afd23de4dc677a3f Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 23 Feb 2023 14:52:30 +0100 Subject: [PATCH 44/82] use 'list_instances' instead of existing instances --- .../hosts/tvpaint/plugins/create/create_render.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/tvpaint/plugins/create/create_render.py b/openpype/hosts/tvpaint/plugins/create/create_render.py index 563d16f847..6a3788cc08 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_render.py +++ b/openpype/hosts/tvpaint/plugins/create/create_render.py @@ -601,13 +601,16 @@ class CreateRenderPass(TVPaintCreator): ] def get_pre_create_attr_defs(self): + # Find available Render Layers + # - instances are created after creators reset + current_instances = self.host.list_instances() render_layers = [ { - "value": instance.id, - "label": instance.label + "value": instance["instance_id"], + "label": instance["subset"] } - for instance in self.create_context.instances - if instance.creator_identifier == CreateRenderlayer.identifier + for instance in current_instances + if instance["creator_identifier"] == CreateRenderlayer.identifier ] if not render_layers: render_layers.append({"value": None, "label": "N/A"}) From 8218e18edf352d377c901197ecf9408988e974e2 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 23 Feb 2023 14:52:53 +0100 Subject: [PATCH 45/82] added label with hint for artist --- openpype/hosts/tvpaint/plugins/create/create_render.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openpype/hosts/tvpaint/plugins/create/create_render.py b/openpype/hosts/tvpaint/plugins/create/create_render.py index 6a3788cc08..d356a07687 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_render.py +++ b/openpype/hosts/tvpaint/plugins/create/create_render.py @@ -41,6 +41,7 @@ from openpype.client import get_asset_by_name from openpype.lib import ( prepare_template_data, AbstractAttrDef, + UILabelDef, UISeparatorDef, EnumDef, TextDef, @@ -621,6 +622,9 @@ class CreateRenderPass(TVPaintCreator): label="Render Layer", items=render_layers ), + UILabelDef( + "NOTE: Try to hit refresh if you don't see a Render Layer" + ), BoolDef( "mark_for_review", label="Review", From 227f1402fe03590b4350124db0a3894335967c04 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 23 Feb 2023 14:57:34 +0100 Subject: [PATCH 46/82] change how instance attributes are filled --- .../tvpaint/plugins/create/create_render.py | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/tvpaint/plugins/create/create_render.py b/openpype/hosts/tvpaint/plugins/create/create_render.py index d356a07687..9711024c79 100644 --- a/openpype/hosts/tvpaint/plugins/create/create_render.py +++ b/openpype/hosts/tvpaint/plugins/create/create_render.py @@ -633,7 +633,34 @@ class CreateRenderPass(TVPaintCreator): ] def get_instance_attr_defs(self): - return self.get_pre_create_attr_defs() + # Find available Render Layers + current_instances = self.create_context.instances + render_layers = [ + { + "value": instance.id, + "label": instance.label + } + for instance in current_instances + if instance.creator_identifier == CreateRenderlayer.identifier + ] + if not render_layers: + render_layers.append({"value": None, "label": "N/A"}) + + return [ + EnumDef( + "render_layer_instance_id", + label="Render Layer", + items=render_layers + ), + UILabelDef( + "NOTE: Try to hit refresh if you don't see a Render Layer" + ), + BoolDef( + "mark_for_review", + label="Review", + default=self.mark_for_review + ) + ] class TVPaintAutoDetectRenderCreator(TVPaintCreator): From eae25bbf0b4dcf4b62bbdeab8d61ce9f4f82cfd9 Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Thu, 23 Feb 2023 11:24:36 +0000 Subject: [PATCH 47/82] 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 48/82] 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 49/82] 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 50/82] 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 51/82] 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 52/82] 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 53/82] 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 54/82] 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 55/82] 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 56/82] 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 57/82] 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 58/82] 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 59/82] 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 60/82] 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 61/82] 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 62/82] 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 9bd3ff7184c14394057e96568fff574a5e0a22cb Mon Sep 17 00:00:00 2001 From: Toke Stuart Jepsen Date: Fri, 24 Feb 2023 17:51:24 +0000 Subject: [PATCH 63/82] 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 64/82] 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 65/82] 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 66/82] 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 67/82] 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 68/82] 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 69/82] 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 70/82] 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 71/82] [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 72/82] 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 73/82] 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 74/82] 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 75/82] 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 76/82] 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 77/82] 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 78/82] 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 79/82] 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 80/82] 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 81/82] 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 82/82] 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)