Skip to content

Instantly share code, notes, and snippets.

@minimalefforttech
Created March 7, 2025 23:32
Show Gist options
  • Save minimalefforttech/2cdfe4742e602b33133da2337753b411 to your computer and use it in GitHub Desktop.
Save minimalefforttech/2cdfe4742e602b33133da2337753b411 to your computer and use it in GitHub Desktop.
Maya Graph Editor Minimap
# 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