Bundles

Shortcuts and keytips

Keyboard chords that run a tool without touching the ribbon, the Alt letters that walk to it, and how users rebind both.

A tool can be reached from the keyboard in three ways. Its AutoCAD command (PYBATCHPLOT) can always be typed at the command line; that is covered in Commands, macros and AutoLISP. This page covers the other two: a chord such as Ctrl+Shift+K that pyACAD watches for itself, and a keytip, the letters AutoCAD shows over the ribbon when you press Alt.

Two places a binding comes from#

An author ships a default in the bundle's bundle.yaml:

bundle.yaml
title: Chord Only
tooltip: Invisible on the ribbon; press Ctrl+Shift+K to run it.
shortcut: Ctrl+Shift+K

A user overrides it in %APPDATA%\pyACAD\config.json, normally through the Shortcuts editor on the pyACAD panel, which writes this section for them:

config.json
{
  "shortcuts": {
    "bindings": {
      "pyACAD.tab/Output.panel/Batch_Plot.pushbutton": "Ctrl+Shift+P",
      "Smoke.tab/Buttons.panel/Chord_Only.nobutton": null
    }
  }
}

The rule is simple: a user entry replaces the author default entirely. A chord string rebinds the tool; null disables the author's default; no entry leaves the default in force. The shipped pyACAD tools declare no shortcut: at all, so every chord on a fresh install is one you chose.

Which bundles can have a chord#

Every pushbutton-shaped bundle, wherever it sits: *.pushbutton, *.toggle, *.smartbutton and *.nobutton, at panel level, inside a stack, inside any of the three pulldown kinds, or inside a slideout. A *.nobutton exists for exactly this: it has no ribbon presence, so a chord (or its typed command) is the only way in.

Not wired: shortcut on a dock pane

The parser reads shortcut: from a *.dockpane's bundle.yaml, but nothing binds it: the chord table is built from pushbutton bundles only. A dock pane cannot be toggled by a chord yet. *.urlbutton and *.linkbutton do not read shortcut: either.

The chord grammar#

A chord is a list of tokens joined by +. Spaces around tokens are ignored and case does not matter. Modifiers may come in any order; exactly one key is required.

TokenAccepted spellings
ModifiersCtrl, Alt, Shift
LettersA to Z
Digits0 to 9 (the top row, not the number pad)
Function keysF1 to F24
NavigationLeft, Up, Right, Down, Home, End, PgUp, PgDn

Nothing else parses: not Control, Win, Space, Esc, Del, Enter, punctuation, or the number pad. A string that does not parse is reported in the log and the tool gets no chord:

text
Shortcut: 'My.tab/Tools.panel/Count.pushbutton': 'Ctrl+Shift+Num1': unknown token 'Num1'
Shortcut: 'My.tab/Tools.panel/Count.pushbutton': 'Ctrl+K+L': more than one key
Shortcut: 'My.tab/Tools.panel/Count.pushbutton': 'Ctrl+Shift': no key

A chord is shown in one canonical form, Ctrl+Alt+Shift+Key in that order, so shift+ctrl+m and Ctrl+Shift+M are the same chord and both display as Ctrl+Shift+M.

The Ctrl-or-Alt policy#

By default a chord must contain Ctrl or Alt. Shift alone does not count, so Shift+M and a bare F5 are both refused:

text
'F5': bindings need Ctrl or Alt (set shortcuts.allowBareKeys to permit bare keys)

Setting "shortcuts": { "allowBareKeys": true } in config.json (the Settings window's Allow shortcuts without Ctrl or Alt box) lifts the rule. Think hard before doing it. In AutoCAD you type commands while the cursor is over the drawing, and the drawing area is not a text box, so a bare L binding would swallow the L of LINE, LAYER and every other command you type there.

How the table is built#

At boot and on every Reload, before the ribbon is drawn, the runtime walks every pushbutton bundle in ribbon order (extension by extension, tab by tab, panel by panel, slideout contents after the panel's own items) and resolves each one:

  1. If shortcuts.bindings has an entry for the bundle key, that entry is the chord text, even when it is null. Otherwise the author's shortcut: is.
  2. No text: the tool has no chord. Next tool.
  3. The text is parsed. A parse failure is logged and the tool has no chord. The author default is not restored behind a bad override.
  4. If an earlier tool already holds the same chord, this tool loses: first wins, and the log says 'X' wants Ctrl+Shift+K but 'Y' already has it - first wins.

An override whose key matches no loaded bundle is logged as shortcut override for unknown tool '...' ignored. Every problem goes to %APPDATA%\pyACAD\logs\pyACAD.log with a Shortcut: prefix, followed by Shortcuts: N binding(s) active. None of them raises a toast, so a chord that silently does nothing is a reason to read the log.

The bundle key#

Overrides are keyed by the bundle key: the bundle folder's path relative to its *.extension folder, with forward slashes and the folder suffixes kept.

BundleKey
A panel button pyACAD.tab/Output.panel/Batch_Plot.pushbutton
A button in a stack pyACAD.tab/AI_(beta).panel/Setup.stack/AI_Settings.pushbutton
A button in a slideout My.tab/Tools.panel/More.slideout/Purge_All.pushbutton
A legacy numbered folder 01_Toggle_Demo.toggle Smoke.tab/Buttons.panel/Toggle_Demo.toggle

A leading NN_ (two or more digits and an underscore) is dropped from every segment, both when the key is made and when config.json is read, so renumbering folders never orphans an override. Keys compare case-insensitively. The extension's own folder name is not part of the key.

Renaming a folder orphans its override

Rename Batch_Plot.pushbutton to Plot_PDFs.pushbutton and the override keyed by the old path matches nothing. The only symptom is the unknown tool line in the log. Set bindings through the Shortcuts editor rather than by hand, and expect to re-bind after a rename.

When a chord fires#

Bindings are watched by a keyboard hook installed on AutoCAD's UI thread. It is thread-scoped (WH_KEYBOARD), not global: it sees keys delivered to AutoCAD and nothing typed in any other application. A matching chord still has to clear three guards.

A key goes down in AutoCAD Exact modifiers + key bound to a tool? Fresh key down, not auto-repeat? AutoCAD's main window in front? Focus outside any text input? Swallow the key, send the tool's command no The key is left alone AutoCAD gets it exactly as if pyACAD were not listening.
Only a dispatched chord is swallowed. Every other path leaves the key press untouched.
  • Exact modifiers. The state of Ctrl, Alt and Shift (either side of the keyboard) must match the binding exactly: Ctrl+K does not fire while Shift is also held. On keyboards with an AltGr key, Windows reports AltGr as Ctrl+Alt, so a Ctrl+Alt+E binding also fires on AltGr+E.
  • Fresh key down only. Holding the chord does not repeat the tool.
  • AutoCAD's main window must be the foreground window. Docked palettes are part of it; a floating palette, the output window, a pyACAD dialog or another application in front means the chord does nothing.
  • Focus must not be a text input. A WPF text box or password box blocks the chord, as does a native control whose window class contains "edit". If focus cannot be read at all, the runtime assumes you are typing and does nothing.

A chord that passes is swallowed (AutoCAD never sees the key) and the tool starts on the next turn of the message loop, exactly as a ribbon click would: its registered command is sent to the active drawing. So a chord runs in document context with the drawing locked and one undo group, and with no drawing open it shows the toast Open a drawing first. The details are in Click actions.

A chord always runs the primary script

Ctrl+Shift+M runs script.py, never config.py, even though the chord contains Shift. Chords bypass the modifier click rules entirely; Shift+Click (or typing the tool's CFG command) is the only way to reach the secondary action.

Conflicts with AutoCAD's own keys#

The hook sees a key before AutoCAD's own accelerators do, and a dispatched chord is swallowed. A pyACAD binding on a key AutoCAD already uses therefore wins silently whenever the guards pass: bind Ctrl+S and AutoCAD stops saving from the keyboard. Nothing warns about this; the Shortcuts editor only checks for conflicts between pyACAD tools.

AutoCAD's default keyboard shortcuts live in the CUI editor (CUI, Keyboard Shortcuts), and that is the list to check. The ones people miss most when shadowed:

  • The clipboard and file keys: Ctrl+C, Ctrl+V, Ctrl+X, Ctrl+Z, Ctrl+Y, Ctrl+S, Ctrl+N, Ctrl+O, Ctrl+P, and Ctrl+Shift+S, Ctrl+Shift+C, Ctrl+Shift+V.
  • Most of the palette toggles on the digit row, Ctrl+0 to Ctrl+9 (Properties is Ctrl+1, the command line Ctrl+9).
  • The drafting toggles on the function keys, F1 to F12. These are bare keys, so only a binding made with allowBareKeys on can take them.

Prefer Ctrl+Shift+letter or Ctrl+Alt+letter, check the CUI list first, and avoid Alt+letter on its own: the Alt key is how AutoCAD walks the ribbon (next section).

Keytips#

Keytips are the letters AutoCAD shows over the ribbon when you press and release Alt. Every tab pyACAD builds carries the tab keytip PY (an extension that adds a second tab gets PY on that one too); inside a tab, every item gets one or two letters, assigned per tab when the ribbon is built.

Assignment covers everything that renders on the tab: panel-level buttons (including toggles and smartbuttons), each child of a stack, the header of each pulldown, *.splitbutton and *.splitpushbutton, urlbuttons, linkbuttons and dock pane toggles, slideout contents included. Pulldown children get none, because a menu is walked with the arrow keys once open, and nobuttons get none because they are not on the ribbon.

The rules, from KeyTips.Assign:

  1. Explicit keytips claim first. A keytip: in bundle.yaml is trimmed and uppercased. If two items on the same tab ask for the same keytip, the first in ribbon order gets it and the second gets no keytip at all; it does not fall back to an automatic one.
  2. Then every other item, in ribbon order, takes the first free candidate built from the letters of its title (digits and spaces are skipped): the first letter, then the first two letters, then the first letter followed by 2, 3 and so on up to 9.
  3. An item whose candidates are all taken, or whose title has no letters, gets none.
bundle.yaml
title: Batch Plot
tooltip: Plots the layouts you pick to PDF, one file per layout.
keytip: OP

The shipped tools set two-letter keytips by panel (OP for Output, Batch Plot; LM for Layers, Layer Manager) so they stay stable as tools are added. Do the same in your own extension: automatic keytips shift whenever a tool is added before another with the same initial.

Only whole keytips are checked for duplicates

The assignment avoids duplicates but not prefixes: an automatic C can sit on the same tab as an explicit CC. How AutoCAD's ribbon resolves a keytip that is the start of another is its own behaviour, so if a two-letter keytip is hard to reach, give the one-letter item an explicit two-letter keytip too.

What the user sees on the button#

A resolved chord shows in two places, both updated on every ribbon build:

  • The tooltip gets the chord appended in brackets, for example Plots the layouts you pick to PDF. (Ctrl+Shift+P). It names the resolved chord, so a rebound tool shows the user's key, not the author's.
  • The caption gets a marker after the name, a dot (●) by default. ribbon.showShortcutMarker turns it off and ribbon.shortcutMarker changes the glyph; a glyph starting with a newline puts the marker on its own line. See Settings.

The Shortcuts editor#

The Shortcuts button on the pyACAD panel (command PYSHORTCUTS) opens the Shortcuts and Aliases window. It lists every pushbutton bundle currently loaded, grouped under its panel's title, with a search box that filters by tool name, panel, command, chord and alias.

  • Recording. Click a tool's field and press the chord. Backspace or Delete clears it (which disables an author default), Esc leaves the field as it was, and Tab moves on rather than being recorded. A chord the runtime would refuse shows "needs Ctrl or Alt" or "unsupported key" and is not taken.
  • Badges. A row away from its author default says custom or disabled, and shows a reset arrow when the tool has a default to go back to.
  • Conflicts. When two tools hold the same chord, the later one in ribbon order is marked conflicts with the earlier one, the same first-wins order the runtime uses, and Save stays disabled until the conflict is resolved.
  • Aliases. Each row has an Alias field (BX, or BX, B2X). It follows the registration rules on Commands: an alias another tool holds, or one of the tools' full command names, is flagged is taken by; one in acad.pgp or already an AutoCAD command is flagged too. On a row you changed, the flag is red and Save stays disabled. On an author's alias it is a muted Alias not active note, since registration skips it anyway.
  • Restore All Defaults puts every row's chord and aliases back to the author's.

Save writes only the rows that differ from their defaults into shortcuts.bindings (a chord string, or null to disable a default) and aliases (the aliases, or null for none), leaves every other key in config.json alone, then reloads pyACAD and toasts Shortcuts and aliases saved and applied. If the file cannot be written, or holds invalid JSON that it refuses to overwrite, it toasts Shortcuts were not saved with the reason.

Saving drops overrides for tools that are not loaded

The editor writes the whole bindings map from the tools it can see. An override for a tool whose extension is not loaded right now (a removed extension root, an offline network folder) is not carried over, so saving from the editor deletes it. Aliases do not have this problem: entries for tools the editor did not show are kept.

Hand edits to config.json take effect on the next Reload.

Checklist#

  • The chord has Ctrl or Alt, one key, and only the tokens in the grammar table.
  • It is not one of AutoCAD's own keys, unless replacing that key is the point.
  • The log shows no Shortcut: line for your tool after a Reload.
  • The tooltip shows the chord in brackets: that is proof it resolved.
  • Your extension's tools carry explicit keytip: values, unique on the tab.