Maya tool for creating, replacing and managing rig control shapes (NURBS curves) with a PySide2/PySide6 UI.
Riggers and animators spend hours creating or tweaking control curves by hand, and replacing a control the traditional way breaks its animation connections and pivots. ControlMe replaces this manual workflow with a browsable shape library, in-place replacement that preserves connections, and a persistent SQLite store for custom shapes — so a rigger can ship consistent controls across a show without losing work.
Comment:
- You can save multiple versions of the same control with different colors to the database, then replace existing controls in your scene while preserving their behavior and connections—without breaking anything. The database is fully exportable.
Comment:
- You can extract controls from existing geometry, change their colors, duplicate them, delete them, and replace existing scene controls with library shapes — all without breaking animation or connections.
- Browse a library of 13 built-in NURBS primitives — circle, square, triangle, arrow, double arrow, cross, cube, diamond, locator, four arrow, three arrow, octahedron, and cone — with live search and QPainter-rendered previews
- Apply a shape to any selected node in one click
- Replace the shape on an existing control without losing its connections, pivot, or position — the hard part of rigging pipeline work
- Orient a control's CVs in 90° steps around X / Y / Z (left-click +90°, right-click −90°)
- Scale a control's CVs with a live slider or manual %, gated per object-space axis (X/Y/Z), with an optional Scale from center mode that pivots on the control's own centroid instead of its transform pivot
- Mirror a control across X / Y / Z — onto its opposite-side counterpart when an
L/Rname match exists (rebuilding the shape when the two sides differ), otherwise flipping its own CVs in place - Remove shapes from a node, or duplicate a library shape
- Set color via RGB override on the shape node
- Save custom shapes from your scene into a local SQLite library that persists between sessions
- Snapshot every NURBS control in the scene to a JSON file and restore the shapes/colors later, in place
- Maya 2022 or newer (Python 3)
- PySide2 or PySide6 — bundled with Maya, no install needed
config.tomlat the repo root — edit this file to change settings, no Python required- Zero pip installs — uses only Maya's bundled Python (
PySide,sqlite3,maya.cmds,maya.api.OpenMaya) and the standard library. Safe for locked studio pipelines.
- Download or clone this repo anywhere on your machine
- Open Maya
- Drag
install.pyinto the Maya viewport - Click OK in the confirmation dialog
- Restart Maya — the ControlMe shelf button appears automatically
Installs into the version-independent ~/Documents/maya/modules/, so the
tool loads in every Maya version. Any older per-version install is removed
automatically. No terminal or Python installation needed.
Requires Python to be available in your terminal (separate from Maya).
# global install — loads in all Maya versions
python install_module.py
# or install for a single Maya version instead
python install_module.py --maya 2025By default installs ControlMe as a proper Maya module (.mod) under the
version-independent ~/maya/modules/ (or ~/Documents/maya/modules/ on
Windows). Pass --maya <version> for a per-version install under
~/Documents/maya/<version>/modules/.
Once installed, click the ControlMe button on the Maya shelf.
To launch manually from the Script Editor (Python tab):
import runpy, sys
sys.path.insert(0, "/path/to/maya-control-tools")
runpy.run_path("main.py")Or paste the contents of main.py directly into the Script Editor — the script re-runs fresh on every execution.
maya-control-tools/
├── main.py # Script Editor entry point (hot-reload safe)
├── config.toml # User-editable settings (version, logging, cache)
├── install.py # Drag-and-drop installer for Maya viewport
├── install_module.py # CLI installer — writes .mod file
├── app/
│ ├── compat.py # PySide2/PySide6 abstraction + headless test stub
│ ├── config.py # TOML loader — exports VERSION, WINDOW_TITLE, etc.
│ ├── logger.py # Rotating file + console logging
│ ├── paths.py # Repo-root path resolution
│ ├── core/
│ │ ├── control.py # Maya curve operations (apply, replace, color)
│ │ ├── control_creation.py # Control creation pipeline
│ │ ├── operations.py # High-level shape operations
│ │ ├── shape_data.py # Shape data containers
│ │ ├── shape_service.py # Service layer between UI and core
│ │ ├── shapes.py # Built-in shape library (CV point data)
│ │ └── om2_utils.py # OpenMaya 2 helpers
│ ├── database/
│ │ └── manager.py # SQLite persistence for custom shapes
│ ├── models/
│ │ └── search.py # Substring search helper
│ ├── styles/ # QSS stylesheets
│ ├── icons/ # UI icons
│ └── views/
│ ├── main_view.py # Main window — wires everything together
│ ├── groups_view.py # Left panel: shape outliner with live search
│ ├── images_view.py # Right panel: icon grid with QPainter previews
│ └── widgets.py # Shared primitives (AbstractWidget, ColorSwatch)
├── module/
│ ├── ControlMe.mod # Maya module descriptor
│ └── scripts/
│ └── userSetup.py # Auto-loads shelf on Maya startup
├── scripts/
│ ├── run_maya_tests.sh # Run Maya integration tests via mayapy (Linux/macOS/Git Bash)
│ └── run_maya_tests.bat # Run Maya integration tests via mayapy (Windows cmd)
└── tests/
├── test_search.py
├── test_database.py
├── test_control.py
├── test_operations.py
├── test_control_creation.py
├── test_shape_service.py
├── test_main_view.py
├── test_views_ui.py # Qt UI tests (headless via compat stub)
└── maya/ # Real Maya integration tests (require mayapy)
%%{init: {'flowchart': {'curve': 'linear'}}}%%
flowchart TD
A["main_view.py (UI)<br/>PySide2/6 via app/compat.py"] --> B["ShapeService"]
B --> C["core/ (control, operations, shapes)"]
C --> D["maya.cmds / OpenMaya"]
C --> E["database/manager.py (SQLite + schema versioning)"]
- Compat layer —
app/compat.pyauto-selects PySide2, PySide6, or a headless test stub; all views import Qt bindings from here, never directly from PySide - Central config —
config.toml→app/config.py; version, title, logging, and cache settings in one place, no Python editing needed - Strategy pattern — search is pluggable (
ContainsStrategy,StartsWithStrategy,EndsWithStrategy) - Service layer —
shape_service.pysits between the UI andcore/; views never call Maya directly - AbstractWidget — enforces
create_widgets / create_layout / create_connectionsacross all panels - QPainter previews — shape thumbnails rendered procedurally from CV data, no external images needed
- SQLite library — built-in shapes seeded on first run; custom shapes persist between sessions
- Rotating log —
app/logger.pywrites to a capped log file next to the repo; configurable viaconfig.toml
This project is a portfolio piece for pipeline TD work. It showcases:
- Cross-version Maya compatibility via a PySide2/PySide6 abstraction layer (
app/compat.py) — one codebase runs on Maya 2022–2026 - Testable architecture — MVP + service layer, so UI and Maya runtime can be mocked independently (86 unit/UI tests run without Maya)
- Production hygiene — rotating logs, SQLite schema migrations with a version counter, TOML-driven config, no hard-coded values
- Deployment discipline — proper Maya
.modpackaging viainstall_module.py, drag-and-drop installer for artists, zero external dependencies - CI pipeline — unit, UI (headless Qt), and real Maya runtime tests run in separate GitHub Actions jobs across Maya 2022–2026 Docker images
cd maya-control-tools
pip install pytest
pytest tests/ --ignore=tests/mayaCurrent test layers:
tests/test_search.py,tests/test_database.py,tests/test_control.py,tests/test_operations.py,tests/test_control_creation.py,tests/test_shape_service.py: unit tests with Maya mockedtests/test_views_ui.py: Qt UI tests forOutlinerWidgetandImageViewtests/maya/: real Maya integration tests run withmayapy
Run unit tests only:
pytest tests --ignore=tests/maya --ignore=tests/test_views_ui.py -qRun UI tests headless:
QT_QPA_PLATFORM=offscreen pytest tests/test_views_ui.py -qRun real Maya integration tests locally:
# Linux / macOS / Git Bash
bash scripts/run_maya_tests.sh
# Windows cmd
scripts\run_maya_tests.batGitHub Actions runs the suite in three groups:
- unit tests on standard Python
- UI tests on standard Python with PySide6
- Maya integration tests in Docker against Maya 2022, 2023, 2024, 2025, and 2026 images when available
This keeps failures isolated instead of mixing pure Python, Qt, and real Maya runtime checks in one job.

