""" 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: 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