Created
March 7, 2025 23:32
-
-
Save minimalefforttech/2cdfe4742e602b33133da2337753b411 to your computer and use it in GitHub Desktop.
Maya Graph Editor Minimap
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Copyright CC-BY 4.0 Alex Telford, minimaleffort.tech | |
""" This is the code used to create the minimap in the graph editor, | |
It was just a quick test so this code is a bit shit, but it works. | |
If you want to take this further and make it a proper tool go for it, | |
I appreciate attribution or a linkback but don't care that much. | |
""" | |
from PySide2 import QtWidgets, QtGui, QtCore | |
from maya import OpenMaya, OpenMayaAnim | |
from maya import cmds | |
""" | |
TODO: use this instead! | |
from maya.api import OpenMaya, OpenMayaUI | |
graphEditor = "graphEditor1GraphEd" | |
info = OpenMayaUI.MPanelCanvasInfo(graphEditor) | |
info.getViewportBounds() | |
""" | |
def scale_number(a, a_min, a_max, b_min=0.0, b_max=1.0): | |
if a_min == a_max: | |
return a_min | |
scaled_value = b_min + ((a - a_min) / (a_max - a_min)) * (b_max - b_min) | |
return scaled_value | |
class CurveInfo(object): | |
color = None | |
values = None | |
keyframes = None | |
selected = False | |
def __init__(self): | |
self.color = QtGui.QColor("#FFFFFF") | |
self.values = [] | |
self.keyframes = [] | |
def bounds(self): | |
if not self.values: | |
return QtCore.QRectF() | |
else: | |
bounds = QtCore.QRectF( | |
QtCore.QPointF( | |
self.keyframes[-1], | |
max(self.values)), | |
QtCore.QPointF( | |
self.keyframes[0], | |
min(self.values)), | |
) | |
#print(bounds) | |
return bounds | |
class GraphEditorMap(QtWidgets.QWidget): | |
def __init__(self, parent=None): | |
super(GraphEditorMap, self).__init__(parent) | |
self._curves = [] | |
self._resolution = 1 | |
self.dragging = False | |
self._curveNodes = [] | |
self._fullBounds = QtCore.QRectF() | |
self._currentBounds = QtCore.QRectF(QtCore.QPointF(20, 10), QtCore.QPointF(80, -10)) | |
self.setFixedSize(400, 200) | |
# self.cbids = [ | |
# OpenMaya.MEventMessage.addEventCallback("graphEditorParamCurveSelected", self._updateNodes), | |
# OpenMaya.MEventMessage.addEventCallback("SelectionChanged", self._updateNodes), | |
# ] | |
self._updateNodes() | |
timer = QtCore.QTimer(self) | |
timer.setInterval(200) | |
timer.timeout.connect(self._updateNodes) | |
timer.start() | |
# def __del__(self): | |
# for cbid in self.cbids: | |
# OpenMaya.MEventMessage.removeCallback(cbid) | |
def mousePressEvent(self, event): | |
super(GraphEditorMap, self).mousePressEvent(event) | |
self.dragging = True | |
self.pressPos = event.pos() | |
def mouseMoveEvent(self, event): | |
super(GraphEditorMap, self).mouseMoveEvent(event) | |
if self.dragging: | |
# distance = (event.pos() - self.pressPos).x() | |
# get position at pos | |
pos = event.pos() | |
center = scale_number(pos.x(), 0.0, float(self.width()), self._fullBounds.left(), self._fullBounds.right()) | |
width = self._currentBounds.width() | |
left = center - width/2.0 | |
right = center + width/2.0 | |
fps = 24.0 | |
cmds.animCurveEditor("graphEditor1GraphEd", e=1, viewLeft=left/fps) | |
cmds.animCurveEditor("graphEditor1GraphEd", e=1, viewRight=right/fps) | |
self._currentBounds.setLeft(left) | |
self._currentBounds.setRight(right) | |
self.update() | |
def mouseReleaseEvent(self, event): | |
self.dragging = False | |
super(GraphEditorMap, self).mouseReleaseEvent(event) | |
def paintEvent(self, event): | |
painter = QtGui.QPainter(self) | |
painter.setPen(QtGui.QColor("#666666")) | |
painter.setBrush(QtGui.QColor("#444444")) | |
painter.drawRoundedRect(event.rect(), 10, 10) | |
for curve in self._curves: | |
painter.setBrush(QtCore.Qt.NoBrush) | |
if curve.selected: | |
painter.setPen(QtGui.QColor(255, 255, 255)) | |
else: | |
painter.setPen(curve.color) | |
path = QtGui.QPainterPath() | |
path.moveTo(self._position(curve.keyframes[0], curve.values[0])) | |
for i in range(1, len(curve.keyframes)): | |
path.lineTo(self._position(curve.keyframes[i], curve.values[i])) | |
painter.drawPath(path) | |
painter.setPen(QtGui.QColor(180, 180, 180)) | |
# painter.drawRect( | |
# QtCore.QRectF( | |
# self._position(self._currentBounds.left(), self._currentBounds.top()), | |
# self._position(self._currentBounds.right(), self._currentBounds.bottom()), | |
# ) | |
# ) | |
painter.drawRect( | |
QtCore.QRectF( | |
QtCore.QPointF(self._position(self._currentBounds.left(), self._currentBounds.top()).x(), event.rect().top()), | |
QtCore.QPointF(self._position(self._currentBounds.right(), self._currentBounds.bottom()).x(), event.rect().bottom()), | |
) | |
) | |
painter.setPen(QtGui.QColor(255, 255, 255)) | |
time = cmds.currentTime(q=1) | |
rel_time = scale_number(time, self._fullBounds.left(), self._fullBounds.right(), 0.0, float(self.width())) | |
painter.drawLine(QtCore.QPointF(rel_time, event.rect().top()), QtCore.QPointF(rel_time, event.rect().bottom())) | |
def _position(self, keyframe, value): | |
#print(keyframe, value, self._fullBounds) | |
return QtCore.QPointF( | |
scale_number(keyframe, self._fullBounds.left(), self._fullBounds.right(), 0.0, float(self.width())), | |
scale_number(value, self._fullBounds.bottom(), self._fullBounds.top(), 0.0, float(self.height()))) | |
@QtCore.Slot() | |
def _updateNodes(self, *args): | |
# nodes = cmds.ls(sl=1, l=1) | |
# curves = set(cmds.ls(nodes, type="animCurve")) | |
# curves.update(cmds.listConnections(nodes, d=0, s=1, type="animCurve", fnn=1) or []) | |
# curves = cmds.keyframe(q=1, n=1) | |
curves = cmds.animCurveEditor("graphEditor1GraphEd", q=1, cs=1) or [] | |
l = OpenMaya.MSelectionList() | |
for curve in curves: | |
l.add(curve) | |
obj = OpenMaya.MObject() | |
self._curveNodes = [] | |
for i in range(l.length()): | |
l.getDependNode(i, obj) | |
self._curveNodes.append(OpenMayaAnim.MFnAnimCurve(obj)) | |
self._populate() | |
@QtCore.Slot() | |
def _populate(self, *args): | |
self._curves = [] | |
bounds = QtCore.QRectF() | |
if not self._curveNodes: | |
return | |
selected = [] | |
if cmds.animCurveEditor("graphEditor1GraphEd", q=1, acs=True): | |
selected = cmds.keyframe(q=1, n=1) | |
for i, curve in enumerate(self._curveNodes): | |
info = CurveInfo() | |
name = curve.name() | |
if name in selected: | |
info.selected = True | |
if name[-1] == "X": | |
info.color = QtGui.QColor(255, 0, 0) | |
elif name[-1] == "Y": | |
info.color = QtGui.QColor(0, 255, 0) | |
elif name[-1] == "Z": | |
info.color = QtGui.QColor(0, 0, 255) | |
else: | |
info.color = QtGui.QColor(0, 0, 0) | |
if self._resolution == 0: | |
info.keyframes = [curve.time(i).asUnits(OpenMaya.MTime.uiUnit()) for i in range(curve.numKeys())] | |
info.values = [curve.value(i) for i in range(curve.numKeys())] | |
else: | |
frame = curve.time(0).asUnits(OpenMaya.MTime.uiUnit()) | |
last_frame = curve.time(curve.numKeys()-1).asUnits(OpenMaya.MTime.uiUnit()) | |
while frame < last_frame: | |
info.keyframes.append(frame) | |
info.values.append(curve.evaluate(OpenMaya.MTime(frame, OpenMaya.MTime.uiUnit()))) | |
frame += self._resolution | |
if frame != last_frame: | |
info.keyframes.append(last_frame) | |
info.values.append(curve.value(last_frame)) | |
if i == 0: | |
bounds = info.bounds() | |
else: | |
bounds = bounds.united(info.bounds()) | |
self._curves.append(info) | |
self._fullBounds = bounds | |
self.update() | |
widget = GraphEditorMap() | |
widget.show() | |
widget.deleteLater() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment