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

487 lines
20 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: ui/sbsar.py
# brief: Substance UI
# author Adobe - 3D & Immersive
# copyright 2023 Adobe Inc. All rights reserved.
import bpy
from ..ops.web import SUBSTANCE_OT_GoToShare, SUBSTANCE_OT_GoToSource
from ..ops.sbsar import (
SUBSTANCE_OT_LoadSBSAR,
SUBSTANCE_OT_ApplySBSAR,
SUBSTANCE_OT_RemoveSBSAR,
SUBSTANCE_OT_ReloadSBSAR,
SUBSTANCE_OT_DuplicateSBSAR
)
from ..ops.inputs import (
SUBSTANCE_OT_RandomizeSeed,
SUBSTANCE_OT_InputGroupsCollapse,
SUBSTANCE_OT_InputGroupsExpand
)
from ..ops.presets import SUBSTANCE_OT_AddPreset, SUBSTANCE_OT_DeletePreset, SUBSTANCE_OT_DeletePresetModal
from .presets import SUBSTANCE_MT_PresetOptions
from ..api import SUBSTANCE_Api
from ..utils import SUBSTANCE_Utils
from ..common import (
ADDON_PACKAGE,
ICONS_DICT,
DRAW_DEFAULT_FACTOR,
Code_InputIdentifier,
Code_OutputSizeSuffix,
Code_InputWidget,
Code_InputType,
INPUT_CHANNELS_GROUP,
OUTPUTS_FILTER_DICT,
INPUT_TECHINCAL_GROUP,
INPUT_DEFAULT_GROUP,
INPUT_IMAGE_DEFAULT_GROUP,
Code_SbsarLoadSuffix
)
class SUBSTANCE_UL_SbsarList(bpy.types.UIList):
bl_idname = 'SUBSTANCE_UL_SbsarList'
bl_label = 'Loaded Substance 3D Materials'
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
_suffix = item.suffix
_is_rendering = SUBSTANCE_Api.sbsar_is_rendering(item.uuid)
if item.icon == Code_SbsarLoadSuffix.render.value[1] or _is_rendering == 2:
_icon = ICONS_DICT["render"].icon_id
_suffix = Code_SbsarLoadSuffix.render.value[0]
elif _is_rendering == 1:
_icon = ICONS_DICT["render_queue"].icon_id
_suffix = Code_SbsarLoadSuffix.render_queue.value[0]
elif item.icon != Code_SbsarLoadSuffix.success.value[1]:
_icon = ICONS_DICT[item.icon].icon_id
else:
_icon = 0
_name = item.name + " " + _suffix
# draw the item in the layout
if self.layout_type in {'DEFAULT', 'COMPACT'}:
layout.label(text=_name, icon_value=_icon)
elif self.layout_type in {'GRID'}:
layout.alignment = 'CENTER'
layout.label(text='', icon_value=_icon)
def SubstanceMainPanelFactory(space):
class SUBSTANCE_PT_MAIN(bpy.types.Panel):
bl_idname = 'SUBSTANCE_PT_MAIN_{}'.format(space)
bl_space_type = space
bl_label = 'Substance 3D Panel'
bl_region_type = 'UI'
bl_category = 'Substance 3D'
def draw(self, context):
# Shortcut
_shortcut = context.preferences.addons[ADDON_PACKAGE].preferences.shortcuts
self.layout.label(text="(Quick Access: " + _shortcut.menu_label + ")")
_row = self.layout.row(align=True)
# Buttons (Operators)
_row.operator(SUBSTANCE_OT_LoadSBSAR.bl_idname, text="Load")
_row.separator()
_row.operator(SUBSTANCE_OT_ApplySBSAR.bl_idname, text="Apply")
_row.separator()
_row.operator(SUBSTANCE_OT_GoToShare.bl_idname, text="", icon_value=ICONS_DICT["share_icon"].icon_id)
_row.separator()
_row.operator(SUBSTANCE_OT_GoToSource.bl_idname, text="", icon_value=ICONS_DICT["source_icon"].icon_id)
_row.separator()
_row.operator(SUBSTANCE_OT_DuplicateSBSAR.bl_idname, text="", icon='DUPLICATE')
_row.separator()
_row.operator(SUBSTANCE_OT_ReloadSBSAR.bl_idname, text="", icon='FILE_REFRESH')
_row.separator()
_row.operator(SUBSTANCE_OT_RemoveSBSAR.bl_idname, text="", icon='TRASH')
_row.separator()
# SBSAR List
if len(context.scene.loaded_sbsars) > 0:
_col = self.layout.column()
_col.label(text="Loaded 3D Substance Materials")
_col.template_list(
SUBSTANCE_UL_SbsarList.bl_idname,
'Loaded 3D Substance Materials',
context.scene,
'loaded_sbsars',
context.scene,
'sbsar_index')
_selected_sbsar = context.scene.loaded_sbsars[context.scene.sbsar_index]
if len(_selected_sbsar.graphs) > 1:
_row = _col.row()
_split = _row.split(factor=0.25)
_col_1 = _split.column()
_col_2 = _split.column()
_col_1.label(text="Graph")
_col_2.prop(_selected_sbsar, "graphs_list", text="")
return SUBSTANCE_PT_MAIN
def SubstancePreviewFactory(space):
class SUBSTANCE_PT_PREVIEW(bpy.types.Panel):
bl_idname = 'SUBSTANCE_PT_PREVIEW_{}'.format(space)
bl_space_type = space
bl_label = 'Preview'
bl_region_type = 'UI'
bl_category = 'Substance 3D'
@classmethod
def poll(cls, context):
return len(context.scene.loaded_sbsars) > 0
def draw(self, context):
_selected_graph = SUBSTANCE_Utils.get_selected_graph(context)
_col = self.layout.column(align=True)
if context.scene.sbsar_redraw == 0:
_col.scale_y = 0.99
elif context.scene.sbsar_redraw == 1:
_col.scale_y = 1.0
else:
_col.scale_y = 1.01
# Thumbnail
if _selected_graph.material.name in bpy.data.materials:
_grid = _col.grid_flow(columns=1, align=True)
if _selected_graph.material.name in bpy.data.materials:
_grid.template_preview(bpy.data.materials[_selected_graph.material.name], show_buttons=False)
# if _selected_graph.thumbnail in bpy.data.textures:
# _grid.template_ID_preview(bpy.data.textures[_selected_graph.thumbnail], "image")
return SUBSTANCE_PT_PREVIEW
def SubstanceGraphPanelFactory(space):
class SUBSTANCE_PT_GRAPH(bpy.types.Panel):
bl_idname = 'SUBSTANCE_PT_GRAPH_{}'.format(space)
bl_space_type = space
bl_label = 'Graph Parameters'
bl_region_type = 'UI'
bl_category = 'Substance 3D'
@classmethod
def poll(cls, context):
return len(context.scene.loaded_sbsars) > 0
def draw(self, context):
_selected_graph = SUBSTANCE_Utils.get_selected_graph(context)
_addon_prefs = context.preferences.addons[ADDON_PACKAGE].preferences
_col = self.layout.column(align=True)
# 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="Preset")
_row = _col_2.row()
_row.prop(_selected_graph, "presets_list", text='')
_row.operator(SUBSTANCE_OT_AddPreset.bl_idname, text='', icon='ADD')
if _addon_prefs.preset_auto_delete_enabled:
_row.operator(SUBSTANCE_OT_DeletePreset.bl_idname, text='', icon='TRASH')
else:
_row.operator(SUBSTANCE_OT_DeletePresetModal.bl_idname, text='', icon='TRASH')
_row.menu(SUBSTANCE_MT_PresetOptions.bl_idname, icon='TRIA_DOWN')
# Phyisical Size
_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="Physical Size")
_row = _col_2.row()
_row.prop(_selected_graph.physical_size, "x", text='')
_row.enabled = False
_row_2 = _row.column()
_row_2.prop(_selected_graph.physical_size, "y", text='')
_row_2.enabled = False
_row_3 = _row.column()
_row_3.prop(_selected_graph.physical_size, "z", text='')
_row_3.enabled = False
# 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(_selected_graph.tiling, "x", text='')
_row_2 = _row.column()
_row_2.prop(_selected_graph.tiling, "y", text='')
_row_2.enabled = not _selected_graph.tiling.linked
_row_3 = _row.column()
_row_3.prop(_selected_graph.tiling, "z", text='')
_row_3.enabled = not _selected_graph.tiling.linked
_row.prop(_selected_graph.tiling, "linked", text='', icon='LOCKED')
_inputs = getattr(context.scene, _selected_graph.inputs_class_name)
if _selected_graph.outputsize_exists:
_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(
_inputs,
Code_InputIdentifier.outputsize.value + Code_OutputSizeSuffix.width.value,
text='')
_row_2 = _row.column()
_row_2.prop(
_inputs,
Code_InputIdentifier.outputsize.value + Code_OutputSizeSuffix.height.value,
text='')
_linked = getattr(_inputs, Code_InputIdentifier.outputsize.value + Code_OutputSizeSuffix.linked.value)
_row_2.enabled = not _linked
_row.prop(
_inputs,
Code_InputIdentifier.outputsize.value + Code_OutputSizeSuffix.linked.value,
text='',
icon='LOCKED')
if _selected_graph.randomseed_exists:
_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="Random Seed")
_row = _col_2.row()
_row.prop(
_inputs,
Code_InputIdentifier.randomseed.value,
text="")
_row.operator(
SUBSTANCE_OT_RandomizeSeed.bl_idname,
text="",
icon_value=ICONS_DICT["random_icon"].icon_id)
# Update textures only
_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.prop(_selected_graph, "update_tx_only", text="Only update texture images")
return SUBSTANCE_PT_GRAPH
def SubstanceOutputsPanelFactory(space):
class SUBSTANCE_PT_OUTPUTS(bpy.types.Panel):
bl_idname = 'SUBSTANCE_PT_OUTPUTS_{}'.format(space)
bl_space_type = space
bl_label = 'Outputs'
bl_region_type = 'UI'
bl_category = 'Substance 3D'
@classmethod
def poll(cls, context):
if len(context.scene.loaded_sbsars) > 0:
return True
return False
def draw(self, context):
_selected_graph = SUBSTANCE_Utils.get_selected_graph(context)
_col = self.layout.column(align=True)
_box = _col.box()
_row = _box.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")
_row = _col_2.row()
_row.prop(_selected_graph, 'shaders_list', text="")
_row.prop(_selected_graph, 'outputs_filter', text="", expand=True)
# Show current outputs
_selected_preset_idx = int(_selected_graph.shaders_list)
_, _, _selected_shader_preset = SUBSTANCE_Utils.get_selected_shader(context, _selected_preset_idx)
_shader_outputs = _selected_shader_preset.outputs
for _output in _selected_graph.outputs:
if _selected_graph.outputs_filter == OUTPUTS_FILTER_DICT[0][0] and not _output.shader_enabled:
continue
if (_selected_graph.outputs_filter == OUTPUTS_FILTER_DICT[1][0] and
_output.defaultChannelUse not in _shader_outputs):
continue
_row = _box.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, "shader_enabled", text="")
if _output.type == "image":
_row.prop(_output, "shader_format", text="")
_row.prop(_output, "shader_bitdepth", text="")
return SUBSTANCE_PT_OUTPUTS
def SubstanceInputsPanelFactory(space):
class SUBSTANCE_PT_INPUTS(bpy.types.Panel):
bl_idname = 'SUBSTANCE_PT_INPUTS_{}'.format(space)
bl_space_type = space
bl_label = 'Parameters'
bl_region_type = 'UI'
bl_category = 'Substance 3D'
@classmethod
def poll(cls, context):
if len(context.scene.loaded_sbsars) > 0:
return True
return False
def draw(self, context):
_selected_graph = SUBSTANCE_Utils.get_selected_graph(context)
_col = self.layout.column(align=True)
_row = _col.row()
_row.operator(SUBSTANCE_OT_InputGroupsExpand.bl_idname, text='Expand Groups', icon='TRIA_DOWN')
_row.operator(SUBSTANCE_OT_InputGroupsCollapse.bl_idname, text='Collapse Groups', icon='TRIA_RIGHT')
if SUBSTANCE_Utils.inputs_empty(_selected_graph.inputs):
_row = _col.row()
_row.label(text="No parameters available")
else:
_group_labels = []
for _group in _selected_graph.input_groups:
if _group.label == INPUT_IMAGE_DEFAULT_GROUP:
_group_labels.append(_group)
for _group in _selected_graph.input_groups:
if _group.label == INPUT_DEFAULT_GROUP:
_group_labels.append(_group)
for _group in _selected_graph.input_groups:
if (_group.label != INPUT_TECHINCAL_GROUP and
_group.label != INPUT_DEFAULT_GROUP and
_group.label != INPUT_IMAGE_DEFAULT_GROUP):
_group_labels.append(_group)
for _group in _selected_graph.input_groups:
if _group.label == INPUT_TECHINCAL_GROUP:
_group_labels.append(_group)
for _group in _group_labels:
if _group.label == INPUT_CHANNELS_GROUP:
continue
_empty = True
for _idx, _input in enumerate(_selected_graph.inputs):
if (_group.label == _input.guiGroup and
_input.visibleIf and
_input.identifier != Code_InputIdentifier.randomseed.value and
_input.identifier != Code_InputIdentifier.outputsize.value):
_empty = False
break
if _empty:
continue
_box = _col.box()
_row = _box.row()
_row.alignment = "LEFT"
_row.prop(
_group,
"collapsed",
icon='TRIA_DOWN' if not _group.collapsed else 'TRIA_RIGHT',
text=_group.label+":",
emboss=False)
if not _group.collapsed:
_inputs = getattr(context.scene, _selected_graph.inputs_class_name)
for _idx, _input in enumerate(_selected_graph.inputs):
if _input.guiGroup != _group.label:
continue
if (_input.guiWidget == Code_InputWidget.nowidget.value and
_input.type != Code_InputType.string.name):
continue
if not _input.visibleIf:
continue
_row = _box.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_InputType.string.name:
_row.prop(_inputs, _input.identifier, text="")
elif _input.guiWidget == Code_InputWidget.combobox.value:
_row.prop(_inputs, _input.identifier, text="")
elif _input.guiWidget == Code_InputWidget.slider.value:
_row.prop(_inputs, _input.identifier, text="", slider=True)
elif _input.guiWidget == Code_InputWidget.togglebutton.value:
_row.prop(_inputs, _input.identifier, expand=True)
elif _input.guiWidget == Code_InputWidget.color.value:
if _input.type == Code_InputType.float.name:
_row.prop(_inputs, _input.identifier, text="", slider=True)
else:
_row.prop(_inputs, _input.identifier, text="")
elif _input.guiWidget == Code_InputWidget.angle.value:
_row.prop(_inputs, _input.identifier, text="", slider=True)
elif _input.guiWidget == Code_InputWidget.position.value:
_row.prop(_inputs, _input.identifier, text="", slider=True)
elif _input.guiWidget == Code_InputWidget.image.value:
_row.template_ID(_inputs, _input.identifier, open="image.open")
else:
_row.alert = True
_row.label(text="Parameter type [{}] not supported yet".format(
_input.guiWidget))
_row.alert = False
return SUBSTANCE_PT_INPUTS