Bundles
Icons
Four PNGs per bundle, how the runtime picks between them for AutoCAD's light and dark themes, and how to draw them so they sit well on the ribbon.
A bundle with no icon renders as a text-only button. It works, but next to AutoCAD's own ribbon glyphs it looks like a placeholder, and on a stacked row or in a pulldown menu it is hard to find. Icons are the cheapest thing you can do to make a tool look like it belongs.
Icons are plain PNG files sitting beside script.py. There is no manifest entry
and no key in bundle.yaml: the runtime looks for fixed file names in the bundle
folder and uses what it finds.
The four files#
| File | Slot | Recommended size |
|---|---|---|
icon.png |
Large button, light theme | 96×96 |
icon.dark.png |
Large button, dark theme | 96×96 |
icon.small.png |
Stacked rows and pulldown menu rows, light theme | 32×32 |
icon.small.dark.png |
Stacked rows and pulldown menu rows, dark theme | 32×32 |
These folder kinds read the four files: *.pushbutton, *.toggle
(with extra on/off art, below), *.smartbutton, the three pulldown kinds
(*.pulldown, *.splitbutton, *.splitpushbutton, for
their own header), *.urlbutton, *.linkbutton and
*.dockpane. A *.stack, a *.slideout, a panel and a tab
never carry icons: a stack has no button of its own, so each child brings its own art. A
*.nobutton may hold icon files, but it never appears on the ribbon, so nothing
shows them.
The theme is decided once per ribbon build, so in a given session a bundle only ever shows two of its four files: the light pair or the dark pair. See Light or dark for how the runtime decides.
The fallback rules#
Each variant is resolved on its own, against icon.png and nothing else. This
is the whole rule, from BundleParser.IconSet:
large = icon.png
dark = icon.dark.png or else icon.png
small = icon.small.png or else icon.png
small dark = icon.small.dark.png or else icon.pngicon.png.Then, when the ribbon item is built, each of its two image slots borrows from the other if it is still empty: the large slot takes the small path when there is no large one, and the small slot takes the large path when there is no small one. Only when both are empty does the button become text-only.
Ship icon.png and icon.dark.png but no small files, and a
stacked or menu row in the dark theme shows icon.png, the light art,
because icon.small.dark.png falls back to icon.png and never to
icon.dark.png. Dark ink on a dark ribbon is close to invisible. Ship all four
files, or ship exactly one.
With no icon.png the base is empty, so the light slots only have what you
ship under their own names. A bundle holding only icon.dark.png shows that file
in the dark theme and no image at all in the light theme. A bundle holding only
icon.small.png shows it (blown up into the large slot) in the light theme and
nothing in the dark theme. icon.png is the file that makes every slot safe.
A file that exists but cannot be decoded logs Could not load icon '<path>'
in %APPDATA%\pyACAD\logs\pyACAD.log and counts as empty for that slot, so a
corrupt PNG behaves like a missing one. Files are read with no lock held, so you can
overwrite an icon while AutoCAD runs and press Reload to see it.
Toggle on and off art#
A *.toggle reads two more pairs for its large slot, and the state decides
which one shows:
| State | Light theme | Dark theme |
|---|---|---|
| Off | icon.off.png, else icon.png |
icon.off.dark.png, else the light off file |
| On | icon.on.png, else icon.png |
icon.on.dark.png, else the light on file |
Three details follow from the code:
- A toggle's dark large slot never reads
icon.dark.png. The dark off file falls back to the light off file, which falls back toicon.png. Shipicon.off.dark.pngandicon.on.dark.pngfor a toggle that looks right in the dark theme. - The first build of an off toggle uses
icon.small(.dark).pngfor its small slot. As soon as the state changes (or the ribbon is rebuilt while the toggle is on), both slots are pointed at the same on or off file, loaded at the small size. A toggle in a stack therefore shows your 96-pixel on/off art shrunk to the row after its first click. - The state lives for the AutoCAD session and survives Reload; the icon
is redrawn the moment the script calls
pyacad.script.set_toggle_state.
Dock pane art#
A *.dockpane renders as a large ribbon toggle that shows or hides its
palette. It reads the ordinary four files, nothing else: the checked look of the button is
AutoCAD's own pressed highlight, not a different image.
The shipped dock panes (Ask AI, Layer Manager) carry icon.on.png and
icon.on.dark.png files, and the icon generator draws them as the "palette open"
state. The runtime does not read them: only *.toggle bundles use on/off art. A
dock pane button shows icon.png or icon.dark.png whether its palette
is open or closed.
Smartbutton icons#
A *.smartbutton gets its four files like a pushbutton, and its build-time run
may replace them through __button__.SetIcon(path). SetIcon points
both slots at the one file and does no theme handling, so pick the file
yourself:
import os
from pyacad import script
if __selfinit__:
# get_bundle_file() reads the last command's folder, which is not this
# bundle during the build-time run, so build the path from __file__.
here = os.path.dirname(__file__)
name = 'busy.dark.png' if script.is_dark_theme() else 'busy.png'
__button__.SetIcon(os.path.join(here, name))Light or dark#
Every pyACAD surface (ribbon icons, dialogs, toasts, the output window) asks one question, "is the host dark?", and the answer is resolved in this order:
"theme"inconfig.json."dark"or"light"(any case) wins outright. The Settings window writes it as Theme: Light or Dark; "Follow AutoCAD" removes the key.- AutoCAD's
COLORTHEMEsystem variable.0is dark,1is light. - The ribbon's background colour, only when
COLORTHEMEcannot be read: a solid background darker than mid-grey (WCAG relative luminance under 0.5) reads as dark. - Otherwise light. Detection never throws; a failure logs and reads light.
The log says which rule decided, for example Theme: COLORTHEME says dark.
The answer is cached until the next Reload, which forgets it and asks again.
That has one practical consequence:
Changing COLORTHEME (Options, Display, Color theme) recolours AutoCAD at once,
but pyACAD's icons and windows keep the theme they were built with until you press
Reload or restart. Scripts can ask the same question with
pyacad.script.is_dark_theme, which returns the cached answer too.
Sizes and scaling#
AdWindows, the ribbon framework AutoCAD uses, draws an image at its natural WPF size rather than fitting it to the button. A 96-pixel PNG stamped at the usual 96 DPI is 96 logical pixels wide, which would overflow a 32-unit button. So the runtime re-stamps every icon's DPI on load, keeping the pixel data and changing only the logical size:
dpi = 96 * pixelWidth / logicalSizeThe large slot loads at a logical size of 32, the small slot at 16. A 96-pixel file in the large slot is stamped at 288 DPI: it lays out 32 units wide but hands the compositor three times the pixels, which is what keeps it sharp at 150% and 200% display scaling. A 32-pixel small file is twice the density of its 16-unit slot. Pulldown menus draw their rows at the 16-unit size too, so the small art matters most inside a pulldown.
Any pixel size loads: the runtime scales by width. That gives three rules of thumb.
- Ship square art. The scale comes from the width alone and the aspect ratio is kept, so a 96×64 file lays out 32 units wide and about 21 tall.
- Do not ship 32-pixel large art. It loads at 1:1 into a 32-unit slot and looks soft on any high-DPI display. 96 is three times the slot; 64 would be the bare minimum for 200% scaling.
- PNG with these exact names. The runtime only looks for the
.pngnames on this page. Windows file names are case-insensitive, soIcon.PNGis found too, but keep them lowercase for the repository's sake.
Drawing them#
The shipped extension's icons share one visual language, and your extension sits on the same ribbon, so it is worth borrowing.
Neutral ink draws the thing. A solid accent mark draws the action. A user scanning a panel reads the grey shape to find what a tool works on (a layer stack, a block, a sheet) and the blue mark to find what it does to it (an arrow for mapping, a tick for an audit). Colour always means the verb.
- Transparent background. Never bake in white or grey: the ribbon background changes with the theme.
- One accent colour, used for the verb. Two accents in one glyph and the eye has nothing to land on.
- Keep red for destruction. The palette has an alert red for a tool that deletes something you cannot get back. Spend it rarely and it keeps meaning that.
- Match the stroke weight. The shipped set draws in a 0 to 96 space with a stroke of 8 for the large files and 14 for the small ones, rounded caps. A hairline icon beside them reads as broken rather than delicate.
- Draw the small size separately. At 16 units on screen a detail either survives at two or three pixels or turns into grey mush. Drop anything under about a fifth of the canvas, thicken every stroke, push the shape towards the edges and keep one accent mark. Export from a simplified artboard, not by shrinking the 96-pixel file.
- Check both themes on a real ribbon. Swapping the ink to a near-white is usually the whole dark variant, but check it against AutoCAD's dark ribbon, not a white page.
How the shipped icons are made#
Every icon in the shipped pyACAD.extension (and in the test fixtures) is
generated by tools/make_icons.py in the pyACAD repository, a development-time
CPython script that needs Pillow. It never ships into AutoCAD and no engine loads it. Each
glyph is a Python function drawing in the 0 to 96 space at four times the size and
downsampled, with a separate branch for the 32-pixel pass, and the per-panel drawings live in
tools/icons/. The rule for the shipped set is that nobody edits a PNG by hand: the
drawing is changed and the generator re-run.
You do not need the generator for your own extension. Any PNGs with the right names work, whatever produced them: Figma, Illustrator, Inkscape or an icon font export.
Checklist#
icon.pngexists and is 96×96. It is the fallback for every other slot.- All four files present, or exactly one. A partial set falls back to light art in the dark theme.
- Square, transparent PNG.
- The small pair is a simplified drawing, not a resize.
- Toggles ship
icon.off(.dark).pngandicon.on(.dark).png;icon.dark.pngalone does not cover a toggle's dark state. - The dark pair is checked against AutoCAD's dark ribbon.
- Reload after adding or changing a file; icons, like the theme, are read when the ribbon is built.