""" 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: ops/sbsar.py # brief: Sinstance Operators # author Adobe - 3D & Immersive # copyright 2023 Adobe Inc. All rights reserved. import os import bpy from bpy_extras.io_utils import ImportHelper from ..api import SUBSTANCE_Api from ..material.manager import SUBSTANCE_MaterialManager from ..utils import SUBSTANCE_Utils from ..thread_ops import SUBSTANCE_Threads from ..sbsar.sbsar import SBSAR from ..sbsar.async_ops import _render_sbsar from .common import load_sbsar from ..common import ( Code_SbsarOp, Code_Response, ADDON_PACKAGE, TOOLKIT_EXPECTED_VERSION ) class SUBSTANCE_OT_LoadSBSAR(bpy.types.Operator, ImportHelper): bl_idname = 'substance.load_sbsar' bl_label = 'Load Substance Material' bl_description = 'Open file browser to select a Substance 3D material' bl_options = {'REGISTER'} filename_ext = '.sbsar' filter_glob: bpy.props.StringProperty(default='*.sbsar', options={'HIDDEN'}) # noqa files: bpy.props.CollectionProperty(name='Substance 3D material files', type=bpy.types.OperatorFileListElement) # noqa directory: bpy.props.StringProperty(subtype="DIR_PATH") # noqa def __init__(self): self.filepath = '' @classmethod def poll(cls, context): _, _toolkit_version = SUBSTANCE_Api.toolkit_version_get() return _toolkit_version is not None and _toolkit_version in TOOLKIT_EXPECTED_VERSION def invoke(self, context, event): if not SUBSTANCE_Api.currently_running: # Initialize SUBSTANCE_Api _result = SUBSTANCE_Api.initialize() if _result[0] != Code_Response.success: SUBSTANCE_Utils.log_data("ERROR", "[{}] The SRE cannot initialize...".format(_result)) return {'RUNNING_MODAL'} _addon_prefs = context.preferences.addons[ADDON_PACKAGE].preferences self.filepath = _addon_prefs.path_library context.window_manager.fileselect_add(self) return {'RUNNING_MODAL'} def execute(self, context): SUBSTANCE_Threads.cursor_push('WAIT') for _f in self.files: if _f.name.endswith('.sbsar'): if len(_f.name) > 0: _filepath = os.path.join(self.directory, _f.name) load_sbsar(_f.name, _filepath) else: SUBSTANCE_Utils.log_data("ERROR", "[{}] is not a valid sbsar file".format(_f.name), display=True) SUBSTANCE_Threads.cursor_pop() return {'FINISHED'} class SUBSTANCE_OT_ApplySBSAR(bpy.types.Operator): bl_idname = 'substance.apply_sbsar' bl_label = 'Apply the Substance 3D material' @classmethod def description(cls, context, properties): _selected_geo = SUBSTANCE_Utils.get_selected_geo(context.selected_objects) if len(context.scene.loaded_sbsars) > 0 and len(_selected_geo) > 0: _selected_sbsar = context.scene.loaded_sbsars[context.scene.sbsar_index] if _selected_sbsar.load_success: return "Applies the selected Substance 3D material to the selected object(s)" else: return "This Substance 3D material loaded incorrectly" else: return "Please load at least one Substance file and select an object in the scene" @classmethod def poll(cls, context): _selected_geo = SUBSTANCE_Utils.get_selected_geo(context.selected_objects) if len(context.scene.loaded_sbsars) > 0 and len(_selected_geo) > 0: _selected_sbsar = context.scene.loaded_sbsars[context.scene.sbsar_index] return _selected_sbsar.load_success return False def execute(self, context): _selected_graph = SUBSTANCE_Utils.get_selected_graph(context) _material = SUBSTANCE_MaterialManager.get_existing_material(_selected_graph.material.name) if _material is None: SUBSTANCE_Utils.log_data( "ERROR", "Substance Material needs to finish rendering before applying", display=True) return {'FINISHED'} _selected_objects = context.selected_objects if len(_selected_objects) == 0: SUBSTANCE_Utils.log_data( "ERROR", "Material [{}] cannot be added, there are no selected objects".format(_material.name), display=True) return {'FINISHED'} SUBSTANCE_MaterialManager.apply_material(context, _selected_objects, _material) SUBSTANCE_Utils.log_data( "INFO", "Material slot with substance [{}] added to the object".format(_material.name), display=True) return {'FINISHED'} class SUBSTANCE_OT_RemoveSBSAR(bpy.types.Operator): bl_idname = 'substance.remove_sbsar' bl_label = 'Remove the Substance 3D material' @classmethod def description(cls, context, properties): if len(context.scene.loaded_sbsars) > 0: return "Remove the selected Substance 3D material" else: return "Please load at least one Substance file" @classmethod def poll(cls, context): if len(context.scene.loaded_sbsars) > 0: return True return False def execute(self, context): _idx = context.scene.sbsar_index _selected_sbsar = context.scene.loaded_sbsars[_idx] _sbsar_uuid = _selected_sbsar.uuid _sbsar_name = _selected_sbsar.name context.scene.loaded_sbsars.remove(_idx) if context.scene.sbsar_index != 0 and context.scene.sbsar_index >= len(context.scene.loaded_sbsars): context.scene.sbsar_index = len(context.scene.loaded_sbsars) - 1 _result = SUBSTANCE_Api.sbsar_remove(_sbsar_uuid) if _result[0] != Code_Response.success: SUBSTANCE_Utils.log_data( "ERROR", "Substance [{}] could not be removed".format(_sbsar_name), display=True) if _result[1]["result"] != Code_Response.success.value: SUBSTANCE_Utils.log_data( "ERROR", "An error ocurred while removing Substance [{}]. Failed with error [{}]".format( _sbsar_name, _result[1]["result"]), display=True) _result = SUBSTANCE_Api.sbsar_unregister(_sbsar_uuid) if _result[0] != Code_Response.success: SUBSTANCE_Utils.log_data( "ERROR", "Classes of Substance [{}] could not be removed".format(_sbsar_name), display=True) context.area.tag_redraw() SUBSTANCE_Utils.log_data("INFO", "Substance [{}] was removed correctly".format(_sbsar_name), display=True) return {'FINISHED'} class SUBSTANCE_OT_ReloadSBSAR(bpy.types.Operator): bl_idname = 'substance.reload_sbsar' bl_label = 'Reload the Substance 3D material' @classmethod def description(cls, context, properties): if len(context.scene.loaded_sbsars) > 0: return "Reload the selected Substance 3D material" else: return "Please load at least one Substance file" @classmethod def poll(cls, context): if len(context.scene.loaded_sbsars) > 0: return True return False def execute(self, context): _idx = context.scene.sbsar_index _selected_sbsar = context.scene.loaded_sbsars[_idx] _sbsar_uuid = _selected_sbsar.uuid _sbsar_name = _selected_sbsar.name _sbsar_filename = _selected_sbsar.filename _sbsar_filepath = _selected_sbsar.filepath _addon_prefs, _selected_shader_idx, _selected_shader_preset = SUBSTANCE_Utils.get_selected_shader(context) _normal_format = _addon_prefs.default_normal_format _output_size = _addon_prefs.default_resolution.get() # REMOVE OLD SBSAR context.scene.loaded_sbsars.remove(_idx) if context.scene.sbsar_index != 0 and context.scene.sbsar_index >= len(context.scene.loaded_sbsars): context.scene.sbsar_index = len(context.scene.loaded_sbsars) - 1 _result = SUBSTANCE_Api.sbsar_remove(_sbsar_uuid) if _result[0] != Code_Response.success: SUBSTANCE_Utils.log_data( "ERROR", "Substance [{}] could not be removed".format(_sbsar_name), display=True) return {'FINISHED'} if _result[1]["result"] != Code_Response.success.value: SUBSTANCE_Utils.log_data( "ERROR", "An error ocurred while removing Substance [{}]. Failed with error [{}]".format( _sbsar_name, _result[1]["result"]), display=True) return {'FINISHED'} _result = SUBSTANCE_Api.sbsar_unregister(_sbsar_uuid) if _result[0] != Code_Response.success: SUBSTANCE_Utils.log_data( "ERROR", "Classes of Substance [{}] could not be removed".format(_sbsar_name), display=True) return {'FINISHED'} # LOAD NEW SBSAR SUBSTANCE_Utils.log_data("INFO", "Begin loading substance from file [{}]".format(_sbsar_filename)) _data = { "filepath": _sbsar_filepath, "name": _sbsar_name, "$outputsize": _output_size, "normal_format": 0 if _normal_format == "DirectX" else 1 } # Load the sbsar to the SRE _result = SUBSTANCE_Api.sbsar_load(_data) if _result[0] != Code_Response.success: SUBSTANCE_Utils.log_data( "ERROR", "Substance file [{}] located at [{}] could not be loaded".format(_sbsar_filename, _sbsar_filepath), display=True) return {'FINISHED'} if _result[1]["result"] != Code_Response.success.value: SUBSTANCE_Utils.log_data( "ERROR", "An error ocurred while loading Substance file [{}]. Failed with error [{}]".format( _sbsar_filepath, _result[1]["result"]), display=True) return {'FINISHED'} _sbsar = SBSAR(_sbsar_name, _sbsar_filename, _result[1]["data"], _addon_prefs) _loaded_sbsar = context.scene.loaded_sbsars.add() _loaded_sbsar.init(_sbsar) _loaded_sbsar.init_shader(_selected_shader_idx, _selected_shader_preset, _addon_prefs.get_default_output()) SUBSTANCE_Api.sbsar_register(_sbsar) context.scene.sbsar_index = len(context.scene.loaded_sbsars) - 1 for _idx, _graph in enumerate(_loaded_sbsar.graphs): SUBSTANCE_Threads.alt_thread_run(_render_sbsar, (context, _loaded_sbsar, _idx)) SUBSTANCE_Utils.log_data("INFO", "Substance from file [{}] loaded".format(_sbsar_filename)) context.area.tag_redraw() SUBSTANCE_Utils.log_data("INFO", "Substance [{}] was removed correctly".format(_sbsar_name), display=True) return {'FINISHED'} class SUBSTANCE_OT_DuplicateSBSAR(bpy.types.Operator): bl_idname = 'substance.duplicate_sbsar' bl_label = 'Duplicate the Substance 3D material' @classmethod def description(cls, context, properties): if len(context.scene.loaded_sbsars) > 0: _selected_sbsar = context.scene.loaded_sbsars[context.scene.sbsar_index] if _selected_sbsar.load_success: return "Duplicate the selected Substance 3D material" else: return "This Substance 3D material loaded incorrectly" else: return "Please load at least one Substance file" @classmethod def poll(cls, context): if len(context.scene.loaded_sbsars) > 0: return True return False def execute(self, context): SUBSTANCE_Threads.cursor_push('WAIT') _tmp_sbsar = context.scene.loaded_sbsars[context.scene.sbsar_index] _sbsar_name = _tmp_sbsar.name _sbsar = context.scene.loaded_sbsars[_sbsar_name] _unique_name = SUBSTANCE_Utils.get_unique_name(_sbsar.name, context) _data = { "filepath": _sbsar.filepath, "uuid": _sbsar.uuid, "name": _unique_name } # Load the sbsar to the SRE _result = SUBSTANCE_Api.sbsar_load(_data, operation=Code_SbsarOp.duplicate) if _result[0] != Code_Response.success: SUBSTANCE_Utils.log_data( "ERROR", "Substance file [{}] located at [{}] could not be loaded".format(_sbsar.filename, _sbsar.filepath), display=True) return {'FINISHED'} if _result[1]["result"] != Code_Response.success.value: SUBSTANCE_Utils.log_data( "ERROR", "An error ocurred while loading Substance file [{}]. Failed with error [{}]".format( _sbsar.filepath, _result[1]["result"]), display=True) return {'FINISHED'} _addon_prefs = bpy.context.preferences.addons[ADDON_PACKAGE].preferences _new_sbsar = SBSAR(_unique_name, _sbsar.filename, _result[1]["data"], _addon_prefs) for _graph_obj in _new_sbsar.graphs: _presets = [] _preset_list = _sbsar.graphs[int(_graph_obj.index)].presets for _item in _preset_list: _obj = { "index": int(_item.index), "label": _item.label, "value": _item.value, "icon": _item.icon, "embedded": _item.embedded } _presets.append(_obj) _graph_obj.reset_presets(_presets) _loaded_sbsar = context.scene.loaded_sbsars.add() # Reset _sbsar reference otherwise we get a memory error _sbsar = context.scene.loaded_sbsars[_sbsar_name] _loaded_sbsar.init(_new_sbsar) _loaded_sbsar.init_shader_duplicate(_sbsar) _loaded_sbsar.init_tiling(_sbsar) _loaded_sbsar.init_outputs(_sbsar) _loaded_sbsar.init_presets(_sbsar) SUBSTANCE_Api.sbsar_register(_new_sbsar) context.scene.sbsar_index = len(context.scene.loaded_sbsars) - 1 for _idx, _graph in enumerate(_loaded_sbsar.graphs): SUBSTANCE_Threads.alt_thread_run(_render_sbsar, (context, _loaded_sbsar, _idx)) SUBSTANCE_Utils.log_data( "INFO", "Substance [{}] duplicated succesfully".format(_sbsar.name), display=True) SUBSTANCE_Threads.cursor_pop() return {'FINISHED'}