- `@design.estate/dees-catalog` ^22.0.0 (from ^20.0.0), with `@design.estate/dees-element` ^4.0.2 and `@design.estate/dees-domtools` ^5.0.1 (from ^3.4.1 and ^3.0.3), the versions the catalog runs on since 21.0.0. `pnpm dedupe` leaves one copy of each in the web bundle's tree. - The chat's conversation list moves to `dees-harness-resource-list`, the tag dees-catalog 22 gives the list that was `dees-harness-session-list`; properties and events are unchanged. The readme names the new tag.
@design.estate/cad
A browser-based OpenSCAD CAD editor with live 3D preview and an AI modeling assistant.
Issue Reporting and Security
For reporting bugs, issues, or security vulnerabilities, please visit community.foss.global/. This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a code.foss.global/ account to submit Pull Requests directly.
Overview
design.estate / CAD is a single-page app served by an @api.global/typedserver backend:
- Code editor — Monaco with an OpenSCAD grammar and design.estate light/dark themes.
- Live 3D preview — a
three.jsviewer (<scad-viewer>) renders the model produced by an OpenSCAD-WASM worker pool. Orbit / zoom / pan, perspective and orthographic projection, an orientation gizmo, a ground grid, and live geometry stats (size, vertices, triangles, bodies, manifoldness). - Server-side rendering — on Linux x64 servers a native OpenSCAD worker
(
@push.rocks/smartscad) renders over the typed socket: it keeps OpenSCAD's geometry cache between renders, so re-rendering an edited model only recomputes what changed, and builds geometry on all cores. It is the default where available; a "Server" toggle switches between it and the in-browser WASM engine, which also takes over on a server error. - AI assistant — describe a part in natural language and the assistant builds it in the
editor: it writes and edits the OpenSCAD, reads every render result, fixes errors, and looks
at the model as an image to check its shape. With a ChatGPT sign-in it can also search the
web (standard part dimensions, datasheets). The model runs server-side as FlexHarness
managed sessions; the chat is the
dees-catalogharness chat. Users sign in with their own ChatGPT account (device code); the server can also bring its own Anthropic key. Tokens and keys never reach the browser. - 3D printing — on a local install the server finds Bambu Lab, Klipper (Moonraker), OctoPrint and PrusaLink printers on the LAN, slices the model with OrcaSlicer, shows the estimate and sends the print; on SaaS the Print dialog hands the model to a desktop slicer instead.
- Part library, STL export, theme + projection toggles, resizable editor/viewer split.
Architecture
| Layer | Stack |
|---|---|
| Backend | @api.global/typedserver (serves the bundle, SPA fallback, CSP) + typedrouter handlers |
| Assistant | @modelprofile.com/flexharness managed sessions (FlexHarness), with a flexharness-models ModelRegistry over the flexharness-providers OpenAI (ChatGPT) and Anthropic providers |
| ChatGPT sign-in | @modelprofile.com/flexharness-providers/auth device-code login and refresh; inference through a ChatGPT resolving connection |
| Persistence | @lossless.org/client/nosqldb models (ChatGPT sign-ins, saved printers, migration records) and @modelprofile.com/flexharness/stores/nosqldb (conversations) on NoSQLDB/MongoDB |
| Transport | @api.global/typedrequest / @api.global/typedsocket (typed requests, numbered transcript pushes, editor actions) |
| Frontend | @design.estate/dees-element components, @design.estate/dees-catalog (dees-harness-chat, dees-harness-resource-list, dees-icon), design tokens |
| 3D / editor | three (bundled), Monaco + OpenSCAD-WASM (loaded from CDN) |
| Server rendering | @push.rocks/smartscad: a long-lived native OpenSCAD worker with warm geometry caches and multi-core Manifold |
| Printing | @ecobridge.xyz/devicemanager (print3d: discovery, status, upload, job control), @push.rocks/smartslicer (OrcaSlicer command line), fflate (3MF hand-off) |
Source layout: ts/ (backend), ts_migration/ (versioned data migrations, run at startup),
ts_web/ (frontend), html/ (shell + render worker), bundled to dist_serve/ by
@git.zone/tsbundle.
Development
pnpm install
gitzone services start # local NoSQLDB; writes MONGODB_URI / MONGODB_DATABASE to .nogit/env.json
pnpm watch # tswatch: rebuilds backend + frontend bundle, restarts the server
# or
pnpm build && pnpm start
pnpm test # build + tstest (boots its own in-memory NoSQLDB)
Open the app through localhost (or HTTPS when deployed): the typed socket refuses plain
http/ws to any other host.
The server reads MONGODB_URI (credentials and database included) and MONGODB_DATABASE, the
serve.zone capability names that gitzone services also writes. Without them the editor and the
server model work, but ChatGPT sign-in is unavailable and the chat panel says so.
AI assistant
Sign in with ChatGPT. The chat panel shows a Sign in with ChatGPT button. It starts the
ChatGPT device-code flow: open the shown auth.openai.com/codex/device link, sign in and enter
the code. The server polls for the confirmation, reads the account's ChatGPT model catalog and
runs the assistant on the catalog's default model. The browser keeps only an opaque session key
(localStorage); the ChatGPT tokens stay on the server, stored in the database under the key's
SHA-256 hash, so sign-ins survive restarts and are shared by every server instance. The server is
the only refresher: a renewal takes a lease on the stored sign-in and is written fenced on its
generation, so two instances never spend the same rotating refresh token. A refresh that OpenAI
refuses ends the sign-in. Sign out deletes it and revokes the refresh token at OpenAI. Device
codes that wait for confirmation are held in memory for at most 15 minutes; a restart during a
sign-in asks the user to start it again.
Editor tools. A turn runs as an agent (up to 24 model steps) with these tools:
| Tool | What it does |
|---|---|
get_editor |
Reads the open file, its code and its last render result. |
write_code |
Replaces the whole file, renders it and returns the result: dimensions (X × Y × Z mm), triangles, bodies, manifold, or the OpenSCAD error. |
edit_code |
Replaces one exact, unique snippet, renders and returns the result. |
render |
Renders the current code again. |
snapshot |
Renders the model from a view (current, iso, front, back, left, right, top, bottom) and hands the model the image. |
web_search |
OpenAI's web search, executed by OpenAI (ChatGPT sign-ins only); the CAD server fetches nothing from the web. |
Conversations. Every conversation is a FlexHarness managed session. A message is admitted
at once (cadHarnessSend); the turn runs on, up to 10 minutes, and Stop ends it. A message
sent while a turn runs steers it: the turn reads it at its next step, and one the turn no longer
takes in waits in the chat to be resent or discarded. The editor tools run in the browser tab that
sent the message: the server sends a typed cadEditorAction to the connection tagged with the
tab's id (a server-owned cadViewer tag the tab claims when it attaches, so a reconnect keeps the
turn), and the tab edits, renders (in-browser WASM or the server renderer) and reports back. Each
action is a tool row in the chat; edits made in this tab carry an Undo that puts the code back,
and snapshots (JPEG) show the image the assistant looked at.
The server follows the open conversation with a FlexHarnessTranscript and sends the tab what
changed as numbered cadHarnessUpdate pushes: changed transcript rows, streamed text, status and
usage. A tab that sees a gap, or reconnects, opens the conversation again and starts from a reset.
The conversation list (dees-harness-resource-list, the history button) updates through
cadHarnessSessions pushes; right-click a conversation to rename or delete it.
With a ChatGPT sign-in, conversations are kept in the database (FlexHarness's
flexharness_records and flexharness_chunks collections) and belong to your ChatGPT identity
(workspace and user): signing in again or from another browser brings them back, and no one else
can read them. The server derives whose conversations a request touches from the sign-in, never
from a session id the browser sends.
Earlier conversations. Conversations stored by the earlier chat (chat_conversations,
chat_messages) are imported at startup by the ts_migration/ runner as archived, read-only
conversations: they can be read, not continued. Their activity rows become tool rows that keep
their one-line summary; attached file names are kept, their contents were never stored. The old
collections stay untouched.
Server model (optional). A browser that is not signed in with ChatGPT uses the server's
Anthropic key when one is provided via qenv (e.g. .nogit/env.yaml). Each such tab gets its own
assistant instance with in-memory storage, held as long as the tab is open; it is disposed together
with its conversations when the tab closes, signs in with ChatGPT or the server stops:
ANTHROPIC_TOKEN: sk-ant-...
# CAD_ASSISTANT_MODEL: claude-sonnet-4-5-20250929 # optional override
Without a ChatGPT sign-in or a server key the editor, viewer, library and export work; the assistant asks the user to sign in.
Server-side rendering
On Linux x64 (glibc 2.39+, e.g. Ubuntu 24.04 and the production image) the server renders with
@push.rocks/smartscad: one long-lived native
OpenSCAD worker that keeps OpenSCAD's geometry cache between renders, so an edited model only
re-evaluates the parts that changed, and computes geometry on all cores. The worker and its
libraries ship in the package; OpenSCAD's text() additionally needs system fonts, which the
production image installs (fontconfig, fonts-liberation). The app probes cadRenderInfo on
load; when the worker is available a Server toggle appears in the toolbar and server rendering
is the default until the user picks a mode. Server renders go over the typed socket
(cadRenderScad), finished STLs are also cached by a canonical hash of the source (so
reindentation/comment-only edits are free), and the viewer falls back to the in-browser engine
on any failure. On other platforms (arm64, macOS) rendering stays in the browser.
Security: server-side rendering executes user-supplied OpenSCAD. Renders have a timeout and a source-size cap, and the worker runs in an empty working directory, but OpenSCAD can still read files by absolute path (
import()), so a hardened deployment should sandbox the process (container / seccomp / read-only fs).
3D printing
Local install or SaaS. CAD_LAN_PRINTING (qenv, default on) decides what the Print button
next to Export STL offers. On a local install it opens a dialog that finds printers on the
server's network, slices the model on the server, shows the estimate and sends the print. A SaaS
deployment sets CAD_LAN_PRINTING=false: the server then never searches the network, downloads a
slicer, saves a printer or slices, refuses every such request, and the dialog offers only the
hand-off below. The switch takes true or false (also 1/0, on/off, yes/no); any other
value stops the server at startup.
CAD_LAN_PRINTING: false # SaaS: no LAN printing, hand-off only
Hand-off. With LAN printing off, the dialog uploads the rendered model (cadPrintHandoff) and
gets two links, /print/handoff/<token>/<name>.stl and .3mf: a random 128-bit token, valid for
10 minutes, held in the server's memory and served with Cache-Control: no-store. Download STL
and Download 3MF save the file for any slicer. Open in Bambu Studio passes the 3MF link to
Bambu Studio's URL scheme (bambustudio://open?file=<url> on Windows and Linux,
bambustudioopen://<url> on macOS); Bambu Studio downloads the file itself, so the server's URL
must be reachable from the user's desktop. Bambu Studio opens only .3mf files this way and asks
for confirmation before it downloads from a site it does not trust. A local install shows the same
links below the printers.
Supported printers.
| Printer | Found on the network by | Credentials | Sliced to | Limits |
|---|---|---|---|---|
| Bambu Lab (X1, P1, P2, A1, H2 series) | SSDP broadcasts on UDP 2021/1990 | serial number + LAN access code | .gcode.3mf |
needs LAN Only Mode and Developer Mode (below); status over MQTT/TLS on 8883, upload by FTPS on 990 |
| Klipper (Moonraker) | mDNS, only with [zeroconf] in moonraker.conf |
API key, or none for Moonraker's trusted_clients |
.gcode |
without [zeroconf], add it by host (port 7125) |
| OctoPrint | mDNS _octoprint._tcp |
API key | .gcode |
reports no layers |
| PrusaLink (Buddy firmware, PrusaLink on a Pi) | mDNS _prusalink._tcp |
printer password (user maker) |
.gcode |
reports no model or build volume |
Search network listens for printers for 30 seconds; Add saves a found printer, Add printer saves one by host. A printer found by its IP address and saved again by its host name is two printers to the server. Saved printers connect at server start and push their status to every open dialog: state, progress, time left, temperatures, AMS slots and the printer's own error messages.
Slicing. The server slices with OrcaSlicer through
@push.rocks/smartslicer. OrcaSlicer is not part
of cad or its image: the first time a user clicks Download OrcaSlicer in the dialog, the server
downloads OrcaSlicer 2.4.2 (the Ubuntu 24.04 AppImage, 138 MB) from its official GitHub release,
verifies its SHA-256 digest and unpacks it (about 380 MB) into SMARTSLICER_HOME. Every open
dialog shows the download; an interrupted one leaves nothing behind and can be started again.
SMARTSLICER_HOME is read from the process environment (not qenv) and defaults to
$XDG_CACHE_HOME/smartslicer, else ~/.cache/smartslicer. The production image sets
/data/smartslicer and carries OrcaSlicer's system libraries (libwebkit2gtk-4.1-0 libglu1-mesa libopengl0 libsecret-1-0 libsm6); mount a volume on /data so the download survives a new
container:
docker run -d -p 3000:3000 -v cad-data:/data <image>
Outside the image, OrcaSlicer runs on Linux x64 and arm64 with glibc 2.39 or newer (e.g. Ubuntu 24.04) once those libraries are installed; elsewhere the dialog says the server cannot slice.
A slice (cadPrintSlice) returns the estimate before anything is sent: print time, filament in
grams and metres, and OrcaSlicer's warnings. For a Bambu Lab printer the machine profile follows
from the printer's model (e.g. Bambu Lab X1 Carbon 0.4 nozzle); you pick an AMS slot or the
external spool, the filament profile defaults to the slot's material, and the print goes to that
slot with an explicit AMS mapping. For the other printers you pick the machine profile once; the
server remembers it per printer after the first slice. Process and filament default to the
profile's own, and layer height, infill, supports, brim and (Bambu Lab) bed type can be
overridden. The server slices one model at a time and keeps a slice for 30 minutes. Send
uploads it and starts the print; the dialog and the statusbar follow the job, and the dialog
pauses, resumes and stops it.
Saved printers are stored in the database (print_printers). Without MONGODB_URI they live
in memory until the server stops, and the dialog says so.
Bambu Lab: LAN Only Mode and Developer Mode. Since the January 2025 firmware, Bambu Lab printers take print and control commands from the LAN only in Developer Mode. cad talks to the printer directly; it uses neither Bambu Connect nor the Bambu cloud.
- On the printer's screen, turn on LAN Only mode: on the X1, H2 and P2 series under Settings, on the P1 series under Settings → WLAN, on the A1 series under Setting.
- On the same page, turn on Developer Mode (read the notice and confirm it). The printer then stays off the Bambu cloud, and Bambu Handy cannot reach it.
- Note the access code shown on the LAN Only page.
- Note the serial number: Settings → Device and Serial Number (X1, H2, P2 series) or Setting → Device (P1, A1 series), or Bambu Studio's Device → Update page.
- X1 series: insert a microSD card; LAN prints need it.
Then add the printer with its IP address, serial number, model and access code. Without Developer Mode the dialog still shows the printer's status, but the printer refuses the print, and the dialog shows its reply.
Security: on a local install anyone who can reach the cad server can print on the saved printers, pause or stop their prints, and add or remove printers; cad has no user accounts. Run it only on a network you trust, or put an authenticating proxy in front of it. Access codes, API keys and passwords stay on the server: the browser only learns whether a printer has one. They are stored as entered in the database, so protect the database like the printers themselves. Open the app through
localhostor HTTPS: the typed socket refuses plainhttpto other hosts.
License and Legal Information
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the repository license file.
The server renderer's dependency @push.rocks/smartscad bundles a native OpenSCAD worker that is
distributed under the GNU General Public License, version 3 or later. It runs as a separate
program; its notices and the location of its corresponding source ship with the package in
node_modules/@push.rocks/smartscad/dist_cpp/linux_amd64/ (notices/, SOURCES.md).
cad does not contain or distribute OrcaSlicer. When a user enables LAN printing, the server
downloads an unmodified OrcaSlicer release from its official GitHub releases through
@push.rocks/smartslicer and runs it as a separate program. OrcaSlicer is licensed under the GNU
Affero General Public License, version 3; its license and the location of its source are copied
into the installation ($SMARTSLICER_HOME/…/notices/orcaslicer/).
Please note: The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
Trademarks
This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH or third parties, and are not included within the scope of the MIT license granted herein.
Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines or the guidelines of the respective third-party owners, and any usage must be approved in writing. Third-party trademarks used herein are the property of their respective owners and used only in a descriptive manner, e.g. for an implementation of an API or similar.
Company Information
Task Venture Capital GmbH
Registered at District Court Bremen HRB 35230 HB, Germany
For any legal inquiries or further information, please contact us via email at hello@task.vc.
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.