185 lines
6.1 KiB
Python
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])
|