Files
blender-portable-repo/scripts/addons/Substance3DInBlender/props/sbsar_graph.py
T
2026-03-17 14:30:01 -06:00

362 lines
13 KiB
Python

"""
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