2025-07-01
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user