mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 21:04:40 +01:00
Merge branch 'develop' into feature/AY-4802_resolve-editorial-load-editorial-exchange-package
This commit is contained in:
commit
b7930187a5
9 changed files with 296 additions and 21 deletions
|
|
@ -3,6 +3,8 @@ import re
|
|||
import json
|
||||
from collections import defaultdict
|
||||
|
||||
import contextlib
|
||||
import substance_painter
|
||||
import substance_painter.project
|
||||
import substance_painter.resource
|
||||
import substance_painter.js
|
||||
|
|
@ -640,3 +642,88 @@ def prompt_new_file_with_mesh(mesh_filepath):
|
|||
return
|
||||
|
||||
return project_mesh
|
||||
|
||||
|
||||
def get_filtered_export_preset(export_preset_name, channel_type_names):
|
||||
"""Return export presets included with specific channels
|
||||
requested by users.
|
||||
|
||||
Args:
|
||||
export_preset_name (str): Name of export preset
|
||||
channel_type_list (list): A list of channel type requested by users
|
||||
|
||||
Returns:
|
||||
dict: export preset data
|
||||
"""
|
||||
|
||||
target_maps = []
|
||||
|
||||
export_presets = get_export_presets()
|
||||
export_preset_nice_name = export_presets[export_preset_name]
|
||||
resource_presets = substance_painter.export.list_resource_export_presets()
|
||||
preset = next(
|
||||
(
|
||||
preset for preset in resource_presets
|
||||
if preset.resource_id.name == export_preset_nice_name
|
||||
), None
|
||||
)
|
||||
if preset is None:
|
||||
return {}
|
||||
|
||||
maps = preset.list_output_maps()
|
||||
for channel_map in maps:
|
||||
for channel_name in channel_type_names:
|
||||
if not channel_map.get("fileName"):
|
||||
continue
|
||||
|
||||
if channel_name in channel_map["fileName"]:
|
||||
target_maps.append(channel_map)
|
||||
# Create a new preset
|
||||
return {
|
||||
"exportPresets": [
|
||||
{
|
||||
"name": export_preset_name,
|
||||
"maps": target_maps
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def set_layer_stack_opacity(node_ids, channel_types):
|
||||
"""Function to set the opacity of the layer stack during
|
||||
context
|
||||
Args:
|
||||
node_ids (list[int]): Substance painter root layer node ids
|
||||
channel_types (list[str]): Channel type names as defined as
|
||||
attributes in `substance_painter.textureset.ChannelType`
|
||||
"""
|
||||
# Do nothing
|
||||
if not node_ids or not channel_types:
|
||||
yield
|
||||
return
|
||||
|
||||
stack = substance_painter.textureset.get_active_stack()
|
||||
stack_root_layers = (
|
||||
substance_painter.layerstack.get_root_layer_nodes(stack)
|
||||
)
|
||||
node_ids = set(node_ids) # lookup
|
||||
excluded_nodes = [
|
||||
node for node in stack_root_layers
|
||||
if node.uid() not in node_ids
|
||||
]
|
||||
|
||||
original_opacity_values = []
|
||||
for node in excluded_nodes:
|
||||
for channel in channel_types:
|
||||
chan = getattr(substance_painter.textureset.ChannelType, channel)
|
||||
original_opacity_values.append((chan, node.get_opacity(chan)))
|
||||
try:
|
||||
for node in excluded_nodes:
|
||||
for channel, _ in original_opacity_values:
|
||||
node.set_opacity(0.0, channel)
|
||||
yield
|
||||
finally:
|
||||
for node in excluded_nodes:
|
||||
for channel, opacity in original_opacity_values:
|
||||
node.set_opacity(opacity, channel)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Creator plugin for creating textures."""
|
||||
|
||||
from ayon_core.pipeline import CreatedInstance, Creator, CreatorError
|
||||
from ayon_core.lib import (
|
||||
EnumDef,
|
||||
|
|
@ -17,6 +16,7 @@ from ayon_core.hosts.substancepainter.api.pipeline import (
|
|||
)
|
||||
from ayon_core.hosts.substancepainter.api.lib import get_export_presets
|
||||
|
||||
import substance_painter
|
||||
import substance_painter.project
|
||||
|
||||
|
||||
|
|
@ -28,9 +28,16 @@ class CreateTextures(Creator):
|
|||
icon = "picture-o"
|
||||
|
||||
default_variant = "Main"
|
||||
channel_mapping = []
|
||||
|
||||
def apply_settings(self, project_settings):
|
||||
settings = project_settings["substancepainter"].get("create", []) # noqa
|
||||
if settings:
|
||||
self.channel_mapping = settings["CreateTextures"].get(
|
||||
"channel_mapping", [])
|
||||
|
||||
|
||||
def create(self, product_name, instance_data, pre_create_data):
|
||||
|
||||
if not substance_painter.project.is_open():
|
||||
raise CreatorError("Can't create a Texture Set instance without "
|
||||
"an open project.")
|
||||
|
|
@ -42,11 +49,20 @@ class CreateTextures(Creator):
|
|||
"exportFileFormat",
|
||||
"exportSize",
|
||||
"exportPadding",
|
||||
"exportDilationDistance"
|
||||
"exportDilationDistance",
|
||||
"useCustomExportPreset",
|
||||
"exportChannel"
|
||||
]:
|
||||
if key in pre_create_data:
|
||||
creator_attributes[key] = pre_create_data[key]
|
||||
|
||||
if pre_create_data.get("use_selection"):
|
||||
stack = substance_painter.textureset.get_active_stack()
|
||||
|
||||
instance_data["selected_node_id"] = [
|
||||
node_number.uid() for node_number in
|
||||
substance_painter.layerstack.get_selected_nodes(stack)]
|
||||
|
||||
instance = self.create_instance_in_context(product_name,
|
||||
instance_data)
|
||||
set_instance(
|
||||
|
|
@ -88,8 +104,53 @@ class CreateTextures(Creator):
|
|||
return instance
|
||||
|
||||
def get_instance_attr_defs(self):
|
||||
if self.channel_mapping:
|
||||
export_channel_enum = {
|
||||
item["value"]: item["name"]
|
||||
for item in self.channel_mapping
|
||||
}
|
||||
else:
|
||||
export_channel_enum = {
|
||||
"BaseColor": "Base Color",
|
||||
"Metallic": "Metallic",
|
||||
"Roughness": "Roughness",
|
||||
"SpecularEdgeColor": "Specular Edge Color",
|
||||
"Emissive": "Emissive",
|
||||
"Opacity": "Opacity",
|
||||
"Displacement": "Displacement",
|
||||
"Glossiness": "Glossiness",
|
||||
"Anisotropylevel": "Anisotropy Level",
|
||||
"AO": "Ambient Occulsion",
|
||||
"Anisotropyangle": "Anisotropy Angle",
|
||||
"Transmissive": "Transmissive",
|
||||
"Reflection": "Reflection",
|
||||
"Diffuse": "Diffuse",
|
||||
"Ior": "Index of Refraction",
|
||||
"Specularlevel": "Specular Level",
|
||||
"BlendingMask": "Blending Mask",
|
||||
"Translucency": "Translucency",
|
||||
"Scattering": "Scattering",
|
||||
"ScatterColor": "Scatter Color",
|
||||
"SheenOpacity": "Sheen Opacity",
|
||||
"SheenRoughness": "Sheen Roughness",
|
||||
"SheenColor": "Sheen Color",
|
||||
"CoatOpacity": "Coat Opacity",
|
||||
"CoatColor": "Coat Color",
|
||||
"CoatRoughness": "Coat Roughness",
|
||||
"CoatSpecularLevel": "Coat Specular Level",
|
||||
"CoatNormal": "Coat Normal",
|
||||
}
|
||||
|
||||
return [
|
||||
EnumDef("exportChannel",
|
||||
items=export_channel_enum,
|
||||
multiselection=True,
|
||||
default=None,
|
||||
label="Export Channel(s)",
|
||||
tooltip="Choose the channel which you "
|
||||
"want to solely export. The value "
|
||||
"is 'None' by default which exports "
|
||||
"all channels"),
|
||||
EnumDef("exportPresetUrl",
|
||||
items=get_export_presets(),
|
||||
label="Output Template"),
|
||||
|
|
@ -149,7 +210,6 @@ class CreateTextures(Creator):
|
|||
},
|
||||
default=None,
|
||||
label="Size"),
|
||||
|
||||
EnumDef("exportPadding",
|
||||
items={
|
||||
"passthrough": "No padding (passthrough)",
|
||||
|
|
@ -172,4 +232,10 @@ class CreateTextures(Creator):
|
|||
|
||||
def get_pre_create_attr_defs(self):
|
||||
# Use same attributes as for instance attributes
|
||||
return self.get_instance_attr_defs()
|
||||
attr_defs = []
|
||||
if substance_painter.application.version_info()[0] >= 10:
|
||||
attr_defs.append(
|
||||
BoolDef("use_selection", label="Use selection",
|
||||
tooltip="Select Layer Stack(s) for exporting")
|
||||
)
|
||||
return attr_defs + self.get_instance_attr_defs()
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import substance_painter.textureset
|
|||
from ayon_core.pipeline import publish
|
||||
from ayon_core.hosts.substancepainter.api.lib import (
|
||||
get_parsed_export_maps,
|
||||
get_filtered_export_preset,
|
||||
strip_template
|
||||
)
|
||||
from ayon_core.pipeline.create import get_product_name
|
||||
|
|
@ -207,5 +208,8 @@ class CollectTextureSet(pyblish.api.InstancePlugin):
|
|||
for key, value in dict(parameters).items():
|
||||
if value is None:
|
||||
parameters.pop(key)
|
||||
|
||||
channel_layer = creator_attrs.get("exportChannel", [])
|
||||
if channel_layer:
|
||||
maps = get_filtered_export_preset(preset_url, channel_layer)
|
||||
config.update(maps)
|
||||
return config
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import substance_painter.export
|
||||
|
||||
from ayon_core.pipeline import KnownPublishError, publish
|
||||
from ayon_core.hosts.substancepainter.api.lib import set_layer_stack_opacity
|
||||
|
||||
|
||||
class ExtractTextures(publish.Extractor,
|
||||
|
|
@ -25,19 +25,24 @@ class ExtractTextures(publish.Extractor,
|
|||
def process(self, instance):
|
||||
|
||||
config = instance.data["exportConfig"]
|
||||
result = substance_painter.export.export_project_textures(config)
|
||||
creator_attrs = instance.data["creator_attributes"]
|
||||
export_channel = creator_attrs.get("exportChannel", [])
|
||||
node_ids = instance.data.get("selected_node_id", [])
|
||||
|
||||
if result.status != substance_painter.export.ExportStatus.Success:
|
||||
raise KnownPublishError(
|
||||
"Failed to export texture set: {}".format(result.message)
|
||||
)
|
||||
with set_layer_stack_opacity(node_ids, export_channel):
|
||||
result = substance_painter.export.export_project_textures(config)
|
||||
|
||||
# Log what files we generated
|
||||
for (texture_set_name, stack_name), maps in result.textures.items():
|
||||
# Log our texture outputs
|
||||
self.log.info(f"Exported stack: {texture_set_name} {stack_name}")
|
||||
for texture_map in maps:
|
||||
self.log.info(f"Exported texture: {texture_map}")
|
||||
if result.status != substance_painter.export.ExportStatus.Success:
|
||||
raise KnownPublishError(
|
||||
"Failed to export texture set: {}".format(result.message)
|
||||
)
|
||||
|
||||
# Log what files we generated
|
||||
for (texture_set_name, stack_name), maps in result.textures.items():
|
||||
# Log our texture outputs
|
||||
self.log.info(f"Exported stack: {texture_set_name} {stack_name}")
|
||||
for texture_map in maps:
|
||||
self.log.info(f"Exported texture: {texture_map}")
|
||||
|
||||
# We'll insert the color space data for each image instance that we
|
||||
# added into this texture set. The collector couldn't do so because
|
||||
|
|
|
|||
|
|
@ -30,11 +30,16 @@ class ValidateOutputMaps(pyblish.api.InstancePlugin):
|
|||
# it will generate without actually exporting the files. So we try to
|
||||
# generate the smallest size / fastest export as possible
|
||||
config = copy.deepcopy(config)
|
||||
invalid_channels = self.get_invalid_channels(instance, config)
|
||||
if invalid_channels:
|
||||
raise PublishValidationError(
|
||||
"Invalid Channel(s): {} found in texture set {}".format(
|
||||
invalid_channels, instance.name
|
||||
))
|
||||
parameters = config["exportParameters"][0]["parameters"]
|
||||
parameters["sizeLog2"] = [1, 1] # output 2x2 images (smallest)
|
||||
parameters["paddingAlgorithm"] = "passthrough" # no dilation (faster)
|
||||
parameters["dithering"] = False # no dithering (faster)
|
||||
|
||||
result = substance_painter.export.export_project_textures(config)
|
||||
if result.status != substance_painter.export.ExportStatus.Success:
|
||||
raise PublishValidationError(
|
||||
|
|
@ -108,3 +113,41 @@ class ValidateOutputMaps(pyblish.api.InstancePlugin):
|
|||
message=message,
|
||||
title="Missing output maps"
|
||||
)
|
||||
|
||||
def get_invalid_channels(self, instance, config):
|
||||
"""Function to get invalid channel(s) from export channel
|
||||
filtering
|
||||
|
||||
Args:
|
||||
instance (pyblish.api.Instance): Instance
|
||||
config (dict): export config
|
||||
|
||||
Raises:
|
||||
PublishValidationError: raise Publish Validation
|
||||
Error if any invalid channel(s) found
|
||||
|
||||
Returns:
|
||||
list: invalid channel(s)
|
||||
"""
|
||||
creator_attrs = instance.data["creator_attributes"]
|
||||
export_channel = creator_attrs.get("exportChannel", [])
|
||||
tmp_export_channel = copy.deepcopy(export_channel)
|
||||
invalid_channel = []
|
||||
if export_channel:
|
||||
for export_preset in config.get("exportPresets", {}):
|
||||
if not export_preset.get("maps", {}):
|
||||
raise PublishValidationError(
|
||||
"No Texture Map Exported with texture set: {}.".format(
|
||||
instance.name)
|
||||
)
|
||||
map_names = [channel_map["fileName"] for channel_map
|
||||
in export_preset["maps"]]
|
||||
for channel in tmp_export_channel:
|
||||
# Check if channel is found in at least one map
|
||||
for map_name in map_names:
|
||||
if channel in map_name:
|
||||
break
|
||||
else:
|
||||
invalid_channel.append(channel)
|
||||
|
||||
return invalid_channel
|
||||
|
|
|
|||
|
|
@ -212,7 +212,13 @@ class ApplicationsAddonSettings(BaseSettingsModel):
|
|||
scope=["studio"]
|
||||
)
|
||||
only_available: bool = SettingsField(
|
||||
True, title="Show only available applications")
|
||||
True,
|
||||
title="Show only available applications",
|
||||
description="Enable to show only applications in AYON Launcher"
|
||||
" for which the executable paths are found on the running machine."
|
||||
" This applies as an additional filter to the applications defined in a "
|
||||
" project's anatomy settings to ignore unavailable applications."
|
||||
)
|
||||
|
||||
@validator("tool_groups")
|
||||
def validate_unique_name(cls, value):
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
name = "substancepainter"
|
||||
title = "Substance Painter"
|
||||
version = "0.1.1"
|
||||
version = "0.1.2"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,59 @@
|
|||
from ayon_server.settings import BaseSettingsModel, SettingsField
|
||||
|
||||
|
||||
class ChannelMappingItemModel(BaseSettingsModel):
|
||||
_layout = "compact"
|
||||
name: str = SettingsField(title="Channel Type")
|
||||
value: str = SettingsField(title="Channel Map")
|
||||
|
||||
|
||||
class CreateTextureModel(BaseSettingsModel):
|
||||
channel_mapping: list[ChannelMappingItemModel] = SettingsField(
|
||||
default_factory=list, title="Channel Mapping")
|
||||
|
||||
|
||||
class CreatorsModel(BaseSettingsModel):
|
||||
CreateTextures: CreateTextureModel = SettingsField(
|
||||
default_factory=CreateTextureModel,
|
||||
title="Create Textures"
|
||||
)
|
||||
|
||||
|
||||
DEFAULT_CREATOR_SETTINGS = {
|
||||
"CreateTextures": {
|
||||
"channel_mapping": [
|
||||
{"name": "Base Color", "value": "BaseColor"},
|
||||
{"name": "Metallic", "value": "Metallic"},
|
||||
{"name": "Roughness", "value": "Roughness"},
|
||||
{"name": "Normal", "value": "Normal"},
|
||||
{"name": "Height", "value": "Height"},
|
||||
{"name": "Specular Edge Color",
|
||||
"value": "SpecularEdgeColor"},
|
||||
{"name": "Opacity", "value": "Opacity"},
|
||||
{"name": "Displacement", "value": "Displacement"},
|
||||
{"name": "Glossiness", "value": "Glossiness"},
|
||||
{"name": "Anisotropy Level",
|
||||
"value": "Anisotropylevel"},
|
||||
{"name": "Ambient Occulsion", "value": "AO"},
|
||||
{"name": "Anisotropy Angle",
|
||||
"value": "Anisotropyangle"},
|
||||
{"name": "Transmissive", "value": "Transmissive"},
|
||||
{"name": "Reflection", "value": "Reflection"},
|
||||
{"name": "Diffuse", "value": "Diffuse"},
|
||||
{"name": "Index of Refraction", "value": "Ior"},
|
||||
{"name": "Specular Level", "value": "Specularlevel"},
|
||||
{"name": "Blending Mask", "value": "BlendingMask"},
|
||||
{"name": "Translucency", "value": "Translucency"},
|
||||
{"name": "Scattering", "value": "Scattering"},
|
||||
{"name": "Scatter Color", "value": "ScatterColor"},
|
||||
{"name": "Sheen Opacity", "value": "SheenOpacity"},
|
||||
{"name": "Sheen Color", "value": "SheenColor"},
|
||||
{"name": "Coat Opacity", "value": "CoatOpacity"},
|
||||
{"name": "Coat Color", "value": "CoatColor"},
|
||||
{"name": "Coat Roughness", "value": "CoatRoughness"},
|
||||
{"name": "CoatSpecularLevel",
|
||||
"value": "Coat Specular Level"},
|
||||
{"name": "CoatNormal", "value": "Coat Normal"}
|
||||
],
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
from ayon_server.settings import BaseSettingsModel, SettingsField
|
||||
from .imageio import ImageIOSettings, DEFAULT_IMAGEIO_SETTINGS
|
||||
from .creator_plugins import CreatorsModel, DEFAULT_CREATOR_SETTINGS
|
||||
from .load_plugins import LoadersModel, DEFAULT_LOADER_SETTINGS
|
||||
|
||||
|
||||
|
|
@ -18,6 +19,8 @@ class SubstancePainterSettings(BaseSettingsModel):
|
|||
default_factory=list,
|
||||
title="Shelves"
|
||||
)
|
||||
create: CreatorsModel = SettingsField(
|
||||
default_factory=DEFAULT_CREATOR_SETTINGS, title="Creators")
|
||||
load: LoadersModel = SettingsField(
|
||||
default_factory=DEFAULT_LOADER_SETTINGS, title="Loaders")
|
||||
|
||||
|
|
@ -25,5 +28,7 @@ class SubstancePainterSettings(BaseSettingsModel):
|
|||
DEFAULT_SPAINTER_SETTINGS = {
|
||||
"imageio": DEFAULT_IMAGEIO_SETTINGS,
|
||||
"shelves": [],
|
||||
"create": DEFAULT_CREATOR_SETTINGS,
|
||||
"load": DEFAULT_LOADER_SETTINGS,
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue