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