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