296 lines
8.7 KiB
Python
296 lines
8.7 KiB
Python
from maya import cmds
|
|
|
|
from ngSkinTools2 import api, signal
|
|
from ngSkinTools2.api.log import getLogger
|
|
from ngSkinTools2.api.python_compatibility import Object
|
|
from ngSkinTools2.api.session import Session
|
|
from ngSkinTools2.decorators import undoable
|
|
from ngSkinTools2.observableValue import ObservableValue
|
|
from ngSkinTools2.operations import layers
|
|
from ngSkinTools2.ui import dialogs
|
|
|
|
logger = getLogger("operation/tools")
|
|
|
|
|
|
def __create_tool_action__(parent, session, action_name, action_tooltip, exec_handler, enabled_handler=None):
|
|
"""
|
|
:type session: Session
|
|
"""
|
|
|
|
from ngSkinTools2.ui import actions
|
|
|
|
def execute():
|
|
if not session.active():
|
|
return
|
|
|
|
exec_handler()
|
|
|
|
result = actions.define_action(parent, action_name, callback=execute, tooltip=action_tooltip)
|
|
|
|
@signal.on(session.events.targetChanged, session.events.currentLayerChanged)
|
|
def update_state():
|
|
enabled = session.state.layersAvailable and session.state.currentLayer.layer is not None
|
|
if enabled and enabled_handler is not None:
|
|
enabled = enabled_handler(session.state.currentLayer.layer)
|
|
result.setEnabled(enabled)
|
|
|
|
update_state()
|
|
|
|
return result
|
|
|
|
|
|
class ClosestJointOptions(Object):
|
|
def __init__(self):
|
|
self.create_new_layer = ObservableValue(False)
|
|
self.all_influences = ObservableValue(True)
|
|
|
|
|
|
def create_action__from_closest_joint(parent, session):
|
|
options = ClosestJointOptions()
|
|
|
|
def exec_handler():
|
|
layer = session.state.currentLayer.layer
|
|
influences = None
|
|
if not options.all_influences():
|
|
influences = layer.paint_targets
|
|
if not influences:
|
|
dialogs.info("Select one or more influences in Influences list")
|
|
return
|
|
|
|
if options.create_new_layer():
|
|
layer = layers.addLayer()
|
|
|
|
api.assign_from_closest_joint(
|
|
session.state.selectedSkinCluster,
|
|
layer,
|
|
influences=influences,
|
|
)
|
|
session.events.currentLayerChanged.emitIfChanged()
|
|
session.events.influencesListUpdated.emit()
|
|
|
|
if layer.paint_target is None:
|
|
used_influences = layer.get_used_influences()
|
|
if used_influences:
|
|
layer.paint_target = min(used_influences)
|
|
|
|
return (
|
|
__create_tool_action__(
|
|
parent,
|
|
session,
|
|
action_name=u"Assign From Closest Joint",
|
|
action_tooltip="Assign 1.0 weight for closest influence per each vertex in selected layer",
|
|
exec_handler=exec_handler,
|
|
),
|
|
options,
|
|
)
|
|
|
|
|
|
class UnifyWeightsOptions(Object):
|
|
overall_effect = ObservableValue(1.0)
|
|
single_cluster_mode = ObservableValue(False)
|
|
|
|
|
|
def create_action__unify_weights(parent, session):
|
|
options = UnifyWeightsOptions()
|
|
|
|
def exec_handler():
|
|
api.unify_weights(
|
|
session.state.selectedSkinCluster,
|
|
session.state.currentLayer.layer,
|
|
overall_effect=options.overall_effect(),
|
|
single_cluster_mode=options.single_cluster_mode(),
|
|
)
|
|
|
|
return (
|
|
__create_tool_action__(
|
|
parent,
|
|
session,
|
|
action_name=u"Unify Weights",
|
|
action_tooltip="For selected vertices, make verts the same for all verts",
|
|
exec_handler=exec_handler,
|
|
),
|
|
options,
|
|
)
|
|
|
|
|
|
def create_action__merge_layers(parent, session):
|
|
"""
|
|
:param parent: UI parent for this action
|
|
:type session: Session
|
|
"""
|
|
|
|
def exec_handler():
|
|
api.merge_layers(layers=session.context.selected_layers(default=[]))
|
|
session.events.layerListChanged.emitIfChanged()
|
|
session.events.currentLayerChanged.emitIfChanged()
|
|
|
|
def enabled_handler(layer):
|
|
return layer is not None and layer.index > 0
|
|
|
|
return __create_tool_action__(
|
|
parent,
|
|
session,
|
|
action_name=u"Merge",
|
|
action_tooltip="Merge contents of this layer into underlying layer. Pre-effects weights will be used for this",
|
|
exec_handler=exec_handler,
|
|
enabled_handler=enabled_handler,
|
|
)
|
|
|
|
|
|
def create_action__duplicate_layer(parent, session):
|
|
"""
|
|
:param parent: UI parent for this action
|
|
:type session: Session
|
|
"""
|
|
|
|
@undoable
|
|
def exec_handler():
|
|
with api.suspend_updates(session.state.selectedSkinCluster):
|
|
for source in session.context.selected_layers(default=[]):
|
|
api.duplicate_layer(layer=source)
|
|
|
|
session.events.layerListChanged.emitIfChanged()
|
|
session.events.currentLayerChanged.emitIfChanged()
|
|
|
|
return __create_tool_action__(
|
|
parent,
|
|
session,
|
|
action_name=u"Duplicate",
|
|
action_tooltip="Duplicate selected layer(s)",
|
|
exec_handler=exec_handler,
|
|
)
|
|
|
|
|
|
def create_action__fill_transparency(parent, session):
|
|
"""
|
|
:param parent: UI parent for this action
|
|
:type session: Session
|
|
"""
|
|
|
|
@undoable
|
|
def exec_handler():
|
|
with api.suspend_updates(session.state.selectedSkinCluster):
|
|
for source in session.context.selected_layers(default=[]):
|
|
api.fill_transparency(layer=source)
|
|
|
|
return __create_tool_action__(
|
|
parent,
|
|
session,
|
|
action_name=u"Fill Transparency",
|
|
action_tooltip="All transparent vertices in the selected layer(s) receive weights from their closest non-empty neighbour vertex",
|
|
exec_handler=exec_handler,
|
|
)
|
|
|
|
|
|
def create_action__copy_component_weights(parent, session):
|
|
"""
|
|
:param parent: UI parent for this action
|
|
:type session: Session
|
|
"""
|
|
|
|
def exec_handler():
|
|
for source in session.context.selected_layers(default=[]):
|
|
api.copy_component_weights(layer=source)
|
|
|
|
return __create_tool_action__(
|
|
parent,
|
|
session,
|
|
action_name=u"Copy Component Weights",
|
|
action_tooltip="Store components weights in memory for further component-based paste actions",
|
|
exec_handler=exec_handler,
|
|
)
|
|
|
|
|
|
def create_action__paste_average_component_weight(parent, session):
|
|
"""
|
|
:param parent: UI parent for this action
|
|
:type session: Session
|
|
"""
|
|
|
|
def exec_handler():
|
|
for l in session.context.selected_layers(default=[]):
|
|
api.paste_average_component_weights(layer=l)
|
|
|
|
return __create_tool_action__(
|
|
parent,
|
|
session,
|
|
action_name=u"Paste Average Component Weight",
|
|
action_tooltip="Compute average of copied component weights and set that value to currently selected components",
|
|
exec_handler=exec_handler,
|
|
)
|
|
|
|
|
|
def create_action__add_influences(parent, session):
|
|
"""
|
|
:param parent: UI parent for this action
|
|
:type session: Session
|
|
"""
|
|
|
|
def exec_handler():
|
|
selection = cmds.ls(sl=True, l=True)
|
|
if len(selection) < 2:
|
|
logger.info("invalid selection: %s", selection)
|
|
return
|
|
api.add_influences(selection[:-1], selection[-1])
|
|
cmds.select(selection[-1])
|
|
session.events.influencesListUpdated.emit()
|
|
|
|
return __create_tool_action__(
|
|
parent,
|
|
session,
|
|
action_name=u"Add Influences",
|
|
action_tooltip="Add selected influences to current skin cluster.",
|
|
exec_handler=exec_handler,
|
|
)
|
|
|
|
|
|
def create_action__select_affected_vertices(parent, session):
|
|
"""
|
|
:param parent: UI parent for this action
|
|
:type session: Session
|
|
"""
|
|
|
|
def exec_handler():
|
|
selected_layers = session.context.selected_layers(default=[])
|
|
if not selected_layers:
|
|
return
|
|
|
|
if not session.state.currentLayer.layer:
|
|
return
|
|
|
|
influences = session.state.currentLayer.layer.paint_targets
|
|
if not influences:
|
|
return
|
|
|
|
non_zero_weights = []
|
|
for layer in selected_layers:
|
|
for i in influences:
|
|
weights = layer.get_weights(i)
|
|
if weights:
|
|
non_zero_weights.append(weights)
|
|
|
|
if not non_zero_weights:
|
|
return
|
|
|
|
current_selection = cmds.ls(sl=True, o=True, l=True)
|
|
if len(current_selection) != 1:
|
|
return
|
|
|
|
# we're not sure - this won't work if skin cluster is selected directly
|
|
selected_mesh_probably = current_selection[0]
|
|
|
|
combined_weights = [sum(i) for i in zip(*non_zero_weights)]
|
|
indexes = [selected_mesh_probably + ".vtx[%d]" % index for index, i in enumerate(combined_weights) if i > 0.00001]
|
|
try:
|
|
cmds.select(indexes)
|
|
except:
|
|
pass
|
|
|
|
return __create_tool_action__(
|
|
parent,
|
|
session,
|
|
action_name=u"Select Affected Vertices",
|
|
action_tooltip="Select vertices that have non-zero weight for current influence.",
|
|
exec_handler=exec_handler,
|
|
)
|