""" Copyright (C) 2023 Adobe. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . """ # file: 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="")