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

658 lines
24 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: preferences.py
# brief: Addon Preferences
# author Adobe - 3D & Immersive
# copyright 2023 Adobe Inc. All rights reserved.
import os
import pathlib
import bpy
import sys
from .api import SUBSTANCE_Api
from .props.utils import get_shaders
from .props.shader import SUBSTANCE_PG_ShaderPreset
from .props.common import SUBSTANCE_PG_Tiling, SUBSTANCE_PG_Resolution
from .props.shortcuts import SUBSTANCE_PG_Shortcuts
from .utils import SUBSTANCE_Utils
from .ops.toolkit import (
SUBSTANCE_OT_UninstallTools,
SUBSTANCE_OT_UpdateTools,
SUBSTANCE_OT_InstallTools,
SUBSTANCE_OT_OpenTools,
SUBSTANCE_OT_ResetTools
)
from .ops.web import SUBSTANCE_OT_GetTools, SUBSTANCE_OT_GoToDocs, SUBSTANCE_OT_GoToForums, SUBSTANCE_OT_GoToDiscord
from .ops.shader import SUBSTANCE_OT_ResetShaderPreset
from .common import (
ADDON_PACKAGE,
PATH_LIBARY_DEFAULT,
TOOLKIT_EXPECTED_VERSION,
DRAW_DEFAULT_FACTOR,
IMAGE_EXPORT_FORMAT,
PATH_DEFAULT,
Code_ShaderInputType,
APPLY_TYPE_DICT,
IMAGE_FORMAT_DICT,
IMAGE_BITDEPTH_DICT,
SRE_ENGINE_DICT,
SRE_DIR
)
def get_toolkit_path():
_home = pathlib.Path.home()
return os.path.join(_home, SRE_DIR)
def get_colorspace_dict(self, context):
return SUBSTANCE_Utils.get_colorspaces()
def get_shader_bitdepths(self, context):
return IMAGE_BITDEPTH_DICT[self.output_default_format]
def get_engine_options(self, context):
if sys.platform == "win32":
return SRE_ENGINE_DICT["WINDOWS"]
elif sys.platform == "linux":
return SRE_ENGINE_DICT["LINUX"]
elif sys.platform == "darwin":
return SRE_ENGINE_DICT["DARWIN"]
class SUBSTANCE_AddonPreferences(bpy.types.AddonPreferences):
bl_idname = ADDON_PACKAGE
default_tiling: bpy.props.PointerProperty(
name="default_tiling",
description="The default tiling to be used in the shader network", # noqa
type=SUBSTANCE_PG_Tiling)
default_resolution: bpy.props.PointerProperty(
name="default_resolution",
description="The default resolution to be used in the substance", # noqa
type=SUBSTANCE_PG_Resolution)
default_normal_format: bpy.props.EnumProperty(
name="default_normal_format",
default="OpenGL", # noqa
description="The default normal format", # noqa
items=[("OpenGL", "OpenGL", ""), ("DirectX", "DirectX", "")]) # noqa
default_apply_type: bpy.props.EnumProperty(
name="default_apply_type",
default="INSERT", # noqa
description="Append the material to the object, or Insert it as top material", # noqa
items=APPLY_TYPE_DICT)
default_export_format: bpy.props.EnumProperty(
name="default_export_format",
default="1", # noqa
description="The image export format for input parameters", # noqa
items=IMAGE_EXPORT_FORMAT)
default_export_format: bpy.props.EnumProperty(
name="default_export_format",
default="1", # noqa
description="The image export format for input parameters", # noqa
items=IMAGE_EXPORT_FORMAT)
default_group_collapse: bpy.props.BoolProperty(
name='Input groups collapsed by default', # noqa
default=False,
description='Automatically collapse the substance material input groups when loaded') # noqa
default_tx_update: bpy.props.BoolProperty(
name='Only update textures by default', # noqa
default=False,
description='Only update the textures without modifing the shader network when updating a substance material') # noqa
auto_apply_material: bpy.props.BoolProperty(
name='Automatically apply the material', # noqa
default=False,
description='Automatically apply the substance material to the selected object(s) when loaded') # noqa
auto_highlight_sbsar: bpy.props.BoolProperty(
name='Automatically highlight the material for selected objects', # noqa
default=True,
description='When an object is selected in the 3D view automatically update the loaded Substance 3D Material') # noqa
cycles_autoupdate_enabled: bpy.props.BoolProperty(
name='Cycles Auto-update textures', # noqa
default=False,
description='Force reload textures when updated in cycles') # noqa
auto_start_sre: bpy.props.BoolProperty(
name='Automatically start the Substance Remote Engine', # noqa
default=False,
description='Automatically start the Substance Remote Engine.') # noqa
auto_fake_usr: bpy.props.BoolProperty(
name='Create materials with fake user enabled.', # noqa
default=True,
description='Automatically enable the fake user when creating Substance materials.') # noqa
path_relative_sbsar_enabled: bpy.props.BoolProperty(
name='Auto-package sbsar files on save', # noqa
default=False,
description='Copy the loaded sbsar files to the defined relative path when you save the blend file') # noqa
path_tools: bpy.props.StringProperty(
name='Integration Tools path', # noqa
default=get_toolkit_path(),
subtype='DIR_PATH', # noqa
description='Open by default this path when loading an Substance file (*.sbsar)') # noqa
path_library: bpy.props.StringProperty(
name='Sbsar library path', # noqa
default=PATH_LIBARY_DEFAULT,
subtype='DIR_PATH', # noqa
description='Open by default this path when loading an Substance file (*.sbsar)') # noqa
path_default: bpy.props.StringProperty(
name='Temporary Path', # noqa
default=PATH_DEFAULT,
subtype='DIR_PATH', # noqa
description='Path used for temporary exports of the material texture maps') # noqa
path_relative_sbsar: bpy.props.StringProperty(
name='Relative path for Substance files', # noqa
default="sbsar/", # noqa
description='When saved, copy the substance file to this relative path in order to pack the project') # noqa
path_relative_tx: bpy.props.StringProperty(
name='Relative path for texture files', # noqa
default="substance tx/$matname", # noqa
description='When saved copy the texture files to this relative path in order to pack the project') # noqa
output_default_enabled: bpy.props.BoolProperty(
name="output_default_colorspace",
default=False, # noqa
description="The default enabled status to be used when creating the shader network") # noqa
output_default_colorspace: bpy.props.EnumProperty(
name="output_default_colorspace",
default=0, # noqa
description="The default colorspace to be used when creating the shader network", # noqa
items=get_colorspace_dict)
output_default_format: bpy.props.EnumProperty(
name="output_default_format",
default="TGA", # noqa
description="The default file format to be used by the Output", # noqa
items=IMAGE_FORMAT_DICT)
output_default_bitdepth: bpy.props.EnumProperty(
name="output_default_bitdepth",
default=0,
description="The default bitdepth of the Output", # noqa
items=get_shader_bitdepths)
shortcuts: bpy.props.PointerProperty(type=SUBSTANCE_PG_Shortcuts)
shader_list: bpy.props.EnumProperty(
name="shader_list",
description="The available shader network presets", # noqa
items=get_shaders)
shaders: bpy.props.CollectionProperty(type=SUBSTANCE_PG_ShaderPreset)
collapse_shader_inputs: bpy.props.BoolProperty(default=True)
collapse_shader_outputs: bpy.props.BoolProperty(default=True)
collapse_shortcuts: bpy.props.BoolProperty(default=True)
preset_auto_delete_enabled: bpy.props.BoolProperty(
name="Remove preset delete confirmation", # noqa
default=False, # noqa
description="Remove delete preset confimation window") # noqa
engine: bpy.props.EnumProperty(
name="engine",
description="The engine use on the Substance Remote Engine", # noqa
items=get_engine_options)
def get_default_output(self):
return {
"enabled": self.output_default_enabled,
"colorspace": self.output_default_colorspace,
"format": self.output_default_format,
"bitdepth": self.output_default_bitdepth
}
def draw(self, context):
_col = self.layout.column(align=True)
_row = _col.row()
_split = _row.split(factor=DRAW_DEFAULT_FACTOR)
_col_1 = _split.column()
_col_2 = _split.column()
_row = _col_1.row()
_row.alignment = "RIGHT"
_row.label(text="Integration Tools Install Path")
_row = _col_2.row()
_row.prop(self, "path_tools", text='')
_row.operator(SUBSTANCE_OT_OpenTools.bl_idname, text='Open Tools')
_row.operator(SUBSTANCE_OT_ResetTools.bl_idname, text='Reset Path')
_result = SUBSTANCE_Api.toolkit_version_get()
if _result[1] is not None and _result[1] in TOOLKIT_EXPECTED_VERSION:
_col.label(text='Thank you for installing the Substance 3D Integration Tools. {}'.format(_result[1]))
_row = _col.row()
_row.operator(SUBSTANCE_OT_UninstallTools.bl_idname, text='Uninstall Tools', icon='TRASH')
_row.operator(SUBSTANCE_OT_UpdateTools.bl_idname, text='Update Tools', icon='FILE_REFRESH')
elif _result[1] is not None and _result[1] not in TOOLKIT_EXPECTED_VERSION:
_col.alert = True
_msg = 'Update Needed! Please download and install a compatible Integration tool version: [{}] '.format(
" , ".join(TOOLKIT_EXPECTED_VERSION))
_col.label(text=_msg)
_col.alert = False
_row = _col.row()
_row.operator(SUBSTANCE_OT_GetTools.bl_idname, text='Download', icon='URL')
_row.operator(SUBSTANCE_OT_UpdateTools.bl_idname, text='Update Tools', icon='FILEBROWSER')
return
else:
_col.alert = True
_msg = 'Please download and install Integration Tools, a separate module for Adobe Substance 3D'
_col.label(text=_msg)
_col.alert = False
_row = _col.row()
_row.operator(SUBSTANCE_OT_GetTools.bl_idname, text='Download', icon='URL')
_row.operator(SUBSTANCE_OT_InstallTools.bl_idname, text='Install from disk', icon='FILEBROWSER')
return
_row = _col.row()
_row.label(text="")
# Docs
_row = _col.row()
_row.label(text="")
_row = _col.row()
_row.label(text="Help & Resources:")
_row = _col.row()
_split = _row.split(factor=DRAW_DEFAULT_FACTOR)
_col_1 = _split.column()
_col_2 = _split.column()
_col_1.alignment = "RIGHT"
_col_1.label(text="Documentation")
_col_2.operator(SUBSTANCE_OT_GoToDocs.bl_idname, text='Documentation', icon='URL')
_row = _col.row()
_split = _row.split(factor=DRAW_DEFAULT_FACTOR)
_col_1 = _split.column()
_col_2 = _split.column()
_col_1.alignment = "RIGHT"
_col_1.label(text="Forums")
_col_2.operator(SUBSTANCE_OT_GoToForums.bl_idname, text='Forums', icon='URL')
_row = _col.row()
_split = _row.split(factor=DRAW_DEFAULT_FACTOR)
_col_1 = _split.column()
_col_2 = _split.column()
_col_1.alignment = "RIGHT"
_col_1.label(text="Discord Sever")
_col_2.operator(SUBSTANCE_OT_GoToDiscord.bl_idname, text='Discord Server', icon='URL')
# Options
_row = _col.row()
_row.label(text="")
_row = _col.row()
_row.label(text="Defaults:")
# Tiling
_row = _col.row()
_split = _row.split(factor=DRAW_DEFAULT_FACTOR)
_col_1 = _split.column()
_col_2 = _split.column()
_row = _col_1.row()
_row.alignment = "RIGHT"
_row.label(text="Tiling")
_row = _col_2.row()
_row.prop(self.default_tiling, "x", text='')
_row_2 = _row.column()
_row_2.prop(self.default_tiling, "y", text='')
_row_2.enabled = not self.default_tiling.linked
_row_3 = _row.column()
_row_3.prop(self.default_tiling, "z", text='')
_row_3.enabled = not self.default_tiling.linked
_row.prop(self.default_tiling, "linked", text='', icon='LOCKED')
# Resolution
_row = _col.row()
_split = _row.split(factor=DRAW_DEFAULT_FACTOR)
_col_1 = _split.column()
_col_2 = _split.column()
_row = _col_1.row()
_row.alignment = "RIGHT"
_row.label(text="Resolution")
_row = _col_2.row()
_row.prop(self.default_resolution, "width", text='')
_row_2 = _row.column()
_row_2.prop(self.default_resolution, "height", text='')
_row_2.enabled = not self.default_resolution.linked
_row.prop(self.default_resolution, "linked", text='', icon='LOCKED')
# Normal Format
# _row = _col.row()
# _split = _row.split(factor=DRAW_DEFAULT_FACTOR)
# _col_1 = _split.column()
# _col_2 = _split.column()
# _row = _col_1.row()
# _row.alignment = "RIGHT"
# _row.label(text="Normal Format")
# _row = _col_2.row()
# _row.prop(self, "default_normal_format", text='')
# APPLY TYPE Format
_row = _col.row()
_split = _row.split(factor=DRAW_DEFAULT_FACTOR)
_col_1 = _split.column()
_col_2 = _split.column()
_row = _col_1.row()
_row.alignment = "RIGHT"
_row.label(text="Apply Type")
_row = _col_2.row()
_row.prop(self, "default_apply_type", text='')
# Export Image Format
_row = _col.row()
_split = _row.split(factor=DRAW_DEFAULT_FACTOR)
_col_1 = _split.column()
_col_2 = _split.column()
_row = _col_1.row()
_row.alignment = "RIGHT"
_row.label(text="Export Image Format")
_row = _col_2.row()
_row.prop(self, "default_export_format", text='')
_row = _col_2.row()
_row.prop(self, 'default_group_collapse')
_row = _col_2.row()
_row.prop(self, 'default_tx_update')
# SRE Engine
_row = _col.row()
_row.label(text="")
_row = _col.row()
_row.label(text="Substance Remote Engine:")
_row = _col.row()
_split = _row.split(factor=DRAW_DEFAULT_FACTOR)
_col_1 = _split.column()
_col_2 = _split.column()
_row = _col_1.row()
_row.alignment = "RIGHT"
_row.label(text="Engine")
_row = _col_2.row()
_row.prop(self, "engine", text='')
# Automations
_row = _col.row()
_row.label(text="")
_row = _col.row()
_row.label(text="Automations:")
_row = _col.row()
_split = _row.split(factor=DRAW_DEFAULT_FACTOR)
_col_1 = _split.column()
_col_2 = _split.column()
_row = _col_2.row()
_col_2.prop(self, 'auto_apply_material')
_row = _col_2.row()
_row.prop(self, 'auto_highlight_sbsar')
_row = _col_2.row()
_row.prop(self, 'cycles_autoupdate_enabled')
_row = _col_2.row()
_row.prop(self, 'preset_auto_delete_enabled')
_row = _col_2.row()
_row.prop(self, 'auto_fake_usr')
_row = _col_2.row()
_row.prop(self, 'auto_start_sre')
# Paths
_row = _col.row()
_row.label(text="")
_row = _col.row()
_row.label(text="Paths:")
_row = _col.row()
_split = _row.split(factor=DRAW_DEFAULT_FACTOR)
_col_1 = _split.column()
_col_2 = _split.column()
_row = _col_1.row()
_row.alignment = "RIGHT"
_row.label(text="SBSAR Library Path")
_row = _col_2.row()
_row.prop(self, "path_library", text='')
_row = _col.row()
_split = _row.split(factor=DRAW_DEFAULT_FACTOR)
_col_1 = _split.column()
_col_2 = _split.column()
_row = _col_1.row()
_row.alignment = "RIGHT"
_row.label(text="Temporary Folder")
_row = _col_2.row()
_row.prop(self, "path_default", text='')
_row = _col.row()
_row.label(text="")
_row = _col.row()
_row.label(text="Relative Paths:")
_row = _col.row()
_split = _row.split(factor=DRAW_DEFAULT_FACTOR)
_col_1 = _split.column()
_col_2 = _split.column()
_row = _col_1.row()
_row.alignment = "RIGHT"
_row.label(text="Copy [*.sbsar] files on save to")
_row = _col_2.row()
_row.prop(self, 'path_relative_sbsar_enabled', text="")
_row = _row.row()
_row.enabled = self.path_relative_sbsar_enabled
_row.prop(self, "path_relative_sbsar", text="")
_row = _col.row()
_split = _row.split(factor=DRAW_DEFAULT_FACTOR)
_col_1 = _split.column()
_col_2 = _split.column()
_row = _col_1.row()
_row.alignment = "RIGHT"
_row.label(text="On save, copy textures to")
_row = _col_2.row()
_row.prop(self, "path_relative_tx", text='')
_row = _col.row()
_row.label(text="")
# Options
_row = _col.row()
_row.label(text="")
_row = _col.row()
_row.label(text="Shader Networks:")
# Show current shader preset
_selected_preset_idx = int(self.shader_list)
_selected_preset = self.shaders[_selected_preset_idx]
_modified = "(*)" if _selected_preset.modified else ""
# Shader Presets
_row = _col.row()
_split = _row.split(factor=DRAW_DEFAULT_FACTOR)
_col_1 = _split.column()
_col_2 = _split.column()
_row = _col_1.row()
_row.alignment = "RIGHT"
_row.label(text="Shader Preset" + _modified)
_row = _col_2.row()
_row.prop(self, "shader_list", text="")
_row.operator(SUBSTANCE_OT_ResetShaderPreset.bl_idname, text="", icon="LOOP_BACK")
_row = _col.row()
_row.label(text="")
# Shader preset parameters
if len(_selected_preset.inputs) != 0:
_row = _col.row()
_row.alignment = "LEFT"
_row.prop(
self,
"collapse_shader_inputs",
icon='TRIA_DOWN' if self.collapse_shader_inputs else 'TRIA_RIGHT',
text="Parameters:",
emboss=False)
if self.collapse_shader_inputs:
_inputs = getattr(context.scene, _selected_preset.inputs_class_name)
for _idx, _input in enumerate(_selected_preset.inputs):
_row = _col.row()
_split = _row.split(factor=DRAW_DEFAULT_FACTOR)
_col_1 = _split.column()
_col_2 = _split.column()
_row = _col_1.row()
_row.alignment = "RIGHT"
_row.label(text=_input.label)
_row = _col_2.row()
if _input.type == Code_ShaderInputType.float_maxmin.value:
_row.prop(_inputs, _input.id, text="")
elif _input.type == Code_ShaderInputType.float_slider.value:
_row.prop(_inputs, _input.id, text="", slider=True)
else:
_row.label(text="Parameter not supported yet")
_row = _col.row()
_row.label(text="")
# Shader preset outputs
_row = _col.row()
_row.alignment = "LEFT"
_row.prop(
self,
"collapse_shader_outputs",
icon='TRIA_DOWN' if self.collapse_shader_outputs else 'TRIA_RIGHT',
text="Outputs:",
emboss=False)
if self.collapse_shader_outputs:
for _idx, _output in enumerate(_selected_preset.outputs):
_row = _col.row()
_split = _row.split(factor=DRAW_DEFAULT_FACTOR)
_col_1 = _split.column()
_col_2 = _split.column()
_row = _col_1.row()
_row.alignment = "RIGHT"
_row.label(text=_output.label)
_row = _col_2.row()
_row.prop(_output, "enabled", text="")
_row.prop(_output, "colorspace", text="")
_row.prop(_output, "format", text="")
_row.prop(_output, "bitdepth", text="")
_row = _col.row()
_row.label(text="")
_row = _col.row()
_split = _row.split(factor=DRAW_DEFAULT_FACTOR)
_col_1 = _split.column()
_col_2 = _split.column()
_row = _col_1.row()
_row.alignment = "RIGHT"
_row.label(text="Generic Output")
_row = _col_2.row()
_row.prop(self, "output_default_enabled", text="")
_row.prop(self, "output_default_colorspace", text="")
_row.prop(self, "output_default_format", text="")
_row.prop(self, "output_default_bitdepth", text="")
# Shortcuts
_row = _col.row()
_row.alignment = "LEFT"
_row.prop(
self,
"collapse_shortcuts",
icon='TRIA_DOWN' if self.collapse_shortcuts else 'TRIA_RIGHT',
text="Shortcuts:",
emboss=False)
if self.collapse_shortcuts:
_row = _col.row()
_split = _row.split(factor=DRAW_DEFAULT_FACTOR)
_col_1 = _split.column()
_col_2 = _split.column()
_row = _col_1.row()
_row.alignment = "RIGHT"
_row.label(text="")
_row = _col_2.row()
_row.label(text="CTRL")
_row.label(text="SHIFT")
_row.label(text="ALT")
_row.label(text="KEY")
_row = _col_1.row()
_row.alignment = "RIGHT"
_row.label(text=self.shortcuts.menu_name)
_row = _col_2.row()
_split = _row.split(factor=0.25)
_col_3 = _split.column()
_row = _col_3.row()
_row.prop(self.shortcuts, 'menu_ctrl', text="")
_col_4 = _split.column()
_row = _col_4.row()
_row.prop(self.shortcuts, 'menu_shift', text="")
_col_5 = _split.column()
_row = _col_5.row()
_row.prop(self.shortcuts, 'menu_alt', text="")
_col_6 = _split.column()
_row = _col_6.row()
_row.prop(self.shortcuts, 'menu_key', text="")
_row = _col_1.row()
_row.alignment = "RIGHT"
_row.label(text=self.shortcuts.load_name)
_row = _col_2.row()
_split = _row.split(factor=0.25)
_col_3 = _split.column()
_row = _col_3.row()
_row.prop(self.shortcuts, 'load_ctrl', text="")
_col_4 = _split.column()
_row = _col_4.row()
_row.prop(self.shortcuts, 'load_shift', text="")
_col_5 = _split.column()
_row = _col_5.row()
_row.prop(self.shortcuts, 'load_alt', text="")
_col_6 = _split.column()
_row = _col_6.row()
_row.prop(self.shortcuts, 'load_key', text="")
_row = _col_1.row()
_row.alignment = "RIGHT"
_row.label(text=self.shortcuts.apply_name)
_row = _col_2.row()
_split = _row.split(factor=0.25)
_col_3 = _split.column()
_row = _col_3.row()
_row.prop(self.shortcuts, 'apply_ctrl', text="")
_col_4 = _split.column()
_row = _col_4.row()
_row.prop(self.shortcuts, 'apply_shift', text="")
_col_5 = _split.column()
_row = _col_5.row()
_row.prop(self.shortcuts, 'apply_alt', text="")
_col_6 = _split.column()
_row = _col_6.row()
_row.prop(self.shortcuts, 'apply_key', text="")
_row = _col_2.row()
_row.label(text="*Shortcut updates need a restart in order to be applied.")
_row = _col.row()
_row.label(text="")