919 lines
26 KiB
Python
919 lines
26 KiB
Python
# Copyright 2020 by Kurt Rathjen. All Rights Reserved.
|
|
#
|
|
# This library is free software: you can redistribute it and/or modify it
|
|
# under the terms of the GNU Lesser General Public License as published by
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version. This library is distributed in the
|
|
# hope that it will be useful, but WITHOUT ANY WARRANTY; without even the
|
|
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
# See the GNU Lesser General Public License for more details.
|
|
# You should have received a copy of the GNU Lesser General Public
|
|
# License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
|
"""
|
|
# mirrortable.py
|
|
|
|
import mutils
|
|
|
|
# Example 1:
|
|
# Create a MirrorTable instance from the given objects
|
|
mt = mutils.MirrorTable.fromObjects(objects, "_l_", "_r_", MirrorPlane.YZ)
|
|
|
|
# Example 2:
|
|
# Create a MirrorTable instance from the selected objects
|
|
objects = maya.cmds.ls(selection=True)
|
|
mt = mutils.MirrorTable.fromObjects(objects, "_l_", "_r_", MirrorPlane.YZ)
|
|
|
|
# Example 3:
|
|
# Save the MirrorTable to the given JSON path
|
|
path = "/tmp/mirrortable.json"
|
|
mt.save(path)
|
|
|
|
# Example 4:
|
|
# Create a MirrorTable instance from the given JSON path
|
|
path = "/tmp/mirrortable.json"
|
|
mt = mutils.MirrorTable.fromPath(path)
|
|
|
|
# Example 5:
|
|
# Mirror all the objects from file
|
|
mt.load()
|
|
|
|
# Example 6:
|
|
# Mirror only the selected objects
|
|
objects = maya.cmds.ls(selection=True) or []
|
|
mt.load(objects=objects)
|
|
|
|
# Example 7:
|
|
# Mirror all objects from file to the given namespaces
|
|
mt.load(namespaces=["character1", "character2"])
|
|
|
|
# Example 8:
|
|
# Mirror only the given objects
|
|
mt.load(objects=["character1:Hand_L", "character1:Finger_L"])
|
|
|
|
# Example 9:
|
|
# Mirror all objects from left to right
|
|
mt.load(option=mutils.MirrorOption.LeftToRight)
|
|
|
|
# Example 10:
|
|
# Mirror all objects from right to left
|
|
mt.load(option=mutils.MirrorOption.RightToLeft)
|
|
|
|
# Example 11:
|
|
# Mirror only the current pose
|
|
mt.load(animation=False)
|
|
"""
|
|
|
|
import re
|
|
import mutils
|
|
import logging
|
|
import traceback
|
|
|
|
from studiovendor import six
|
|
|
|
try:
|
|
import maya.cmds
|
|
except ImportError:
|
|
traceback.print_exc()
|
|
|
|
|
|
__all__ = [
|
|
"MirrorTable",
|
|
"MirrorPlane",
|
|
"MirrorOption",
|
|
"saveMirrorTable",
|
|
]
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
RE_LEFT_SIDE = "Left|left|Lf|lt_|_lt|lf_|_lf|_l_|_L|L_|:l_|^l_|_l$|:L|^L"
|
|
RE_RIGHT_SIDE = "Right|right|Rt|rt_|_rt|_r_|_R|R_|:r_|^r_|_r$|:R|^R"
|
|
|
|
VALID_NODE_TYPES = ["joint", "transform"]
|
|
|
|
|
|
class MirrorPlane:
|
|
YZ = [-1, 1, 1]
|
|
XZ = [1, -1, 1]
|
|
XY = [1, 1, -1]
|
|
|
|
|
|
class MirrorOption:
|
|
Swap = 0
|
|
LeftToRight = 1
|
|
RightToLeft = 2
|
|
|
|
|
|
class KeysOption:
|
|
All = "All Keys"
|
|
SelectedRange = "Selected Range"
|
|
|
|
|
|
def saveMirrorTable(path, objects, metadata=None, *args, **kwargs):
|
|
"""
|
|
Convenience function for saving a mirror table to the given disc location.
|
|
|
|
:type path: str
|
|
:type objects: list[str]
|
|
:type metadata: dict or None
|
|
:type args: list
|
|
:type kwargs: dict
|
|
:rtype: MirrorTable
|
|
"""
|
|
mirrorTable = MirrorTable.fromObjects(objects, *args, **kwargs)
|
|
|
|
if metadata:
|
|
mirrorTable.updateMetadata(metadata)
|
|
|
|
mirrorTable.save(path)
|
|
|
|
return mirrorTable
|
|
|
|
|
|
class MirrorTable(mutils.TransferObject):
|
|
|
|
@classmethod
|
|
@mutils.timing
|
|
@mutils.unifyUndo
|
|
@mutils.showWaitCursor
|
|
@mutils.restoreSelection
|
|
def fromObjects(
|
|
cls,
|
|
objects,
|
|
leftSide=None,
|
|
rightSide=None,
|
|
mirrorPlane=None
|
|
):
|
|
"""
|
|
Create a new Mirror Table instance from the given Maya object/controls.
|
|
|
|
:type objects: list[str]
|
|
:type leftSide: str
|
|
:type rightSide: str
|
|
:type mirrorPlane: mirrortable.MirrorPlane or str
|
|
|
|
:rtype: MirrorTable
|
|
"""
|
|
mirrorPlane = mirrorPlane or MirrorPlane.YZ
|
|
|
|
if isinstance(mirrorPlane, six.string_types):
|
|
|
|
if mirrorPlane.lower() == "yz":
|
|
mirrorPlane = MirrorPlane.YZ
|
|
|
|
elif mirrorPlane.lower() == "xz":
|
|
mirrorPlane = MirrorPlane.XZ
|
|
|
|
elif mirrorPlane.lower() == "xy":
|
|
mirrorPlane = MirrorPlane.XY
|
|
|
|
mirrorTable = cls()
|
|
mirrorTable.setMetadata("left", leftSide)
|
|
mirrorTable.setMetadata("right", rightSide)
|
|
mirrorTable.setMetadata("mirrorPlane", mirrorPlane)
|
|
|
|
for obj in objects:
|
|
nodeType = maya.cmds.nodeType(obj)
|
|
if nodeType in VALID_NODE_TYPES:
|
|
mirrorTable.add(obj)
|
|
else:
|
|
msg = "Node of type {0} is not supported. Node name: {1}"
|
|
msg = msg.format(nodeType, obj)
|
|
logger.info(msg)
|
|
|
|
return mirrorTable
|
|
|
|
@staticmethod
|
|
def findLeftSide(objects):
|
|
"""
|
|
Return the left side naming convention for the given objects
|
|
|
|
:type objects: list[str]
|
|
:rtype: str
|
|
"""
|
|
return MirrorTable.findSide(objects, RE_LEFT_SIDE)
|
|
|
|
@staticmethod
|
|
def findRightSide(objects):
|
|
"""
|
|
Return the right side naming convention for the given objects
|
|
|
|
:type objects: list[str]
|
|
:rtype: str
|
|
"""
|
|
return MirrorTable.findSide(objects, RE_RIGHT_SIDE)
|
|
|
|
@classmethod
|
|
def findSide(cls, objects, reSides):
|
|
"""
|
|
Return the naming convention for the given object names.
|
|
|
|
:type objects: list[str]
|
|
:type reSides: str or list[str]
|
|
:rtype: str
|
|
"""
|
|
if isinstance(reSides, six.string_types):
|
|
reSides = reSides.split("|")
|
|
|
|
# Compile the list of regular expressions into a re.object
|
|
reSides = [re.compile(side) for side in reSides]
|
|
|
|
for obj in objects:
|
|
obj = obj.split("|")[-1]
|
|
obj = obj.split(":")[-1]
|
|
|
|
for reSide in reSides:
|
|
|
|
m = reSide.search(obj)
|
|
if m:
|
|
side = m.group()
|
|
|
|
if obj.startswith(side):
|
|
side += "*"
|
|
|
|
if obj.endswith(side):
|
|
side = "*" + side
|
|
|
|
return side
|
|
|
|
return ""
|
|
|
|
@staticmethod
|
|
def matchSide(name, side):
|
|
"""
|
|
Return True if the name contains the given side.
|
|
|
|
:type name: str
|
|
:type side: str
|
|
:rtype: bool
|
|
"""
|
|
if side:
|
|
return MirrorTable.rreplace(name, side, "X") != name
|
|
|
|
return False
|
|
|
|
@staticmethod
|
|
def rreplace(name, old, new):
|
|
"""
|
|
convenience method.
|
|
|
|
Example:
|
|
print MirrorTable.rreplace("CHR1:RIG:RhandCON", ":R", ":L")
|
|
"CHR1:RIG:LhandCON"
|
|
|
|
:type name: str
|
|
:type old: str
|
|
:type new: str
|
|
:rtype: str
|
|
"""
|
|
names = []
|
|
|
|
for n in name.split("|"):
|
|
|
|
namespaces = ''
|
|
if ':' in n:
|
|
namespaces, n = n.rsplit(':', 1)
|
|
|
|
other = mutils.mirrortable.MirrorTable.replace(n, old, new)
|
|
|
|
if namespaces:
|
|
other = ':'.join([namespaces, other])
|
|
|
|
names.append(other)
|
|
|
|
return "|".join(names)
|
|
|
|
@staticmethod
|
|
def replace(name, old, new):
|
|
"""
|
|
A static method that is called by self.mirrorObject.
|
|
|
|
:type name: str
|
|
:type old: str
|
|
:type new: str
|
|
:rtype: str
|
|
"""
|
|
# Support for replacing a prefix naming convention.
|
|
if old.endswith("*") or new.endswith("*"):
|
|
name = MirrorTable.replacePrefix(name, old, new)
|
|
|
|
# Support for replacing a suffix naming convention.
|
|
elif old.startswith("*") or new.startswith("*"):
|
|
name = MirrorTable.replaceSuffix(name, old, new)
|
|
|
|
# Support for all other naming conventions.
|
|
else:
|
|
name = name.replace(old, new)
|
|
|
|
return name
|
|
|
|
@staticmethod
|
|
def replacePrefix(name, old, new):
|
|
"""
|
|
Replace the given old prefix with the given new prefix.
|
|
It should also support long names.
|
|
|
|
# Example:
|
|
self.replacePrefix("R_footRoll", "R", "L")
|
|
# Result: "L_footRoll" and not "L_footLoll".
|
|
|
|
self.replacePrefix("Grp|Ch1:R_footExtra|Ch1:R_footRoll", "R_", "L_")
|
|
# Result: "Grp|Ch1:L_footExtra|Ch1:L_footRoll"
|
|
|
|
:type name: str
|
|
:type old: str
|
|
:type new: str
|
|
:rtype: str
|
|
"""
|
|
old = old.replace("*", "")
|
|
new = new.replace("*", "")
|
|
|
|
if name.startswith(old):
|
|
name = name.replace(old, new, 1)
|
|
|
|
return name
|
|
|
|
@staticmethod
|
|
def replaceSuffix(name, old, new):
|
|
"""
|
|
Replace the given old suffix with the given new suffix.
|
|
It should also support long names.
|
|
|
|
# Example:
|
|
self.replaceSuffix("footRoll_R", "R", "L")
|
|
# Result: "footRoll_L" and not "footLoll_L".
|
|
|
|
self.replaceSuffix("Grp|Ch1:footExtra_R|Ch1:footRoll_R", "_R", "_L")
|
|
# Result: "Grp|Ch1:footExtra_L|Ch1:footRoll_L"
|
|
|
|
:type name: str
|
|
:type old: str
|
|
:type new: str
|
|
:rtype: str
|
|
"""
|
|
old = old.replace("*", "")
|
|
new = new.replace("*", "")
|
|
|
|
if name.endswith(old):
|
|
name = name[:-len(old)] + new
|
|
|
|
return name
|
|
|
|
def mirrorObject(self, obj):
|
|
"""
|
|
Return the other/opposite side for the given name.
|
|
|
|
Example:
|
|
print self.mirrorObject("FKSholder_L")
|
|
# FKShoulder_R
|
|
|
|
:type obj: str
|
|
:rtype: str or None
|
|
"""
|
|
leftSide = self.leftSide()
|
|
rightSide = self.rightSide()
|
|
return self._mirrorObject(obj, leftSide, rightSide)
|
|
|
|
@staticmethod
|
|
def _mirrorObject(obj, leftSide, rightSide):
|
|
"""
|
|
A static method that is called by self.mirrorObject.
|
|
|
|
:type obj: str
|
|
:rtype: str or None
|
|
"""
|
|
dstName = MirrorTable.rreplace(obj, leftSide, rightSide)
|
|
|
|
if obj == dstName:
|
|
dstName = MirrorTable.rreplace(obj, rightSide, leftSide)
|
|
|
|
if dstName != obj:
|
|
return dstName
|
|
|
|
# Return None as the given name is probably a center control
|
|
# and doesn't have an opposite side.
|
|
return None
|
|
|
|
@staticmethod
|
|
def animCurve(obj, attr):
|
|
"""
|
|
:type obj: str
|
|
:type attr: str
|
|
:rtype: str
|
|
"""
|
|
fullname = obj + "." + attr
|
|
connections = maya.cmds.listConnections(fullname, d=False, s=True)
|
|
if connections:
|
|
return connections[0]
|
|
return None
|
|
|
|
@staticmethod
|
|
def scaleKey(obj, attr, time=None):
|
|
"""
|
|
:type obj: str
|
|
:type attr: str
|
|
"""
|
|
curve = MirrorTable.animCurve(obj, attr)
|
|
if curve:
|
|
maya.cmds.selectKey(curve)
|
|
if time:
|
|
maya.cmds.scaleKey(time=time, iub=False, ts=1, fs=1, vs=-1, vp=0, animation="keys")
|
|
else:
|
|
maya.cmds.scaleKey(iub=False, ts=1, fs=1, vs=-1, vp=0, animation="keys")
|
|
@staticmethod
|
|
def formatValue(attr, value, mirrorAxis):
|
|
"""
|
|
:type attr: str
|
|
:type value: float
|
|
:type mirrorAxis: list[int]
|
|
:rtype: float
|
|
"""
|
|
if MirrorTable.isAttrMirrored(attr, mirrorAxis):
|
|
return value * -1
|
|
return value
|
|
|
|
@staticmethod
|
|
def maxIndex(numbers):
|
|
"""
|
|
Finds the largest number in a list
|
|
:type numbers: list[float] or list[str]
|
|
:rtype: int
|
|
"""
|
|
m = 0
|
|
result = 0
|
|
for i in numbers:
|
|
v = abs(float(i))
|
|
if v > m:
|
|
m = v
|
|
result = numbers.index(i)
|
|
return result
|
|
|
|
@staticmethod
|
|
def axisWorldPosition(obj, axis):
|
|
"""
|
|
:type obj: str
|
|
:type axis: list[int]
|
|
:rtype: list[float]
|
|
"""
|
|
transform1 = maya.cmds.createNode("transform", name="transform1")
|
|
try:
|
|
transform1, = maya.cmds.parent(transform1, obj, r=True)
|
|
maya.cmds.setAttr(transform1 + ".t", *axis)
|
|
maya.cmds.setAttr(transform1 + ".r", 0, 0, 0)
|
|
maya.cmds.setAttr(transform1 + ".s", 1, 1, 1)
|
|
return maya.cmds.xform(transform1, q=True, ws=True, piv=True)
|
|
finally:
|
|
maya.cmds.delete(transform1)
|
|
|
|
@staticmethod
|
|
def isAttrMirrored(attr, mirrorAxis):
|
|
"""
|
|
:type attr: str
|
|
:type mirrorAxis: list[int]
|
|
:rtype: float
|
|
"""
|
|
if mirrorAxis == [-1, 1, 1]:
|
|
if attr == "translateX" or attr == "rotateY" or attr == "rotateZ":
|
|
return True
|
|
|
|
elif mirrorAxis == [1, -1, 1]:
|
|
if attr == "translateY" or attr == "rotateX" or attr == "rotateZ":
|
|
return True
|
|
|
|
elif mirrorAxis == [1, 1, -1]:
|
|
if attr == "translateZ" or attr == "rotateX" or attr == "rotateY":
|
|
return True
|
|
|
|
elif mirrorAxis == [-1, -1, -1]:
|
|
if attr == "translateX" or attr == "translateY" or attr == "translateZ":
|
|
return True
|
|
return False
|
|
|
|
@staticmethod
|
|
def isAxisMirrored(srcObj, dstObj, axis, mirrorPlane):
|
|
"""
|
|
:type srcObj: str
|
|
:type dstObj: str
|
|
:type axis: list[int]
|
|
:type mirrorPlane: list[int]
|
|
:rtype: int
|
|
"""
|
|
old1 = maya.cmds.xform(srcObj, q=True, ws=True, piv=True)
|
|
old2 = maya.cmds.xform(dstObj, q=True, ws=True, piv=True)
|
|
|
|
new1 = MirrorTable.axisWorldPosition(srcObj, axis)
|
|
new2 = MirrorTable.axisWorldPosition(dstObj, axis)
|
|
|
|
mp = mirrorPlane
|
|
v1 = mp[0]*(new1[0]-old1[0]), mp[1]*(new1[1]-old1[1]), mp[2]*(new1[2]-old1[2])
|
|
v2 = new2[0]-old2[0], new2[1]-old2[1], new2[2]-old2[2]
|
|
|
|
d = sum(p*q for p, q in zip(v1, v2))
|
|
|
|
if d >= 0.0:
|
|
return False
|
|
return True
|
|
|
|
@staticmethod
|
|
def _calculateMirrorAxis(obj, mirrorPlane):
|
|
"""
|
|
:type obj: str
|
|
:rtype: list[int]
|
|
"""
|
|
result = [1, 1, 1]
|
|
transform0 = maya.cmds.createNode("transform", name="transform0")
|
|
|
|
try:
|
|
transform0, = maya.cmds.parent(transform0, obj, r=True)
|
|
transform0, = maya.cmds.parent(transform0, w=True)
|
|
maya.cmds.setAttr(transform0 + ".t", 0, 0, 0)
|
|
|
|
t1 = MirrorTable.axisWorldPosition(transform0, [1, 0, 0])
|
|
t2 = MirrorTable.axisWorldPosition(transform0, [0, 1, 0])
|
|
t3 = MirrorTable.axisWorldPosition(transform0, [0, 0, 1])
|
|
|
|
t1 = "%.3f" % t1[0], "%.3f" % t1[1], "%.3f" % t1[2]
|
|
t2 = "%.3f" % t2[0], "%.3f" % t2[1], "%.3f" % t2[2]
|
|
t3 = "%.3f" % t3[0], "%.3f" % t3[1], "%.3f" % t3[2]
|
|
|
|
if mirrorPlane == MirrorPlane.YZ: # [-1, 1, 1]:
|
|
x = [t1[0], t2[0], t3[0]]
|
|
i = MirrorTable.maxIndex(x)
|
|
result[i] = -1
|
|
|
|
if mirrorPlane == MirrorPlane.XZ: # [1, -1, 1]:
|
|
y = [t1[1], t2[1], t3[1]]
|
|
i = MirrorTable.maxIndex(y)
|
|
result[i] = -1
|
|
|
|
if mirrorPlane == MirrorPlane.XY: # [1, 1, -1]:
|
|
z = [t1[2], t2[2], t3[2]]
|
|
i = MirrorTable.maxIndex(z)
|
|
result[i] = -1
|
|
|
|
finally:
|
|
maya.cmds.delete(transform0)
|
|
|
|
return result
|
|
|
|
def select(self, objects=None, namespaces=None, **kwargs):
|
|
"""
|
|
Select the objects contained in file.
|
|
|
|
:type objects: list[str] or None
|
|
:type namespaces: list[str] or None
|
|
:rtype: None
|
|
"""
|
|
selectionSet = mutils.SelectionSet.fromPath(self.path())
|
|
selectionSet.load(objects=objects, namespaces=namespaces, **kwargs)
|
|
|
|
def leftSide(self):
|
|
"""
|
|
:rtype: str | None
|
|
"""
|
|
return self.metadata().get("left")
|
|
|
|
def rightSide(self):
|
|
"""
|
|
:rtype: str | None
|
|
"""
|
|
return self.metadata().get("right")
|
|
|
|
def mirrorPlane(self):
|
|
"""
|
|
:rtype: lsit[int] | None
|
|
"""
|
|
return self.metadata().get("mirrorPlane")
|
|
|
|
def mirrorAxis(self, name):
|
|
"""
|
|
:rtype: list[int]
|
|
"""
|
|
return self.objects()[name]["mirrorAxis"]
|
|
|
|
def leftCount(self, objects=None):
|
|
"""
|
|
:type objects: list[str]
|
|
:rtype: int
|
|
"""
|
|
if objects is None:
|
|
objects = self.objects()
|
|
return len([obj for obj in objects if self.isLeftSide(obj)])
|
|
|
|
def rightCount(self, objects=None):
|
|
"""
|
|
:type objects: list[str]
|
|
:rtype: int
|
|
"""
|
|
if objects is None:
|
|
objects = self.objects()
|
|
return len([obj for obj in objects if self.isRightSide(obj)])
|
|
|
|
def createObjectData(self, name):
|
|
"""
|
|
:type name:
|
|
:rtype:
|
|
"""
|
|
result = {"mirrorAxis": self.calculateMirrorAxis(name)}
|
|
return result
|
|
|
|
def matchObjects(
|
|
self,
|
|
objects=None,
|
|
namespaces=None,
|
|
):
|
|
"""
|
|
:type objects: list[str]
|
|
:type namespaces: list[str]
|
|
:rtype: list[str]
|
|
"""
|
|
srcObjects = self.objects().keys()
|
|
|
|
matches = mutils.matchNames(
|
|
srcObjects=srcObjects,
|
|
dstObjects=objects,
|
|
dstNamespaces=namespaces,
|
|
)
|
|
|
|
for srcNode, dstNode in matches:
|
|
dstName = dstNode.name()
|
|
mirrorAxis = self.mirrorAxis(srcNode.name())
|
|
yield srcNode.name(), dstName, mirrorAxis
|
|
|
|
def rightToLeft(self):
|
|
""""""
|
|
self.load(option=MirrorOption.RightToLeft)
|
|
|
|
def leftToRight(self):
|
|
""""""
|
|
self.load(option=MirrorOption.LeftToRight)
|
|
|
|
@mutils.timing
|
|
@mutils.unifyUndo
|
|
@mutils.showWaitCursor
|
|
@mutils.restoreSelection
|
|
def load(
|
|
self,
|
|
objects=None,
|
|
namespaces=None,
|
|
option=None,
|
|
keysOption=None,
|
|
time=None,
|
|
):
|
|
"""
|
|
Load the mirror table for the given objects.
|
|
|
|
:type objects: list[str]
|
|
:type namespaces: list[str]
|
|
:type option: mirrorOptions
|
|
:type keysOption: None or KeysOption.SelectedRange
|
|
:type time: None or list[int]
|
|
"""
|
|
if option and not isinstance(option, int):
|
|
if option.lower() == "swap":
|
|
option = 0
|
|
elif option.lower() == "left to right":
|
|
option = 1
|
|
elif option.lower() == "right to left":
|
|
option = 2
|
|
else:
|
|
raise ValueError('Invalid load option=' + str(option))
|
|
|
|
self.validate(namespaces=namespaces)
|
|
|
|
results = {}
|
|
animation = True
|
|
foundObject = False
|
|
srcObjects = self.objects().keys()
|
|
|
|
if option is None:
|
|
option = MirrorOption.Swap
|
|
|
|
if keysOption == KeysOption.All:
|
|
time = None
|
|
elif keysOption == KeysOption.SelectedRange:
|
|
time = mutils.selectedFrameRange()
|
|
|
|
# Check to make sure that the given time is not a single frame
|
|
if time and time[0] == time[1]:
|
|
time = None
|
|
animation = False
|
|
|
|
matches = mutils.matchNames(
|
|
srcObjects=srcObjects,
|
|
dstObjects=objects,
|
|
dstNamespaces=namespaces,
|
|
)
|
|
|
|
for srcNode, dstNode in matches:
|
|
dstObj = dstNode.name()
|
|
dstObj2 = self.mirrorObject(dstObj) or dstObj
|
|
|
|
if dstObj2 not in results:
|
|
results[dstObj] = dstObj2
|
|
|
|
mirrorAxis = self.mirrorAxis(srcNode.name())
|
|
dstObjExists = maya.cmds.objExists(dstObj)
|
|
dstObj2Exists = maya.cmds.objExists(dstObj2)
|
|
|
|
if dstObjExists and dstObj2Exists:
|
|
foundObject = True
|
|
if animation:
|
|
self.transferAnimation(dstObj, dstObj2, mirrorAxis=mirrorAxis, option=option, time=time)
|
|
else:
|
|
self.transferStatic(dstObj, dstObj2, mirrorAxis=mirrorAxis, option=option)
|
|
else:
|
|
if not dstObjExists:
|
|
msg = "Cannot find destination object {0}"
|
|
msg = msg.format(dstObj)
|
|
logger.debug(msg)
|
|
|
|
if not dstObj2Exists:
|
|
msg = "Cannot find mirrored destination object {0}"
|
|
msg = msg.format(dstObj2)
|
|
logger.debug(msg)
|
|
|
|
# Return the focus to the Maya window
|
|
maya.cmds.setFocus("MayaWindow")
|
|
|
|
if not foundObject:
|
|
|
|
text = "No objects match when loading data. " \
|
|
"Turn on debug mode to see more details."
|
|
|
|
raise mutils.NoMatchFoundError(text)
|
|
|
|
def transferStatic(self, srcObj, dstObj, mirrorAxis=None, attrs=None, option=MirrorOption.Swap):
|
|
"""
|
|
:type srcObj: str
|
|
:type dstObj: str
|
|
:type mirrorAxis: list[int]
|
|
:type attrs: None | list[str]
|
|
:type option: MirrorOption
|
|
"""
|
|
srcValue = None
|
|
dstValue = None
|
|
srcValid = self.isValidMirror(srcObj, option)
|
|
dstValid = self.isValidMirror(dstObj, option)
|
|
|
|
if attrs is None:
|
|
attrs = maya.cmds.listAttr(srcObj, keyable=True) or []
|
|
|
|
for attr in attrs:
|
|
dstAttr = dstObj + "." + attr
|
|
srcAttr = srcObj + "." + attr
|
|
if maya.cmds.objExists(dstAttr):
|
|
if dstValid:
|
|
srcValue = maya.cmds.getAttr(srcAttr)
|
|
|
|
if srcValid:
|
|
dstValue = maya.cmds.getAttr(dstAttr)
|
|
|
|
if dstValid:
|
|
self.setAttr(dstObj, attr, srcValue, mirrorAxis=mirrorAxis)
|
|
|
|
if srcValid:
|
|
self.setAttr(srcObj, attr, dstValue, mirrorAxis=mirrorAxis)
|
|
else:
|
|
logger.debug("Cannot find destination attribute %s" % dstAttr)
|
|
|
|
def setAttr(self, name, attr, value, mirrorAxis=None):
|
|
"""
|
|
:type name: str
|
|
:type: attr: str
|
|
:type: value: int | float
|
|
:type mirrorAxis: Axis or None
|
|
"""
|
|
if mirrorAxis is not None:
|
|
value = self.formatValue(attr, value, mirrorAxis)
|
|
try:
|
|
maya.cmds.setAttr(name + "." + attr, value)
|
|
except RuntimeError:
|
|
msg = "Cannot mirror static attribute {name}.{attr}"
|
|
msg = msg.format(name=name, attr=attr)
|
|
logger.debug(msg)
|
|
|
|
def transferAnimation(self, srcObj, dstObj, mirrorAxis=None, option=MirrorOption.Swap, time=None):
|
|
"""
|
|
:type srcObj: str
|
|
:type dstObj: str
|
|
:type mirrorAxis: Axis or None
|
|
"""
|
|
srcValid = self.isValidMirror(srcObj, option)
|
|
dstValid = self.isValidMirror(dstObj, option)
|
|
|
|
tmpObj, = maya.cmds.duplicate(srcObj, name='DELETE_ME', parentOnly=True)
|
|
try:
|
|
if dstValid:
|
|
self._transferAnimation(srcObj, tmpObj, time=time)
|
|
if srcValid:
|
|
self._transferAnimation(dstObj, srcObj, mirrorAxis=mirrorAxis, time=time)
|
|
if dstValid:
|
|
self._transferAnimation(tmpObj, dstObj, mirrorAxis=mirrorAxis, time=time)
|
|
finally:
|
|
maya.cmds.delete(tmpObj)
|
|
|
|
def _transferAnimation(self, srcObj, dstObj, attrs=None, mirrorAxis=None, time=None):
|
|
"""
|
|
:type srcObj: str
|
|
:type dstObj: str
|
|
:type attrs: list[str]
|
|
:type time: list[int]
|
|
:type mirrorAxis: list[int]
|
|
"""
|
|
maya.cmds.cutKey(dstObj, time=time or ()) # remove keys
|
|
if maya.cmds.copyKey(srcObj, time=time or ()):
|
|
if not time:
|
|
maya.cmds.pasteKey(dstObj, option="replaceCompletely")
|
|
else:
|
|
maya.cmds.pasteKey(dstObj, time=time, option="replace")
|
|
|
|
if attrs is None:
|
|
attrs = maya.cmds.listAttr(srcObj, keyable=True) or []
|
|
|
|
for attr in attrs:
|
|
srcAttribute = mutils.Attribute(srcObj, attr)
|
|
dstAttribute = mutils.Attribute(dstObj, attr)
|
|
|
|
if dstAttribute.exists():
|
|
if dstAttribute.isConnected():
|
|
if self.isAttrMirrored(attr, mirrorAxis):
|
|
maya.cmds.scaleKey(dstAttribute.name(), valueScale=-1, attribute=attr)
|
|
else:
|
|
value = srcAttribute.value()
|
|
self.setAttr(dstObj, attr, value, mirrorAxis)
|
|
|
|
def isValidMirror(self, obj, option):
|
|
"""
|
|
:type obj: str
|
|
:type option: MirrorOption
|
|
:rtype: bool
|
|
"""
|
|
if option == MirrorOption.Swap:
|
|
return True
|
|
|
|
elif option == MirrorOption.LeftToRight and self.isLeftSide(obj):
|
|
return False
|
|
|
|
elif option == MirrorOption.RightToLeft and self.isRightSide(obj):
|
|
return False
|
|
|
|
else:
|
|
return True
|
|
|
|
def isLeftSide(self, name):
|
|
"""
|
|
Return True if the object contains the left side string.
|
|
|
|
# Group|Character1:footRollExtra_L|Character1:footRoll_L
|
|
# Group|footRollExtra_L|footRoll_LShape
|
|
# footRoll_L
|
|
# footRoll_LShape
|
|
|
|
:type name: str
|
|
:rtype: bool
|
|
"""
|
|
side = self.leftSide()
|
|
return self.matchSide(name, side)
|
|
|
|
def isRightSide(self, name):
|
|
"""
|
|
Return True if the object contains the right side string.
|
|
|
|
# Group|Character1:footRollExtra_R|Character1:footRoll_R
|
|
# Group|footRollExtra_R|footRoll_RShape
|
|
# footRoll_R
|
|
# footRoll_RShape
|
|
|
|
:type name: str
|
|
:rtype: bool
|
|
"""
|
|
side = self.rightSide()
|
|
return self.matchSide(name, side)
|
|
|
|
def calculateMirrorAxis(self, srcObj):
|
|
"""
|
|
:type srcObj: str
|
|
:rtype: list[int]
|
|
"""
|
|
result = [1, 1, 1]
|
|
dstObj = self.mirrorObject(srcObj) or srcObj
|
|
mirrorPlane = self.mirrorPlane()
|
|
|
|
if dstObj == srcObj or not maya.cmds.objExists(dstObj):
|
|
# The source object does not have an opposite side
|
|
result = MirrorTable._calculateMirrorAxis(srcObj, mirrorPlane)
|
|
else:
|
|
# The source object does have an opposite side
|
|
if MirrorTable.isAxisMirrored(srcObj, dstObj, [1, 0, 0], mirrorPlane):
|
|
result[0] = -1
|
|
|
|
if MirrorTable.isAxisMirrored(srcObj, dstObj, [0, 1, 0], mirrorPlane):
|
|
result[1] = -1
|
|
|
|
if MirrorTable.isAxisMirrored(srcObj, dstObj, [0, 0, 1], mirrorPlane):
|
|
result[2] = -1
|
|
|
|
return result
|