Files
Nexus/2023/scripts/rigging_tools/ngskintools2/api/mirror.py
2025-11-24 08:27:50 +08:00

185 lines
6.1 KiB
Python

import itertools
from maya import cmds
from ngSkinTools2.api import influenceMapping, internals, plugin, target_info
from ngSkinTools2.api.cmd_wrappers import get_source_node
from ngSkinTools2.api.layers import Layers
from ngSkinTools2.api.log import getLogger
from ngSkinTools2.api.python_compatibility import Object
log = getLogger("mirror")
class Mirror(Object):
"""
query and configure mirror options for provided target
"""
axis = internals.make_editable_property('mirrorAxis')
seam_width = internals.make_editable_property('mirrorWidth')
vertex_transfer_mode = internals.make_editable_property('vertexTransferMode')
def __init__(self, target):
"""
:type target: skin target (skinCluster or mesh)
"""
self.target = target
self.__skin_cluster__ = None
self.__data_node__ = None
# noinspection PyMethodMayBeStatic
def __query__(self, **kwargs):
return plugin.ngst2Layers(self.target, q=True, **kwargs)
# noinspection PyMethodMayBeStatic
def __edit__(self, **kwargs):
plugin.ngst2Layers(self.target, configureMirrorMapping=True, **kwargs)
self.recalculate_influences_mapping()
def __mapper_config_attr(self):
return self.__get_data_node__() + ".influenceMappingOptions"
def build_influences_mapper(self, defaults=None):
mapper = influenceMapping.InfluenceMapping()
layers = Layers(self.target)
mapper.influences = layers.list_influences()
mapper.config.load_json(cmds.getAttr(self.__mapper_config_attr()))
mapper.config.mirror_axis = self.axis
return mapper
def save_influences_mapper(self, mapper):
"""
:type mapper: influenceMapping.InfluenceMapping
"""
self.set_mirror_config(mapper.config.as_json())
def set_mirror_config(self, config_as_json):
cmds.setAttr(self.__mapper_config_attr(), config_as_json, type='string')
def set_influences_mapping(self, mapping):
"""
:type mapping: map[int] -> int
"""
log.info("mapping updated: %r", mapping)
mapping_as_string = ','.join(str(k) + "," + str(v) for (k, v) in list(mapping.items()))
plugin.ngst2Layers(self.target, configureMirrorMapping=True, influencesMapping=mapping_as_string)
def recalculate_influences_mapping(self):
"""
loads current influence mapping settings, and update influences mapping with these values
"""
m = self.build_influences_mapper().calculate()
self.set_influences_mapping(influenceMapping.InfluenceMapping.asIntIntMapping(m))
def mirror(self, options):
"""
:type options: MirrorOptions
"""
plugin.ngst2Layers(
self.target,
mirrorLayerWeights=options.mirrorWeights,
mirrorLayerMask=options.mirrorMask,
mirrorLayerDq=options.mirrorDq,
mirrorDirection=options.direction,
)
def set_reference_mesh(self, mesh_shape):
dest = self.__get_data_node__() + ".mirrorMesh"
if mesh_shape:
cmds.connectAttr(mesh_shape + ".outMesh", dest)
else:
for i in cmds.listConnections(dest, source=True, plugs=True):
cmds.disconnectAttr(i, dest)
def get_reference_mesh(self):
return get_source_node(self.__get_data_node__() + '.mirrorMesh')
def __get_skin_cluster__(self):
if self.__skin_cluster__ is None:
self.__skin_cluster__ = target_info.get_related_skin_cluster(self.target)
return self.__skin_cluster__
def __get_data_node__(self):
if self.__data_node__ is None:
self.__data_node__ = target_info.get_related_data_node(self.target)
return self.__data_node__
# noinspection PyStatementEffect
def build_reference_mesh(self):
sc = self.__get_skin_cluster__()
dn = self.__get_data_node__()
if sc is None or dn is None:
return
existing_ref_mesh = self.get_reference_mesh()
if existing_ref_mesh:
cmds.select(existing_ref_mesh)
raise Exception("symmetry mesh already configured for %s: %s" % (str(sc), existing_ref_mesh))
def get_shape(node):
return cmds.listRelatives(node, shapes=True)[0]
result, _ = cmds.polyCube()
g = cmds.group(empty=True, name="ngskintools_mirror_mesh_setup")
cmds.parent(result, g)
result = cmds.rename(g + "|" + result, "mirror_reference_mesh")
cmds.delete(result, ch=True)
cmds.connectAttr(sc + ".input[0].inputGeometry", get_shape(result) + ".inMesh")
cmds.delete(result, ch=True)
(mirrored,) = cmds.duplicate(result)
mirrored = cmds.rename(g + "|" + mirrored, 'flipped_preview')
mirrored_shape = get_shape(mirrored)
cmds.setAttr(mirrored + ".sx", -1)
cmds.setAttr(mirrored + ".overrideEnabled", 1)
cmds.setAttr(mirrored + ".overrideDisplayType", 2)
cmds.setAttr(mirrored + ".overrideShading", 0)
cmds.setAttr(mirrored + ".overrideTexturing", 1)
(blend,) = cmds.blendShape(result, mirrored_shape)
cmds.setAttr(blend + ".weight[0]", 1.0)
# lock accidental transformations
for c, t, m in itertools.product('xyz', 'trs', (result, mirrored)):
cmds.setAttr(m + "." + t + c, lock=True)
# shift setup to the right by slightly more than bounding box width
bb = cmds.exactWorldBoundingBox(g)
cmds.move((bb[3] - bb[0]) * 1.2, 0, 0, g, r=True)
self.set_reference_mesh(str(result))
cmds.select(result)
return result
class MirrorOptions(Object):
directionNegativeToPositive = 0
directionPositiveToNegative = 1
directionGuess = 2
directionFlip = 3
def __init__(self):
self.mirrorWeights = True
self.mirrorMask = True
self.mirrorDq = True
self.direction = MirrorOptions.directionPositiveToNegative
def set_reference_mesh_from_selection():
selection = cmds.ls(sl=True, long=True)
if len(selection) != 2:
log.debug("wrong selection size")
return
m = Mirror(selection[1])
m.set_reference_mesh(selection[0])