201 lines
8.1 KiB
Python
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="multiply layer mask to control overall transparency of the layer.")
|
|
opacity.set_value(1.0)
|
|
layout.addLayout(createTitledRow("Opacity:", 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("Layer properties")
|
|
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("Positive to negative", MirrorOptions.directionPositiveToNegative)
|
|
mirror_direction.addItem("Negative to positive", MirrorOptions.directionNegativeToPositive)
|
|
mirror_direction.addItem("Flip", MirrorOptions.directionFlip)
|
|
mirror_direction.setMinimumWidth(1)
|
|
|
|
@qt.on(mirror_direction.currentIndexChanged)
|
|
def value_changed():
|
|
configure_mirror_all_layers("mirror_direction", mirror_direction.currentData())
|
|
|
|
influences = QtWidgets.QCheckBox("Influence weights")
|
|
mask = QtWidgets.QCheckBox("Layer mask")
|
|
dq = QtWidgets.QCheckBox("Dual quaternion weights")
|
|
|
|
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("Mirror effect on:", elements()))
|
|
layout.addLayout(createTitledRow("Mirror direction:", mirror_direction))
|
|
|
|
group = QtWidgets.QGroupBox("Mirror")
|
|
group.setLayout(layout)
|
|
return group
|
|
|
|
def build_skin_properties():
|
|
use_max_influences = QtWidgets.QCheckBox("Limit max influences per vertex")
|
|
max_influences = widgets.NumberSliderGroup(min_value=1, max_value=5, tooltip="", value_type=int)
|
|
use_prune_weight = QtWidgets.QCheckBox("Prune small weights before writing to skin cluster")
|
|
|
|
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("start", 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("updating effects tab")
|
|
|
|
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:", max_influences.layout()))
|
|
layout.addWidget(use_prune_weight)
|
|
layout.addLayout(createTitledRow("Prune below:", prune_weight.layout()))
|
|
|
|
group = QtWidgets.QGroupBox("Skin Properties")
|
|
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
|