487 lines
20 KiB
Python
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
|