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

379 lines
15 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: 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'}