2025-07-01

This commit is contained in:
2026-03-17 14:30:01 -06:00
parent f9a22056dd
commit 62b5978595
4579 changed files with 1257472 additions and 0 deletions
@@ -0,0 +1,122 @@
"""
Copyright (C) 2023 Adobe.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
# file: props/common.py
# brief: Common Property Groups
# author Adobe - 3D & Immersive
# copyright 2023 Adobe Inc. All rights reserved.
import bpy
from ..common import RESOLUTIONS_DICT
class SUBSTANCE_PG_SbsarPhysicalSize(bpy.types.PropertyGroup):
x: bpy.props.FloatProperty(
name="x",
default=1.0,
description="The X physical size to be used") # noqa
y: bpy.props.FloatProperty(
name="y",
default=1.0,
description="The Y physical size to be used") # noqa
z: bpy.props.FloatProperty(
name="z",
default=1.0,
description="The Z physical size to be used") # noqa
def init(self, value):
self.x = value[0]
self.y = value[1]
self.z = value[2]
def get(self):
return [self.x, self.y, self.z]
def on_linked_tiling_changed(self, context):
if self.linked:
self.y = self.x
self.z = self.x
else:
on_tiling_changed(self, context)
def on_tiling_changed(self, context):
pass
class SUBSTANCE_PG_Tiling(bpy.types.PropertyGroup):
x: bpy.props.FloatProperty(
name="x",
default=2.0,
description="The X tiling to be used", # noqa
update=on_linked_tiling_changed)
y: bpy.props.FloatProperty(
name="y",
default=2.0,
description="The Y tiling to be used", # noqa
update=on_tiling_changed)
z: bpy.props.FloatProperty(
name="z",
default=2.0,
description="The Z tiling to be used", # noqa
update=on_tiling_changed)
linked: bpy.props.BoolProperty(
name="linked",
default=True,
description='Lock/Unlock the tiling', # noqa
update=on_linked_tiling_changed)
def init(self, value):
self.x = value[0]
self.y = value[1]
self.z = value[2]
def get(self):
return [self.x, self.y, self.z]
def on_update_resolution(self, context):
if self.linked:
self.height = self.width
class SUBSTANCE_PG_Resolution(bpy.types.PropertyGroup):
width: bpy.props.EnumProperty(
name="width",
default="10",
description="The width of the exported map", # noqa
items=RESOLUTIONS_DICT,
update=on_update_resolution)
height: bpy.props.EnumProperty(
name="height",
default="10",
description="The height of the exported map", # noqa
items=RESOLUTIONS_DICT)
linked: bpy.props.BoolProperty(
name="linked",
default=True,
description='Lock/Unlock the resolution', # noqa
update=on_update_resolution)
def init(self, value):
self.width = value[0]
self.height = value[1]
def get(self):
return [int(self.width), int(self.height)]
@@ -0,0 +1,135 @@
"""
Copyright (C) 2023 Adobe.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
# file: props/sbsar.py
# brief: Substance Property Groups
# author Adobe - 3D & Immersive
# copyright 2023 Adobe Inc. All rights reserved.
import bpy
from .sbsar_graph import SUBSTANCE_PG_SbsarGraph
from ..common import (
Code_SbsarLoadSuffix,
ADDON_PACKAGE
)
def get_graph_items(self, context):
_graphs = []
for _idx, _graph in enumerate(self.graphs):
_item = (_graph.index, _graph.label, "{}:{}:{}".format(_graph.index, _graph.label, _graph.uid))
_graphs.append(_item)
if len(_graphs) == 0:
return [("0", "NONE", "NONE:NONE:NONE")]
return _graphs
def on_graph_update(self, context):
pass
def on_suffix_update(self, context):
if self.suffix == Code_SbsarLoadSuffix.success.value[0]:
self.loading = Code_SbsarLoadSuffix.success.value[1]
self.load_success = True
elif self.suffix == Code_SbsarLoadSuffix.error.value[0]:
self.loading = Code_SbsarLoadSuffix.error.value[1]
self.load_success = False
class SUBSTANCE_PG_Sbsar(bpy.types.PropertyGroup):
uuid: bpy.props.StringProperty(
name="uuid",
description="The ID of the Substance 3D *.sbsar file") # noqa
version: bpy.props.IntProperty(
name="version",
description="The Version of the Substance Engine used by the .sbsar file") # noqa
name: bpy.props.StringProperty(
name="name",
description="The name of the Substance *.sbsar file") # noqa
filename: bpy.props.StringProperty(
name='filename',
description='The name of the *.sbsar file') # noqa
filepath: bpy.props.StringProperty(
name='filepath',
description='The path to the *.sbsar file') # noqa
graphs: bpy.props.CollectionProperty(type=SUBSTANCE_PG_SbsarGraph)
graphs_list: bpy.props.EnumProperty(
name="graphs",
description="The available graphs of the Substance 3D material", # noqa
items=get_graph_items,
update=on_graph_update)
suffix: bpy.props.StringProperty(
name="suffix",
default=Code_SbsarLoadSuffix.loading.value[0],
update=on_suffix_update)
icon: bpy.props.StringProperty(
name="icon",
default=Code_SbsarLoadSuffix.loading.value[1],
update=on_suffix_update)
loading: bpy.props.StringProperty(name="loading", default="TEMP") # noqa
load_success: bpy.props.BoolProperty(default=False)
def init(self, sbsar):
self.uuid = sbsar.uuid
self.version = sbsar.version
self.name = sbsar.name
self.filename = sbsar.filename
self.filepath = sbsar.filepath
_addon_prefs = bpy.context.preferences.addons[ADDON_PACKAGE].preferences
for _graph in sbsar.graphs:
_new_graph = self.graphs.add()
_new_graph.init(_graph, _addon_prefs)
def init_shader(self, shader_idx, shader, default_output):
for _graph in self.graphs:
_graph.init_shader(shader_idx, shader, default_output)
def init_tiling(self, sbsar):
for _idx, _graph in enumerate(self.graphs):
_graph.init_tiling(sbsar.graphs[_idx].tiling.get(), sbsar.graphs[_idx].tiling.linked)
def init_shader_duplicate(self, sbsar):
for _idx, _graph in enumerate(self.graphs):
_graph.init_shader_duplicate(sbsar.graphs[_idx].shaders_list)
def init_outputs(self, sbsar):
for _idx, _graph in enumerate(self.graphs):
_graph.init_outputs(sbsar.graphs[_idx].outputs)
def init_presets(self, sbsar):
for _idx, _graph in enumerate(self.graphs):
_graph.init_preset(sbsar.graphs[_idx].presets_list)
def get(self):
_obj = {
"uuid": self.uuid,
"version": self.version,
"name": self.name,
"filename": self.filename,
"filepath": self.filepath,
"graphs": []
}
for _graph in self.graphs:
_obj["graphs"].append(_graph.get())
return _obj
@@ -0,0 +1,361 @@
"""
Copyright (C) 2023 Adobe.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
# file: props/sbsar_graph.py
# brief: Substance Property Groups
# author Adobe - 3D & Immersive
# copyright 2023 Adobe Inc. All rights reserved.
import os
import bpy
from .utils import get_shaders
from ..sbsar.async_ops import _render_sbsar, _set_input_visibility
from ..api import SUBSTANCE_Api
from ..thread_ops import SUBSTANCE_Threads
from ..material.manager import SUBSTANCE_MaterialManager
from ..utils import SUBSTANCE_Utils
from .common import SUBSTANCE_PG_SbsarPhysicalSize
from .sbsar_input import SUBSTANCE_PG_SbsarInput, SUBSTANCE_PG_SbsarInputGroup
from .sbsar_output import SUBSTANCE_PG_SbsarOutput
from .sbsar_preset import SUBSTANCE_PG_SbsarPreset
from ..common import OUTPUTS_FILTER_DICT, Code_InputIdentifier, Code_Response
class SUBSTANCE_PG_SbsarMaterial(bpy.types.PropertyGroup):
name: bpy.props.StringProperty(name="name", description="The ID of the Substance 3D shader material") # noqa
shader: bpy.props.StringProperty(name="shader", default="") # noqa
def on_linked_tiling_changed(self, context):
if self.linked:
self.y = self.x
self.z = self.x
else:
on_tiling_changed(self, context)
def on_tiling_changed(self, context):
if not self.callback:
return
_sbsar = context.scene.loaded_sbsars[context.scene.sbsar_index]
_graph = _sbsar.graphs[int(_sbsar.graphs_list)]
_addons_prefs, _selected_shader_idx, _selected_shader_preset = SUBSTANCE_Utils.get_selected_shader(
context,
int(_graph.shaders_list))
_shader_filepath = SUBSTANCE_Utils.get_shader_file(_selected_shader_preset.filename)
_shader_graph = SUBSTANCE_Utils.get_json(_shader_filepath)
_material = SUBSTANCE_MaterialManager.get_existing_material(_graph.material.name)
if _material is None:
return
for _property in _shader_graph["properties"]:
if _property["type"] == "tiling":
_node_name = None
for _node in _shader_graph["nodes"]:
if _node["id"] == _property["id"]:
_node_name = _node["name"]
break
if _node_name is None:
continue
if _node_name in _material.node_tree.nodes:
_node = _material.node_tree.nodes[_node_name]
_value = self.get()
if "input" in _property:
setattr(_node.inputs[_property["input"]], _property["name"], _value)
class SUBSTANCE_PG_SbsarTiling(bpy.types.PropertyGroup):
x: bpy.props.FloatProperty(
name="x",
default=2.0,
description="The X tiling to be used", # noqa
update=on_linked_tiling_changed)
y: bpy.props.FloatProperty(
name="y",
default=2.0,
description="The Y tiling to be used", # noqa
update=on_tiling_changed)
z: bpy.props.FloatProperty(
name="z",
default=2.0,
description="The Z tiling to be used", # noqa
update=on_tiling_changed)
linked: bpy.props.BoolProperty(
name="linked",
default=True,
description='Lock/Unlock the tiling', # noqa
update=on_linked_tiling_changed)
callback: bpy.props.BoolProperty(default=True)
def init(self, value):
self.callback = False
self.x = value[0]
self.y = value[1]
self.z = value[2]
self.callback = True
def set(self, value, linked):
self.callback = False
self.x = value[0]
self.y = value[1]
self.z = value[2]
self.linked = linked
self.callback = True
def get(self):
return [self.x, self.y, self.z]
def on_shader_update(self, context):
if not self.shader_callback:
return
_sbsar = context.scene.loaded_sbsars[context.scene.sbsar_index]
_graph_idx = int(_sbsar.graphs_list)
_addons_prefs, _selected_shader_idx, _selected_shader = SUBSTANCE_Utils.get_selected_shader(
context,
int(self.shaders_list))
for _output in self.outputs:
_output.shader_callback_enabled = False
if _output.name in _selected_shader.outputs:
_shader_output = _selected_shader.outputs[_output.name]
_output.shader_enabled = _shader_output.enabled
_output.shader_format = _shader_output.format
_output.shader_bitdepth = _shader_output.bitdepth
else:
_output.shader_enabled = _addons_prefs.output_default_enabled
_output.shader_format = _addons_prefs.output_default_format
_output.shader_bitdepth = _addons_prefs.output_default_bitdepth
_output.shader_callback_enabled = True
SUBSTANCE_Threads.alt_thread_run(_render_sbsar, (context, _sbsar, _graph_idx))
def on_preset_update(self, context):
if not self.preset_callback:
return
_sbsar = context.scene.loaded_sbsars[context.scene.sbsar_index]
_preset_idx = int(self.presets_list)
_preset = self.presets[_preset_idx]
_result = SUBSTANCE_Api.sbsar_preset_load(
_sbsar,
self,
_preset.value)
if _result[0] != Code_Response.success:
return
_data = _result[1]["data"]["inputs"]
SUBSTANCE_Threads.alt_thread_run(_set_input_visibility, (
_sbsar,
self,
_data))
SUBSTANCE_Threads.alt_thread_run(_render_sbsar, (
context,
_sbsar,
int(self.index)))
_input_class = getattr(context.scene, self.inputs_class_name)
_input_class.callback["enabled"] = False
_input_class.from_json(self.inputs, _data)
_input_class.callback["enabled"] = True
def get_preset_items(self, context):
_presets = []
for _preset in self.presets:
_item = (
_preset.index,
_preset.label,
"{}:{}".format(_preset.index, _preset.label),
int(_preset.index)
)
_presets.append(_item)
return _presets
class SUBSTANCE_PG_SbsarGraph(bpy.types.PropertyGroup):
index: bpy.props.StringProperty(
name="index",
description="The index of the graph in the Substance 3D material") # noqa
uid: bpy.props.StringProperty(
name="uid",
description="The ID of the Substance 3D graph") # noqa
name: bpy.props.StringProperty(name="name")
label: bpy.props.StringProperty(
name="label",
description="The name of the Substance 3D graph") # noqa
identifier: bpy.props.StringProperty(
name="identifier",
description="The identifier of the Substance 3D graph") # noqa
packageUrl: bpy.props.StringProperty(
name="packageUrl",
description="The package URL of the Substance 3D graph") # noqa
material: bpy.props.PointerProperty(type=SUBSTANCE_PG_SbsarMaterial)
tiling: bpy.props.PointerProperty(
name="tiling",
description="The default tiling to be used in the shader network", # noqa
type=SUBSTANCE_PG_SbsarTiling)
physical_size: bpy.props.PointerProperty(
name="physical_size",
description="The physical size of the material", # noqa
type=SUBSTANCE_PG_SbsarPhysicalSize)
presets: bpy.props.CollectionProperty(type=SUBSTANCE_PG_SbsarPreset)
presets_list: bpy.props.EnumProperty(
name="presets",
description="The available presets on ths SBSAR file", # noqa
items=get_preset_items,
update=on_preset_update)
preset_callback: bpy.props.BoolProperty(name="preset_callback", default=True)
outputs_filter: bpy.props.EnumProperty(
name="outputs_filter",
description="Filter the outputs displayed", # noqa
items=OUTPUTS_FILTER_DICT)
inputs_class_name: bpy.props.StringProperty(name="inputs_class_name")
input_groups: bpy.props.CollectionProperty(type=SUBSTANCE_PG_SbsarInputGroup)
inputs: bpy.props.CollectionProperty(type=SUBSTANCE_PG_SbsarInput)
outputs: bpy.props.CollectionProperty(type=SUBSTANCE_PG_SbsarOutput)
outputsize_exists: bpy.props.BoolProperty(
name="outputsize_exists",
default=False)
randomseed_exists: bpy.props.BoolProperty(
name="randomseed_exists",
default=False)
shaders_list: bpy.props.EnumProperty(
name="shaders_list",
description="The available shader network presets", # noqa
items=get_shaders,
update=on_shader_update)
shader_callback: bpy.props.BoolProperty(name="shader_callback", default=True)
update_tx_only: bpy.props.BoolProperty(name="update_tx_only", default=False)
thumbnail: bpy.props.StringProperty(name="thumbnail")
def init(self, graph, addon_prefs):
self.index = graph.index
self.uid = graph.uid
self.name = graph.identifier
self.identifier = graph.identifier
self.packageUrl = graph.packageUrl
self.label = graph.label
self.material.name = graph.material
self.physical_size.init(graph.physicalSize)
self.tiling.init(graph.tiling)
self.inputs_class_name = graph.inputs_class_name
self.update_tx_only = addon_prefs.default_tx_update
for _preset in graph.presets:
_new_preset = self.presets.add()
_new_preset.init(_preset)
for _idx, _group in enumerate(graph.inputs_groups):
new_group = self.input_groups.add()
new_group.init(_idx, _group, addon_prefs)
for _key, _input in graph.inputs.items():
_new_input = self.inputs.add()
_new_input.init(_input)
if _input.identifier == Code_InputIdentifier.outputsize.value:
self.outputsize_exists = True
if _input.identifier == Code_InputIdentifier.randomseed.value:
self.randomseed_exists = True
for _key, _outputs in graph.outputs.items():
_new_output = self.outputs.add()
_new_output.init(_outputs)
def init_shader(self, shader_idx, shader, default_output):
self.shader_callback = False
self.shaders_list = shader_idx
self.shader_callback = True
for _output in self.outputs:
_output.init_shader(shader, default_output)
def init_shader_duplicate(self, shader_index):
_shader_idx = int(shader_index)
_addon_prefs, _selected_shader_idx, _selected_shader_preset = SUBSTANCE_Utils.get_selected_shader(
bpy.context, _shader_idx)
self.shader_callback = False
self.shaders_list = _selected_shader_idx
self.shader_callback = True
for _output in self.outputs:
_output.init_shader(_selected_shader_preset, _addon_prefs.get_default_output())
def init_tiling(self, value, linked):
self.tiling.set(value, linked)
def init_outputs(self, outputs):
for _output in self.outputs:
if _output.name in outputs:
_output.init_output(outputs[_output.name])
def init_preset(self, preset_value):
self.preset_callback = False
self.presets_list = preset_value
self.preset_callback = True
def set_thumbnail(self, data):
if "graph_thumb" not in data:
return
_file = data["graph_thumb"]
if os.path.exists(_file):
_thumbnail_img = bpy.data.images.load(_file, check_existing=True)
_thumbnail_txt = bpy.data.textures.new(name=_thumbnail_img.name, type="IMAGE")
_thumbnail_txt.image = _thumbnail_img
_thumbnail_txt.extension = "EXTEND"
self.thumbnail = _thumbnail_img.name
else:
self.thumbnail = ""
def get(self):
_obj = {
"index": self.index,
"uid": self.uid,
"identifier": self.identifier,
"packagrUrl": self.packageUrl,
"label": self.label,
"physical_size": self.physical_size.get(),
"tiling": self.tiling.get(),
"presets": [],
"inputs": [],
"outputs": [],
"thumbnail": self.thumbnail
}
for _preset in self.presets:
_obj["presets"].append(_preset.get())
for _input in self.inputs:
_obj["inputs"].append(_input.get())
for _output in self.outputs:
_obj["outputs"].append(_output.get())
return _obj
@@ -0,0 +1,95 @@
"""
Copyright (C) 2023 Adobe.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
# file: props/sbsar_input.py
# brief: Substance Property Groups
# author Adobe - 3D & Immersive
# copyright 2023 Adobe Inc. All rights reserved.
import bpy
class SUBSTANCE_PG_SbsarInput(bpy.types.PropertyGroup):
id: bpy.props.StringProperty(name="id")
name: bpy.props.StringProperty(name="name")
graphID: bpy.props.StringProperty(name="graphID")
identifier: bpy.props.StringProperty(name="identifier")
label: bpy.props.StringProperty(name="label")
guiDescription: bpy.props.StringProperty(name="label")
guiGroup: bpy.props.StringProperty(name="guiGroup")
guiVisibleIf: bpy.props.StringProperty(name="guiVisibleIf")
userTag: bpy.props.StringProperty(name="userTag")
type: bpy.props.StringProperty(name="type")
guiWidget: bpy.props.StringProperty(name="guiWidget")
showAsPin: bpy.props.BoolProperty(name="showAsPin", default=False)
useCache: bpy.props.BoolProperty(name="useCache", default=False)
visibleIf: bpy.props.BoolProperty(name="visibleIf", default=False)
isHeavyDuty: bpy.props.BoolProperty(name="isHeavyDuty", default=False)
def init(self, input):
self.id = str(input.id)
self.name = input.identifier
self.graphID = str(input.graphID)
self.identifier = input.identifier
self.label = input.label
self.guiDescription = input.guiDescription
self.guiGroup = input.guiGroup
self.guiVisibleIf = input.guiVisibleIf
self.userTag = input.userTag
self.type = input.type
self.guiWidget = input.guiWidget
self.showAsPin = input.showAsPin
self.useCache = input.useCache
self.visibleIf = input.visibleIf
self.isHeavyDuty = input.isHeavyDuty
def get(self):
_obj = {
"id": int(self.id),
"graphID": int(self.graphID),
"identifier": self.identifier,
"label": self.label,
"guiDescription": self.guiDescription,
"guiGroup": self.guiGroup,
"guiVisibleIf": self.guiVisibleIf,
"userTag": self.userTag,
"type": self.type,
"guiWidget": self.guiWidget,
"showAsPin": self.showAsPin,
"useCache": self.useCache,
"visibleIf": self.visibleIf,
"isHeavyDuty": self.isHeavyDuty
}
return _obj
class SUBSTANCE_PG_SbsarInputGroup(bpy.types.PropertyGroup):
index: bpy.props.IntProperty(name="index")
label: bpy.props.StringProperty(name="label")
collapsed: bpy.props.BoolProperty(default=False)
def init(self, index, value, addon_prefs):
self.index = index
self.label = value
self.collapsed = addon_prefs.default_group_collapse
def get(self):
_obj = {
"label": self.label,
"collapsed": self.collapsed
}
return _obj
@@ -0,0 +1,169 @@
"""
Copyright (C) 2023 Adobe.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
# file: props/sbsar_output.py
# brief: Substance Property Groups
# author Adobe - 3D & Immersive
# copyright 2023 Adobe Inc. All rights reserved.
import bpy
from ..thread_ops import SUBSTANCE_Threads
from ..sbsar.async_ops import _render_sbsar
from ..utils import SUBSTANCE_Utils
from ..common import (
IMAGE_FORMAT_DICT,
IMAGE_BITDEPTH_DICT
)
class SUBSTANCE_PG_SbsarOutputChannelUse(bpy.types.PropertyGroup):
value: bpy.props.StringProperty(name="value")
def on_output_update(self, context):
if not self.shader_callback_enabled:
return
_sbsar = context.scene.loaded_sbsars[context.scene.sbsar_index]
_graph = SUBSTANCE_Utils.get_selected_graph(context)
SUBSTANCE_Threads.alt_thread_run(_render_sbsar, (context, _sbsar, int(_graph.index)))
def get_colorspace_dict(self, context):
return SUBSTANCE_Utils.get_colorspaces()
def get_bitdepths(self, context):
return IMAGE_BITDEPTH_DICT[self.shader_format]
class SUBSTANCE_PG_SbsarOutput(bpy.types.PropertyGroup):
id: bpy.props.StringProperty(name="id")
index: bpy.props.IntProperty(name="index")
name: bpy.props.StringProperty(name="name")
graphID: bpy.props.StringProperty(name="graphID")
format: bpy.props.IntProperty(name="format")
mipmaps: bpy.props.IntProperty(name="mipmaps")
identifier: bpy.props.StringProperty(name="identifier")
label: bpy.props.StringProperty(name="label")
guiDescription: bpy.props.StringProperty(name="guiDescription")
group: bpy.props.StringProperty(name="group")
guiVisibleIf: bpy.props.StringProperty(name="guiVisibleIf")
userTag: bpy.props.StringProperty(name="userTag")
type: bpy.props.StringProperty(name="type")
guiType: bpy.props.StringProperty(name="guiType")
defaultChannelUse: bpy.props.StringProperty(name="defaultChannelUse")
enabled: bpy.props.BoolProperty(name="enabled")
channelUseSpecified: bpy.props.BoolProperty(name="channelUseSpecified")
channelUse: bpy.props.CollectionProperty(type=SUBSTANCE_PG_SbsarOutputChannelUse)
shader_callback_enabled: bpy.props.BoolProperty(name="shader_callback_enabled", default=False)
shader_enabled: bpy.props.BoolProperty(
name="shader_enabled",
default=False,
update=on_output_update)
shader_colorspace: bpy.props.EnumProperty(
name="shader_colorspace",
description="The available colorspaces to be applied to this map", # noqa
items=get_colorspace_dict,
update=on_output_update)
shader_format: bpy.props.EnumProperty(
name="shader_format",
description="The available colorspaces to be applied to this map", # noqa
items=IMAGE_FORMAT_DICT,
update=on_output_update)
shader_bitdepth: bpy.props.EnumProperty(
name="shader_bitdepth",
description="The available colorspaces to be applied to this map", # noqa
items=get_bitdepths,
update=on_output_update)
filepath: bpy.props.StringProperty(name="filepath", default="") # noqa
filename: bpy.props.StringProperty(name="filename", default="") # noqa
def init(self, output):
self.id = str(output.id)
self.index = output.index
self.name = output.defaultChannelUse if output.defaultChannelUse != "UNKNOWN" else output.identifier
self.graphID = str(output.graphID)
self.format = output.format
self.mipmaps = output.mipmaps
self.identifier = output.identifier
self.label = output.label
self.guiDescription = output.guiDescription
self.group = output.group
self.guiVisibleIf = output.guiVisibleIf
self.userTag = output.userTag
self.type = output.type
self.guiType = output.guiType
self.defaultChannelUse = output.defaultChannelUse
self.enabled = output.enabled
self.channelUseSpecified = output.channelUseSpecified
for _use in output.channelUse:
_new_use = self.channelUse.add()
_new_use.value = _use
def init_shader(self, shader, default_output):
if self.name not in shader.outputs:
self.shader_enabled = default_output["enabled"]
self.shader_format = default_output["format"]
self.shader_bitdepth = default_output["bitdepth"]
else:
for _output in shader.outputs:
if _output.id == self.name:
self.shader_enabled = _output.enabled
self.shader_format = _output.format
self.shader_bitdepth = _output.bitdepth
self.shader_callback_enabled = True
def init_output(self, default_output):
self.shader_callback_enabled = False
self.shader_enabled = default_output.shader_enabled
self.shader_format = default_output.shader_format
self.shader_bitdepth = default_output.shader_bitdepth
self.shader_callback_enabled = True
def get(self):
_obj = {
"id": int(self.id),
"graphID": int(self.graphID),
"format": self.format,
"mipmaps": self.mipmaps,
"identifier": self.identifier,
"label": self.label,
"guiDescription": self.guiDescription,
"group": self.group,
"guiVisibleIf": self.guiVisibleIf,
"userTag": self.userTag,
"type": self.type,
"guiType": self.guiType,
"defaultChannelUse": self.defaultChannelUse,
"enabled": self.enabled,
"channelUseSpecified": self.channelUseSpecified,
"channelUse": [],
"shader_enabled": self.shader_enabled,
"shader_colorspace": self.shader_colorspace,
"shader_format": self.shader_format,
"shader_bitdepth": self.shader_bitdepth
}
for _use in self.channelUse:
_obj["channelUse"].append(_use.value)
return _obj
@@ -0,0 +1,59 @@
"""
Copyright (C) 2023 Adobe.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
# file: props/sbsar_preset.py
# brief: Substance Property Groups
# author Adobe - 3D & Immersive
# copyright 2023 Adobe Inc. All rights reserved.
import bpy
class SUBSTANCE_PG_SbsarPreset(bpy.types.PropertyGroup):
name: bpy.props.StringProperty(name="name")
index: bpy.props.StringProperty(
name="index",
description="The index of the preset in the Substance 3D graph") # noqa
label: bpy.props.StringProperty(
name="label",
description="The preset name of the Substance 3D graph") # noqa
value: bpy.props.StringProperty(
name="value",
description="The preset value of the Substance 3D graph") # noqa
icon: bpy.props.StringProperty(
name="icon",
description="The preset icon that shows if a preset is editable") # noqa
embedded: bpy.props.BoolProperty(
name="embedded",
description="The preset origin") # noqa
def init(self, preset):
self.name = preset.label
self.index = preset.index
self.label = preset.label
self.value = preset.value
self.icon = preset.icon
self.embedded = preset.embedded
def get(self):
return {
"index": self.index,
"label": self.label,
"value": self.value,
"icon": self.icon,
"embedded": self.embedded
}
@@ -0,0 +1,160 @@
"""
Copyright (C) 2023 Adobe.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
# file: props/shader.py
# brief: Shader Preset Property Groups
# author Adobe - 3D & Immersive
# copyright 2023 Adobe Inc. All rights reserved.
import bpy
from ..shader.callbacks import SUBSTANCE_ShaderCallbacks
from ..common import IMAGE_FORMAT_DICT, IMAGE_BITDEPTH_DICT
from ..utils import SUBSTANCE_Utils
class SUBSTANCE_PG_ShaderInput(bpy.types.PropertyGroup):
id: bpy.props.StringProperty(name="id")
label: bpy.props.StringProperty(name="label")
type: bpy.props.StringProperty(name="type")
def init(self, input):
self.id = input.id
self.label = input.label
self.type = input.type
def on_enabled_update(self, context):
if not self.callback_enabled:
return
SUBSTANCE_ShaderCallbacks.on_output_changed(self, context)
def on_shader_colorspace_update(self, context):
if not self.callback_enabled:
return
SUBSTANCE_ShaderCallbacks.on_output_changed(self, context)
def on_shader_format_update(self, context):
if not self.callback_enabled:
return
_callback_enabled = self.callback_enabled
self.callback_enabled = False
self.bitdepth = IMAGE_BITDEPTH_DICT[self.format][0][0]
self.callback_enabled = _callback_enabled
SUBSTANCE_ShaderCallbacks.on_output_changed(self, context)
def on_shader_bitdepth_update(self, context):
if not self.callback_enabled:
return
SUBSTANCE_ShaderCallbacks.on_output_changed(self, context)
def get_colorspace_dict(self, context):
return SUBSTANCE_Utils.get_colorspaces()
def get_shader_bitdepths(self, context):
return IMAGE_BITDEPTH_DICT[self.format]
class SUBSTANCE_PG_ShaderOutput(bpy.types.PropertyGroup):
id: bpy.props.StringProperty(name="id")
name: bpy.props.StringProperty(name="name")
label: bpy.props.StringProperty(name="label")
optional: bpy.props.BoolProperty(name="optional")
enabled: bpy.props.BoolProperty(
name="enabled",
update=on_enabled_update)
colorspace: bpy.props.EnumProperty(
name="colorspace",
description="The default colorspace to be used when creating the shader network", # noqa
items=get_colorspace_dict,
update=on_shader_colorspace_update)
format: bpy.props.EnumProperty(
name="format",
description="The default format to be used when exporting this maps", # noqa
items=IMAGE_FORMAT_DICT,
update=on_shader_format_update)
bitdepth: bpy.props.EnumProperty(
name="bitdepth",
description="The default bitdepth to be applied to this map", # noqa
items=get_shader_bitdepths,
update=on_shader_bitdepth_update)
normal: bpy.props.BoolProperty(name="normal", default=False)
callback_enabled: bpy.props.BoolProperty(name="callback_enabled", default=False)
def init(self, output):
self.id = output.id
self.name = output.id
self.label = output.label
self.enabled = output.enabled
self.optional = output.optional
self.format = output.format
self.bitdepth = output.bitdepth
self.normal = output.normal
self.callback_enabled = True
self.colorspace = output.colorspace
def to_json(self):
_object = {
"id": self.id,
"label": self.label,
"enabled": self.enabled,
"optional": self.optional,
"colorspace": self.colorspace,
"format": self.format,
"bitdepth": self.bitdepth,
}
if self.normal:
_object["normal"] = self.normal
return _object
class SUBSTANCE_PG_ShaderPreset(bpy.types.PropertyGroup):
filename: bpy.props.StringProperty(name="filename")
name: bpy.props.StringProperty(name="name")
label: bpy.props.StringProperty(name="label")
modified: bpy.props.BoolProperty(name="modified", default=False)
inputs: bpy.props.CollectionProperty(type=SUBSTANCE_PG_ShaderInput)
outputs: bpy.props.CollectionProperty(type=SUBSTANCE_PG_ShaderOutput)
inputs_class_name: bpy.props.StringProperty(name="inputs_class_name")
def init(self, shader):
self.filename = shader.filename
self.name = shader.name
self.label = shader.label
self.inputs_class_name = shader.inputs_class_name
for _key, _input in shader.inputs.items():
_parm_item = self.inputs.add()
_parm_item.init(_input)
for _key, _output in shader.outputs.items():
_output_item = self.outputs.add()
_output_item.init(_output)
def to_json(self):
_data = []
for _output in self.outputs:
_data.append(_output.to_json())
return _data
@@ -0,0 +1,54 @@
"""
Copyright (C) 2023 Adobe.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
# file: props/shortcuts.py
# brief: Shortcuts Property Groups
# author Adobe - 3D & Immersive
# copyright 2023 Adobe Inc. All rights reserved.
import bpy
def _on_shortcut_update(self, context):
_ctrl = "Ctrl+" if self.menu_ctrl else ""
_shift = "Shift+" if self.menu_shift else ""
_alt = "Alt+" if self.menu_alt else ""
self.menu_label = _ctrl + _shift + _alt + self.menu_key
class SUBSTANCE_PG_Shortcuts(bpy.types.PropertyGroup):
# Floating Menu
menu_name: bpy.props.StringProperty(default="Floating Menu") # noqa
menu_ctrl: bpy.props.BoolProperty(default=True, update=_on_shortcut_update)
menu_shift: bpy.props.BoolProperty(default=True, update=_on_shortcut_update)
menu_alt: bpy.props.BoolProperty(default=False, update=_on_shortcut_update)
menu_key: bpy.props.StringProperty(default='U', update=_on_shortcut_update) # noqa
menu_label: bpy.props.StringProperty(default="Ctrl+Shift+U") # noqa
# Load SBSAR
load_name: bpy.props.StringProperty(default="Load SBSAR") # noqa
load_ctrl: bpy.props.BoolProperty(default=True)
load_shift: bpy.props.BoolProperty(default=True)
load_alt: bpy.props.BoolProperty(default=False)
load_key: bpy.props.StringProperty(default='L') # noqa
# Apply Material
apply_name: bpy.props.StringProperty(default="Apply Current Material") # noqa
apply_ctrl: bpy.props.BoolProperty(default=True)
apply_shift: bpy.props.BoolProperty(default=True)
apply_alt: bpy.props.BoolProperty(default=False)
apply_key: bpy.props.StringProperty(default='M') # noqa
@@ -0,0 +1,33 @@
"""
Copyright (C) 2023 Adobe.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
# file: props/utils.py
# brief: Auxiliary functions for Property Groups
# author Adobe - 3D & Immersive
# copyright 2023 Adobe Inc. All rights reserved.
from ..common import ADDON_PACKAGE
def get_shaders(self, context):
_shader_presets = []
_addon_prefs = context.preferences.addons[ADDON_PACKAGE].preferences
for _idx, _shader_preset in enumerate(_addon_prefs.shaders):
_shader_presets.append(
(str(_idx), _shader_preset.label, _shader_preset.filename)
)
return _shader_presets