diff --git a/client/ayon_substancepainter/plugins/create/create_textures.py b/client/ayon_substancepainter/plugins/create/create_textures.py index 5f1d4be..027ee5a 100644 --- a/client/ayon_substancepainter/plugins/create/create_textures.py +++ b/client/ayon_substancepainter/plugins/create/create_textures.py @@ -53,7 +53,9 @@ def create(self, product_name, instance_data, pre_create_data): "exportPadding", "exportDilationDistance", "useCustomExportPreset", - "exportChannel" + "exportChannel", + "exportTextureSets", + "flattenTextureSets" ]: if key in pre_create_data: creator_attributes[key] = pre_create_data[key] @@ -152,6 +154,11 @@ def get_instance_attr_defs(self): label="Review", tooltip="Mark as reviewable", default=True), + BoolDef("flattenTextureSets", + label="Flatten Texture Sets As One Texture Output", + tooltip="Export multiple texture set(s) " + "as one Texture Output", + default=False), EnumDef("exportTextureSets", items=export_texture_set_enum, multiselection=True, diff --git a/client/ayon_substancepainter/plugins/publish/collect_textureset_images.py b/client/ayon_substancepainter/plugins/publish/collect_textureset_images.py index 4e65bd4..facd35e 100644 --- a/client/ayon_substancepainter/plugins/publish/collect_textureset_images.py +++ b/client/ayon_substancepainter/plugins/publish/collect_textureset_images.py @@ -63,6 +63,8 @@ def create_image_instance(self, instance, template, outputs, context = instance.context first_filepath = outputs[0]["filepath"] + is_single_output = instance.data["creator_attributes"].get( + "flattenTextureSets", False) fnames = [os.path.basename(output["filepath"]) for output in outputs] ext = os.path.splitext(first_filepath)[1] assert ext.lstrip("."), f"No extension: {ext}" @@ -146,6 +148,22 @@ def create_image_instance(self, instance, template, outputs, image_instance.data["families"] = [product_type, "textures"] if instance.data["creator_attributes"].get("review"): image_instance.data["families"].append("review") + if is_single_output: + # Function to remove textureSet from filepath + def remove_texture_set_token(filepath, texture_set): + return filepath.replace(f".{texture_set}", "") + + single_fnames = { + remove_texture_set_token( + output["output"], output["udim"] + ) + for output in outputs + } + + image_instance.data["image_outputs"] = [ + os.path.basename(fname) for fname in single_fnames + ] + self.log.debug(f"image_outputs: {fnames}") image_instance.data["representations"] = [representation] diff --git a/client/ayon_substancepainter/plugins/publish/extract_maketx.py b/client/ayon_substancepainter/plugins/publish/extract_maketx.py index a3d6add..7f42717 100644 --- a/client/ayon_substancepainter/plugins/publish/extract_maketx.py +++ b/client/ayon_substancepainter/plugins/publish/extract_maketx.py @@ -120,7 +120,6 @@ def process(self, instance): return representations: "list[dict]" = instance.data["representations"] - # If a tx representation is present we skip extraction if any(repre["name"] == "tx" for repre in representations): return diff --git a/client/ayon_substancepainter/plugins/publish/extract_map_as_single_output.py b/client/ayon_substancepainter/plugins/publish/extract_map_as_single_output.py new file mode 100644 index 0000000..57a99dc --- /dev/null +++ b/client/ayon_substancepainter/plugins/publish/extract_map_as_single_output.py @@ -0,0 +1,93 @@ +import os + +from ayon_core.pipeline import publish +from ayon_core.lib import ( + get_oiio_tool_args, + run_subprocess, +) + + +def get_texture_outputs(staging_dir, image_outputs): + """Getting the expected texture output(s) before merging + them with oiio tools. + + Args: + staging_dir (str): staging dir + image_outputs (list): source image outputs + + Returns: + list: Texture outputs which are used for merging. + """ + return [ + os.path.join(staging_dir, output) for output in image_outputs + ] + + +def convert_texture_maps_as_single_output(staging_dir, source_image_outputs, + dest_image_outputs, log=None): + oiio_tool_args = get_oiio_tool_args("oiiotool") + + source_maps = get_texture_outputs( + staging_dir, source_image_outputs) + dest_map = next( + (dest_texture for dest_texture in + get_texture_outputs( + staging_dir, dest_image_outputs)), None) + + log.info(f"{source_maps} composited as {dest_map}") + oiio_cmd = oiio_tool_args + source_maps + [ + "--mosaic", + "{}x1".format(len(source_maps)), + "-o", + dest_map + ] + + env = os.environ.copy() + + try: + run_subprocess(oiio_cmd, env=env) + except Exception as exc: + raise RuntimeError("Flattening texture stack to single output image failed") from exc + + +class ExtractTexturesAsSingleOutput(publish.Extractor): + """Extract Texture As Single Output + + Combine the multliple texture sets into one single texture output. + + """ + + label = "Extract Texture Sets as Single Texture Output" + hosts = ["substancepainter"] + families = ["image"] + settings_category = "substancepainter" + + # Run directly after textures export + order = publish.Extractor.order - 0.0991 + + def process(self, instance): + if not instance.data.get("creator_attributes", {}).get( + "flattenTextureSets", False): + self.log.debug( + "Skipping to export texture sets as single texture output.." + ) + return + + representations: "list[dict]" = instance.data["representations"] + repre = representations[0] + + staging_dir = instance.data["stagingDir"] + dest_image_outputs = instance.data["image_outputs"] + source_image = repre["files"] + is_sequence = isinstance(source_image, (list, tuple)) + if not is_sequence: + source_image_outputs = [source_image] + else: + source_image_outputs = source_image + repre["files"] = dest_image_outputs[0] + repre.pop("udim", None) + + convert_texture_maps_as_single_output( + staging_dir, source_image_outputs, + dest_image_outputs, log=self.log + )