Files
Nexus/2023/scripts/animation_tools/studiolibrary/mutils/cmds.py
2025-11-24 00:15:32 +08:00

426 lines
9.9 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/>.
import logging
import platform
import traceback
from studiovendor.Qt import QtCore
from studiovendor.Qt import QtWidgets
try:
import maya.mel
import maya.cmds
except ImportError:
traceback.print_exc()
import mutils
logger = logging.getLogger(__name__)
class MayaUtilsError(Exception):
"""Base class for exceptions in this module."""
class ObjectsError(MayaUtilsError):
pass
class SelectionError(MayaUtilsError):
pass
class NoMatchFoundError(MayaUtilsError):
pass
class NoObjectFoundError(MayaUtilsError):
pass
class MoreThanOneObjectFoundError(MayaUtilsError):
pass
class ModelPanelNotInFocusError(MayaUtilsError):
pass
def system():
"""
Return the current platform in lowercase.
:rtype: str
"""
return platform.system().lower()
def isMac():
"""
Return True if the current OS is Mac.
:rtype: bool
"""
return system().startswith("os") or \
system().startswith("mac") or \
system().startswith("darwin")
def isLinux():
"""
Return True if the current OS is linux.
:rtype: bool
"""
return system().lower().startswith("lin")
def isWindows():
"""
Return True if the current OS is windows.
:rtype: bool
"""
return system().lower().startswith("win")
def isMaya():
"""
Return True if the current python session is in Maya.
:rtype: bool
"""
try:
import maya.cmds
maya.cmds.about(batch=True)
return True
except ImportError:
return False
def selectionModifiers():
"""
Return a dictionary of the current key modifiers
:rtype: dict
"""
result = {"add": False, "deselect": False}
modifiers = QtWidgets.QApplication.keyboardModifiers()
if modifiers == QtCore.Qt.ShiftModifier:
result["deselect"] = True
elif modifiers == QtCore.Qt.ControlModifier:
result["add"] = True
return result
def ls(*args, **kwargs):
"""
List all the node objects for the given options.
:type args: list
:type kwargs: dict
"""
return [mutils.Node(name) for name in maya.cmds.ls(*args, **kwargs) or []]
def listAttr(name, **kwargs):
"""
List all the attributes for the given object name.
:type name: str
:type kwargs: str
:rtype: list[mutils.Attribute]
"""
attrs = maya.cmds.listAttr(name, **kwargs) or []
attrs = list(set(attrs))
return [mutils.Attribute(name, attr) for attr in attrs]
def disconnectAll(name):
"""
Disconnect all connections from the given object name.
:type name: str
"""
plugins = maya.cmds.listConnections(name, plugs=True, source=False) or []
for plug in plugins:
source, = maya.cmds.listConnections(plug, plugs=True)
maya.cmds.disconnectAttr(source, plug)
def animCurve(fullname):
"""
Return the animation curve name for the give attribute.
:type fullname: str or None
:rtype: str or None
"""
attribute = mutils.Attribute(fullname)
return attribute.animCurve()
def deleteUnknownNodes():
"""Delete all unknown nodes in the Maya scene."""
nodes = maya.cmds.ls(type="unknown")
if nodes:
for node in nodes:
if maya.cmds.objExists(node) and \
maya.cmds.referenceQuery(node, inr=True):
maya.cmds.delete(node)
def currentModelPanel():
"""
Get the current model panel name.
:rtype: str or None
"""
currentPanel = maya.cmds.getPanel(withFocus=True)
currentPanelType = maya.cmds.getPanel(typeOf=currentPanel)
if currentPanelType not in ['modelPanel']:
return None
return currentPanel
def getBakeAttrs(objects):
"""
Get the attributes that are not connected to an animation curve.
:type objects: list[str]
:rtype: list[str]
"""
result = []
if not objects:
raise Exception("No objects specified")
connections = maya.cmds.listConnections(
objects,
plugs=True,
source=True,
connections=True,
destination=False
) or []
for i in range(0, len(connections), 2):
dstObj = connections[i]
srcObj = connections[i + 1]
nodeType = maya.cmds.nodeType(srcObj)
if "animCurve" not in nodeType:
result.append(dstObj)
return result
def bakeConnected(objects, time, sampleBy=1):
"""
Bake the given objects.
:type objects: list[str]
:type time: (int, int)
:type sampleBy: int
"""
bakeAttrs = getBakeAttrs(objects)
if bakeAttrs:
maya.cmds.bakeResults(
bakeAttrs,
time=time,
shape=False,
simulation=True,
sampleBy=sampleBy,
controlPoints=False,
minimizeRotation=True,
bakeOnOverrideLayer=False,
preserveOutsideKeys=False,
sparseAnimCurveBake=False,
disableImplicitControl=True,
removeBakedAttributeFromLayer=False,
)
else:
logger.error("Cannot find any connection to bake!")
def getSelectedObjects():
"""
Get a list of the selected objects in Maya.
:rtype: list[str]
:raises: mutils.SelectionError:
"""
selection = maya.cmds.ls(selection=True)
if not selection:
raise mutils.SelectionError("No objects selected!")
return selection
def getSelectedAttrs():
"""
Get the attributes that are selected in the channel box.
:rtype: list[str]
"""
attributes = maya.cmds.channelBox(
"mainChannelBox",
query=True,
selectedMainAttributes=True
)
if attributes is not None:
attributes = str(attributes)
attributes = attributes.replace("tx", "translateX")
attributes = attributes.replace("ty", "translateY")
attributes = attributes.replace("tz", "translateZ")
attributes = attributes.replace("rx", "rotateX")
attributes = attributes.replace("ry", "rotateY")
attributes = attributes.replace("rz", "rotateZ")
attributes = eval(attributes)
return attributes
def currentFrameRange():
"""
Get the current first and last frame depending on the context.
:rtype: (int, int)
"""
start, end = selectedFrameRange()
if end == start:
start, end = selectedObjectsFrameRange()
if start == end:
start, end = playbackFrameRange()
return start, end
def playbackFrameRange():
"""
Get the first and last frame from the play back options.
:rtype: (int, int)
"""
start = maya.cmds.playbackOptions(query=True, min=True)
end = maya.cmds.playbackOptions(query=True, max=True)
return start, end
def selectedFrameRange():
"""
Get the first and last frame from the play back slider.
:rtype: (int, int)
"""
result = maya.mel.eval("timeControl -q -range $gPlayBackSlider")
start, end = result.replace('"', "").split(":")
start, end = int(start), int(end)
if end - start == 1:
end = start
return start, end
def selectedObjectsFrameRange(objects=None):
"""
Get the first and last animation frame from the given objects.
:type objects: list[str]
:rtype: (int, int)
"""
start = 0
end = 0
if not objects:
objects = maya.cmds.ls(selection=True) or []
if objects:
start = int(maya.cmds.findKeyframe(objects, which='first'))
end = int(maya.cmds.findKeyframe(objects, which='last'))
return start, end
def getDurationFromNodes(objects, time=None):
"""
Get the duration of the animation from the given object names.
:type time: [str, str]
:type objects: list[str]
:rtype: float
"""
if objects:
first = maya.cmds.findKeyframe(objects, which='first')
last = maya.cmds.findKeyframe(objects, which='last')
if time:
startKey = maya.cmds.findKeyframe(objects, time=(time[0], time[0]), which="next")
if startKey > time[1] or startKey < time[0]:
return 0
if first == last:
if maya.cmds.keyframe(objects, query=True, keyframeCount=True) > 0:
return 1
else:
return 0
return last - first
else:
return 0
def getReferencePaths(objects, withoutCopyNumber=False):
"""
Get the reference paths for the given objects.
:type objects: list[str]
:type withoutCopyNumber: bool
:rtype: list[str]
"""
paths = []
for obj in objects:
if maya.cmds.referenceQuery(obj, isNodeReferenced=True):
paths.append(maya.cmds.referenceQuery(obj, f=True, wcn=withoutCopyNumber))
return list(set(paths))
def getReferenceData(objects):
"""
Get the reference paths for the given objects.
:type objects: list[str]
:rtype: list[dict]
"""
data = []
paths = getReferencePaths(objects)
for path in paths:
data.append({
"filename": path,
"unresolved": maya.cmds.referenceQuery(path, filename=True, withoutCopyNumber=True),
"namespace": maya.cmds.file(path, q=True, namespace=True),
"node": maya.cmds.referenceQuery(path, referenceNode=True)
})
return data