316 lines
11 KiB
Python
316 lines
11 KiB
Python
from ngSkinTools2 import signal
|
|
from ngSkinTools2.api import influence_names
|
|
from ngSkinTools2.api.influenceMapping import InfluenceInfo
|
|
from ngSkinTools2.api.layers import Layer
|
|
from ngSkinTools2.api.log import getLogger
|
|
from ngSkinTools2.api.pyside import QtCore, QtGui, QtWidgets
|
|
from ngSkinTools2.api.target_info import list_influences
|
|
from ngSkinTools2.ui import actions, qt
|
|
from ngSkinTools2.ui.layout import scale_multiplier
|
|
from ngSkinTools2.ui.options import Config, config
|
|
|
|
log = getLogger("influencesView")
|
|
_ = Layer # only imported for type reference
|
|
|
|
|
|
def build_used_influences_action(parent):
|
|
def toggle():
|
|
config.influences_show_used_influences_only.set(not config.influences_show_used_influences_only())
|
|
|
|
result = actions.define_action(
|
|
parent,
|
|
"Used Influences Only",
|
|
callback=toggle,
|
|
tooltip="If enabled, influences view will only show influences that have weights on current layer",
|
|
)
|
|
|
|
@signal.on(config.influences_show_used_influences_only.changed, qtParent=parent)
|
|
def update():
|
|
result.setChecked(config.influences_show_used_influences_only())
|
|
|
|
result.setCheckable(True)
|
|
update()
|
|
return result
|
|
|
|
|
|
def build_set_influences_sorted_action(parent):
|
|
from ngSkinTools2.ui import actions
|
|
|
|
def toggle():
|
|
new_value = Config.InfluencesSortDescending
|
|
if config.influences_sort() == new_value:
|
|
new_value = Config.InfluencesSortUnsorted
|
|
config.influences_sort.set(new_value)
|
|
|
|
result = actions.define_action(
|
|
parent,
|
|
"Show influences sorted",
|
|
callback=toggle,
|
|
tooltip="Sort influences by name",
|
|
)
|
|
|
|
@signal.on(config.influences_show_used_influences_only.changed, qtParent=parent)
|
|
def update():
|
|
result.setChecked(config.influences_sort() == Config.InfluencesSortDescending)
|
|
|
|
result.setCheckable(True)
|
|
update()
|
|
return result
|
|
|
|
|
|
icon_mask = QtGui.QIcon(":/blendColors.svg")
|
|
icon_dq = QtGui.QIcon(":/rotate_M.png")
|
|
icon_joint = QtGui.QIcon(":/joint.svg")
|
|
icon_joint_disabled = qt.image_icon("joint_disabled.png")
|
|
icon_transform = QtGui.QIcon(":/cube.png")
|
|
icon_transform_disabled = qt.image_icon("cube_disabled.png")
|
|
|
|
|
|
def build_view(parent, actions, session, filter):
|
|
"""
|
|
:param parent: ui parent
|
|
:type actions: ngSkinTools2.ui.actions.Actions
|
|
:type session: ngSkinTools2.ui.session.Session
|
|
:type filter: InfluenceNameFilter
|
|
"""
|
|
|
|
icon_locked = QtGui.QIcon(":/Lock_ON.png")
|
|
icon_unlocked = QtGui.QIcon(":/Lock_OFF_grey.png")
|
|
|
|
id_role = QtCore.Qt.UserRole + 1
|
|
item_size_hint = QtCore.QSize(25 * scale_multiplier, 25 * scale_multiplier)
|
|
|
|
def get_item_id(item):
|
|
if item is None:
|
|
return None
|
|
return item.data(0, id_role)
|
|
|
|
tree_items = {}
|
|
|
|
def build_items(view, items, layer):
|
|
# type: (QtWidgets.QTreeWidget, list[InfluenceInfo], Layer) -> None
|
|
is_group_layer = layer is not None and layer.num_children != 0
|
|
|
|
def rebuild_buttons(item, item_id, buttons):
|
|
bar = QtWidgets.QToolBar(parent=parent)
|
|
bar.setMovable(False)
|
|
bar.setIconSize(QtCore.QSize(13 * scale_multiplier, 13 * scale_multiplier))
|
|
|
|
def add_or_remove(input_list, items, should_add):
|
|
if should_add:
|
|
return list(input_list) + list(items)
|
|
return [i for i in input_list if i not in items]
|
|
|
|
def lock_unlock_handler(lock):
|
|
def handler():
|
|
targets = layer.paint_targets
|
|
if item_id not in targets:
|
|
targets = (item_id,)
|
|
|
|
layer.locked_influences = add_or_remove(layer.locked_influences, targets, lock)
|
|
log.info("updated locked influences to %r", layer.locked_influences)
|
|
session.events.influencesListUpdated.emit()
|
|
|
|
return handler
|
|
|
|
if "unlocked" in buttons:
|
|
a = bar.addAction(icon_unlocked, "Toggle locked/unlocked")
|
|
qt.on(a.triggered)(lock_unlock_handler(True))
|
|
|
|
if "locked" in buttons:
|
|
a = bar.addAction(icon_locked, "Toggle locked/unlocked")
|
|
qt.on(a.triggered)(lock_unlock_handler(False))
|
|
|
|
view.setItemWidget(item, 1, bar)
|
|
|
|
selected_ids = []
|
|
if session.state.currentLayer.layer:
|
|
selected_ids = session.state.currentLayer.layer.paint_targets
|
|
current_id = None if not selected_ids else selected_ids[0]
|
|
|
|
with qt.signals_blocked(view):
|
|
tree_items.clear()
|
|
tree_root = view.invisibleRootItem()
|
|
|
|
item_index = 0
|
|
for item_id, displayName, icon, buttons in wanted_tree_items(
|
|
items=items,
|
|
include_dq_item=session.state.skin_cluster_dq_channel_used,
|
|
is_group_layer=is_group_layer,
|
|
layer=layer,
|
|
config=config,
|
|
filter=filter,
|
|
):
|
|
if item_index >= tree_root.childCount():
|
|
item = QtWidgets.QTreeWidgetItem([displayName])
|
|
else:
|
|
item = tree_root.child(item_index)
|
|
item.setText(0, displayName)
|
|
|
|
item.setData(0, id_role, item_id)
|
|
item.setIcon(0, icon)
|
|
item.setSizeHint(0, item_size_hint)
|
|
tree_root.addChild(item)
|
|
|
|
tree_items[item_id] = item
|
|
if item_id == current_id:
|
|
view.setCurrentItem(item, 0, QtCore.QItemSelectionModel.NoUpdate)
|
|
item.setSelected(item_id in selected_ids)
|
|
|
|
rebuild_buttons(item, item_id, buttons)
|
|
|
|
item_index += 1
|
|
|
|
while item_index < tree_root.childCount():
|
|
tree_root.removeChild(tree_root.child(item_index))
|
|
|
|
view = QtWidgets.QTreeWidget(parent)
|
|
view.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
|
|
view.setUniformRowHeights(True)
|
|
view.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
|
|
view.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu)
|
|
actions.addInfluencesActions(view)
|
|
view.addAction(actions.separator(parent, "View Options"))
|
|
view.addAction(actions.show_used_influences_only)
|
|
view.addAction(actions.set_influences_sorted)
|
|
view.setIndentation(10 * scale_multiplier)
|
|
view.header().setStretchLastSection(False)
|
|
view.header().setSectionResizeMode(0, QtWidgets.QHeaderView.Stretch)
|
|
|
|
view.setHeaderLabels(["Influences", ""])
|
|
view.header().setSectionResizeMode(1, QtWidgets.QHeaderView.Fixed)
|
|
view.setColumnWidth(1, 25 * scale_multiplier)
|
|
|
|
# view.setHeaderHidden(True)
|
|
def refresh_items():
|
|
items = list_influences(session.state.currentLayer.selectedSkinCluster)
|
|
|
|
def sort_func(a):
|
|
"""
|
|
:type a: InfluenceInfo
|
|
"""
|
|
return a.name
|
|
|
|
# items = sorted(items, key=sort_func)
|
|
build_items(view, items, session.state.currentLayer.layer)
|
|
|
|
@signal.on(
|
|
filter.changed,
|
|
config.influences_show_used_influences_only.changed,
|
|
config.influences_sort.changed,
|
|
session.events.influencesListUpdated,
|
|
)
|
|
def filter_changed():
|
|
refresh_items()
|
|
|
|
@signal.on(session.events.currentLayerChanged, qtParent=view)
|
|
def current_layer_changed():
|
|
if not session.state.currentLayer.layer:
|
|
build_items(view, [], None)
|
|
else:
|
|
log.info("current layer changed to %s", session.state.currentLayer.layer)
|
|
refresh_items()
|
|
current_influence_changed()
|
|
|
|
@signal.on(session.events.currentInfluenceChanged, qtParent=view)
|
|
def current_influence_changed():
|
|
if session.state.currentLayer.layer is None:
|
|
return
|
|
|
|
log.info("current influence changed - updating item selection")
|
|
with qt.signals_blocked(view):
|
|
targets = session.state.currentLayer.layer.paint_targets
|
|
first = True
|
|
for tree_item in tree_items.values():
|
|
selected = get_item_id(tree_item) in targets
|
|
if selected and first:
|
|
view.setCurrentItem(tree_item, 0, QtCore.QItemSelectionModel.NoUpdate)
|
|
first = False
|
|
tree_item.setSelected(selected)
|
|
|
|
@qt.on(view.currentItemChanged)
|
|
def current_item_changed(curr, prev):
|
|
if curr is None:
|
|
return
|
|
|
|
if session.state.selectedSkinCluster is None:
|
|
return
|
|
|
|
if not session.state.currentLayer.layer:
|
|
return
|
|
|
|
log.info("focused item changed: %r", get_item_id(curr))
|
|
sync_paint_targets_to_selection()
|
|
|
|
@qt.on(view.itemSelectionChanged)
|
|
def sync_paint_targets_to_selection():
|
|
log.info("syncing paint targets")
|
|
selected_ids = [get_item_id(item) for item in view.selectedItems()]
|
|
selected_ids = [i for i in selected_ids if i is not None]
|
|
|
|
current_item = view.currentItem()
|
|
if current_item and current_item.isSelected():
|
|
# move id of current item to front, if it's selected
|
|
item_id = get_item_id(current_item)
|
|
selected_ids.remove(item_id)
|
|
selected_ids = [item_id] + selected_ids
|
|
|
|
if session.state.currentLayer.layer:
|
|
session.state.currentLayer.layer.paint_targets = selected_ids
|
|
|
|
current_layer_changed()
|
|
|
|
return view
|
|
|
|
|
|
def get_icon(influence, is_joint):
|
|
if influence.used:
|
|
return icon_joint if is_joint else icon_transform
|
|
return icon_joint_disabled if is_joint else icon_transform_disabled
|
|
|
|
|
|
def wanted_tree_items(
|
|
layer,
|
|
config,
|
|
is_group_layer,
|
|
include_dq_item,
|
|
filter,
|
|
items,
|
|
):
|
|
"""
|
|
|
|
:type items: list[InfluenceInfo]
|
|
"""
|
|
|
|
if layer is None:
|
|
return
|
|
|
|
# calculate "used" regardless as we're displaying it visually even if "show used influences only" is toggled off
|
|
used = set((layer.get_used_influences() or []))
|
|
locked = set((layer.locked_influences or []))
|
|
for i in items:
|
|
i.used = i.logicalIndex in used
|
|
i.locked = i.logicalIndex in locked
|
|
|
|
if config.influences_show_used_influences_only() and layer is not None:
|
|
items = [i for i in items if i.used]
|
|
|
|
if is_group_layer:
|
|
items = []
|
|
|
|
yield "mask", "[Mask]", icon_mask, []
|
|
if not is_group_layer and include_dq_item:
|
|
yield "dq", "[DQ Weights]", icon_dq, []
|
|
|
|
names = influence_names.unique_names([i.path_name() for i in items])
|
|
for i, name in zip(items, names):
|
|
i.unique_name = name
|
|
|
|
if config.influences_sort() == Config.InfluencesSortDescending:
|
|
items = list(sorted(items, key=lambda i: i.unique_name))
|
|
|
|
for i in items:
|
|
is_joint = i.path is not None
|
|
if filter.is_match(i.path_name()):
|
|
yield i.logicalIndex, i.unique_name, get_icon(i, is_joint), ["locked" if i.locked else "unlocked"]
|