Parametric 3D-printable sign generator (build123d + OpenSCAD)
  • Python 55.3%
  • OpenSCAD 44.7%
Find a file
2026-07-10 17:24:24 -05:00
docs Add README with sign preview image 2026-07-10 17:24:24 -05:00
.gitignore Clean up tree: drop Customizer preset, ignore build artifacts 2026-06-01 12:19:38 -05:00
CLAUDE.md Add optional through-hole hanging holes to both implementations 2026-06-01 12:27:28 -05:00
gen_fontlist.py scad: font dropdown auto-generated from installed system fonts 2026-05-30 17:38:28 -05:00
parametric_sign.scad Add optional through-hole hanging holes to both implementations 2026-06-01 12:27:28 -05:00
README.md Add README with sign preview image 2026-07-10 17:24:24 -05:00
sign.py Add optional through-hole hanging holes to both implementations 2026-06-01 12:27:28 -05:00

Parametric Sign

A parametric 3D-printable sign: a rounded-rectangle base plate with an inset raised border ring on top, up to three lines of raised text, and optional holes for hanging it on a wall.

Sign preview

(Default sign: 200 × 60 mm, 25% rounding, "OPEN" / "9 AM - 5 PM" in Martian Mono, no hanging holes.)

Two independent implementations

The same object, built two different ways — pick whichever fits your workflow. There's no shared code between them; geometry changes have to be ported to both by hand.

sign.py parametric_sign.scad
Tool Python + build123d OpenSCAD (Customizer-annotated)
Best for Scripting, CLI-driven generation, batch exports Interactive tweaking in the OpenSCAD GUI
Base plate + border
Up to 3 lines of raised text
Through-hole hanging holes (--holes)
Keyhole hanging slots (--hang) (intentional — see below)
Interactive prompt mode (--interactive) n/a (use the GUI Customizer)

Keyhole slots are Python-only, on purpose. They were never ported to the .scad file — the sign is normally hung with a 3M Command strip, so the recessed keyhole feature is a nice-to-have that only exists on the scriptable side. Don't "fix" this without checking CLAUDE.md first.

Quick start

Python (sign.py)

Self-contained uv script — dependencies are declared inline, no venv setup needed.

uv run sign.py                            # defaults -> sign.stl
uv run sign.py -w 80 -H 50 -r 100 -o pill.stl
uv run sign.py -v                         # print derived dimensions, then build
uv run sign.py --hang single -v           # one centered keyhole slot on the back
uv run sign.py --holes double             # two mirrored through-holes near the top
uv run sign.py --holes single --hole-dia 4
uv run sign.py --line1 OPEN --line2 "9 AM - 5 PM"   # raised text (up to 3 lines)
uv run sign.py --interactive              # prompt for any args not given

OpenSCAD (parametric_sign.scad)

openscad -o sign.stl parametric_sign.scad                          # headless STL export
openscad -D 'hole_mode="double"' -o sign.stl parametric_sign.scad  # with through-holes
openscad parametric_sign.scad                                       # open GUI / Customizer

Font choices in the Customizer's font_family dropdown are auto-generated from your installed system fonts:

uv run gen_fontlist.py   # rewrites the font_family // [...] option list

Geometry model

All units are millimeters. Rounding is a percentage of the maximum possible corner radius, not an absolute value:

corner_r       = (min(width, height) / 2) * rounding_pct / 100
border_outer_r = corner_r - border_inset
border_inner_r = corner_r - border_inset - border_width

So 0% gives sharp corners and 100% gives fully rounded, semicircular short ends. The raised border is concentric with the base plate — same center, radii stepped inward by border_inset and border_width.

Both implementations validate the spec up front and fail loudly on invalid combinations (border doesn't fit inside the corner radius, hanging holes run off an edge, etc.) rather than emitting broken geometry.

Text

Raised text on the face, up to three lines, each extruded flush with the top of the border. Only visible (enabled + non-blank) lines are drawn, stacked top-to-bottom and centered as a block — hide a line and the rest re-center.

Fonts are resolved differently by each engine: the .scad looks up fontconfig family names (falls back silently on an unknown one), while sign.py resolves an actual font file path and fails loudly if it can't find one. See CLAUDE.md for the details if you're adding a new font.

Hanging

Two independent, combinable ways to hang a sign:

  • Through-holes (--holes / hole_mode, both implementations) — plain cylindrical holes punched straight through near the top, for a nail or screw. single centers one hole; double mirrors two about the vertical centerline.
  • Keyhole slots (--hang, Python only) — recessed slots on the back that let a screw head slide up and lock behind a narrower lip, so nothing shows from the front.

Development

No tests, linters, or build config — uv run sign.py is the full dev loop. See CLAUDE.md for the full geometry/validation contract shared across both implementations.