English | 中文
A batch attribute discovery & editing system for Maya: recursively find nodes (including shapes) inside the selected hierarchy, search attributes by name, resolve the real Maya types, generate a matching editor for each type, safely skip Locked / Connected / Missing / type-incompatible attributes, complete the batch edit with a single Apply, and make the whole batch take only one Undo.
Target environment: Maya 2024 / 2024.2 (Python 3.10, Maya API 2.0, PySide2 5.15.2 or PySide6).
Every API behaviour described in this document was measured on this machine with mayapy; for the
evidence see docs/ARCHITECTURE.md.
Batch Attribute Editor: scope / search / results (left), attribute details, value editor and preview (middle), technical inspector and operation audit log (right).
Maya's native Attribute Editor only ever targets the current single node, the Channel Box can only display a limited set of attributes, and neither can "search attributes by name across a whole hierarchy and edit them in bulk". This tool fills that gap:
Select node → recursive traversal (incl. Shape / Intermediate Shape) → search by attribute name
→ resolve the real type → generate the editor for that type → preview what will change / what will be skipped → batch write → one Undo
It is not for node in nodes: cmds.setAttr(...); the core capabilities are:
Hierarchy Traversal + Attribute Discovery + Type Resolution
+ Compatibility Validation + Type-aware Editing + Batch Apply + Undo/Redo
Double-click copy_launch.bat in the project root: it copies a launch command with this
folder's absolute path already filled in. Paste it into Maya's Script Editor (Python tab)
and press Enter:
import sys; sys.path.insert(0, r"C:\opencode\BatchAttributeEditor"); import main; main.reload_and_launch()The script does not start Maya — it only fills the clipboard, so you keep using the session you
already have open. It calls reload_and_launch() rather than launch(): Python caches imported
modules, so after you edit a file a plain import main would keep handing back the old code until
Maya is restarted. The path is resolved when the script runs, so the project can live on a USB
stick, a network share, any drive letter or a renamed folder. (The console window also prints the
self-check and test commands for copying by hand.)
Then:
- Select one (or several) nodes in the viewport — for example a
Character_GRP; - Type
visibilityinto Attribute Search; - Click the
visibility Boolean 182row in Results; - Untick the checkbox in Value (set it to
False); - Click Preview — you will see "175 nodes will be modified, 7 skipped, of which 5 Locked and 2 Connected";
- Click Apply;
- Press
Ctrl+Zonce — all 175 nodes are restored.
For installation (shelf button, copying into the scripts directory, loading automatically with Maya)
see docs/INSTALL.md; for the full feature reference see
docs/USAGE.md.
| Type | Editor | Notes |
|---|---|---|
| Float / Double | Numeric input | Honours the hard range declared by Maya |
| Integer | Integer input | Strict integer validation, never silently rounds |
| Boolean | Checkbox | |
| String | Text input | Never treated as a number |
| Enum | Drop-down | Shows the enum names, writes Maya's enum index internally |
| Angle / Distance / Time | Numeric input + unit hint | Uses Maya's working units (degrees / centimetres / frames) |
| Float3 / Double3 | Three X / Y / Z channels | Each channel can be ticked independently |
| Color3 | R / G / B + Color Picker… | Does not clamp to 0-1 on its own |
| Compound | Per-child editing | Addressed through the plug.child(i) index, never by concatenating names |
| Multi (array) | One row per element that exists | The first version only edits elements that already exist |
| Matrix / Message | Recognition and inspection only | Does not pretend to support editing |
Non-destructive by default. The following actions never happen automatically:
- unlocking (
unlock) - disconnecting (
disconnect) - creating / deleting attributes or array elements
- implicit type conversion (for example writing a float attribute as an int, or treating a String as a number)
- deleting / renaming nodes
Targets that are locked, connected, missing the attribute, or type-mismatched are skipped with a reason, listed one by one in the Preview and the Report, while the technical details (exception type, plug name) are kept in the log.
The interface ships in English (the default) and Simplified Chinese. The selector in the
top-right corner of the window switches languages instantly — no Maya restart — and the choice is
remembered for the next launch. All tool text is translated (titles, buttons, tooltips, filters,
statuses, reports, log messages); Maya data is never translated (node / attribute / plug names,
enum field values, type labels such as Float / Double3, exception text). See
docs/USAGE.md for the details.
BatchAttributeEditor/ ← add this directory to sys.path
__init__.py optional facade: injects sys.path and forwards to main
bootstrap.py sys.path injection and same-name module conflict handling
main.py entry point: launch() / close() / __version__
core/ Qt-independent, fully testable under mayapy
types.py type data model (AttributeKind / ChannelSpec / AttributeDefinition)
type_resolver.py real type resolution (MObject + MFn* metadata)
selection.py selection resolution and de-duplication
traversal.py DAG traversal (incl. Shape / Intermediate)
attributes.py attribute enumeration and type identification
compatibility.py Locked / Connected / Missing / type compatibility validation
search.py search, matching, aggregation by (name, type)
batch_setter.py preview and batch writing
undo.py Undo Chunk management
cache.py scan cache
results.py preview / apply result dataclasses
session.py Core facade (the UI talks only to this)
ui/ reaches Maya only through Core
qt.py PySide6 / PySide2 compatibility layer
panels.py Scope / Attribute Search / Results / Attribute Details / Value / Preview / Report sections
attribute_model.py results table model
editors/ editors generated on the fly from the attribute type (ValueEditorFactory)
settings.py language persistence (QSettings wrapper)
main_window.py window orchestration
i18n/ pure-Python localization used by Core and UI
manager.py translation manager: lookup, English fallback, {param} formatting, plural()
en.py English reference catalog
zh_cn.py Simplified Chinese catalog
utils/
maya_utils.py node/plug name derivation, UUID re-checks
logging_utils.py two-channel logging (user-readable / technical detail)
tests/ 174 tests (run under mayapy)
tools/selfcheck.py self-check script that runs inside a real Maya
docs/ documentation (install / usage / architecture / limitations)
The price of the flat layout is that top-level names such as core / ui / utils / i18n /
tests are very common and may collide with other plug-ins using the same layout (for example the
sibling materialConvert tool). At launch the tool therefore:
- puts its own root first on
sys.pathso its packages win the lookup; - evicts foreign modules with the same top-level names — including cached submodules such as a
stale
core.resultsleft by the other tool, which would otherwise shadow this tool's imports; - prints a notice whenever such a take-over happens.
The result is a "last launched tool wins" contract that works in both directions: the other tool's
open window keeps running from its already-imported modules, and both tools can be used in the same
Maya session. Use main.launch(release_conflicts=False) to skip the take-over.
The Core layer and part of the UI layer can be fully automated under Maya's bundled mayapy (run
from the project root):
& "C:\Program Files\Autodesk\Maya2024\bin\mayapy.exe" tests\run_tests.pyTo run a single module:
& "C:\Program Files\Autodesk\Maya2024\bin\mayapy.exe" tests\run_tests.py -k undoCurrent result: all 174 tests pass (of which the 16 widget tests that need a real GUI are skipped in batch mode).
| Test file | Coverage |
|---|---|
test_traversal.py |
Single transform, transform + shape, intermediate shape, multiple roots, de-duplication, a 5-level deep hierarchy, DG nodes |
test_type_resolution.py |
Float / Int / Bool / String / Enum / Angle / Distance / Time / Float3 / Double3 / Color3 / Compound / Multi / User Defined / Matrix / missing attributes / hard range |
test_search.py |
Full / partial / long and short names / case sensitivity / fuzzy matching, aggregation, splitting same-name-different-type entries |
test_compatibility.py |
Locked, Connected (incl. a writable connection source and connected compound children), Missing, type mismatch, deleted node, renamed node |
test_batch_setter.py |
Batch writing per type, colours not clamped, multi does not create new elements, missing/locked/connected skips, one failing node does not abort the batch, same name with different types only edits compatible nodes |
test_undo.py |
One Apply = one Undo, Redo, still one Undo after a partial failure, still one Undo with 150 nodes, a control group proving the chunk is necessary |
test_session.py |
End-to-end workflow, self-consistent preview statistics, cache and refresh, 1500+ node performance |
test_i18n.py |
Language manager switching / fallback / formatting / plural, key + placeholder parity, literal tr("...") key scan, QSettings persistence (fake + real backends), Core report texts, GUI language-switch test, audit-log language freeze tests |
test_ui_smoke.py |
UI module imports, factory registry completeness, results table model; the widget tests run in a GUI session |
To run one end-to-end self-check inside a real Maya GUI (it creates temporary nodes and deletes them again when done):
import sys
sys.path.insert(0, r"C:\opencode\BatchAttributeEditor")
import tools.selfcheck
tools.selfcheck.run(create_test_nodes=True)| Area | Status |
|---|---|
| Core (traversal / type resolution / validation / search / writing / Undo) | ✅ 174 tests pass under Maya 2024.2 mayapy (16 GUI tests skipped in batch mode) |
| Undo granularity (one Apply = one Undo) | ✅ verified by measurement (including a 150-node batch and partial-failure scenarios) |
| UI module import and factory dispatch | ✅ verified automatically |
| Localization (language switch / fallback / persistence / Core texts) | ✅ automated under mayapy; the widget-level retranslation was additionally smoke-verified with PySide6 6.11 (offscreen, fake maya) |
| UI window construction and display | ✅ confirmed in a real Maya 2024.2 GUI (tools/selfcheck.py, PySide2 5.15.2) |
| UI interaction details (button clicks, colour picker, dock dragging) |
The process and results of confirming window construction and display are documented in section 1 of
docs/LIMITATIONS.md.
The self-check builds the window, runs the complete workflow on temporary nodes (including "the whole
batch write takes only one Undo"), and finally deletes the temporary nodes.
- The first version does not create / delete array elements; it only edits elements that already exist;
- No destructive options such as "force unlock" or "force disconnect" are provided;
- Types such as Matrix / Message are only recognised, not edited;
- By default only DAG descendants are traversed, not the whole Dependency Graph (the extension point exists but is not enabled);
- Colour management fails to initialise on this machine, so
cmds.addAttr(attributeType="float3")fails silently — this is an environment problem and does not affect the tool (Color3 is tested with real colour attributes such asoverrideColorRGBandlambert.color).
For the complete list see docs/LIMITATIONS.md.
| File | Contents |
|---|---|
docs/ARCHITECTURE.md |
Module responsibilities, data flow, API strategy, Undo strategy, measured evidence |
docs/INSTALL.md |
Installation, verification, uninstallation |
docs/USAGE.md |
Interface and workflow reference |
docs/LIMITATIONS.md |
Known limitations and caveats |
