Files
Nexus/2025/scripts/rigging_tools/ngskintools2/ui/tabLayerEffects.py
2026-01-22 00:06:13 +08:00

201 lines
8.1 KiB
Python

from ngSkinTools2 import signal
from ngSkinTools2.api.log import getLogger
from ngSkinTools2.api.mirror import MirrorOptions
from ngSkinTools2.api.pyside import QtCore, QtWidgets
from ngSkinTools2.api.session import session
from ngSkinTools2.ui import qt, widgets
from ngSkinTools2.ui.layout import TabSetup, createTitledRow
log = getLogger("tab layer effects")
def check_state_from_boolean_states(states):
"""
for a list of booleans, return checkbox check state - one of Qt.Checked, Qt.Unchecked and Qt.PartiallyChecked
:type states: list[bool]
"""
current_state = None
for i in states:
if current_state is None:
current_state = i
continue
if i != current_state:
return QtCore.Qt.PartiallyChecked
if current_state:
return QtCore.Qt.Checked
return QtCore.Qt.Unchecked
def build_ui():
def list_layers():
return [] if not session.state.layersAvailable else session.context.selected_layers(default=[])
def build_properties():
layout = QtWidgets.QVBoxLayout()
opacity = widgets.NumberSliderGroup(tooltip="多层蒙版来控制图层的整体透明度.")
opacity.set_value(1.0)
layout.addLayout(createTitledRow("不透明度:", opacity.layout()))
def default_selection_opacity(layers):
if len(layers) > 0:
return layers[0].opacity
return 1.0
@signal.on(session.context.selected_layers.changed, session.events.currentLayerChanged, qtParent=tab.tabContents)
def update_values():
layers = list_layers()
enabled = len(layers) > 0
opacity.set_enabled(enabled)
opacity.set_value(default_selection_opacity(layers))
@signal.on(opacity.valueChanged)
def opacity_edited():
layers = list_layers()
# avoid changing opacity of all selected layers if we just changed slider value based on changed layer selection
if opacity.value() == default_selection_opacity(layers):
return
val = opacity.value()
for i in list_layers():
if abs(i.opacity - val) > 0.00001:
i.opacity = val
update_values()
group = QtWidgets.QGroupBox("图层属性")
group.setLayout(layout)
return group
def build_mirror_effect():
def configure_mirror_all_layers(option, value):
for i in list_layers():
i.effects.configure_mirror(**{option: value})
mirror_direction = QtWidgets.QComboBox()
mirror_direction.addItem("从正到负", MirrorOptions.directionPositiveToNegative)
mirror_direction.addItem("从负到正", MirrorOptions.directionNegativeToPositive)
mirror_direction.addItem("翻转", MirrorOptions.directionFlip)
mirror_direction.setMinimumWidth(1)
@qt.on(mirror_direction.currentIndexChanged)
def value_changed():
configure_mirror_all_layers("镜像方向", mirror_direction.currentData())
influences = QtWidgets.QCheckBox("影响物权重")
mask = QtWidgets.QCheckBox("图层蒙板")
dq = QtWidgets.QCheckBox("双四元数权重")
def configure_checkbox(checkbox, option):
@qt.on(checkbox.stateChanged)
def update_pref():
if checkbox.checkState() == QtCore.Qt.PartiallyChecked:
checkbox.setCheckState(QtCore.Qt.Checked)
enabled = checkbox.checkState() == QtCore.Qt.Checked
configure_mirror_all_layers(option, enabled)
configure_checkbox(influences, 'mirror_weights')
configure_checkbox(mask, 'mirror_mask')
configure_checkbox(dq, 'mirror_dq')
@signal.on(session.context.selected_layers.changed, session.events.currentLayerChanged, qtParent=tab.tabContents)
def update_values():
layers = list_layers()
with qt.signals_blocked(influences):
influences.setCheckState(check_state_from_boolean_states([i.effects.mirror_weights for i in layers]))
with qt.signals_blocked(mask):
mask.setCheckState(check_state_from_boolean_states([i.effects.mirror_mask for i in layers]))
with qt.signals_blocked(dq):
dq.setCheckState(check_state_from_boolean_states([i.effects.mirror_dq for i in layers]))
with qt.signals_blocked(mirror_direction):
qt.select_data(mirror_direction, MirrorOptions.directionPositiveToNegative if not layers else layers[0].effects.mirror_direction)
update_values()
def elements():
result = QtWidgets.QVBoxLayout()
for i in [influences, mask, dq]:
i.setTristate(True)
result.addWidget(i)
return result
layout = QtWidgets.QVBoxLayout()
layout.addLayout(createTitledRow("镜像效果打开:", elements()))
layout.addLayout(createTitledRow("镜像方向:", mirror_direction))
group = QtWidgets.QGroupBox("镜像")
group.setLayout(layout)
return group
def build_skin_properties():
use_max_influences = QtWidgets.QCheckBox("限制每个顶点的最大影响物")
max_influences = widgets.NumberSliderGroup(min_value=1, max_value=5, tooltip="", value_type=int)
use_prune_weight = QtWidgets.QCheckBox("在写入蒙皮簇之前修剪小权重")
prune_weight = widgets.NumberSliderGroup(decimals=6, min_value=0.000001, max_value=0.05, tooltip="")
prune_weight.set_value(prune_weight.min_value)
prune_weight.set_expo("开始", 3)
@signal.on(session.events.targetChanged)
def update_ui():
group.setEnabled(session.state.layersAvailable)
if session.state.layersAvailable:
with qt.signals_blocked(use_max_influences):
use_max_influences.setChecked(session.state.layers.influence_limit_per_vertex != 0)
with qt.signals_blocked(max_influences):
max_influences.set_value(session.state.layers.influence_limit_per_vertex if use_max_influences.isChecked() else 4)
with qt.signals_blocked(use_prune_weight):
use_prune_weight.setChecked(session.state.layers.prune_weights_filter_threshold != 0)
with qt.signals_blocked(prune_weight):
prune_weight.set_value(session.state.layers.prune_weights_filter_threshold if use_prune_weight.isChecked() else 0.0001)
update_ui_enabled()
def update_ui_enabled():
max_influences.set_enabled(use_max_influences.isChecked())
prune_weight.set_enabled(use_prune_weight.isChecked())
@qt.on(use_max_influences.stateChanged, use_prune_weight.stateChanged)
@signal.on(max_influences.valueChanged, prune_weight.valueChanged)
def update_values():
log.info("更新效果标签页")
if session.state.layersAvailable:
session.state.layers.influence_limit_per_vertex = max_influences.value() if use_max_influences.isChecked() else 0
session.state.layers.prune_weights_filter_threshold = 0 if not use_prune_weight.isChecked() else prune_weight.value_trimmed()
update_ui_enabled()
layout = QtWidgets.QVBoxLayout()
layout.addWidget(use_max_influences)
layout.addLayout(createTitledRow("最大影响物:", max_influences.layout()))
layout.addWidget(use_prune_weight)
layout.addLayout(createTitledRow("修剪以下:", prune_weight.layout()))
group = QtWidgets.QGroupBox("蒙皮属性")
group.setLayout(layout)
update_ui()
return group
tab = TabSetup()
tab.innerLayout.addWidget(build_properties())
tab.innerLayout.addWidget(build_mirror_effect())
tab.innerLayout.addWidget(build_skin_properties())
tab.innerLayout.addStretch()
@signal.on(session.events.targetChanged, qtParent=tab.tabContents)
def update_tab_enabled():
tab.tabContents.setEnabled(session.state.layersAvailable)
update_tab_enabled()
return tab.tabContents