Early prototype for Texture publishing in Substance Painter (WIP - not functional; doesn't integrate yet)

This commit is contained in:
Roy Nieterau 2023-01-09 18:13:49 +01:00
parent 564e8f4d40
commit f9d3c9f772
2 changed files with 220 additions and 0 deletions

View file

@ -0,0 +1,149 @@
# -*- coding: utf-8 -*-
"""Creator plugin for creating textures."""
import os
from openpype.pipeline import CreatedInstance, Creator
from openpype.hosts.substancepainter.api.pipeline import (
set_project_metadata,
get_project_metadata
)
from openpype.lib import (
EnumDef,
UILabelDef,
NumberDef
)
import substance_painter.project
import substance_painter.resource
def get_export_presets():
import substance_painter.resource
preset_resources = {}
# TODO: Find more optimal way to find all export templates
for shelf in substance_painter.resource.Shelves.all():
shelf_path = os.path.normpath(shelf.path())
presets_path = os.path.join(shelf_path, "export-presets")
if not os.path.exists(presets_path):
continue
for fname in os.listdir(presets_path):
if fname.endswith(".spexp"):
template_name = os.path.splitext(fname)[0]
resource = substance_painter.resource.ResourceID(
context=shelf.name(),
name=template_name
)
resource_url = resource.url()
preset_resources[resource_url] = template_name
# Sort by template name
export_templates = dict(sorted(preset_resources.items(),
key=lambda x: x[1]))
return export_templates
class CreateTextures(Creator):
"""Create a texture set."""
identifier = "io.openpype.creators.substancepainter.textures"
label = "Textures"
family = "textures"
icon = "picture-o"
default_variant = "Main"
def create(self, subset_name, instance_data, pre_create_data):
if not substance_painter.project.is_open():
return
instance = self.create_instance_in_context(subset_name, instance_data)
set_project_metadata("textures", instance.data_to_store())
def collect_instances(self):
workfile = get_project_metadata("textures")
if workfile:
self.create_instance_in_context_from_existing(workfile)
def update_instances(self, update_list):
for instance, _changes in update_list:
# Update project's metadata
data = get_project_metadata("textures") or {}
data.update(instance.data_to_store())
set_project_metadata("textures", data)
def remove_instances(self, instances):
for instance in instances:
# TODO: Implement removal
# api.remove_instance(instance)
self._remove_instance_from_context(instance)
# Helper methods (this might get moved into Creator class)
def create_instance_in_context(self, subset_name, data):
instance = CreatedInstance(
self.family, subset_name, data, self
)
self.create_context.creator_adds_instance(instance)
return instance
def create_instance_in_context_from_existing(self, data):
instance = CreatedInstance.from_existing(data, self)
self.create_context.creator_adds_instance(instance)
return instance
def get_instance_attr_defs(self):
return [
EnumDef("exportPresetUrl",
items=get_export_presets(),
label="Output Template"),
EnumDef("exportFileFormat",
items={
None: "Based on output template",
# TODO: implement extensions
},
label="File type"),
EnumDef("exportSize",
items={
None: "Based on each Texture Set's size",
# The key is size of the texture file in log2.
# (i.e. 10 means 2^10 = 1024)
7: "128",
8: "256",
9: "512",
10: "1024",
11: "2048",
12: "4096"
},
label="Size"),
EnumDef("exportPadding",
items={
"passthrough": "No padding (passthrough)",
"infinite": "Dilation infinite",
"transparent": "Dilation + transparent",
"color": "Dilation + default background color",
"diffusion": "Dilation + diffusion"
},
label="Padding"),
NumberDef("exportDilationDistance",
minimum=0,
maximum=256,
decimals=0,
default=16,
label="Dilation Distance"),
UILabelDef("Note: Dilation Distance is only used with "
"'Dilation + <xxx>' padding options"),
]
def get_pre_create_attr_defs(self):
# Use same attributes as for instance attributes
return self.get_instance_attr_defs()

View file

@ -0,0 +1,71 @@
from openpype.pipeline import KnownPublishError, publish
import substance_painter.export
class ExtractTextures(publish.Extractor):
"""Extract Textures using an output template config"""
label = "Extract Texture Sets"
hosts = ['substancepainter']
families = ["textures"]
def process(self, instance):
staging_dir = self.staging_dir(instance)
# See: https://substance3d.adobe.com/documentation/ptpy/api/substance_painter/export # noqa
creator_attrs = instance.data["creator_attributes"]
config = {
"exportShaderParams": True,
"exportPath": staging_dir,
"defaultExportPreset": creator_attrs["exportPresetUrl"],
# Custom overrides to the exporter
"exportParameters": [
{
"parameters": {
"fileFormat": creator_attrs["exportFileFormat"],
"sizeLog2": creator_attrs["exportSize"],
"paddingAlgorithm": creator_attrs["exportPadding"],
"dilationDistance": creator_attrs["exportDilationDistance"] # noqa
}
}
]
}
# Create the list of Texture Sets to export.
config["exportList"] = []
for texture_set in substance_painter.textureset.all_texture_sets():
# stack = texture_set.get_stack()
config["exportList"].append({"rootPath": texture_set.name()})
# Consider None values optionals
for override in config["exportParameters"]:
parameters = override.get("parameters")
for key, value in dict(parameters).items():
if value is None:
parameters.pop(key)
result = substance_painter.export.export_project_textures(config)
if result.status != substance_painter.export.ExportStatus.Success:
raise KnownPublishError(
"Failed to export texture set: {}".format(result.message)
)
files = []
for stack, maps in result.textures.items():
for texture_map in maps:
self.log.info(f"Exported texture: {texture_map}")
files.append(texture_map)
# TODO: add the representations so they integrate the way we'd want
"""
instance.data['representations'] = [{
'name': ext.lstrip("."),
'ext': ext.lstrip("."),
'files': file,
"stagingDir": folder,
}]
"""