LICENSE
MIT License
Copyright (c) 2026 Thompson Labs LLC
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
references/viewer-features.md
# CAD Viewer Features
Load this only when a task needs Viewer file-support details or UI control guidance.
## Supported Files
- `.step`, `.stp`: STEP/STP review through the document's tree in the store (compiled from the file's bytes on open when missing); supports assembly trees, part hide/show, inspect/focus, face/edge/vertex/part selection, copied `#...` CAD references, display modes, clip planes, and live pose sliders and animation clips when the model's sidecar declares kinematics or animation.
- `.stl`, `.3mf`, `.glb`: mesh viewing with orbit/pan/zoom, screenshots, theme controls, and solid/wireframe display where available. Measure snaps to triangle vertices only (two clicks, distance in mm) — not STEP faces/edges. A plain GLB's `COLOR_0` vertex colors render as source colors, exactly like authored material colors.
- `.dxf`: read-only 3D flat-pattern viewing. The drawing file is parsed directly and rendered client-side — no render artifact exists for a `.dxf`, so generated and imported drawings alike render straight from their own bytes.
- `.urdf`: robot link/mesh viewing with movable joint sliders, reset pose, and copied joint values.
- `.srdf`: paired-URDF viewing with planning groups, group-state presets, and joint controls.
- `.sdf`: SDF model/world viewing with metadata, counts, warnings, and joint controls when available.
## Controls
- Navigation: left-drag to orbit, right/middle-drag to pan, wheel or pinch to zoom, and Arrow/WASD keys to orbit. Use the view sphere for top/bottom/front/back/left/right views; click its center for the default isometric view.
- File browser: toggle the left CAD Viewer sidebar, search files/ids/paths, expand folders, select entries, or switch files from the breadcrumb menus.
- File names read exactly as they do on disk. The tab title, breadcrumb, catalog rows and the file picker all show the artifact's own basename and its own path — `moonwatch.step`, never the `moonwatch.py` that generated it. The Viewer never learns whether a document was generated: its status badge is one of not compiled / compiling / rendered / failed, decided from the file's bytes, the store and the build pool's job ledger, and no part of the UI names or opens a model script. (Copied topology references are the one exception, and a deliberate one: see the bare-stem rule below.)
- Floating toolbar: `Select` copies STEP topology references, `Pan` drags the camera, `Measure` picks measurement points, `Draw` opens annotation tools, `Orbit` starts an auto-rotating preview (with `Exit orbit` to leave it), `Play`/`Pause` appears when the model has animation clips, and `Copy screenshot` puts a viewport capture on the clipboard — screenshots are clipboard-only; nothing downloads. DXF drawings get their own 2D/3D view pill beside the toolbar.
- File context menu (file browser rows and the breadcrumb): `Reveal in Explorer View` highlights the entry in the file browser, and the copy items hand out references to the file — `Copy Filename`, `Copy Path` (absolute), `Copy Relative Path` (relative to the served directory), and `Copy Link`, which copies the viewer deep link for the entry (the bare origin plus `?file=<root-relative path>`, byte-identical to the URL the app itself lands on). There is no download and no native file-manager reveal; paths and links are how bytes leave the Viewer.
- Drawing tools: freehand, line, arrow, expand, rectangle, circle, fill, erase, undo, redo, and clear.
- File sheet: open the right sheet for file-specific tabs. STEP files get Tree, Reference, and Measure, plus a Kinematics tab when the document's sidecar declares kinematics, an Animation tab when a render module (`<name>.step.js`) sits beside the document — its load errors (a syntax error, an export the renderer does not know) and any clip target the compiled tree does not carry are reported in that tab — and Display. In Kinematics, a DOF that exactly one coupling gears is marked "driven by <coupling>": its slider shows the effective value and drags the coupling, so sliding one member of a gear train turns the whole train. In Animation, the "Clip" dropdown lists the model's authored clips and nothing else, opening on the first one; the section header's switch turns animation off, which idles the transport so the model holds the Kinematics tab's pose, and Play turns it back on. URDF/SRDF/SDF files get joints and metadata. Mesh files show a Measure tab for vertex-to-vertex distance. DXF drawings have material/bend controls.
- Display vs theme: the file sheet's Display tab holds per-file view state (display mode, clip, exploded view); the navbar theme button opens the theme sidebar, holding the global, persistent theme — preset, surface colors, backdrop, floor/grid, lighting, and color mode.
- Theme sidebar: a "Preset" dropdown (System, then the built-in presets, each with a two-box swatch showing its backdrop and default part colour) followed by the settings groups. Presets are read-only and there is only one custom theme: editing any setting writes it into that single custom slot and the dropdown reads "Custom", and picking a preset again is how you reset. Custom is a state, not a list entry — you leave it by choosing a preset. There is no save, restore, rename, or delete.
- Sidebars: the file sheet and the theme sidebar are mutually exclusive. Each navbar button toggles its own sidebar; opening one replaces the other, and closing one leaves nothing open.
- Copied references carry their file: the Viewer prefixes every copied ref with the shortest
path suffix that names that file uniquely (`bracket#o1.2.f1`, or
`lyra/STEP/palm.step#o1.3` where a filename is not unique), so a ref pasted into a prompt
still says which model it belongs to. A generated model shows as a bare stem — the common
case, so it gets the shortest name — while everything else keeps its suffix (`bracket.step`,
`plate.stl`, `plate.3mf`). That means a bare stem is NOT a literal path suffix, so resolving
one back to a file means expanding it; the CAD skill's
`references/inspection-and-validation.md` documents the split-and-expand steps. Bare `#...`
refs remain valid everywhere.
- Tutorial tips: the first time a selection produces a copyable reference — a component, a subassembly, or a face/edge — a one-shot tip above the "Copy #…" button explains that references can be pasted into prompts to edit specific parts. Only its X closes it; clicking away, Escape, and reloads leave it to reappear on the next selection, and once dismissed it never returns. Append `?resetTips=1` to a Viewer URL to clear the record and re-arm every tip; the param applies once and is stripped from the address bar.
- Display tab: a "Mode" dropdown (solid/rendered/x-ray/hidden/lines/flat/wire), then Clip and Exploded as subsections of the same tab.
- Clip: X/Y/Z position sliders plus Flip and Reset, always visible — an offset of 0 means no cut.
- Exploded view: a switch beside the "Exploded" subheading pulls an assembly apart (it moves the Amount scrub to/from zero) and reveals an Amount scrub, an Automatic/Custom layout switch, a Direction dropdown (Auto/X/Y/Z/Radial), Reverse, Spread, Detail, Order, explode-line, and Reset controls, where Custom lets you edit per-part moves.
SKILL.md
---
name: cad-viewer
description: Start CAD Viewer and return review links for CAD and robot-description files. Use when visually reviewing `.step`, `.stp`, `.glb`, `.stl`, `.3mf`, `.dxf`, `.urdf`, `.srdf`, or `.sdf` files, especially when handed off from CAD, URDF, SRDF, or SDF generation skills.
---
# CAD Viewer
Provenance: maintained in [earthtojake/text-to-cad](https://github.com/earthtojake/text-to-cad).
Use the installed local skill files as the runtime source of truth; the
repository link is only for provenance and release review. If the user asks to
modify, debug, or iterate on CAD Viewer source itself, that is the repository's
work, not this skill's — this skill runs the Viewer, it is not where you edit it.
Use this skill to open existing or newly generated CAD,
robot-description, or DXF files in CAD Viewer and hand back live review links. The expected input is one or more explicit file paths.
## Setup
The Viewer is part of `cadgen`: install this skill's `requirements.txt` into a
Python >= 3.11 and the `cadgen` command carries the server and the prebuilt
client. There is nothing else to install and no Node at run time.
```bash
python -m pip install -r requirements.txt
```
`cadgen doctor <this skill's directory>` confirms the installed cadgen matches
the version this skill was published against.
## Start Viewer
Launching is unconditional: the command below always ends with the URL of a
live Viewer for the launch directory. If one is already running for that
directory with the same Viewer code on disk (the reuse key is
realpath(directory) x an identity token — the cadgen version salted with the
Viewer files' newest mtime, so an upgraded Viewer never hands back a stale
instance), its URL is returned (`"action": "reused"`);
otherwise a new server starts on the first free port from `3245` upward
(`"action": "started"`). Never pick or reason about ports — read the URL the
command prints. Each instance serves ONE directory — the directory it is
launched from — fixed for the life of the process. There is no flag for it:
the cwd IS the served directory.
> The base port `3245` is `0xCAD` — "CAD" in hexadecimal.
```bash
cd /absolute/project/models && cadgen viewer --host 127.0.0.1 --json
```
(`cadgen` must be the one installed from this skill's `requirements.txt`. If it
is not on `PATH`, `python -m cadgen.viewer` with that interpreter is the same
launcher.)
**Choose the launch directory deliberately — it is the whole ballgame.** The
cwd decides what the catalog SCANS (a project root drags in `node_modules`,
`.git` and build output) and it is the instance REUSE key, so launching from
wherever you happen to be can hand back a Viewer serving somewhere else. `cd`
to the directory the user thinks of as their model workspace — usually the
project's `models/` directory — and launch from there. Never launch from
inside this skill's directory: that serves the skill, not the models.
Flags: `--json` prints the machine-readable last stdout line
(`{"url", "port", "action": "started"|"reused"}`) — always pass it and take the
URL from there. `--new` forces a fresh instance instead of reusing. An
explicit `--port <n>` is strict — "this port or fail" — and disables
both reuse and rolling. `cadgen viewer --help` lists the rest.
## URL shape
The page is the bare origin, and `file=` selects one artifact inside the served root:
```text
http://127.0.0.1:3245/?file=gripper/STEP/gear_rack_gripper.step
```
The `file=` value is relative to the served directory. Nothing about the
directory appears in the URL, so the same link means different files under
different instances — the root is the server's, not the link's.
**The launch directory is the workspace, not the file's folder.** The Viewer
scans it recursively, so the file browser lists every model beneath it and the
user can switch files without a new link. Launch from the directory the user
thinks of as their model workspace — typically the project's `models/`
directory, or the nearest common parent of the files you were asked to review —
and put the rest of the path in `file=`. Launching from the artifact's own deep
folder (`cd .../models/gripper/STEP`, `?file=gear_rack_gripper.step`) opens
the same model but hides the rest of the project, which is almost never what
the user wants.
Port collisions are not your problem: the launcher rolls to a free port and the
URL it prints is the truth. In sandboxed agent environments, local binding
failures such as `EPERM`/`EACCES` can still occur; rerun with the needed
permission/escalation.
`cadgen viewer list` shows every running instance with the directory it
serves; `cadgen viewer stop --port <n>` ends one. (Both run from anywhere —
only launching cares about the cwd.)
To review a directory outside the current root, just `cd` there and launch
again — reuse-or-start makes the second launch cheap and correct.
## Generation is the CAD skill's job; documents compile in the Viewer
The Viewer is a static visualization tool: it renders artifacts that already
exist. Generated models must be built first by running their model script (see
the CAD skill); the Viewer never runs a script and never learns whether a
document has one.
A `.step`/`.stp` document's status in the Viewer is one of four, decided from
the file's bytes and the store alone: **not compiled** (the store has no tree
for these bytes — the Viewer offers to compile, and compiles on open),
**compiling · <phase> n/total** (a job in cadgen's build pool is producing a
tree whose outputs include this document — the Viewer's own compile, a
`python model.py` in a terminal, or a parent's child build alike),
**rendered**, or **failed** (the last job for it failed; the message is shown).
A compile is a job submitted to the same pool every cadgen door uses, so
progress and errors come back as data. There is no "stale vs source" state:
whether a document is behind its script is `cadgen store why`'s question, not
the Viewer's. When an agent is doing the work there is nothing to run first:
just use the file and return the link.
## Links
- Before returning any link, resolve `<directory>/<file>` and confirm it
exists. Pass the `.step`/`.stp` artifact itself — generated and imported
alike. The catalog lists artifacts and names them exactly as they read on
disk: `moonwatch.step` is `moonwatch.step` in the tab, the breadcrumb, the
catalog row and the file picker, whether it was generated or imported.
The Viewer never learns whether a document was generated: its status is
artifact-side only (not compiled / compiling / rendered / failed), and the
model script is not shown anywhere in the UI. A generated model's document
must already exist (run the model script); a document the store has no tree
for is compiled from its bytes on open. If the resolved path is missing, do
not return the link; report the problem and point to the correct path.
- Return one Viewer URL per requested file.
- Start the Viewer once and pick one workspace root for the session. Every link is
the same origin plus `?file=<path relative to that root>`, so all of them share one
browsable catalog. An artifact outside that root needs its own Viewer — launch
again with that root (reuse-or-start makes this idempotent); a link alone cannot
reach it.
- For directory-only review links, return the origin without `?file=`.
- Do not stop an existing Viewer server unless the user asks.
- If Viewer startup fails, report the failure and continue with the owning skill's non-GUI validation or artifacts.
## References
- Read `references/viewer-features.md` when you need supported file types, Viewer controls, or file-specific feature details.