2025-07-01
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
"""
|
||||
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/material.py
|
||||
# brief: Material operators
|
||||
# author Adobe - 3D & Immersive
|
||||
# copyright 2023 Adobe Inc. All rights reserved.
|
||||
|
||||
|
||||
import bpy
|
||||
|
||||
from ..api import SUBSTANCE_Api
|
||||
from ..utils import SUBSTANCE_Utils
|
||||
from ..common import Code_Response, Code_RequestType
|
||||
from ..sbsar.sbsar import SBSAR
|
||||
from ..thread_ops import SUBSTANCE_Threads
|
||||
from ..sbsar.async_ops import _render_sbsar
|
||||
|
||||
|
||||
class SUBSTANCE_OT_Version(bpy.types.Operator):
|
||||
bl_idname = 'substance.version'
|
||||
bl_label = 'Version'
|
||||
bl_description = 'Get the current plugin version'
|
||||
|
||||
def execute(self, context):
|
||||
_version = SUBSTANCE_Api.version()
|
||||
|
||||
SUBSTANCE_Utils.log_data(
|
||||
"INFO",
|
||||
"Substance Addon version {}.{}.{}".format(_version[0], _version[1], _version[2]),
|
||||
display=True)
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class SUBSTANCE_OT_Message(bpy.types.Operator):
|
||||
bl_idname = 'substance.send_message'
|
||||
bl_label = 'Message'
|
||||
bl_description = "Send the user a message"
|
||||
|
||||
type: bpy.props.StringProperty(default="INFO") # noqa
|
||||
message: bpy.props.StringProperty(default="") # noqa
|
||||
_timer = None
|
||||
|
||||
def modal(self, context, event):
|
||||
if event.type == 'TIMER':
|
||||
self.cancel(context)
|
||||
self.report({self.type}, self.message)
|
||||
return {'FINISHED'}
|
||||
return {'PASS_THROUGH'}
|
||||
|
||||
def execute(self, context):
|
||||
wm = context.window_manager
|
||||
self._timer = wm.event_timer_add(time_step=1, window=context.window)
|
||||
wm.modal_handler_add(self)
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
def cancel(self, context):
|
||||
wm = context.window_manager
|
||||
wm.event_timer_remove(self._timer)
|
||||
|
||||
|
||||
def load_sbsar(filename, filepath, uuid=None, type=Code_RequestType.r_async):
|
||||
_addon_prefs, _selected_shader_idx, _selected_shader_preset = SUBSTANCE_Utils.get_selected_shader(bpy.context)
|
||||
_normal_format = _addon_prefs.default_normal_format
|
||||
_output_size = _addon_prefs.default_resolution.get()
|
||||
|
||||
_unique_name = SUBSTANCE_Utils.get_unique_name(filename, bpy.context)
|
||||
|
||||
SUBSTANCE_Utils.log_data("INFO", "Begin loading substance from file [{}]".format(filename), display=True)
|
||||
if uuid:
|
||||
_data = {
|
||||
"uuid": uuid,
|
||||
"filepath": filepath,
|
||||
"name": _unique_name,
|
||||
"$outputsize": _output_size,
|
||||
"normal_format": 0 if _normal_format == "DirectX" else 1
|
||||
}
|
||||
else:
|
||||
_data = {
|
||||
"filepath": filepath,
|
||||
"name": _unique_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(filename, filepath),
|
||||
display=True)
|
||||
return
|
||||
if _result[1]["result"] != Code_Response.success.value:
|
||||
SUBSTANCE_Utils.log_data(
|
||||
"ERROR",
|
||||
"An error ocurred while loading Substance file [{}]. Failed with error [{}]".format(
|
||||
filepath, _result[1]["result"]),
|
||||
display=True)
|
||||
return
|
||||
|
||||
_sbsar = SBSAR(_unique_name, filename, _result[1]["data"], _addon_prefs)
|
||||
_loaded_sbsar = bpy.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)
|
||||
bpy.context.scene.sbsar_index = len(bpy.context.scene.loaded_sbsars) - 1
|
||||
|
||||
if type == Code_RequestType.r_async:
|
||||
for _idx, _graph in enumerate(_loaded_sbsar.graphs):
|
||||
SUBSTANCE_Threads.alt_thread_run(_render_sbsar, (bpy.context, _loaded_sbsar, _idx))
|
||||
|
||||
SUBSTANCE_Utils.log_data("INFO", "Substance from file [{}] loaded".format(filename))
|
||||
|
||||
|
||||
def load_usdz(filename, filepath, uuid=None):
|
||||
def load_file():
|
||||
bpy.ops.wm.usd_import(filepath=filepath, relative_path=True, set_material_blend=False)
|
||||
SUBSTANCE_Utils.log_data(
|
||||
"INFO",
|
||||
"Substance Connector file [{}] loaded".format(filename),
|
||||
display=True)
|
||||
SUBSTANCE_Threads.main_thread_run(load_file)
|
||||
@@ -0,0 +1,70 @@
|
||||
"""
|
||||
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/inputs.py
|
||||
# brief: Parameter Operators
|
||||
# author Adobe - 3D & Immersive
|
||||
# copyright 2023 Adobe Inc. All rights reserved.
|
||||
|
||||
|
||||
import bpy
|
||||
from random import randint
|
||||
|
||||
from ..utils import SUBSTANCE_Utils
|
||||
from ..common import INPUTS_MAX_RANDOM_SEED, Code_InputIdentifier
|
||||
|
||||
|
||||
class SUBSTANCE_OT_RandomizeSeed(bpy.types.Operator):
|
||||
bl_idname = 'substance.randomize_seed'
|
||||
bl_label = 'Randomize'
|
||||
bl_description = 'Generate a new random value for the current SBSAR randomseed parameter'
|
||||
|
||||
def execute(self, context):
|
||||
|
||||
_selected_graph = SUBSTANCE_Utils.get_selected_graph(context)
|
||||
_parms = getattr(context.scene, _selected_graph.inputs_class_name)
|
||||
_value = randint(0, INPUTS_MAX_RANDOM_SEED)
|
||||
setattr(_parms, Code_InputIdentifier.randomseed.value, _value)
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class SUBSTANCE_OT_InputGroupsCollapse(bpy.types.Operator):
|
||||
bl_idname = 'substance.input_groups_collapse'
|
||||
bl_label = 'Collapse Groups'
|
||||
bl_description = 'Collapse all groups'
|
||||
|
||||
def execute(self, context):
|
||||
|
||||
_selected_graph = SUBSTANCE_Utils.get_selected_graph(context)
|
||||
for _group in _selected_graph.input_groups:
|
||||
_group.collapsed = True
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class SUBSTANCE_OT_InputGroupsExpand(bpy.types.Operator):
|
||||
bl_idname = 'substance.input_groups_expand'
|
||||
bl_label = 'Expand Groups'
|
||||
bl_description = 'Expand all groups'
|
||||
|
||||
def execute(self, context):
|
||||
|
||||
_selected_graph = SUBSTANCE_Utils.get_selected_graph(context)
|
||||
for _group in _selected_graph.input_groups:
|
||||
_group.collapsed = False
|
||||
|
||||
return {'FINISHED'}
|
||||
@@ -0,0 +1,85 @@
|
||||
"""
|
||||
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/material.py
|
||||
# brief: Material operators
|
||||
# author Adobe - 3D & Immersive
|
||||
# copyright 2023 Adobe Inc. All rights reserved.
|
||||
|
||||
|
||||
import bpy
|
||||
import json
|
||||
import traceback
|
||||
|
||||
from ..utils import SUBSTANCE_Utils
|
||||
from ..common import Code_SbsarLoadSuffix
|
||||
from ..material.manager import SUBSTANCE_MaterialManager
|
||||
|
||||
|
||||
class SUBSTANCE_OT_SetMaterial(bpy.types.Operator):
|
||||
bl_idname = 'substance.set_material'
|
||||
bl_label = 'Set material'
|
||||
bl_description = "Set the material shader network"
|
||||
|
||||
data: bpy.props.StringProperty(default="") # noqa
|
||||
|
||||
def execute(self, context):
|
||||
_data = json.loads(self.data)
|
||||
|
||||
if len(_data["outputs"]) == 0:
|
||||
SUBSTANCE_Utils.log_data("INFO", "No render changes")
|
||||
return {'FINISHED'}
|
||||
|
||||
_sbsar = None
|
||||
for _item in context.scene.loaded_sbsars:
|
||||
if _item.uuid == _data["uuid"]:
|
||||
_sbsar = _item
|
||||
|
||||
if _sbsar is None:
|
||||
SUBSTANCE_Utils.log_data("ERROR", "Substance file cannot be found", display=True)
|
||||
return {'FINISHED'}
|
||||
|
||||
try:
|
||||
_graph = None
|
||||
for _item in _sbsar.graphs:
|
||||
_uid = str(_data["graph_uid"])
|
||||
if _uid == _item.uid:
|
||||
_graph = _item
|
||||
break
|
||||
|
||||
if _graph is None:
|
||||
SUBSTANCE_Utils.log_data("ERROR", "Substance graph cannot be found", display=True)
|
||||
return {'FINISHED'}
|
||||
|
||||
_graph.set_thumbnail(_data)
|
||||
|
||||
_outputs = {}
|
||||
for _output in _data["outputs"]:
|
||||
if _output["usage"] == "UNKNOWN":
|
||||
_outputs[_output["identifier"]] = _output
|
||||
else:
|
||||
_outputs[_output["usage"]] = _output
|
||||
_data["outputs"] = _outputs
|
||||
|
||||
SUBSTANCE_MaterialManager.create_shader_network(context, _sbsar, _graph, _data)
|
||||
|
||||
except Exception:
|
||||
_sbsar.suffix = Code_SbsarLoadSuffix.error.value[0]
|
||||
_sbsar.icon = Code_SbsarLoadSuffix.error.value[1]
|
||||
SUBSTANCE_Utils.log_data("ERROR", "Exception - Susbtance material creation error:")
|
||||
SUBSTANCE_Utils.log_traceback(traceback.format_exc())
|
||||
|
||||
return {'FINISHED'}
|
||||
@@ -0,0 +1,301 @@
|
||||
"""
|
||||
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/presets.py
|
||||
# brief:Presets Operators
|
||||
# author Adobe - 3D & Immersive
|
||||
# copyright 2023 Adobe Inc. All rights reserved.
|
||||
|
||||
|
||||
import os
|
||||
import bpy
|
||||
from bpy_extras.io_utils import ImportHelper
|
||||
from ..common import PRESET_DEFAULT, PRESET_CUSTOM, PRESET_EXTENSION, Code_Response
|
||||
from ..utils import SUBSTANCE_Utils
|
||||
from ..api import SUBSTANCE_Api
|
||||
from ..sbsar.sbsar import SBS_Preset
|
||||
|
||||
|
||||
class SUBSTANCE_OT_AddPreset(bpy.types.Operator):
|
||||
bl_idname = 'substance.add_preset'
|
||||
bl_label = 'New Preset'
|
||||
bl_description = "Set the current parameters as a new preset"
|
||||
|
||||
preset_name: bpy.props.StringProperty(name="Preset Name") # noqa
|
||||
|
||||
def invoke(self, context, event):
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
|
||||
def draw(self, context):
|
||||
_layout = self.layout
|
||||
|
||||
_row = _layout.row()
|
||||
_row.label(text="Preset Name: ")
|
||||
_row.prop(self, 'preset_name', text="")
|
||||
|
||||
def execute(self, context):
|
||||
if len(self.preset_name) > 0:
|
||||
_sbsar = context.scene.loaded_sbsars[context.scene.sbsar_index]
|
||||
_graph = SUBSTANCE_Utils.get_selected_graph(context)
|
||||
_result = SUBSTANCE_Api.sbsar_preset_add(_sbsar, _graph, self.preset_name)
|
||||
|
||||
if _result[0] != Code_Response.success:
|
||||
SUBSTANCE_Utils.log_data(
|
||||
"ERROR",
|
||||
"[{}] Error while creating the preset".format(Code_Response.preset_create_get_error))
|
||||
return {'FINISHED'}
|
||||
if _result[1]["result"] != Code_Response.success.value:
|
||||
SUBSTANCE_Utils.log_data(
|
||||
"ERROR",
|
||||
"[{}] Error while getting the newly created preset".format(
|
||||
Code_Response.preset_create_get_error))
|
||||
return {'FINISHED'}
|
||||
|
||||
_preset_value = _result[1]["data"]["value"]
|
||||
|
||||
_obj = {
|
||||
"index": len(_graph.presets),
|
||||
"label": self.preset_name,
|
||||
"embedded": False,
|
||||
"icon": "LOCKED",
|
||||
"value": _preset_value
|
||||
}
|
||||
_preset = SBS_Preset(_obj)
|
||||
_new_preset = _graph.presets.add()
|
||||
_new_preset.init(_preset)
|
||||
|
||||
_graph.preset_callback = False
|
||||
_graph.presets_list = _preset.index
|
||||
_graph.preset_callback = True
|
||||
|
||||
SUBSTANCE_Utils.log_data("INFO", "Preset [{}] created".format(self.preset_name), display=True)
|
||||
|
||||
self.preset_name = ""
|
||||
else:
|
||||
SUBSTANCE_Utils.log_data(
|
||||
"ERROR",
|
||||
"[{}] Error while creating new preset, please set a name for the preset".format(
|
||||
Code_Response.preset_create_no_name_error),
|
||||
display=True)
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class SUBSTANCE_OT_DeletePreset(bpy.types.Operator):
|
||||
bl_idname = 'substance.delete_preset'
|
||||
bl_label = 'Delete selected preset?'
|
||||
bl_description = "Delete the current preset"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
_selected_graph = SUBSTANCE_Utils.get_selected_graph(context)
|
||||
_selected_preset_idx = int(_selected_graph.presets_list)
|
||||
_selected_preset = _selected_graph.presets[_selected_preset_idx]
|
||||
return not _selected_preset.embedded and _selected_preset.icon != "UNLOCKED"
|
||||
|
||||
def execute(self, context):
|
||||
_selected_graph = SUBSTANCE_Utils.get_selected_graph(context)
|
||||
_selected_preset_idx = int(_selected_graph.presets_list)
|
||||
|
||||
_selected_graph.presets_list = "0"
|
||||
_selected_graph.presets.remove(_selected_preset_idx)
|
||||
|
||||
SUBSTANCE_Utils.log_data("INFO", "Preset deleted", display=True)
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class SUBSTANCE_OT_DeletePresetModal(bpy.types.Operator):
|
||||
bl_idname = 'substance.delete_preset_modal'
|
||||
bl_label = 'Delete selected preset?'
|
||||
bl_description = "Delete the current preset"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
_selected_graph = SUBSTANCE_Utils.get_selected_graph(context)
|
||||
_selected_preset_idx = int(_selected_graph.presets_list)
|
||||
_selected_preset = _selected_graph.presets[_selected_preset_idx]
|
||||
return not _selected_preset.embedded and _selected_preset.icon != "UNLOCKED"
|
||||
|
||||
def invoke(self, context, event):
|
||||
return context.window_manager.invoke_props_dialog(self)
|
||||
|
||||
def draw(self, context):
|
||||
_layout = self.layout
|
||||
|
||||
_row = _layout.row()
|
||||
_row.label(text="")
|
||||
|
||||
def execute(self, context):
|
||||
_selected_graph = SUBSTANCE_Utils.get_selected_graph(context)
|
||||
_selected_preset_idx = int(_selected_graph.presets_list)
|
||||
|
||||
_selected_graph.presets_list = "0"
|
||||
_selected_graph.presets.remove(_selected_preset_idx)
|
||||
|
||||
SUBSTANCE_Utils.log_data("INFO", "Preset deleted", display=True)
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class SUBSTANCE_OT_ImportPreset(bpy.types.Operator, ImportHelper):
|
||||
bl_idname = 'substance.import_preset'
|
||||
bl_label = 'Import preset'
|
||||
bl_description = "Import a preset from a file"
|
||||
|
||||
filter_glob: bpy.props.StringProperty(default='*' + PRESET_EXTENSION, options={'HIDDEN'}) # noqa
|
||||
files: bpy.props.CollectionProperty(name='SBSAR Presets Files', type=bpy.types.OperatorFileListElement) # noqa
|
||||
directory: bpy.props.StringProperty(subtype="DIR_PATH") # noqa
|
||||
|
||||
def __init__(self):
|
||||
self.filepath = ''
|
||||
|
||||
def execute(self, context):
|
||||
_sbsar = context.scene.loaded_sbsars[context.scene.sbsar_index]
|
||||
|
||||
for _f in self.files:
|
||||
_filepath = os.path.join(self.directory, _f.name)
|
||||
_result = SUBSTANCE_Api.sbsar_preset_read(_filepath)
|
||||
|
||||
if _result[0] != Code_Response.success:
|
||||
SUBSTANCE_Utils.log_data("ERROR", "[{}] Error while reading preset from file".format(_result))
|
||||
continue
|
||||
|
||||
_obj = _result[1]
|
||||
|
||||
_not_found = len(_sbsar.graphs)
|
||||
for _graph in _sbsar.graphs:
|
||||
if _graph.identifier not in _obj:
|
||||
_not_found -= 1
|
||||
continue
|
||||
|
||||
for _preset in _obj[_graph.identifier]:
|
||||
if _preset["label"] in _graph.presets:
|
||||
if _preset["label"] == PRESET_CUSTOM or _preset["label"] == PRESET_DEFAULT:
|
||||
SUBSTANCE_Utils.log_data(
|
||||
"ERROR",
|
||||
"[{}] Error [{}] cannot be overwritten".format(
|
||||
Code_Response.preset_import_protected_error,
|
||||
_preset["label"]),
|
||||
display=True)
|
||||
continue
|
||||
|
||||
for _existing_preset in _graph.presets:
|
||||
if _preset["label"] != _existing_preset.name:
|
||||
continue
|
||||
if _existing_preset.embedded:
|
||||
SUBSTANCE_Utils.log_data(
|
||||
"ERROR",
|
||||
"[{}] Error [{}] cannot be overwritten".format(
|
||||
Code_Response.preset_import_protected_error,
|
||||
_preset["label"]),
|
||||
display=True)
|
||||
continue
|
||||
|
||||
_existing_preset.value = _preset["value"]
|
||||
SUBSTANCE_Utils.log_data(
|
||||
"INFO",
|
||||
"Preset [{}] updated.".format(_preset["label"]),
|
||||
display=True)
|
||||
else:
|
||||
_obj = {
|
||||
"index": len(_graph.presets),
|
||||
"label": _preset["label"],
|
||||
"embedded": False,
|
||||
"icon": "LOCKED",
|
||||
"value": _preset["value"]
|
||||
}
|
||||
_preset = SBS_Preset(_obj)
|
||||
_new_preset = _graph.presets.add()
|
||||
_new_preset.init(_preset)
|
||||
|
||||
SUBSTANCE_Utils.log_data(
|
||||
"INFO",
|
||||
"Preset [{}] imported.".format(_preset.label),
|
||||
display=True)
|
||||
|
||||
if _not_found == 0:
|
||||
SUBSTANCE_Utils.log_data(
|
||||
"ERROR",
|
||||
"[{}] Error Preset [{}] cannot be imported in this substance".format(
|
||||
Code_Response.preset_import_not_graph,
|
||||
_f.name),
|
||||
display=True)
|
||||
return {'FINISHED'}
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class SUBSTANCE_OT_ExportPreset(bpy.types.Operator, ImportHelper):
|
||||
bl_idname = 'substance.export_preset'
|
||||
bl_label = 'Export preset'
|
||||
bl_description = "Export current preset to a file"
|
||||
|
||||
preset_name: bpy.props.StringProperty(name="Preset Name") # noqa
|
||||
|
||||
def __init__(self):
|
||||
self.filepath = ''
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
_selected_graph = SUBSTANCE_Utils.get_selected_graph(context)
|
||||
_selected_preset_idx = int(_selected_graph.presets_list)
|
||||
_selected_preset = _selected_graph.presets[_selected_preset_idx]
|
||||
|
||||
return _selected_preset.name != PRESET_DEFAULT and _selected_preset.name != PRESET_CUSTOM
|
||||
|
||||
def execute(self, context):
|
||||
_selected_graph = SUBSTANCE_Utils.get_selected_graph(context)
|
||||
_selected_preset_idx = int(_selected_graph.presets_list)
|
||||
_selected_preset = _selected_graph.presets[_selected_preset_idx]
|
||||
|
||||
_result = SUBSTANCE_Api.sbsar_preset_write(
|
||||
self.filepath,
|
||||
_selected_preset)
|
||||
if _result != Code_Response.success:
|
||||
SUBSTANCE_Utils.log_data(
|
||||
"ERROR",
|
||||
"[{}] Error while writting the current preset".format(_result),
|
||||
display=True)
|
||||
else:
|
||||
SUBSTANCE_Utils.log_data("INFO", "Preset exported", display=True)
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class SUBSTANCE_OT_ExportAll(bpy.types.Operator, ImportHelper):
|
||||
bl_idname = 'substance.export_all'
|
||||
bl_label = 'Export all custom presets'
|
||||
bl_description = "Export all custom presets"
|
||||
|
||||
def __init__(self):
|
||||
self.filepath = ''
|
||||
|
||||
def execute(self, context):
|
||||
_selected_graph = SUBSTANCE_Utils.get_selected_graph(context)
|
||||
|
||||
for _preset in _selected_graph.presets:
|
||||
if _preset.embedded or _preset.name == PRESET_CUSTOM:
|
||||
continue
|
||||
|
||||
_result = SUBSTANCE_Api.sbsar_preset_write(
|
||||
self.filepath,
|
||||
_preset)
|
||||
if _result != Code_Response.success:
|
||||
SUBSTANCE_Utils.log_data(
|
||||
"ERROR",
|
||||
"[{}] Error while writting the current preset".format(_result),
|
||||
display=True)
|
||||
else:
|
||||
SUBSTANCE_Utils.log_data("INFO", "Preset {} exported".format(_preset.label), display=True)
|
||||
|
||||
return {'FINISHED'}
|
||||
@@ -0,0 +1,378 @@
|
||||
"""
|
||||
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'}
|
||||
@@ -0,0 +1,151 @@
|
||||
"""
|
||||
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/shader.py
|
||||
# brief: Shader Operators
|
||||
# author Adobe - 3D & Immersive
|
||||
# copyright 2023 Adobe Inc. All rights reserved.
|
||||
|
||||
|
||||
import bpy
|
||||
import os
|
||||
|
||||
from ..common import ADDON_ROOT, ADDON_PACKAGE, Code_Response
|
||||
from ..api import SUBSTANCE_Api
|
||||
from ..utils import SUBSTANCE_Utils
|
||||
from ..shader.shader import ShaderPreset
|
||||
|
||||
|
||||
class SUBSTANCE_OT_LoadShaderPresets(bpy.types.Operator):
|
||||
bl_idname = 'substance.load_shader_presets'
|
||||
bl_label = 'Initialize shader preset'
|
||||
bl_description = "Initialize the shader presets"
|
||||
|
||||
def execute(self, context):
|
||||
_addon_prefs = context.preferences.addons[ADDON_PACKAGE].preferences
|
||||
_addon_prefs.shaders.clear()
|
||||
|
||||
_shaders_dir = os.path.join(ADDON_ROOT, "_presets/default").replace('\\', '/')
|
||||
for _filepath in os.listdir(_shaders_dir):
|
||||
# Load the shader presets
|
||||
_result = SUBSTANCE_Api.shader_load(_filepath)
|
||||
if _result[0] != Code_Response.success:
|
||||
SUBSTANCE_Utils.log_data(
|
||||
"ERROR",
|
||||
"Substance file [{}] could not be loaded".format(
|
||||
_filepath),
|
||||
display=True)
|
||||
continue
|
||||
_shader_preset = ShaderPreset(_filepath, _result[1])
|
||||
_new_shader = _addon_prefs.shaders.add()
|
||||
_new_shader.init(_shader_preset)
|
||||
SUBSTANCE_Api.shader_register(_shader_preset)
|
||||
SUBSTANCE_Utils.log_data("INFO", "Shader Presets [{}] initialized...".format(_filepath))
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class SUBSTANCE_OT_RemoveShaderPresets(bpy.types.Operator):
|
||||
bl_idname = 'substance.remove_shader_presets'
|
||||
bl_label = 'Remove shader preset'
|
||||
bl_description = "Remove the shader presets"
|
||||
|
||||
def execute(self, context):
|
||||
_addon_prefs = context.preferences.addons[ADDON_PACKAGE].preferences
|
||||
|
||||
_result = SUBSTANCE_Api.shader_presets_remove(_addon_prefs.shaders)
|
||||
if _result != Code_Response.success:
|
||||
SUBSTANCE_Utils.log_data("ERROR", "Shader Presets could not be removed...")
|
||||
_addon_prefs.shader_presets.clear()
|
||||
return {'FINISHED'}
|
||||
|
||||
SUBSTANCE_Utils.log_data("INFO", "Shader Presets fully removed...")
|
||||
_addon_prefs.shaders.clear()
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class SUBSTANCE_OT_SaveShaderPresets(bpy.types.Operator):
|
||||
bl_idname = 'substance.save_shader_presets'
|
||||
bl_label = 'Save shader preset'
|
||||
bl_description = "Save the shader presets"
|
||||
|
||||
def execute(self, context):
|
||||
_addon_prefs = context.preferences.addons[ADDON_PACKAGE].preferences
|
||||
|
||||
for _shader_preset in _addon_prefs.shaders:
|
||||
if _shader_preset.modified:
|
||||
_inputs = getattr(context.scene, _shader_preset.inputs_class_name)
|
||||
|
||||
_outputs = {}
|
||||
for _output in _shader_preset.outputs:
|
||||
_outputs[_output.id] = _output.to_json()
|
||||
|
||||
_obj = {
|
||||
"label": _shader_preset.label,
|
||||
"filename": _shader_preset.filename,
|
||||
"inputs": _inputs.get(),
|
||||
"outputs": _outputs,
|
||||
}
|
||||
|
||||
_result = SUBSTANCE_Api.shader_presets_save(_obj)
|
||||
if _result != Code_Response.success:
|
||||
SUBSTANCE_Utils.log_data(
|
||||
"ERROR",
|
||||
"Shader Presets [{}] could not be saved...[{}]".format(_obj["label"], _result))
|
||||
SUBSTANCE_Utils.log_data("INFO", "Shader Presets [{}] saved...".format(_obj["label"]))
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class SUBSTANCE_OT_ResetShaderPreset(bpy.types.Operator):
|
||||
bl_idname = 'substance.reset_shader_preset'
|
||||
bl_label = 'Reset shader preset'
|
||||
bl_description = "Reset the shader preset"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
_addons_prefs, _shader_idx, _shader = SUBSTANCE_Utils.get_selected_shader(context)
|
||||
return _shader.modified
|
||||
|
||||
def execute(self, context):
|
||||
_addons_prefs, _shader_idx, _shader = SUBSTANCE_Utils.get_selected_shader(context)
|
||||
|
||||
_inputs = getattr(context.scene, _shader.inputs_class_name)
|
||||
_inputs.reset()
|
||||
|
||||
# Load the shader presets
|
||||
_result = SUBSTANCE_Api.shader_load(_shader.filename)
|
||||
if _result[0] != Code_Response.success:
|
||||
SUBSTANCE_Utils.log_data(
|
||||
"ERROR",
|
||||
"Substance file [{}] could not be loaded".format(
|
||||
_shader.filename),
|
||||
display=True)
|
||||
return {'FINISHED'}
|
||||
|
||||
_outputs = _result[1]["outputs"]
|
||||
for _output in _shader.outputs:
|
||||
_output.enabled = _outputs[_output.id]["enabled"]
|
||||
_output.colorspace = _outputs[_output.id]["colorspace"]
|
||||
_output.format = _outputs[_output.id]["format"]
|
||||
_output.bitdepth = _outputs[_output.id]["bitdepth"]
|
||||
|
||||
_shader.modified = False
|
||||
|
||||
SUBSTANCE_Utils.log_data(
|
||||
"INFO",
|
||||
"Shader Presets [{}] is back to default values".format(_shader.label),
|
||||
display=True)
|
||||
return {'FINISHED'}
|
||||
@@ -0,0 +1,62 @@
|
||||
"""
|
||||
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/sync.py
|
||||
# brief: Sinstance Operators
|
||||
# author Adobe - 3D & Immersive
|
||||
# copyright 2023 Adobe Inc. All rights reserved.
|
||||
|
||||
|
||||
import os
|
||||
import bpy
|
||||
|
||||
from ..api import SUBSTANCE_Api
|
||||
from ..utils import SUBSTANCE_Utils
|
||||
from .common import load_sbsar, Code_RequestType, Code_Response
|
||||
|
||||
|
||||
class SUBSTANCE_OT_SyncInitTools(bpy.types.Operator):
|
||||
bl_idname = 'substance.init_tools_sync'
|
||||
bl_label = 'Initialize Substance Tools'
|
||||
|
||||
def execute(self, context):
|
||||
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 {'FINISHED'}
|
||||
|
||||
|
||||
class SUBSTANCE_OT_SyncLoadSBSAR(bpy.types.Operator):
|
||||
bl_idname = 'substance.load_sbsar_sync'
|
||||
bl_label = 'Load a Substance 3D file'
|
||||
|
||||
filepath: bpy.props.StringProperty(name="filepath", default="") # noqa
|
||||
|
||||
def execute(self, context):
|
||||
if len(self.filepath) > 0:
|
||||
if self.filepath.endswith('.sbsar'):
|
||||
_filename = os.path.basename(self.filepath)
|
||||
_filename = _filename.replace(".sbsar", "")
|
||||
|
||||
load_sbsar(_filename, self.filepath, type=Code_RequestType.r_sync)
|
||||
else:
|
||||
SUBSTANCE_Utils.log_data("ERROR", "[{}] is not a valid sbsar file".format(self.filepath))
|
||||
else:
|
||||
SUBSTANCE_Utils.log_data("ERROR", "[{}] is empty, is not a valid sbsar file".format(self.filepath))
|
||||
|
||||
return {'FINISHED'}
|
||||
@@ -0,0 +1,142 @@
|
||||
"""
|
||||
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/toolkit.py
|
||||
# brief:SRE Toolkit Operators
|
||||
# author Adobe - 3D & Immersive
|
||||
# copyright 2023 Adobe Inc. All rights reserved.
|
||||
|
||||
|
||||
import os
|
||||
import bpy
|
||||
import pathlib
|
||||
import subprocess
|
||||
import platform
|
||||
from bpy_extras.io_utils import ImportHelper
|
||||
|
||||
|
||||
from ..api import SUBSTANCE_Api
|
||||
from ..common import TOOLKIT_EXT, Code_Response, ADDON_PACKAGE, SRE_DIR
|
||||
from ..thread_ops import SUBSTANCE_Threads
|
||||
from ..utils import SUBSTANCE_Utils
|
||||
|
||||
|
||||
class SUBSTANCE_OT_OpenTools(bpy.types.Operator):
|
||||
bl_idname = 'substance.open_tools'
|
||||
bl_label = 'Open tools folder'
|
||||
bl_options = {'REGISTER'}
|
||||
|
||||
def execute(self, context):
|
||||
_addon_prefs = context.preferences.addons[ADDON_PACKAGE].preferences
|
||||
|
||||
_result = _addon_prefs.path_tools
|
||||
if platform.system() == "Windows":
|
||||
os.startfile(_result)
|
||||
elif platform.system() == "Darwin":
|
||||
subprocess.Popen(["open", _result])
|
||||
|
||||
SUBSTANCE_Utils.log_data(
|
||||
"INFO",
|
||||
"Substance 3D Integration Tools folder [{}] opened correctly".format(_result),
|
||||
display=True)
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class SUBSTANCE_OT_ResetTools(bpy.types.Operator):
|
||||
bl_idname = 'substance.reset_tools'
|
||||
bl_label = 'Reset tools folder'
|
||||
bl_options = {'REGISTER'}
|
||||
|
||||
def execute(self, context):
|
||||
_addon_prefs = context.preferences.addons[ADDON_PACKAGE].preferences
|
||||
|
||||
_home = pathlib.Path.home()
|
||||
_result = os.path.join(_home, SRE_DIR)
|
||||
_addon_prefs.path_tools = _result
|
||||
|
||||
SUBSTANCE_Utils.log_data(
|
||||
"INFO",
|
||||
"Substance 3D Integration Tools folder [{}] reset correctly".format(_result),
|
||||
display=True)
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class SUBSTANCE_OT_InstallTools(bpy.types.Operator, ImportHelper):
|
||||
bl_idname = 'substance.install_tools'
|
||||
bl_label = 'Install tools'
|
||||
bl_options = {'REGISTER'}
|
||||
filter_glob: bpy.props.StringProperty(default='*{}'.format(TOOLKIT_EXT), options={'HIDDEN'}) # noqa
|
||||
|
||||
def __init__(self):
|
||||
self.filepath = ''
|
||||
|
||||
def execute(self, context):
|
||||
SUBSTANCE_Threads.cursor_push('WAIT')
|
||||
_result = SUBSTANCE_Api.toolkit_install(self.filepath)
|
||||
SUBSTANCE_Threads.cursor_pop()
|
||||
|
||||
if _result[0] == Code_Response.success:
|
||||
SUBSTANCE_Utils.log_data("INFO", "Substance 3D Integration Tools installed correctly", display=True)
|
||||
else:
|
||||
SUBSTANCE_Utils.log_data(
|
||||
"ERROR",
|
||||
"[{}] Error while installing the Substance 3D Integration Tools".format(_result[0]),
|
||||
display=True)
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class SUBSTANCE_OT_UpdateTools(bpy.types.Operator, ImportHelper):
|
||||
bl_idname = 'substance.update_tools'
|
||||
bl_label = 'Update tools'
|
||||
bl_options = {'REGISTER'}
|
||||
filter_glob: bpy.props.StringProperty(default='*{}'.format(TOOLKIT_EXT), options={'HIDDEN'}) # noqa
|
||||
|
||||
def __init__(self):
|
||||
self.filepath = ''
|
||||
|
||||
def execute(self, context):
|
||||
SUBSTANCE_Threads.cursor_push('WAIT')
|
||||
_result = SUBSTANCE_Api.toolkit_install(self.filepath)
|
||||
SUBSTANCE_Threads.cursor_pop()
|
||||
|
||||
if _result[0] == Code_Response.success:
|
||||
SUBSTANCE_Utils.log_data("INFO", "Substance 3D Integration Tools updated correctly", display=True)
|
||||
else:
|
||||
SUBSTANCE_Utils.log_data(
|
||||
"ERROR",
|
||||
"[{}] Error while updating the Substance 3D Integration Tools".format(_result[0]),
|
||||
display=True)
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class SUBSTANCE_OT_UninstallTools(bpy.types.Operator):
|
||||
bl_idname = 'substance.uninstall_tools'
|
||||
bl_label = 'UnInstall tools'
|
||||
bl_options = {'REGISTER'}
|
||||
|
||||
def execute(self, context):
|
||||
SUBSTANCE_Threads.cursor_push('WAIT')
|
||||
_result = SUBSTANCE_Api.toolkit_uninstall()
|
||||
SUBSTANCE_Threads.cursor_pop()
|
||||
|
||||
if _result[0] == Code_Response.success:
|
||||
SUBSTANCE_Utils.log_data("INFO", "Substance 3D Integration Tools fully removed", display=True)
|
||||
else:
|
||||
SUBSTANCE_Utils.log_data(
|
||||
"ERROR",
|
||||
"[{}] Error while removing the Substance 3D Integration Tools".format(_result[0]),
|
||||
display=True)
|
||||
return {'FINISHED'}
|
||||
@@ -0,0 +1,90 @@
|
||||
"""
|
||||
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/web.py
|
||||
# brief: Web Operators
|
||||
# author Adobe - 3D & Immersive
|
||||
# copyright 2023 Adobe Inc. All rights reserved.
|
||||
|
||||
|
||||
import bpy
|
||||
|
||||
from ..common import (
|
||||
WEB_SUBSTANCE_TOOLS,
|
||||
WEB_SUBSTANCE_SHARE,
|
||||
WEB_SUBSTANCE_SOURCE,
|
||||
WEB_SUBSTANCE_DOCS,
|
||||
WEB_SUBSTANCE_FORUMS,
|
||||
WEB_SUBSTANCE_DISCORD
|
||||
)
|
||||
|
||||
|
||||
class SUBSTANCE_OT_GoToWebsite(bpy.types.Operator):
|
||||
bl_idname = 'substance.goto_web'
|
||||
bl_label = 'Substance 3D Open Website'
|
||||
bl_description = 'Go to Website'
|
||||
bl_options = {'REGISTER'}
|
||||
|
||||
url: bpy.props.StringProperty()
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.wm.url_open(url=self.url)
|
||||
return {'FINISHED'}
|
||||
|
||||
def invoke(self, context, event):
|
||||
return self.execute(context)
|
||||
|
||||
|
||||
class SUBSTANCE_OT_GetTools(SUBSTANCE_OT_GoToWebsite):
|
||||
bl_idname = 'substance.goto_tools'
|
||||
bl_label = 'Substance 3D Integration Tools'
|
||||
bl_description = 'Go to Substance 3D Integration Tools'
|
||||
url: bpy.props.StringProperty(default=WEB_SUBSTANCE_TOOLS)
|
||||
|
||||
|
||||
class SUBSTANCE_OT_GoToShare(SUBSTANCE_OT_GoToWebsite):
|
||||
bl_idname = 'substance.goto_share'
|
||||
bl_label = 'Substance 3D Community Assets'
|
||||
bl_description = 'Go to Substance 3D Community Assets'
|
||||
url: bpy.props.StringProperty(default=WEB_SUBSTANCE_SHARE)
|
||||
|
||||
|
||||
class SUBSTANCE_OT_GoToSource(SUBSTANCE_OT_GoToWebsite):
|
||||
bl_idname = 'substance.goto_source'
|
||||
bl_label = 'Substance 3D Assets'
|
||||
bl_description = 'Go to Substance 3D Assets'
|
||||
url: bpy.props.StringProperty(default=WEB_SUBSTANCE_SOURCE)
|
||||
|
||||
|
||||
class SUBSTANCE_OT_GoToDocs(SUBSTANCE_OT_GoToWebsite):
|
||||
bl_idname = 'substance.goto_docs'
|
||||
bl_label = 'Substance Plugin for Blender Documentation'
|
||||
bl_description = 'Go to Substance Plugin for Blender Documentation'
|
||||
url: bpy.props.StringProperty(default=WEB_SUBSTANCE_DOCS)
|
||||
|
||||
|
||||
class SUBSTANCE_OT_GoToForums(SUBSTANCE_OT_GoToWebsite):
|
||||
bl_idname = 'substance.goto_forums'
|
||||
bl_label = 'Substance 3D Forums'
|
||||
bl_description = 'Go to Substance 3D Forums'
|
||||
url: bpy.props.StringProperty(default=WEB_SUBSTANCE_FORUMS)
|
||||
|
||||
|
||||
class SUBSTANCE_OT_GoToDiscord(SUBSTANCE_OT_GoToWebsite):
|
||||
bl_idname = 'substance.goto_discord'
|
||||
bl_label = 'Substance 3D Discord Server'
|
||||
bl_description = 'Go to Substance 3D Discord Server'
|
||||
url: bpy.props.StringProperty(default=WEB_SUBSTANCE_DISCORD)
|
||||
Reference in New Issue
Block a user