jkunz 6651e34cdf chore(deps): build on dees-catalog 22 and dees-element 4
- `@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.
2026-10-11 14:35:15 +00:00
…
…
…
…
…
…
…

@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.js viewer (<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-catalog harness 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.

  1. 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.
  2. 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.
  3. Note the access code shown on the LAN Only page.
  4. 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.
  5. 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 localhost or HTTPS: the typed socket refuses plain http to other hosts.

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.

S
Description
No description provided
Readme
723 KiB
Languages
TypeScript 98.9%
JavaScript 0.6%
Dockerfile 0.3%
HTML 0.2%