429 lines
18 KiB
Python
429 lines
18 KiB
Python
from ngSkinTools2 import signal
|
|
from ngSkinTools2.api import BrushShape, PaintMode, PaintTool, WeightsDisplayMode
|
|
from ngSkinTools2.api import eventtypes as et
|
|
from ngSkinTools2.api.log import getLogger
|
|
from ngSkinTools2.api.paint import BrushProjectionMode, MaskDisplayMode
|
|
from ngSkinTools2.api.pyside import QAction, QActionGroup, QtGui, QtWidgets
|
|
from ngSkinTools2.api.session import session
|
|
from ngSkinTools2.ui import qt, widgets
|
|
from ngSkinTools2.ui.layout import TabSetup, createTitledRow
|
|
from ngSkinTools2.ui.qt import bind_action_to_button
|
|
|
|
log = getLogger("tab paint")
|
|
|
|
|
|
# noinspection PyShadowingNames
|
|
def build_ui(parent, global_actions):
|
|
"""
|
|
:type parent: PySide2.QtWidgets.QWidget
|
|
:type global_actions: ngSkinTools2.ui.actions.Actions
|
|
"""
|
|
paint = session.paint_tool
|
|
# TODO: move paint model to session maybe?
|
|
|
|
on_signal = session.signal_hub.on
|
|
|
|
def update_ui():
|
|
pass # noop until it's defined
|
|
|
|
def build_brush_settings_group():
|
|
def brush_mode_row3():
|
|
row = QtWidgets.QVBoxLayout()
|
|
|
|
group = QActionGroup(parent)
|
|
|
|
actions = {}
|
|
|
|
# noinspection PyShadowingNames
|
|
def create_brush_mode_button(t, mode, label, tooltip):
|
|
a = QAction(label, parent)
|
|
a.setToolTip(tooltip)
|
|
a.setCheckable(True)
|
|
actions[mode] = a
|
|
group.addAction(a)
|
|
|
|
@qt.on(a.toggled)
|
|
def toggled(checked):
|
|
if checked and paint.paint_mode != mode:
|
|
paint.paint_mode = mode
|
|
|
|
t.addAction(a)
|
|
|
|
t = QtWidgets.QToolBar()
|
|
create_brush_mode_button(t, PaintMode.replace, "替换", "替换")
|
|
create_brush_mode_button(t, PaintMode.add, "添加", "")
|
|
create_brush_mode_button(t, PaintMode.scale, "减少", "")
|
|
row.addWidget(t)
|
|
|
|
t = QtWidgets.QToolBar()
|
|
create_brush_mode_button(t, PaintMode.smooth, "平滑", "")
|
|
create_brush_mode_button(t, PaintMode.sharpen, "锐化", "")
|
|
row.addWidget(t)
|
|
|
|
@on_signal(et.tool_settings_changed, scope=row)
|
|
def update_current_brush_mode():
|
|
actions[paint.paint_mode].setChecked(True)
|
|
|
|
update_current_brush_mode()
|
|
|
|
return row
|
|
|
|
# noinspection DuplicatedCode
|
|
def brush_shape_row():
|
|
# noinspection PyShadowingNames
|
|
result = QtWidgets.QToolBar()
|
|
group = QActionGroup(parent)
|
|
|
|
def add_brush_shape_action(icon, title, shape, checked=False):
|
|
a = QAction(title, parent)
|
|
a.setCheckable(True)
|
|
a.setIcon(QtGui.QIcon(icon))
|
|
a.setChecked(checked)
|
|
result.addAction(a)
|
|
group.addAction(a)
|
|
|
|
# noinspection PyShadowingNames
|
|
def toggled(checked):
|
|
if checked:
|
|
paint.brush_shape = shape
|
|
update_ui()
|
|
|
|
# noinspection PyShadowingNames
|
|
@on_signal(et.tool_settings_changed, scope=a)
|
|
def update_to_tool():
|
|
a.setChecked(paint.brush_shape == shape)
|
|
|
|
update_to_tool()
|
|
qt.on(a.toggled)(toggled)
|
|
|
|
add_brush_shape_action(':/circleSolid.png', '硬边圆', BrushShape.solid, checked=True)
|
|
add_brush_shape_action(':/circlePoly.png', '软边圆', BrushShape.smooth)
|
|
add_brush_shape_action(':/circleGaus.png', '喷枪', BrushShape.gaus)
|
|
|
|
return result
|
|
|
|
# noinspection DuplicatedCode
|
|
def brush_projection_mode_row():
|
|
# noinspection PyShadowingNames
|
|
result = QtWidgets.QToolBar()
|
|
group = QActionGroup(parent)
|
|
|
|
def add(title, tooltip, mode, use_volume, checked):
|
|
a = QAction(title, parent)
|
|
a.setCheckable(True)
|
|
a.setChecked(checked)
|
|
a.setToolTip(tooltip)
|
|
a.setStatusTip(tooltip)
|
|
result.addAction(a)
|
|
group.addAction(a)
|
|
|
|
# noinspection PyShadowingNames
|
|
@qt.on(a.toggled)
|
|
def toggled(checked):
|
|
if checked:
|
|
paint.brush_projection_mode = mode
|
|
paint.use_volume_neighbours = use_volume
|
|
update_ui()
|
|
|
|
# noinspection PyShadowingNames
|
|
@on_signal(et.tool_settings_changed, scope=a)
|
|
def update_to_tool():
|
|
a.setChecked(
|
|
paint.brush_projection_mode == mode and (mode != BrushProjectionMode.surface or paint.use_volume_neighbours == use_volume)
|
|
)
|
|
|
|
with qt.signals_blocked(a):
|
|
update_to_tool()
|
|
|
|
add(
|
|
'表面',
|
|
'绘制由与曲面相连的模型顶点.'
|
|
+ '仅更新当前shell.',
|
|
BrushProjectionMode.surface,
|
|
use_volume=False,
|
|
checked=True,
|
|
)
|
|
add(
|
|
'体积',
|
|
'绘制附近所有模型顶点,包括没有连接的顶点.',
|
|
BrushProjectionMode.surface,
|
|
use_volume=True,
|
|
checked=False,
|
|
)
|
|
add(
|
|
'屏幕',
|
|
'从屏幕方向投影到笔刷半径内所有曲面上的顶点.',
|
|
BrushProjectionMode.screen,
|
|
use_volume=False,
|
|
checked=False,
|
|
)
|
|
|
|
return result
|
|
|
|
def stylus_pressure_selection():
|
|
# noinspection PyShadowingNames
|
|
result = QtWidgets.QComboBox()
|
|
result.addItem("未使用")
|
|
result.addItem("倍增强度")
|
|
result.addItem("倍增不透明度")
|
|
result.addItem("倍增半径")
|
|
return result
|
|
|
|
layout = QtWidgets.QVBoxLayout()
|
|
layout.addLayout(createTitledRow("笔刷投影:", brush_projection_mode_row()))
|
|
layout.addLayout(createTitledRow("笔刷模式:", brush_mode_row3()))
|
|
layout.addLayout(createTitledRow("笔刷形状:", brush_shape_row()))
|
|
intensity = widgets.NumberSliderGroup()
|
|
radius = widgets.NumberSliderGroup(
|
|
max_value=100, tooltip="可以通过在视口中按住 <b>B</b> " "并鼠标左键按住拖动来设置笔刷半径"
|
|
)
|
|
iterations = widgets.NumberSliderGroup(value_type=int, min_value=1, max_value=100)
|
|
layout.addLayout(createTitledRow("强度:", intensity.layout()))
|
|
layout.addLayout(createTitledRow("笔刷半径:", radius.layout()))
|
|
layout.addLayout(createTitledRow("笔刷迭代次数:", iterations.layout()))
|
|
|
|
influences_limit = widgets.NumberSliderGroup(value_type=int, min_value=0, max_value=10)
|
|
layout.addLayout(createTitledRow("影响物限制:", influences_limit.layout()))
|
|
|
|
@signal.on(influences_limit.valueChanged)
|
|
def influences_limit_changed():
|
|
paint.influences_limit = influences_limit.value()
|
|
update_ui()
|
|
|
|
fixed_influences = QtWidgets.QCheckBox("仅调整顶点现有影响物,防止周围权重扩散")
|
|
fixed_influences.setToolTip(
|
|
"启用此选项后,平滑-将仅调整每个顶点的现有影响物, "
|
|
"而不会包含来自附近顶点的影响物,防止周围权重扩散"
|
|
)
|
|
layout.addLayout(createTitledRow("平滑辅助:", fixed_influences))
|
|
|
|
@qt.on(fixed_influences.stateChanged)
|
|
def fixed_influences_changed():
|
|
paint.fixed_influences_per_vertex = fixed_influences.isChecked()
|
|
|
|
limit_to_component_selection = QtWidgets.QCheckBox("限制在选中组件中平滑")
|
|
limit_to_component_selection.setToolTip("启用此选项后,仅在选定组件之间进行平滑")
|
|
layout.addLayout(createTitledRow("隔离选中组件:", limit_to_component_selection))
|
|
|
|
@qt.on(limit_to_component_selection.stateChanged)
|
|
def limit_to_component_selection_changed():
|
|
paint.limit_to_component_selection = limit_to_component_selection.isChecked()
|
|
|
|
interactive_mirror = QtWidgets.QCheckBox("交互式镜像")
|
|
layout.addLayout(createTitledRow("", interactive_mirror))
|
|
|
|
@qt.on(interactive_mirror.stateChanged)
|
|
def interactive_mirror_changed():
|
|
paint.mirror = interactive_mirror.isChecked()
|
|
update_ui()
|
|
|
|
sample_joint_on_stroke_start = QtWidgets.QCheckBox("在笔画开始时取样当前骨骼")
|
|
layout.addLayout(createTitledRow("", sample_joint_on_stroke_start))
|
|
|
|
@qt.on(sample_joint_on_stroke_start.stateChanged)
|
|
def interactive_mirror_changed():
|
|
paint.sample_joint_on_stroke_start = sample_joint_on_stroke_start.isChecked()
|
|
update_ui()
|
|
|
|
redistribute_removed_weight = QtWidgets.QCheckBox("分配给其它影响物")
|
|
layout.addLayout(createTitledRow("移除的权重:", redistribute_removed_weight))
|
|
|
|
@qt.on(redistribute_removed_weight.stateChanged)
|
|
def redistribute_removed_weight_changed():
|
|
paint.distribute_to_other_influences = redistribute_removed_weight.isChecked()
|
|
update_ui()
|
|
|
|
stylus = stylus_pressure_selection()
|
|
layout.addLayout(createTitledRow("手绘板压力:", stylus))
|
|
|
|
@on_signal(et.tool_settings_changed, scope=layout)
|
|
def update_ui():
|
|
log.info("更新的绘制设置的ui")
|
|
log.info("画笔模式:%s, 画笔形状: %s", paint.mode, paint.brush_shape)
|
|
paint.update_plugin_brush_radius()
|
|
paint.update_plugin_brush_intensity()
|
|
|
|
with qt.signals_blocked(intensity):
|
|
intensity.set_value(paint.intensity)
|
|
widgets.set_paint_expo(intensity, paint.paint_mode)
|
|
|
|
with qt.signals_blocked(radius):
|
|
radius.set_range(0, 1000 if paint.brush_projection_mode == BrushProjectionMode.screen else 100, soft_max=True)
|
|
radius.set_value(paint.brush_radius)
|
|
|
|
with qt.signals_blocked(iterations):
|
|
iterations.set_value(paint.iterations)
|
|
iterations.set_enabled(paint.paint_mode in [PaintMode.smooth, PaintMode.sharpen])
|
|
|
|
with qt.signals_blocked(stylus):
|
|
stylus.setCurrentIndex(paint.tablet_mode)
|
|
|
|
with qt.signals_blocked(interactive_mirror):
|
|
interactive_mirror.setChecked(paint.mirror)
|
|
|
|
with qt.signals_blocked(redistribute_removed_weight):
|
|
redistribute_removed_weight.setChecked(paint.distribute_to_other_influences)
|
|
|
|
with qt.signals_blocked(influences_limit):
|
|
influences_limit.set_value(paint.influences_limit)
|
|
|
|
with qt.signals_blocked(sample_joint_on_stroke_start):
|
|
sample_joint_on_stroke_start.setChecked(paint.sample_joint_on_stroke_start)
|
|
|
|
with qt.signals_blocked(fixed_influences):
|
|
fixed_influences.setChecked(paint.fixed_influences_per_vertex)
|
|
fixed_influences.setEnabled(paint.paint_mode == PaintMode.smooth)
|
|
|
|
with qt.signals_blocked(limit_to_component_selection):
|
|
limit_to_component_selection.setChecked(paint.limit_to_component_selection)
|
|
limit_to_component_selection.setEnabled(fixed_influences.isEnabled())
|
|
|
|
@signal.on(radius.valueChanged, qtParent=layout)
|
|
def radius_edited():
|
|
log.info("更新笔刷半径")
|
|
paint.brush_radius = radius.value()
|
|
update_ui()
|
|
|
|
@signal.on(intensity.valueChanged, qtParent=layout)
|
|
def intensity_edited():
|
|
paint.intensity = intensity.value()
|
|
update_ui()
|
|
|
|
@signal.on(iterations.valueChanged, qtParent=layout)
|
|
def iterations_edited():
|
|
paint.iterations = iterations.value()
|
|
update_ui()
|
|
|
|
@qt.on(stylus.currentIndexChanged)
|
|
def stylus_edited():
|
|
paint.tablet_mode = stylus.currentIndex()
|
|
update_ui()
|
|
|
|
update_ui()
|
|
|
|
result = QtWidgets.QGroupBox("笔刷行为")
|
|
result.setLayout(layout)
|
|
return result
|
|
|
|
def build_display_settings():
|
|
result = QtWidgets.QGroupBox("显示设置")
|
|
layout = QtWidgets.QVBoxLayout()
|
|
|
|
influences_display = QtWidgets.QComboBox()
|
|
influences_display.addItem("所有权重,多种颜色", WeightsDisplayMode.allInfluences)
|
|
influences_display.addItem("当前权重,灰度", WeightsDisplayMode.currentInfluence)
|
|
influences_display.addItem("当前权重,彩色", WeightsDisplayMode.currentInfluenceColored)
|
|
influences_display.setMinimumWidth(1)
|
|
influences_display.setCurrentIndex(paint.display_settings.weights_display_mode)
|
|
|
|
display_toolbar = QtWidgets.QToolBar()
|
|
display_toolbar.addAction(global_actions.randomize_influences_colors)
|
|
|
|
@qt.on(influences_display.currentIndexChanged)
|
|
def influences_display_changed():
|
|
paint.display_settings.weights_display_mode = influences_display.currentData()
|
|
update_ui_to_tool()
|
|
|
|
display_layout = QtWidgets.QVBoxLayout()
|
|
display_layout.addWidget(influences_display)
|
|
display_layout.addWidget(display_toolbar)
|
|
layout.addLayout(createTitledRow("权重显示:", display_layout))
|
|
|
|
mask_display = QtWidgets.QComboBox()
|
|
mask_display.addItem("默认", MaskDisplayMode.default_)
|
|
mask_display.addItem("彩色渐变", MaskDisplayMode.color_ramp)
|
|
mask_display.setMinimumWidth(1)
|
|
mask_display.setCurrentIndex(paint.display_settings.weights_display_mode)
|
|
|
|
@qt.on(mask_display.currentIndexChanged)
|
|
def influences_display_changed():
|
|
paint.display_settings.mask_display_mode = mask_display.currentData()
|
|
update_ui_to_tool()
|
|
|
|
layout.addLayout(createTitledRow("表面显示:", mask_display))
|
|
|
|
show_effects = QtWidgets.QCheckBox("显示图层效果")
|
|
layout.addLayout(createTitledRow("", show_effects))
|
|
show_masked = QtWidgets.QCheckBox("显示蒙皮权重")
|
|
layout.addLayout(createTitledRow("", show_masked))
|
|
|
|
show_selected_verts_only = QtWidgets.QCheckBox("隐藏未选择的顶点")
|
|
layout.addLayout(createTitledRow("", show_selected_verts_only))
|
|
|
|
@qt.on(show_effects.stateChanged)
|
|
def show_effects_changed():
|
|
paint.display_settings.layer_effects_display = show_effects.isChecked()
|
|
|
|
@qt.on(show_masked.stateChanged)
|
|
def show_masked_changed():
|
|
paint.display_settings.display_masked = show_masked.isChecked()
|
|
|
|
@qt.on(show_selected_verts_only.stateChanged)
|
|
def show_selected_verts_changed():
|
|
paint.display_settings.show_selected_verts_only = show_selected_verts_only.isChecked()
|
|
|
|
mesh_toolbar = QtWidgets.QToolBar()
|
|
toggle_original_mesh = QAction("显示原本模型", mesh_toolbar)
|
|
toggle_original_mesh.setCheckable(True)
|
|
mesh_toolbar.addAction(toggle_original_mesh)
|
|
layout.addLayout(createTitledRow("", mesh_toolbar))
|
|
|
|
@qt.on(toggle_original_mesh.triggered)
|
|
def toggle_display_node_visible():
|
|
paint.display_settings.display_node_visible = not toggle_original_mesh.isChecked()
|
|
update_ui_to_tool()
|
|
|
|
wireframe_color_button = widgets.ColorButton()
|
|
layout.addLayout(createTitledRow("线框颜色:", wireframe_color_button))
|
|
|
|
@signal.on(wireframe_color_button.color_changed)
|
|
def update_wireframe_color():
|
|
if paint.display_settings.weights_display_mode == WeightsDisplayMode.allInfluences:
|
|
paint.display_settings.wireframe_color = wireframe_color_button.get_color_3f()
|
|
else:
|
|
paint.display_settings.wireframe_color_single_influence = wireframe_color_button.get_color_3f()
|
|
|
|
@signal.on(session.events.toolChanged, qtParent=tab.tabContents)
|
|
def update_ui_to_tool():
|
|
ds = paint.display_settings
|
|
toggle_original_mesh.setChecked(PaintTool.is_painting() and not ds.display_node_visible)
|
|
|
|
qt.select_data(influences_display, ds.weights_display_mode)
|
|
qt.select_data(mask_display, ds.mask_display_mode)
|
|
show_effects.setChecked(ds.layer_effects_display)
|
|
show_masked.setChecked(ds.display_masked)
|
|
show_selected_verts_only.setChecked(ds.show_selected_verts_only)
|
|
global_actions.randomize_influences_colors.setEnabled(ds.weights_display_mode == WeightsDisplayMode.allInfluences)
|
|
display_toolbar.setVisible(global_actions.randomize_influences_colors.isEnabled())
|
|
|
|
if ds.weights_display_mode == WeightsDisplayMode.allInfluences:
|
|
wireframe_color_button.set_color(ds.wireframe_color)
|
|
else:
|
|
wireframe_color_button.set_color(ds.wireframe_color_single_influence)
|
|
|
|
update_ui_to_tool()
|
|
|
|
result.setLayout(layout)
|
|
return result
|
|
|
|
tab = TabSetup()
|
|
tab.innerLayout.addWidget(build_brush_settings_group())
|
|
tab.innerLayout.addWidget(build_display_settings())
|
|
tab.innerLayout.addStretch()
|
|
|
|
tab.lowerButtonsRow.addWidget(bind_action_to_button(global_actions.paint, QtWidgets.QPushButton()))
|
|
tab.lowerButtonsRow.addWidget(bind_action_to_button(global_actions.flood, QtWidgets.QPushButton()))
|
|
|
|
@signal.on(session.events.toolChanged, qtParent=tab.tabContents)
|
|
def update_to_tool():
|
|
tab.scrollArea.setEnabled(PaintTool.is_painting())
|
|
|
|
@signal.on(session.events.targetChanged, qtParent=tab.tabContents)
|
|
def update_tab_enabled():
|
|
tab.tabContents.setEnabled(session.state.layersAvailable)
|
|
|
|
update_to_tool()
|
|
update_tab_enabled()
|
|
|
|
return tab.tabContents
|