Skip to content

AI design with OpenSCAD (/print-design, alias /design, #388) ​

Describe a part in chat — Discord, Telegram or Matrix — or start from a model you already have — and Print Handy designs it for you: a model writes OpenSCAD, Print Handy renders it with the OpenSCAD binary already in the install, measures the result, and shows you a preview with the real dimensions. You then choose:

  • Slice it — the STL goes through the normal prepared intake (filament, fit, slicing and the usual print confirmation). Design never slices or prints anything itself, and nothing reaches the printer without the same approvals as a model link.
  • Change it — your next message in the chat is the change ("make the holes 5 mm", "add a lip around the edge"); the design is edited and re-rendered. Up to 10 turns per design.
  • Cancel — the design session ends.

Turning it on ​

Design is on automatically whenever a model endpoint is configured — the same posture as roasts. The [design] enabled switch is tri-state:

  • Unset (the default): auto. Design works when an endpoint resolves (below); with no endpoint configured anywhere, /print-design, /design and the design phrases answer "design is not set up".
  • enabled = false: off in all cases, whatever is configured.
  • enabled = true: forced on; an unconfigured endpoint is then a visible "no design model is configured" failure, not a silent off.

Where the model comes from:

  • Every mode, Hermes included: the [design] settings pick the model. Leave chat_url / model / api_key_env empty to reuse the [llm] endpoint, or point them at a stronger model — design turns are the ones worth spending a bigger model on, while everyday chat stays on the small one. This is also how Telegram and Matrix get design: an explicitly configured [design] endpoint (or the [llm] values it inherits) serves design there, and nothing else changes — ordinary chat on those platforms still has no LLM fallback and unmatched text still gets the short “didn't understand” reply.
  • Hermes with no endpoint configured: the design model is the profile's auxiliary task instead. Uncomment the auxiliary.print_design block in the profile config (the shipped template carries a commented example) to pin a provider/model for design; chat stays on the profile model. As soon as a [design] (or [llm]) endpoint is configured it takes over, and the profile pin becomes the fallback.

bot check prints a design row naming the mode (off, auto-off, auto-on or on), the model in effect, and whether OpenSCAD is present.

Standalone installs outside the container need the openscad binary on the machine (the container image includes it); with design on and no openscad on PATH, bot check fails that row and design turns report it.

The sandbox, in plain language ​

The model writes the part, but it never gets to run anything general:

  • The generated file may not use import, include, use or surface — the statements that could read files off the machine. A file containing one is refused before OpenSCAD ever runs. (Mesh edits are the one place an import appears in a render: the engine's own fixed header imports the session-local copy of the model being edited — the model's text is still scanned and still may not contain one.)
  • Rendering runs with --hardwarnings (any warning is a failure, so a design that silently lost a piece cannot slip through), in the design's own folder, with a near-empty environment, a CPU/memory limit and a wall-clock timeout ([design] timeout_s, 180 s by default).
  • If OpenSCAD rejects the file, the exact error goes back to the model for a repair, up to [design] repair_attempts times (3 by default). If it still fails, the turn says so and shows the last error — nothing is handed to intake.

Honest limits ​

  • Each turn is a real model call plus a real render: expect seconds to a couple of minutes per turn, and model cost per turn. Sessions are kept under the cache directory (design-sessions/) for 14 days.
  • The preview and measurements are of the rendered STL: dimensions, volume, shell count and whether the mesh is watertight and manifold. A design that renders to nothing is rejected, not posted.
  • Mesh edits (below) can only do what a mesh allows; a change that needs the original design's parameters is refused in plain words, not faked.

Editing an existing model ​

Attach the model to the design message — drop the file in the same message as /print-design <the change>, or paste a MakerWorld/Printables link in it. The original is never modified: design works on a copy inside its session folder.

  • .scad — edited in source form. The model returns the complete updated file, so parameters change directly (there is no separate parameter-override mechanism; the session file always shows the real design). If the file references other files (import/include/use/surface), the first reply says so: a design session holds one self-contained file, so referenced parts cannot render here.
  • .stl, and .3mf after conversion to STL — edited as a mesh. The engine composes the render: its own header defines base() (the model being edited) and the model's edit body calls it. Scaling, mirroring, moving, adding shapes and cutting holes work this way. Changes that need the original design's parameters or generated structure (a thread pitch, a pattern) do not — the design says so in plain words instead of guessing. A .3mf is converted through the same geometry loader intake uses.
  • A model link — the file is fetched through the ordinary link machinery first (MakerWorld picks its default profile; Printables must offer one STL/3MF file), then edited like a dropped file.

Saying it instead of the command ​

With design on and a model configured, these phrases start design without the command (case-insensitive):

  • "design me …" / "help me design …" — a new part; the rest of the message is the description.
  • "design this", "design it", "redesign this" — edit the model attached to the message, linked in it, or the channel's recent model; with none of those, design asks for one.
  • "edit this", "modify this", "change this" — the same edits, but only when a model is actually there (attached, linked or recent); a bare "change this" with no model anywhere is left to normal chat.

Anything without a design phrase — a plain link, a plain file drop, "print it", every existing command — behaves exactly as before, and while a design is waiting for a revision, your next plain message is the revision, phrases or not. When design cannot run — switched off, or auto with no endpoint configured — a message with a design phrase gets the same "design is not set up" reply as the command — and with design forced on but no model configured, the same "no design model is configured" reply — while messages without a design phrase are untouched either way. The phrases work on Telegram and Matrix exactly as on Discord; on Matrix the Slice it / Change it / Cancel choices arrive as a numbered list you answer by number or by typing the choice, like every other Matrix prompt.

Browsing designs in the Web UI ​

The Web UI has a read-only Designs page (/designs, behind the same access token as every other page) listing the design sessions on the machine, newest first: what each one is (created from a description, or an edit with its source named), its state, how many turns it took, when it was made and last changed, and its preview. Opening a design shows its final .scad source and offers downloads of the .scad, the final .stl and the preview PNG.

The page is a catalog of what exists on disk — the same sessions chat works with, kept for the same 14 days — so it lists sessions even while [design] is switched off. It has no actions: nothing there deletes, slices or prints. Slicing a design still happens from chat, through the normal confirmation flow.

Documentation content is copied from catduckgnaf/print-handy — that repo's docs/ directory is the source of truth.