Update
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
from ngSkinTools2 import api, signal
|
||||
from ngSkinTools2.api import Layer, PasteOperation
|
||||
from ngSkinTools2.api.session import Session
|
||||
|
||||
|
||||
def action_copy_cut(session, parent, cut):
|
||||
"""
|
||||
:type session: Session
|
||||
:type parent: PySide2.QtWidgets.QWidget
|
||||
:type cut: bool
|
||||
"""
|
||||
from ngSkinTools2.ui import actions
|
||||
|
||||
def cut_copy_callback():
|
||||
if session.state.selectedSkinCluster is None:
|
||||
return
|
||||
if session.state.currentLayer.layer is None:
|
||||
return
|
||||
influences = session.state.currentLayer.layer.paint_targets
|
||||
operation = api.copy_weights # type: Callable[[Layer, list], None]
|
||||
if cut:
|
||||
operation = api.cut_weights
|
||||
|
||||
operation(session.state.currentLayer.layer, influences)
|
||||
|
||||
operation_name = "Cut" if cut else "Copy"
|
||||
result = actions.define_action(parent, operation_name + " weights to clipboard", callback=cut_copy_callback)
|
||||
|
||||
@signal.on(session.events.currentLayerChanged, session.events.currentInfluenceChanged, qtParent=parent)
|
||||
def on_selection_changed():
|
||||
layer = session.state.currentLayer.layer
|
||||
result.setEnabled(layer is not None and len(layer.paint_targets) > 0)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def action_paste(session, parent, operation):
|
||||
"""
|
||||
:type session: Session
|
||||
:type parent: PySide2.QtWidgets.QWidget
|
||||
:type cut: bool
|
||||
"""
|
||||
from ngSkinTools2.ui import actions
|
||||
|
||||
def paste_callback():
|
||||
if session.state.currentLayer.layer is None:
|
||||
return
|
||||
influences = session.state.currentLayer.layer.paint_targets
|
||||
api.paste_weights(session.state.currentLayer.layer, operation, influences=influences)
|
||||
|
||||
labels = {
|
||||
PasteOperation.add: 'Paste weights (add to existing)',
|
||||
PasteOperation.subtract: 'Paste weight (subtract from existing)',
|
||||
PasteOperation.replace: 'Paste weights (replace existing)',
|
||||
}
|
||||
|
||||
result = actions.define_action(parent, labels[operation], callback=paste_callback)
|
||||
result.setToolTip("Paste previously copied weights from clipboard")
|
||||
|
||||
@signal.on(session.events.currentLayerChanged)
|
||||
def on_selection_changed():
|
||||
result.setEnabled(session.state.currentLayer.layer is not None)
|
||||
|
||||
return result
|
||||
@@ -0,0 +1,90 @@
|
||||
from ngSkinTools2 import api, signal
|
||||
from ngSkinTools2.api.pyside import QtWidgets
|
||||
from ngSkinTools2.ui.options import PersistentValue
|
||||
|
||||
filter_normal_json = 'JSON files(*.json)'
|
||||
filter_compressed = 'Compressed JSON(*.json.gz)'
|
||||
file_dialog_filters = ";;".join([filter_normal_json, filter_compressed])
|
||||
|
||||
format_map = {
|
||||
filter_normal_json: api.FileFormat.JSON,
|
||||
filter_compressed: api.FileFormat.CompressedJSON,
|
||||
}
|
||||
|
||||
default_filter = PersistentValue("default_import_filter", default_value=api.FileFormat.JSON)
|
||||
|
||||
|
||||
def buildAction_export(session, parent):
|
||||
from ngSkinTools2.ui import actions
|
||||
|
||||
def export_callback():
|
||||
file_name, selected_filter = QtWidgets.QFileDialog.getSaveFileName(
|
||||
parent, "Export to Json", filter=file_dialog_filters, selectedFilter=default_filter.get()
|
||||
)
|
||||
if not file_name:
|
||||
return
|
||||
|
||||
default_filter.set(selected_filter)
|
||||
|
||||
if session.state.layersAvailable:
|
||||
api.export_json(session.state.selectedSkinCluster, file_name, format=format_map[selected_filter])
|
||||
|
||||
result = actions.define_action(
|
||||
parent,
|
||||
"Export Layers to Json...",
|
||||
callback=export_callback,
|
||||
tooltip="Save layer info to external file, suitable for importing weights to different scene/mesh",
|
||||
)
|
||||
|
||||
@signal.on(session.events.targetChanged, qtParent=parent)
|
||||
def update_to_target():
|
||||
result.setEnabled(session.state.layersAvailable)
|
||||
|
||||
update_to_target()
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def buildAction_import(session, parent, file_dialog_func=None):
|
||||
from ngSkinTools2.ui import actions
|
||||
from ngSkinTools2.ui.transferDialog import LayersTransfer, UiModel, open
|
||||
|
||||
def default_file_dialog_func():
|
||||
file_name, selected_filter = QtWidgets.QFileDialog.getOpenFileName(
|
||||
parent, "Import from Json", filter=file_dialog_filters, selectedFilter=default_filter.get()
|
||||
)
|
||||
if file_name:
|
||||
default_filter.set(selected_filter)
|
||||
return file_name, selected_filter
|
||||
|
||||
if file_dialog_func is None:
|
||||
file_dialog_func = default_file_dialog_func
|
||||
|
||||
def transfer_dialog(transfer):
|
||||
model = UiModel()
|
||||
model.transfer = transfer
|
||||
open(parent, model)
|
||||
|
||||
def import_callback():
|
||||
if session.state.selectedSkinCluster is None:
|
||||
return
|
||||
|
||||
file_name, selected_format = file_dialog_func()
|
||||
if not file_name:
|
||||
return
|
||||
|
||||
t = LayersTransfer()
|
||||
t.load_source_from_file(file_name, format=format_map[selected_format])
|
||||
t.target = session.state.selectedSkinCluster
|
||||
t.customize_callback = transfer_dialog
|
||||
t.execute()
|
||||
|
||||
result = actions.define_action(parent, "Import Layers from Json...", callback=import_callback, tooltip="Load previously exported weights")
|
||||
|
||||
@signal.on(session.events.targetChanged, qtParent=parent)
|
||||
def update():
|
||||
result.setEnabled(session.state.selectedSkinCluster is not None)
|
||||
|
||||
update()
|
||||
|
||||
return result
|
||||
@@ -0,0 +1,40 @@
|
||||
from ngSkinTools2 import api, signal
|
||||
|
||||
|
||||
def can_import(session):
|
||||
"""
|
||||
:type session: ngSkinTools2.api.session.Session
|
||||
"""
|
||||
|
||||
if not session.state.selection:
|
||||
return False
|
||||
|
||||
if session.state.layersAvailable:
|
||||
return False
|
||||
|
||||
return api.import_v1.can_import(session.state.selection[-1])
|
||||
|
||||
|
||||
def build_action_import_v1(session, parent):
|
||||
"""
|
||||
:type parent: PySide2.QtWidgets.QWidget
|
||||
:type session: ngSkinTools2.api.session.Session
|
||||
"""
|
||||
from ngSkinTools2.ui import actions
|
||||
|
||||
def do_convert():
|
||||
api.import_v1.import_layers(session.state.selection[-1])
|
||||
api.import_v1.cleanup(session.state.selection[-1:])
|
||||
update_state()
|
||||
session.events.targetChanged.emitIfChanged()
|
||||
|
||||
result = actions.define_action(parent, "Convert From v1.0 Layers", callback=do_convert)
|
||||
result.setToolTip("Convert skinning layers from previous version of ngSkinTools; after completing this action, v1 nodes will be deleted.")
|
||||
|
||||
@signal.on(session.events.targetChanged)
|
||||
def update_state():
|
||||
result.setVisible(can_import(session))
|
||||
|
||||
update_state()
|
||||
|
||||
return result
|
||||
229
2023/scripts/rigging_tools/ngskintools2/operations/layers.py
Normal file
229
2023/scripts/rigging_tools/ngskintools2/operations/layers.py
Normal file
@@ -0,0 +1,229 @@
|
||||
import random
|
||||
|
||||
from maya import cmds
|
||||
|
||||
import ngSkinTools2.api
|
||||
from ngSkinTools2 import signal
|
||||
from ngSkinTools2.api import Mirror
|
||||
from ngSkinTools2.api.layers import generate_layer_name
|
||||
from ngSkinTools2.api.log import getLogger
|
||||
from ngSkinTools2.api.pyside import QAction, QtCore
|
||||
from ngSkinTools2.api.session import session, withSession
|
||||
from ngSkinTools2.decorators import undoable
|
||||
from ngSkinTools2.ui import dialogs, qt
|
||||
from ngSkinTools2.ui.action import Action
|
||||
from ngSkinTools2.ui.options import config
|
||||
|
||||
logger = getLogger("layer operations")
|
||||
|
||||
|
||||
@withSession
|
||||
@undoable
|
||||
def initializeLayers(createFirstLayer=True):
|
||||
if session.state.layersAvailable:
|
||||
return # ignore
|
||||
|
||||
target = session.state.selectedSkinCluster
|
||||
|
||||
layers = ngSkinTools2.api.init_layers(target)
|
||||
with ngSkinTools2.api.suspend_updates(target):
|
||||
if createFirstLayer:
|
||||
layer = layers.add("Base weights")
|
||||
layer.set_current()
|
||||
Mirror(target).set_mirror_config(config.mirrorInfluencesDefaults)
|
||||
|
||||
session.events.targetChanged.emitIfChanged()
|
||||
|
||||
if ngSkinTools2.api.is_slow_mode_skin_cluster(target):
|
||||
dialogs.info(
|
||||
"ngSkinTools switched to slow maya API for setting skin cluster weights for this skinCluster, to workaround a Maya bug when skinCluster uses dg nodes as inputs"
|
||||
)
|
||||
|
||||
|
||||
@undoable
|
||||
def addLayer():
|
||||
layers = ngSkinTools2.api.Layers(session.state.selectedSkinCluster)
|
||||
|
||||
def guessParent():
|
||||
currentLayer = layers.current_layer()
|
||||
if currentLayer is None:
|
||||
return None
|
||||
|
||||
# current layer is a parent?
|
||||
if currentLayer.num_children > 0:
|
||||
return currentLayer
|
||||
|
||||
return currentLayer.parent
|
||||
|
||||
with ngSkinTools2.api.suspend_updates(layers.mesh):
|
||||
new_layer = layers.add(generate_layer_name(session.state.all_layers, "New Layer"))
|
||||
new_layer.parent = guessParent()
|
||||
|
||||
session.events.layerListChanged.emitIfChanged()
|
||||
setCurrentLayer(new_layer)
|
||||
|
||||
return new_layer
|
||||
|
||||
|
||||
def build_action_initialize_layers(session, parent):
|
||||
from ngSkinTools2.ui import actions
|
||||
|
||||
from . import import_v1_actions
|
||||
|
||||
def do_initialize():
|
||||
if import_v1_actions.can_import(session):
|
||||
q = (
|
||||
"Skinning layers from previous version of ngSkinTools are present on this mesh. This operation will initialize "
|
||||
"skinning layers from scratch, discarding previous layers information. Do you want to continue?"
|
||||
)
|
||||
if not dialogs.yesNo(q):
|
||||
return
|
||||
|
||||
initializeLayers()
|
||||
|
||||
result = actions.define_action(parent, "Initialize Skinning Layers", callback=do_initialize)
|
||||
|
||||
@signal.on(session.events.nodeSelectionChanged)
|
||||
def update():
|
||||
result.setEnabled(session.state.selectedSkinCluster is not None)
|
||||
|
||||
update()
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def buildAction_createLayer(session, parent):
|
||||
from ngSkinTools2.ui import actions
|
||||
|
||||
result = actions.define_action(parent, "Create Layer", callback=addLayer, icon=":/newLayerEmpty.png", shortcut=QtCore.Qt.Key_Insert)
|
||||
|
||||
@signal.on(session.events.targetChanged)
|
||||
def update_to_target():
|
||||
result.setEnabled(session.state.layersAvailable)
|
||||
|
||||
update_to_target()
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def buildAction_deleteLayer(session, parent):
|
||||
from ngSkinTools2.ui import actions
|
||||
|
||||
result = actions.define_action(parent, "Delete Layer", callback=deleteSelectedLayers, shortcut=QtCore.Qt.Key_Delete)
|
||||
|
||||
@signal.on(session.context.selected_layers.changed, session.events.targetChanged, qtParent=parent)
|
||||
def update_to_target():
|
||||
result.setEnabled(session.state.layersAvailable and bool(session.context.selected_layers(default=[])))
|
||||
|
||||
update_to_target()
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@undoable
|
||||
def setCurrentLayer(layer):
|
||||
"""
|
||||
:type layer: ngSkinTools2.api.layers.Layer
|
||||
"""
|
||||
if not session.active():
|
||||
logger.info("didn't set current layer: no session")
|
||||
|
||||
if not session.state.layersAvailable:
|
||||
logger.info("didn't set current layer: layers not enabled")
|
||||
|
||||
logger.info("setting current layer to %r on %r", layer, session.state.selectedSkinCluster)
|
||||
layer.set_current()
|
||||
session.events.currentLayerChanged.emitIfChanged()
|
||||
|
||||
|
||||
def getCurrentLayer():
|
||||
layers = ngSkinTools2.api.Layers(session.state.selectedSkinCluster)
|
||||
return layers.current_layer()
|
||||
|
||||
|
||||
@undoable
|
||||
def renameLayer(layer, newName):
|
||||
layer.name = newName
|
||||
cmds.evalDeferred(session.events.layerListChanged.emitIfChanged)
|
||||
|
||||
|
||||
@undoable
|
||||
def deleteSelectedLayers():
|
||||
layers = ngSkinTools2.api.Layers(session.state.selectedSkinCluster)
|
||||
for i in session.context.selected_layers(default=[]):
|
||||
layers.delete(i)
|
||||
|
||||
session.events.layerListChanged.emitIfChanged()
|
||||
session.events.currentLayerChanged.emitIfChanged()
|
||||
|
||||
|
||||
class ToggleEnabledAction(Action):
|
||||
name = "Enabled"
|
||||
checkable = True
|
||||
|
||||
def __init__(self, session):
|
||||
Action.__init__(self, session)
|
||||
|
||||
def checked(self):
|
||||
"""
|
||||
return true if most of selected layers are enabled
|
||||
:return:
|
||||
"""
|
||||
layers = session.context.selected_layers(default=[])
|
||||
if not layers:
|
||||
return True
|
||||
|
||||
enabled_disabled_balance = 0
|
||||
for layer in layers:
|
||||
try:
|
||||
# eat up the exception if layer id is invalid
|
||||
enabled_disabled_balance += 1 if layer.enabled else -1
|
||||
except:
|
||||
pass
|
||||
|
||||
return enabled_disabled_balance >= 0
|
||||
|
||||
def run(self):
|
||||
enabled = not self.checked()
|
||||
selected_layers = session.context.selected_layers()
|
||||
if not selected_layers:
|
||||
return
|
||||
|
||||
for i in selected_layers:
|
||||
i.enabled = enabled
|
||||
|
||||
logger.info("layers toggled: %r", selected_layers)
|
||||
|
||||
session.events.layerListChanged.emitIfChanged()
|
||||
|
||||
def enabled(self):
|
||||
return session.state.layersAvailable and bool(session.context.selected_layers(default=[]))
|
||||
|
||||
def update_on_signals(self):
|
||||
return [session.context.selected_layers.changed, session.events.layerListChanged, session.events.targetChanged]
|
||||
|
||||
|
||||
def build_action_randomize_influences_colors(session, parent):
|
||||
"""
|
||||
builds a UI action for randomly choosing new colors for influences
|
||||
:type session: ngSkinTools2.api.session.Session
|
||||
"""
|
||||
|
||||
result = QAction("Randomize colors", parent)
|
||||
result.setToolTip("Choose random colors for each influence, selecting from Maya's pallete of indexed colors")
|
||||
|
||||
def color_filter(c):
|
||||
brightness = c[0] * c[0] + c[1] * c[1] + c[2] * c[2]
|
||||
return brightness > 0.001 and brightness < 0.99 # just a fancy way to skip white and black
|
||||
|
||||
colors = set([tuple(cmds.colorIndex(i, q=True)) for i in range(1, 30)])
|
||||
colors = [c for c in colors if color_filter(c)]
|
||||
|
||||
@qt.on(result.triggered)
|
||||
def triggered():
|
||||
if session.state.selectedSkinCluster is None:
|
||||
return
|
||||
layers = ngSkinTools2.api.Layers(session.state.selectedSkinCluster)
|
||||
layers.config.influence_colors = {i.logicalIndex: random.choice(colors) for i in layers.list_influences()}
|
||||
|
||||
return result
|
||||
45
2023/scripts/rigging_tools/ngskintools2/operations/paint.py
Normal file
45
2023/scripts/rigging_tools/ngskintools2/operations/paint.py
Normal file
@@ -0,0 +1,45 @@
|
||||
from maya import cmds
|
||||
|
||||
from ngSkinTools2.api import PaintTool
|
||||
from ngSkinTools2.api.log import getLogger
|
||||
from ngSkinTools2.api.session import session
|
||||
from ngSkinTools2.ui.action import Action
|
||||
|
||||
log = getLogger("operations/paint")
|
||||
|
||||
|
||||
class FloodAction(Action):
|
||||
name = "Flood"
|
||||
tooltip = "Apply current brush to whole selection"
|
||||
|
||||
def run(self):
|
||||
session.paint_tool.flood(self.session.state.currentLayer.layer, influences=self.session.state.currentLayer.layer.paint_targets)
|
||||
|
||||
def enabled(self):
|
||||
return PaintTool.is_painting() and self.session.state.selectedSkinCluster is not None and self.session.state.currentLayer.layer is not None
|
||||
|
||||
def update_on_signals(self):
|
||||
return [
|
||||
self.session.events.toolChanged,
|
||||
self.session.events.currentLayerChanged,
|
||||
self.session.events.currentInfluenceChanged,
|
||||
self.session.events.targetChanged,
|
||||
]
|
||||
|
||||
|
||||
class PaintAction(Action):
|
||||
name = "Paint"
|
||||
tooltip = "Toggle paint tool"
|
||||
checkable = True
|
||||
|
||||
def run(self):
|
||||
if self.checked():
|
||||
cmds.setToolTo("moveSuperContext")
|
||||
else:
|
||||
self.session.paint_tool.start()
|
||||
|
||||
def update_on_signals(self):
|
||||
return [self.session.events.toolChanged]
|
||||
|
||||
def checked(self):
|
||||
return PaintTool.is_painting()
|
||||
@@ -0,0 +1,101 @@
|
||||
import itertools
|
||||
|
||||
from maya import cmds
|
||||
|
||||
from ngSkinTools2.api import PaintTool, target_info
|
||||
from ngSkinTools2.api.session import Session
|
||||
from ngSkinTools2.decorators import undoable
|
||||
|
||||
|
||||
def as_list(arg):
|
||||
return [] if arg is None else arg
|
||||
|
||||
|
||||
customNodeTypes = ['ngst2MeshDisplay', 'ngst2SkinLayerData']
|
||||
|
||||
|
||||
def list_custom_nodes():
|
||||
"""
|
||||
list all custom nodes in the scene
|
||||
"""
|
||||
|
||||
result = []
|
||||
for nodeType in customNodeTypes:
|
||||
result.extend(as_list(cmds.ls(type=nodeType)))
|
||||
return result
|
||||
|
||||
|
||||
def list_custom_nodes_for_mesh(mesh=None):
|
||||
"""
|
||||
list custom nodes only related to provided mesh. None means current selection
|
||||
"""
|
||||
|
||||
skin_cluster = target_info.get_related_skin_cluster(mesh)
|
||||
if skin_cluster is None:
|
||||
return []
|
||||
|
||||
# delete any ngSkinTools deformers from the history, and find upstream stuff from given skinCluster.
|
||||
hist = as_list(cmds.listHistory(skin_cluster, future=True, levels=1))
|
||||
return [i for i in hist if cmds.nodeType(i) in customNodeTypes]
|
||||
|
||||
|
||||
def list_custom_nodes_for_meshes(meshes):
|
||||
return list(itertools.chain.from_iterable([list_custom_nodes_for_mesh(i) for i in meshes]))
|
||||
|
||||
|
||||
message_scene_noCustomNodes = 'Scene does not contain any custom ngSkinTools nodes.'
|
||||
message_selection_noCustomNodes = 'Selection does not contain any custom ngSkinTools nodes.'
|
||||
message_scene_warning = (
|
||||
'This command deletes all custom ngSkinTools nodes. Skin weights ' 'will be preserved, but all layer data will be lost. Do you want to continue?'
|
||||
)
|
||||
message_selection_warning = (
|
||||
'This command deletes custom ngSkinTools nodes for selection. Skin weights '
|
||||
'will be preserved, but all layer data will be lost. Do you want to continue?'
|
||||
)
|
||||
|
||||
|
||||
@undoable
|
||||
def remove_custom_nodes(interactive=False, session=None, meshes=None):
|
||||
"""
|
||||
Removes custom ngSkinTools2 nodes from the scene or selection.
|
||||
|
||||
:type meshes: list[str]
|
||||
:param meshes: list of node names; if empty, operation will be scene-wide.
|
||||
:type session: Session
|
||||
:param session: optional; if specified, will fire events for current session about changed status of selected mesh
|
||||
:type interactive: bool
|
||||
:param interactive: if True, user warnings will be emited
|
||||
"""
|
||||
from ngSkinTools2.ui import dialogs
|
||||
|
||||
if meshes is None:
|
||||
meshes = []
|
||||
|
||||
is_selection_mode = len(meshes) > 0
|
||||
|
||||
custom_nodes = list_custom_nodes() if not is_selection_mode else list_custom_nodes_for_meshes(meshes)
|
||||
|
||||
if not custom_nodes:
|
||||
if interactive:
|
||||
dialogs.info(message_selection_noCustomNodes if is_selection_mode else message_scene_noCustomNodes)
|
||||
return
|
||||
|
||||
delete_confirmed = True
|
||||
if interactive:
|
||||
delete_confirmed = dialogs.yesNo(message_selection_warning if is_selection_mode else message_scene_warning)
|
||||
|
||||
if delete_confirmed:
|
||||
cmds.delete(custom_nodes)
|
||||
|
||||
if PaintTool.is_painting():
|
||||
# make sure that painting is canceled to restore mesh display etc
|
||||
cmds.setToolTo("Move")
|
||||
|
||||
if session is not None:
|
||||
session.events.targetChanged.emitIfChanged()
|
||||
|
||||
|
||||
@undoable
|
||||
def remove_custom_nodes_from_selection(interactive=False, session=None):
|
||||
selection = cmds.ls(sl=True)
|
||||
remove_custom_nodes(interactive=interactive, session=session, meshes=as_list(selection))
|
||||
295
2023/scripts/rigging_tools/ngskintools2/operations/tools.py
Normal file
295
2023/scripts/rigging_tools/ngskintools2/operations/tools.py
Normal file
@@ -0,0 +1,295 @@
|
||||
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,
|
||||
)
|
||||
@@ -0,0 +1,31 @@
|
||||
from ngSkinTools2.api.python_compatibility import Object
|
||||
|
||||
|
||||
def website_base_url():
|
||||
import ngSkinTools2
|
||||
|
||||
if ngSkinTools2.DEBUG_MODE:
|
||||
return "http://localhost:1313"
|
||||
|
||||
return "https://www.ngskintools.com"
|
||||
|
||||
|
||||
class WebsiteLinksActions(Object):
|
||||
def __init__(self, parent):
|
||||
self.api_root = make_documentation_action(parent, "API Documentation", "/v2/api")
|
||||
self.user_guide = make_documentation_action(parent, "User Guide", "/v2/")
|
||||
self.changelog = make_documentation_action(parent, "Change Log", "/v2/changelog", icon=None)
|
||||
self.contact = make_documentation_action(parent, "Contact", "/contact/", icon=None)
|
||||
|
||||
|
||||
def make_documentation_action(parent, title, url, icon=":/help.png"):
|
||||
from ngSkinTools2.ui import actions
|
||||
|
||||
def handler():
|
||||
import webbrowser
|
||||
|
||||
webbrowser.open_new(website_base_url() + url)
|
||||
|
||||
result = actions.define_action(parent, title, callback=handler, icon=icon)
|
||||
result.setToolTip("opens {0} in a browser".format(url))
|
||||
return result
|
||||
Reference in New Issue
Block a user