dsh-cad 0.15.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,313 +1,309 @@
1
- # dsh-cad — CAD Plugin for DeepSeek Harness
2
-
3
- ![dsh-cad banner](docs/img/banner.svg)
4
-
5
- [![homepage](https://img.shields.io/badge/homepage-dsh--cad-4D6BFE)](https://lau-mars.github.io/dsh-cad/)
6
- [![npm](https://img.shields.io/npm/v/dsh-cad)](https://www.npmjs.com/package/dsh-cad)
7
- [![downloads](https://img.shields.io/npm/dm/dsh-cad)](https://www.npmjs.com/package/dsh-cad)
8
- [![dsh](https://img.shields.io/npm/v/@deepseek-ai%2Fdsh?label=dsh)](https://www.npmjs.com/package/@deepseek-ai/dsh)
9
- [![Node](https://img.shields.io/badge/node-%3E%3D%2022-4D6BFE)](https://nodejs.org/)
10
- [![occt](https://img.shields.io/badge/occt-7.9.3-4D6BFE)](https://github.com/LAU-MARS/opencascade-ts)
11
- [![occt.ts](https://img.shields.io/npm/v/occt.ts?label=occt.ts)](https://www.npmjs.com/package/occt.ts)
12
- [![ansatz](https://img.shields.io/npm/v/ansatz-wasm?label=ansatz)](https://www.npmjs.com/package/ansatz-wasm)
13
- [![License: MIT](https://img.shields.io/badge/license-MIT-4D6BFE)](./LICENSE)
14
-
15
- English | [简体中文](./README.zh-CN.md)
16
-
17
- A CAD plugin for [DeepSeek Harness (dsh)](https://github.com/deepseek-ai/deepseek-harness):
18
- an **embedded 3D/2D CAD viewer** plus a **native parametric modeling tool family**
19
- (OCCT kernel) in the Web UI, letting the agent build and inspect CAD geometry
20
- step by step — "model while you watch".
21
-
22
- ## Preview
23
-
24
- A full modeling session, end to end: the agent builds a parametric assembly
25
- from plain chat — a 120×80×8 base plate with R5 rounded corners, then four
26
- bolt instances placed at the corners — while the resident CAD panel tracks
27
- every step live (parts tree with per-instance colors, ViewCube, "装配体"
28
- assembly tab, 5 instances · 1,740 triangles). The finished assembly exports
29
- as a structured STEP document and the agent verifies it by reading the file
30
- back — 2 solids, 3 products, 2 assembly usages, 1,125.00 mm³:
31
-
32
- ![dsh-cad assembly session](docs/img/assembly-preview.png)
33
-
34
- ## Downloads
35
-
36
- Cumulative downloads from the npm registry, refreshed daily by CI:
37
-
38
- ![npm cumulative downloads](docs/img/downloads-cumulative.svg)
39
-
40
- ## Feature Overview
41
-
42
- | Capability | Description |
43
- | --- | --- |
44
- | ⚙️ Kernel chain: cadorange → occt.ts → opencascade.js | Modeling goes through **[cadorange](https://github.com/LAU-MARS/cadorange)** (the agent-native CAD runtime wrapping the same occt.ts/OCCT WASM; public API + the supported `cadorange/occt` escape hatch for meshing/HLR/wire construction); the direct occt.ts adapter and opencascade.js stay as fallback tiers — `DSH_CAD_KERNEL` pins one explicitly. The old "no editing after shell" seam is gone — fillet/chamfer/boolean keep working after shelling |
45
- | 🔍 CAD viewing | STL / OBJ / STEP / IGES / BREP / DCPRT (3D), DXF / SVG (2D); interactive viewport in the resident right panel (orbit / zoom / wireframe / pan), with a one-line result row in the conversation |
46
- | 🌗 Dark-mode surfaces | Every rendered surface follows the host theme — viewport slab, DXF/drawing strokes, the Three.js grid / XYZ triad / feature edges / default mesh color / sketch overlays, and the ViewCube plate; a theme flip restyles mounted viewers live. Overrides: `--dsh-cad-bg`, `--dsh-cad-stroke`, `--dsh-cad-paper` |
47
- | 🧭 CAD editor interactions | Onshape-style ViewCube (26-zone click-to-orient), hover/click face & edge picking with live measurement (area mm² / length mm), Faces+Edges / Faces / Wireframe render modes, switchable BRep demo parts (bracket / flange / shaft) |
48
- | 🏗️ Parametric modeling | Primitives, profile extrusion, **loft**, **sweep**, **revolve**, booleans, all-edge fillet/chamfer, **shell**, **draft**, **patterns** (linear/circular), transforms; profiles take **curve segments** (arcs/circles exact BRep; B-splines sampled approximation) — exact OCCT BRep, not a mesh approximation. `cad_loft` sections accept **curves** too: point loops, `{through}` splines or planar `{profile}` chains; `smooth: true` fits a **periodic B-spline** through point loops — one continuous B-spline lateral face instead of planar strips meeting at G0 creases |
49
- | ✏️ Named sketches | Standalone named profiles (Sketch1…) kept in the document (`cad_sketch_new` / `edit` / `list` / `delete`); extrude/revolve/sweep reference them **by name**, and `cad_sketch_edit` rewrites the definition in place + replays the log, rebuilding every dependent feature — the parametric loop. Sketches render as Onshape-style **blue curves + plane frame** in the viewport (refreshing live on edit), listed in a left-side **feature tree** (sketches + features, eye-toggle visibility, click-to-highlight bodies) over the Part tab |
50
- | 🗂️ Codex-style document tabs | The resident display panel gets a tab strip with a "+" menu: **Part** (Part Studio, the default) / **Assembly** (instance insert/move/remove) / **Drawing** (true hidden-line sheets); tabs are closable and keep their state |
51
- | 📁 Multi-document file space | Named documents per workspace (`.dsh-cad/docs/`), each session bound to its own active document — new sessions start empty instead of inheriting leftovers; a folder button in the panel lists every document (preview / delete), and `cad_doc_new` / `cad_doc_open` manage the modeling target from chat |
52
- | 📐 Engineering drawings | GB first-angle layout: front / top / left views + isometric, true OCCT hidden-line removal via the **occt.ts** kernel (an npm dependency, dashed); sheet frame, title block, overall dimensions, standard scale series; exports SVG / DXF |
53
- | 🔗 Constraint solving & motion | **Ansatz** geometric constraint solver (**one command: `npm install ansatz-wasm`** — a wasm dependency, no Rust toolchain, no native binary): entity/constraint modeling (assembly instances ↔ rigid3 poses mapped automatically), solve-with-writeback, full DOF/residual/redundancy/suggestion diagnostics (LLM-oriented), and parametric kinematic sweeps (`cad_constraint` / `cad_solve` / `cad_motion`) |
54
- | 📐 Geometry measurement | Exact volume (mm³), bounding box, triangle counts, DXF layers |
55
- | 📤 On-demand export | STEP (parametric) / STL (mesh); files are written only when the user asks |
56
- | 🖥️ Resident CAD panel | A permanent panel right of the conversation: Codex-style tabs (Part / Assembly / Drawing), tracking the latest model in real time while modeling. A host-independent liveness poll (`GET /dsh-cad/docs` → per-document op count) keeps the viewport following modeling ops even when the conversation meta pipeline goes stale, and auto-returns from a file preview |
57
- | ⚡ Zero-copy render pipeline | worker mesh → in-memory binary → three.js typed arrays; zero base64 / zero intermediate files / zero per-step disk writes |
58
- | 💾 Modeling document persistence | Operation log (JSON) + debounced disk mirror; automatically replayed to restore state after a process restart |
59
- | 🖼️ Image → profile | PNG sketch/screenshot → Otsu binarization → contour tracing → extrusion-ready polygon (`cad_image_profile`) |
60
- | 🔌 FreeCAD executor | Run the same op family on an external FreeCAD console (STEP in/out); requires a local FreeCAD install |
61
- | ☁️ Onshape executor | Run the same op family on **Onshape** over its signed REST API — zero local install; every op compiles to a **standard Onshape feature** pushed into the target Part Studio (no Feature Studio setup). Readback is three-tier: cheap per-part STL by default, `readback: "step"` for exact-BRep-derived named meshes via a server-side STEP translation, or `readback: "none"` — build only and return the document link (~3 calls, quota-friendliest). Exports `.stl` / `.step` / `.x_t` (Parasolid). Quota-aware errors distinguish credential problems from free-plan cooldowns; the Onshape document URL is always surfaced |
62
-
63
- ## Installation
64
-
65
- The plugin is published to npm — one line:
66
-
67
- ```sh
68
- dsh plugin --profile web add dsh-cad
69
- ```
70
-
71
- The installer applies the bundled `cordis.patch.yml` (declared in the `dsh.bundle` manifest) automatically, so there is nothing to configure by hand.
72
-
73
- ### Version requirements
74
-
75
- - **Node.js** and **dsh CLI** (`@deepseek-ai/dsh`): minimum versions live in
76
- the badge row above and the `engines` field of `package.json` — kept out of
77
- the prose so they never go stale
78
-
79
- ### Install from source (dev mode)
80
-
81
- ```sh
82
- git clone https://github.com/LAU-MARS/dsh-cad.git
83
- cd dsh-cad
84
- npm install && npm run build && npm test # deps include occt.ts (true-HLR drawing kernel, ~20MB wasm)
85
-
86
- npm install -g @deepseek-ai/dsh pnpm
87
- dsh web # let the first launch init the profile, then Ctrl-C
88
-
89
- dsh plugin --profile web add /path/to/dsh-cad
90
-
91
- dsh web
92
- ```
93
-
94
- The patch insert ships as `cordis.patch.yml` in the package root and is applied by the installer via the same `dsh.bundle` manifest.
95
-
96
- Set `DEEPSEEK_API_KEY` and you are ready — for example:
97
-
98
- - “open bracket.stl” → `cad_view`
99
- - “model a 100×60×5 plate, punch a ⌀20 hole in the middle, R2 fillets on the four
100
- corners, add a ⌀16 boss 20 tall, export plate.step”
101
- → `cad_create_prim` + `cad_boolean` + `cad_fillet` + `cad_export`, with the 3D tab
102
- updating live at every step
103
- - “build a snowman” → spheres + a cone nose + a cylinder hat (precise `at`/`axis` placement)
104
- - “add two more b1, one rotated 90 degrees” → `cad_assembly_insert` + `cad_assembly_move`, the Assembly tab updates live
105
- - “make an A3 drawing of b1 and export dxf” → `cad_drawing` (three views + iso + dashed hidden lines + dimensions) → `cad_export` `.dxf`
106
-
107
- ## Modeling Tool Family
108
-
109
- | Tool | Description |
110
- | --- | --- |
111
- | `cad_view` | Open a CAD file and render it in the viewer (resident panel) |
112
- | `cad_info` | Read-only geometry metadata (format / counts / bounding box / units / layers) |
113
- | `cad_create_prim` | Primitives (mm, Z-up); `at` for placement, `axis` for orientation (exact axis-angle rotation) |
114
- | `cad_sketch_new` | Create a named sketch (Sketch1…): {start, segments} / {circle} / flat points loop — referenced by name from the profile features |
115
- | `cad_sketch_edit` | Redefine a sketch (回改): the definition op is rewritten in place and the log replayed — every referencing feature rebuilds |
116
- | `cad_sketch_list` | List the document's sketches (name / form / size) |
117
- | `cad_sketch_delete` | Delete a sketch (refused while features still reference it) |
118
- | `cad_extrude_profile` | Extrude a closed XY-plane profile along +Z into a solid — inline points/segments/circle or a named sketch (`sketch`) |
119
- | `cad_loft` | Loft: skin a solid through successive closed sections (each a [x,y,z,…] loop in its own plane, or a curve/`profile` section); sections may differ in shape and point count; `smooth` fits a periodic B-spline through point loops for a **crease-free** skin; `ruled` for straight sides |
120
- | `cad_revolve` | Solid of revolution: revolve a closed profile around an axis (profile coords = (radius, height); curve segments supported — rounded rims exact); optional `angle`; accepts a named sketch |
121
- | `cad_chamfer` | Chamfer: bevel every sharp edge with one equal distance (mm) |
122
- | `cad_pattern` | Pattern: linear (`delta` spacing) or circular (principal axis + `at` point + sweep angle); creates copy bodies, fuse afterwards for one body |
123
- | `cad_shell` | Shell: hollow to a wall thickness (inward, outer skin preserved); `open` lists outward normals of the faces to open (empty = sealed). Runs on the occt.ts kernel; the result is a HOSTED body |
124
- | `cad_draft` | Draft: tilt the walls `angle` degrees about a neutral plane (mold release); `direction` is the pull direction, walls auto-selected by default. Runs on the occt.ts kernel; the result is a HOSTED body |
125
- | `cad_sweep` | Sweep: pipe a closed 2D profile along a 3D [x,y,z,…] path; the profile is auto-placed on the start plane, so no manual orientation; accepts a named sketch |
126
- | `cad_boolean` | fuse / cut / common (classic hole punching: plate cut cylinder) |
127
- | `cad_fillet` | Constant-radius fillet on all sharp edges |
128
- | `cad_transform` | Translate / Euler rotate / mirror |
129
- | `cad_volume` | Exact BRep volume (mm³) |
130
- | `cad_drawing` | Engineering drawing: front/top/left + isometric views, dashed hidden lines, frame, title block, overall dimensions, standard scale, A4/A3 |
131
- | `cad_assembly_insert` | Insert a body into the assembly as a placed instance (`at` position, `rotate` orientation) |
132
- | `cad_assembly_move` | Set an instance's absolute placement |
133
- | `cad_assembly_remove` | Remove an instance from the assembly (the body stays) |
134
- | `cad_constraint` | Declare the constraint model: entities (assembly-instance bindings) + constraints (distance/angle/mate/coaxial…), persisted in the op log |
135
- | `cad_solve` | Solve with Ansatz and write poses back; returns DOF/residual/redundancy/suggestion diagnostics |
136
- | `cad_motion` | Kinematic sweep: drive one constraint value from `from` to `to`, solve frame by frame, apply the last pose, return the motion table |
137
- | `cad_export` | Export STEP / STL / DCPRT (the native replayable part document) to a workspace path; `target: "assembly"` writes the assembly STEP, `target: "drawing"` writes the sheet as SVG / DXF |
138
- | `cad_delete` | Delete a body |
139
- | `cad_docs` | List the workspace's modeling documents (id / name / bodies / updated, active marked) |
140
- | `cad_doc_new` | Create a named document and make it the session's modeling target (start multi-part projects here) |
141
- | `cad_doc_open` | Open an existing document (by id or name) as the session's modeling target — bodies replay exactly |
142
- | `cad_doc_rename` | Rename a document |
143
- | `cad_doc_delete` | Permanently delete a document (requires `confirm: true`) |
144
- | `cad_freecad` | Run an op program on an external FreeCAD executor (optional STEP input / export) |
145
- | `cad_fusion` | Run an op program on an external Fusion 360 executor (GUI bridge; optional export) |
146
- | `cad_onshape` | Run an op program on the Onshape cloud (signed REST API): creates or drives a document, returns its URL; `readback: "step"` for exact BRep meshes; exports `.stl` / `.step` / `.x_t` (Parasolid) |
147
- | `cad_image_profile` | PNG → contours → extrusion-ready polygon points |
148
- | `cad_script` | Deterministic script evaluation for geometry math (profile point arrays, arc coordinates): `js` runs in-process (Node vm — works even when the host shell is unavailable), `py` uses a system Python |
149
-
150
- After every modeling step: **the chat result row updates in place** (stable viewId +
151
- versioned URL), and the resident panel's "Part" tab tracks the latest model in real time.
152
-
153
- ## Connectors (roadmap)
154
-
155
- Modeling primarily runs on the **occt.ts kernel** (opencascade.js as the fallback backend) (OCCT in the browser —
156
- zero install). The connectors below refer to **external CAD engines** acting as
157
- executors for the same tool family, planned for future support:
158
-
159
- | Connector | Suite | Platform | Status |
160
- | --- | --- | --- | --- |
161
- | **Built-in kernel** | CAD modeling kernel based on OCCT + WebGL, runs in the browser — zero install | All platforms (WebGL rendering) | ✅ Built-in |
162
- | FreeCAD | open-source parametric suite — natural local executor via its Python API (console + GUI window modes) | Windows / macOS / Linux | ✅ Available (needs local install) |
163
- | Fusion 360 | Autodesk CAD/CAM — resident add-in + spool bridge (no headless; the Fusion window doubles as a viewer) | Windows / macOS | 🧪 Experimental (`cad_fusion`) |
164
- | SolidWorks | Dassault Systèmes industry-standard 3D CAD, COM/.NET automation | Windows only | 🚧 Windows demo scaffold (`scripts/solidworks-bridge/`) |
165
- | Onshape | PTC cloud-native SaaS CAD, fully in the browser — signed REST API (cad.onshape.com or chamber hosts); every op compiles to a standard Onshape feature pushed into the Part Studio, results read back as per-part STL + mass properties, the cloud is the viewer | All platforms (browser) | ✅ Available (`cad_onshape`; set `DSH_ONSHAPE_ACCESS_KEY` / `DSH_ONSHAPE_SECRET_KEY`) |
166
- | ZW3D(中望3D) | ZWSOFT all-in-one CAD/CAM | Windows / Linux | 🚧 Planned |
167
- | GstarCAD 3D(浩辰3D) | Gstarsoft 3D CAD | Windows | 🚧 Planned |
168
-
169
- All external engines implement the same **GeometryExecutor contract** (`available()` /
170
- `run(opProgram) → meshes`), so the WebGL display layer never changes — swapping a
171
- backend changes only the quality of the produced geometry.
172
-
173
- ## Architecture
174
-
175
- ```
176
- cad_view(path) modeling tools (cad_create_prim, …)
177
- → import worker (occt-import-js) → modeling worker (opencascade.js WASM)
178
- → CadScene JSON (base64-f32) → exact BRep geometry + meshing
179
- → GET /dsh-cad/scene/<id> → in-memory binary scene (f32/u32 packed)
180
- → GET /dsh-cad/bin/<docId>
181
- ↓ session presentationMeta (viewId + versioned URL) ↓
182
- browser card + persistent "3D" tab (three.js / SVG, Z-up, XYZ axes)
183
- ```
184
-
185
- - **Two workers**: import (occt-import-js, read-only STEP/IGES/BREP) and modeling
186
- (opencascade.js, full OCCT) are separate, both lazily started; the `_N`
187
- suffix convention of embind overloaded constructors is wrapped in
188
- `src/modeling/occt-adapter.cjs` (all verified at runtime)
189
- - **Zero-copy pipeline**: modeling scenes use zero base64 / zero large JSON arrays /
190
- zero per-step disk writes (disk mirror debounced 1.5s, replayed only on service
191
- restart); `cad_export` is the only explicit file export
192
- - **Modeling document**: `<workspace>/.dsh-cad/model.json` operation log; all bodies
193
- are restored by replay after a restart
194
- - **Drawing HLR kernel**: hidden lines run on **occt.ts** (npm dependency,
195
- `npm i occt.ts`) — true OCCT removal, no fallback engine, a missing kernel is
196
- a hard error. New APIs over opencascade.js: `hiddenLines()` true hidden-line
197
- removal, byte-level STEP/BRep `readStep`/`readBrep`/`writeStep`/`writeBrep`
198
- without MEMFS, tessellation with built-in feature-edge extraction, and a
199
- `hasError()`/`lastError()` error contract. Geometry crosses kernels as STEP
200
- bytes and the projected segments are remapped into the sheet frame. Kernel
201
- dist resolution: `DSH_OCCTJS_DIST` env var → Node's own resolution of the
202
- `occt.ts` dependency (walks up every parent node_modules — npm/yarn flat and
203
- pnpm symlink layouts all hit) → `<repo>/../opencascade-ts/dist` (sibling
204
- checkout) → `vendor/` → `node_modules/opencascade-ts`
205
- - **Constraint solving (Ansatz)**: the solver is the npm dependency
206
- **`ansatz-wasm`** (a wasm-bindgen build, single package ~538KB, zero deps) —
207
- installing dsh-cad pulls it automatically, and `npm install ansatz-wasm` alone
208
- upgrades it. **No Rust toolchain, no native binary, platform independent.**
209
- Resolution: the npm package (node_modules) → a `DSH_ANSATZ_WASM` directory → a
210
- sibling Ansatz checkout's pkg-node. The solver speaks one JSON envelope
211
- contract, with tool-layer errors (e.g. `unsupported_constraint`) surfaced
212
- verbatim for the LLM. Assembly placements (translate + XYZ Euler
213
- degrees) map bidirectionally to the solver's rigid3 poses (translate +
214
- exponential-map rotation, radians). Solver capability is staged
215
- (point-distance-to-origin today) — the plumbing contract is finished and needs
216
- no changes as it grows
217
- - **Kernel chain**: modeling goes through **cadorange** (agent-native CAD
218
- runtime; public build123d-style API for prims/curve-segment profiles (TRUE
219
- B-spline interpolation)/loft/sweep/revolve/booleans (cleaned by default)/
220
- fillet/chamfer/shell/draft/native mirror/centroid, plus the supported
221
- `cadorange/occt` escape hatch for viewer tessellation, engineering-drawing
222
- HLR (zero STEP hops, one wasm session) and wire construction); the direct
223
- occt.ts adapter and opencascade.js stay as fallback tiers
224
- (`DSH_CAD_KERNEL` pins one; see docs/cadorange-integration.md for the
225
- verb-by-verb mapping). Editing after shelling is seamless.
226
- opencascade.js remains as the FALLBACK backend (auto-selected when occt.ts
227
- cannot load). Assembly STEP export writes a **structured document** (occt.ts
228
- `writeStepDocument`): one root product plus one named, placed child
229
- per instance — instance separation survives the file (STL stays a single
230
- fused mesh; the fallback backend also fuses). Instance separation stays in
231
- the assembly scene/document for the fuse paths.
232
- Face selection matches `describe()` plane normals (outermost along ±normal)
233
- - **Client**: esbuild single-file CJS factory (three.js inlined ~560KB, react provided
234
- by the host module table), Z-up CAD convention, empty scene with XYZ axis labels
235
- and a ground grid always displayed
236
-
237
- ## Theming (dark mode)
238
-
239
- The viewer follows the host appearance automatically. DSH publishes the theme as
240
- `color-scheme` on `<html>` plus `body[data-ds-dark-theme]`; the plugin resolves
241
- that (falling back to `prefers-color-scheme`) and themes every surface the host
242
- tokens cannot reach:
243
-
244
- - the viewport slab behind the transparent WebGL canvas and 2D scenes,
245
- - default 2D entity strokes (DXF / engineering-drawing entities),
246
- - the Three.js ground grid, XYZ axis triad/labels, feature edges, default mesh
247
- color and sketch overlays,
248
- - the ViewCube face plate (canvas texture).
249
-
250
- A theme flip restyles mounted cards and the resident panel live — no reload and
251
- no lost camera. Engineering drawings loaded as SVG keep a white sheet; in dark
252
- mode it is framed as paper on the dark slab instead of filling the viewport.
253
-
254
- Hosts can override the surfaces the plugin owns; an explicit definition wins in
255
- either theme:
256
-
257
- | CSS variable | Applies to | Default |
258
- | --- | --- | --- |
259
- | `--dsh-cad-bg` | viewport background (any CSS `background` value) | light `#f5f6f8→#e9ecf0`, dark `#1b1b1c→#151517` |
260
- | `--dsh-cad-stroke` | default 2D entity stroke | `#4c5561` light / `#c8ced6` dark |
261
- | `--dsh-cad-paper` | drawing paper behind SVG sheets | `#ffffff` |
262
-
263
- ```css
264
- :root { --dsh-cad-bg: #0b0d10; } /* flat canvas in both themes */
265
- ```
266
-
267
- ## Tests
268
-
269
- ```sh
270
- npm test # full suite: converters / modeling worker (exact volume assertions) / DCPRT round-trip / FreeCAD + Onshape executors / image profiles / binary pipeline / document persistence
271
- node test/m0-kernel-check.cjs # OCCT kernel API smoke test
272
- node test/route-check.mjs # JSON scene routing layer
273
- node test/visual/serve.mjs # browser card/tab visual verification page (http://127.0.0.1:3987)
274
- ```
275
-
276
- Representative assertions covered: the boolean-punched volume exactly equals the
277
- analytic value (28429.20 mm³), L-shaped profile extrusion 3000 mm³, volumes and
278
- bounding-box flips of sphere/cone/torus placed with `at`/`axis`, 8-byte alignment
279
- of the binary packing, an STL export round-trip (export → read back by the
280
- phase-1 parser), and a DCPRT document round-trip (serialize → replay on the
281
- OCCT worker → exact bounds).
282
-
283
- ## Known Limitations
284
-
285
- - DWG (closed-source) is unsupported; DXF bulge arcs are approximated by chords;
286
- glTF/3MF viewing is not implemented (the structure is reserved)
287
- - `cad_fillet` is all-edge constant-radius (per-edge selection is unstable under
288
- embind); chamfer is not implemented
289
- - Sketch extrusion supports polygon profiles only (arc profiles are constructed by
290
- boolean combinations of cylinders/tori)
291
- - dsh framework limitation: an already-mounted single slot (the right-side details
292
- panel itself) does not respond to components registered later, so the persistent
293
- view is provided as the "3D" view tab (a list slot, the official composition)
294
- - The host reads CAD files via node:fs (the platform fs service supports UTF-8 text
295
- only and cannot carry binary data)
296
-
297
- ## Community
298
-
299
- | QQ Group (🇨🇳 China) | Feishu (🇨🇳 China) | X / Twitter (🌍 International) | Discord (🌍 International) |
300
- | --- | --- | --- | --- |
301
- | `485038246` (join note: dsh-cad) | 🚧 Coming soon | 🚧 Coming soon | 🚧 Coming soon |
302
-
303
- <!-- TODO: fill in the X handle / Discord invite link before publishing -->
304
-
305
- ## Contributors
306
-
307
- Auto-generated from the commit history — thanks to everyone who has contributed!
308
-
309
- [![Contributors](https://contrib.rocks/image?repo=LAU-MARS/dsh-cad)](https://github.com/LAU-MARS/dsh-cad/graphs/contributors)
310
-
311
- ## License
312
-
313
- MIT
1
+ # dsh-cad — CAD Plugin for DeepSeek Harness
2
+
3
+ ![dsh-cad banner](docs/img/banner.svg)
4
+
5
+ [![homepage](https://img.shields.io/badge/homepage-dsh--cad-4D6BFE)](https://lau-mars.github.io/dsh-cad/)
6
+ [![npm](https://img.shields.io/npm/v/dsh-cad)](https://www.npmjs.com/package/dsh-cad)
7
+ [![downloads](https://img.shields.io/npm/dm/dsh-cad)](https://www.npmjs.com/package/dsh-cad)
8
+ [![dsh](https://img.shields.io/npm/v/@deepseek-ai%2Fdsh?label=dsh)](https://www.npmjs.com/package/@deepseek-ai/dsh)
9
+ [![Node](https://img.shields.io/badge/node-%3E%3D%2022-4D6BFE)](https://nodejs.org/)
10
+ [![occt](https://img.shields.io/badge/occt-7.9.3-4D6BFE)](https://github.com/LAU-MARS/opencascade-ts)
11
+ [![occt.ts](https://img.shields.io/npm/v/occt.ts?label=occt.ts)](https://www.npmjs.com/package/occt.ts)
12
+ [![ansatz](https://img.shields.io/npm/v/ansatz-wasm?label=ansatz)](https://www.npmjs.com/package/ansatz-wasm)
13
+ [![License: MIT](https://img.shields.io/badge/license-MIT-4D6BFE)](./LICENSE)
14
+
15
+ English | [简体中文](./README.zh-CN.md)
16
+
17
+ A CAD plugin for [DeepSeek Harness (dsh)](https://github.com/deepseek-ai/deepseek-harness):
18
+ an **embedded 3D/2D CAD viewer** plus a **native parametric modeling tool family**
19
+ (OCCT kernel) in the Web UI, letting the agent build and inspect CAD geometry
20
+ step by step — "model while you watch".
21
+
22
+ ## Preview
23
+
24
+ A full modeling session, end to end: the agent builds a parametric assembly
25
+ from plain chat — a 120×80×8 base plate with R5 rounded corners, then four
26
+ bolt instances placed at the corners — while the resident CAD panel tracks
27
+ every step live (parts tree with per-instance colors, ViewCube, "装配体"
28
+ assembly tab, 5 instances · 1,740 triangles). The finished assembly exports
29
+ as a structured STEP document and the agent verifies it by reading the file
30
+ back — 2 solids, 3 products, 2 assembly usages, 1,125.00 mm³:
31
+
32
+ ![dsh-cad assembly session](docs/img/assembly-preview.png)
33
+
34
+ ## Downloads
35
+
36
+ Cumulative downloads from the npm registry, refreshed daily by CI:
37
+
38
+ ![npm cumulative downloads](docs/img/downloads-cumulative.svg)
39
+
40
+ ## Feature Overview
41
+
42
+ | Capability | Description |
43
+ | --- | --- |
44
+ | ⚙️ Kernel chain: cadorange → occt.ts | Modeling goes through **[cadorange](https://github.com/LAU-MARS/cadorange)** (the agent-native CAD runtime wrapping the occt.ts/OCCT WASM; public API + the supported `cadorange/occt` escape hatch for meshing/HLR/wire construction); the direct occt.ts adapter stays as the fallback tier — `DSH_CAD_KERNEL` pins one explicitly (the opencascade.js tier was removed in v0.15.1, −64MB install). The old "no editing after shell" seam is gone — fillet/chamfer/boolean keep working after shelling |
45
+ | 🔍 CAD viewing | STL / OBJ / STEP / IGES / BREP / DCPRT (3D), DXF / SVG (2D); interactive viewport in the resident right panel (orbit / zoom / wireframe / pan), with a one-line result row in the conversation |
46
+ | 🌗 Dark-mode surfaces | Every rendered surface follows the host theme — viewport slab, DXF/drawing strokes, the Three.js grid / XYZ triad / feature edges / default mesh color / sketch overlays, and the ViewCube plate; a theme flip restyles mounted viewers live. Overrides: `--dsh-cad-bg`, `--dsh-cad-stroke`, `--dsh-cad-paper` |
47
+ | 🧭 CAD editor interactions | Onshape-style ViewCube (26-zone click-to-orient), hover/click face & edge picking with live measurement (area mm² / length mm), Faces+Edges / Faces / Wireframe render modes, switchable BRep demo parts (bracket / flange / shaft) |
48
+ | 🏗️ Parametric modeling | Primitives, profile extrusion, **loft**, **sweep**, **revolve**, booleans, all-edge fillet/chamfer, **shell**, **draft**, **patterns** (linear/circular), transforms; profiles take **curve segments** (arcs/circles exact BRep; B-splines sampled approximation) — exact OCCT BRep, not a mesh approximation. `cad_loft` sections accept **curves** too: point loops, `{through}` splines or planar `{profile}` chains; `smooth: true` fits a **periodic B-spline** through point loops — one continuous B-spline lateral face instead of planar strips meeting at G0 creases |
49
+ | ✏️ Named sketches | Standalone named profiles (Sketch1…) kept in the document (`cad_sketch_new` / `edit` / `list` / `delete`); extrude/revolve/sweep reference them **by name**, and `cad_sketch_edit` rewrites the definition in place + replays the log, rebuilding every dependent feature — the parametric loop. Sketches render as Onshape-style **blue curves + plane frame** in the viewport (refreshing live on edit), listed in a left-side **feature tree** (sketches + features, eye-toggle visibility, click-to-highlight bodies) over the Part tab |
50
+ | 🗂️ Codex-style document tabs | The resident display panel gets a tab strip with a "+" menu: **Part** (Part Studio, the default) / **Assembly** (instance insert/move/remove) / **Drawing** (true hidden-line sheets); tabs are closable and keep their state |
51
+ | 📁 Multi-document file space | Named documents per workspace (`.dsh-cad/docs/`), each session bound to its own active document — new sessions start empty instead of inheriting leftovers; a folder button in the panel lists every document (preview / delete), and `cad_doc_new` / `cad_doc_open` manage the modeling target from chat |
52
+ | 📐 Engineering drawings | GB first-angle layout: front / top / left views + isometric, true OCCT hidden-line removal via the **occt.ts** kernel (an npm dependency, dashed); sheet frame, title block, overall dimensions, standard scale series; exports SVG / DXF |
53
+ | 🔗 Constraint solving & motion | **Ansatz** geometric constraint solver (**one command: `npm install ansatz-wasm`** — a wasm dependency, no Rust toolchain, no native binary): entity/constraint modeling (assembly instances ↔ rigid3 poses mapped automatically), solve-with-writeback, full DOF/residual/redundancy/suggestion diagnostics (LLM-oriented), and parametric kinematic sweeps (`cad_constraint` / `cad_solve` / `cad_motion`) |
54
+ | 📐 Geometry measurement | Exact volume (mm³), bounding box, triangle counts, DXF layers |
55
+ | 📤 On-demand export | STEP (parametric) / STL (mesh); files are written only when the user asks |
56
+ | 🖥️ Resident CAD panel | A permanent panel right of the conversation: Codex-style tabs (Part / Assembly / Drawing), tracking the latest model in real time while modeling. A host-independent liveness poll (`GET /dsh-cad/docs` → per-document op count) keeps the viewport following modeling ops even when the conversation meta pipeline goes stale, and auto-returns from a file preview |
57
+ | ⚡ Zero-copy render pipeline | worker mesh → in-memory binary → three.js typed arrays; zero base64 / zero intermediate files / zero per-step disk writes |
58
+ | 💾 Modeling document persistence | Operation log (JSON) + debounced disk mirror; automatically replayed to restore state after a process restart |
59
+ | 🖼️ Image → profile | PNG sketch/screenshot → Otsu binarization → contour tracing → extrusion-ready polygon (`cad_image_profile`) |
60
+ | 🔌 FreeCAD executor | Run the same op family on an external FreeCAD console (STEP in/out); requires a local FreeCAD install |
61
+ | ☁️ Onshape executor | Run the same op family on **Onshape** over its signed REST API — zero local install; every op compiles to a **standard Onshape feature** pushed into the target Part Studio (no Feature Studio setup). Readback is three-tier: cheap per-part STL by default, `readback: "step"` for exact-BRep-derived named meshes via a server-side STEP translation, or `readback: "none"` — build only and return the document link (~3 calls, quota-friendliest). Exports `.stl` / `.step` / `.x_t` (Parasolid). Quota-aware errors distinguish credential problems from free-plan cooldowns; the Onshape document URL is always surfaced |
62
+
63
+ ## Installation
64
+
65
+ The plugin is published to npm — one line:
66
+
67
+ ```sh
68
+ dsh plugin --profile web add dsh-cad
69
+ ```
70
+
71
+ The installer applies the bundled `cordis.patch.yml` (declared in the `dsh.bundle` manifest) automatically, so there is nothing to configure by hand.
72
+
73
+ ### Version requirements
74
+
75
+ - **Node.js** and **dsh CLI** (`@deepseek-ai/dsh`): minimum versions live in
76
+ the badge row above and the `engines` field of `package.json` — kept out of
77
+ the prose so they never go stale
78
+
79
+ ### Install from source (dev mode)
80
+
81
+ ```sh
82
+ git clone https://github.com/LAU-MARS/dsh-cad.git
83
+ cd dsh-cad
84
+ npm install && npm run build && npm test # deps include occt.ts (true-HLR drawing kernel, ~20MB wasm)
85
+
86
+ npm install -g @deepseek-ai/dsh pnpm
87
+ dsh web # let the first launch init the profile, then Ctrl-C
88
+
89
+ dsh plugin --profile web add /path/to/dsh-cad
90
+
91
+ dsh web
92
+ ```
93
+
94
+ The patch insert ships as `cordis.patch.yml` in the package root and is applied by the installer via the same `dsh.bundle` manifest.
95
+
96
+ Set `DEEPSEEK_API_KEY` and you are ready — for example:
97
+
98
+ - “open bracket.stl” → `cad_view`
99
+ - “model a 100×60×5 plate, punch a ⌀20 hole in the middle, R2 fillets on the four
100
+ corners, add a ⌀16 boss 20 tall, export plate.step”
101
+ → `cad_create_prim` + `cad_boolean` + `cad_fillet` + `cad_export`, with the 3D tab
102
+ updating live at every step
103
+ - “build a snowman” → spheres + a cone nose + a cylinder hat (precise `at`/`axis` placement)
104
+ - “add two more b1, one rotated 90 degrees” → `cad_assembly_insert` + `cad_assembly_move`, the Assembly tab updates live
105
+ - “make an A3 drawing of b1 and export dxf” → `cad_drawing` (three views + iso + dashed hidden lines + dimensions) → `cad_export` `.dxf`
106
+
107
+ ## Modeling Tool Family
108
+
109
+ | Tool | Description |
110
+ | --- | --- |
111
+ | `cad_view` | Open a CAD file and render it in the viewer (resident panel) |
112
+ | `cad_info` | Read-only geometry metadata (format / counts / bounding box / units / layers) |
113
+ | `cad_create_prim` | Primitives (mm, Z-up); `at` for placement, `axis` for orientation (exact axis-angle rotation) |
114
+ | `cad_sketch_new` | Create a named sketch (Sketch1…): {start, segments} / {circle} / flat points loop — referenced by name from the profile features |
115
+ | `cad_sketch_edit` | Redefine a sketch (回改): the definition op is rewritten in place and the log replayed — every referencing feature rebuilds |
116
+ | `cad_sketch_list` | List the document's sketches (name / form / size) |
117
+ | `cad_sketch_delete` | Delete a sketch (refused while features still reference it) |
118
+ | `cad_extrude_profile` | Extrude a closed XY-plane profile along +Z into a solid — inline points/segments/circle or a named sketch (`sketch`) |
119
+ | `cad_loft` | Loft: skin a solid through successive closed sections (each a [x,y,z,…] loop in its own plane, or a curve/`profile` section); sections may differ in shape and point count; `smooth` fits a periodic B-spline through point loops for a **crease-free** skin; `ruled` for straight sides |
120
+ | `cad_revolve` | Solid of revolution: revolve a closed profile around an axis (profile coords = (radius, height); curve segments supported — rounded rims exact); optional `angle`; accepts a named sketch |
121
+ | `cad_chamfer` | Chamfer: bevel every sharp edge with one equal distance (mm) |
122
+ | `cad_pattern` | Pattern: linear (`delta` spacing) or circular (principal axis + `at` point + sweep angle); creates copy bodies, fuse afterwards for one body |
123
+ | `cad_shell` | Shell: hollow to a wall thickness (inward, outer skin preserved); `open` lists outward normals of the faces to open (empty = sealed). Runs on the occt.ts kernel; the result is a HOSTED body |
124
+ | `cad_draft` | Draft: tilt the walls `angle` degrees about a neutral plane (mold release); `direction` is the pull direction, walls auto-selected by default. Runs on the occt.ts kernel; the result is a HOSTED body |
125
+ | `cad_sweep` | Sweep: pipe a closed 2D profile along a 3D [x,y,z,…] path; the profile is auto-placed on the start plane, so no manual orientation; accepts a named sketch |
126
+ | `cad_boolean` | fuse / cut / common (classic hole punching: plate cut cylinder) |
127
+ | `cad_fillet` | Constant-radius fillet on all sharp edges |
128
+ | `cad_transform` | Translate / Euler rotate / mirror |
129
+ | `cad_volume` | Exact BRep volume (mm³) |
130
+ | `cad_drawing` | Engineering drawing: front/top/left + isometric views, dashed hidden lines, frame, title block, overall dimensions, standard scale, A4/A3 |
131
+ | `cad_assembly_insert` | Insert a body into the assembly as a placed instance (`at` position, `rotate` orientation) |
132
+ | `cad_assembly_move` | Set an instance's absolute placement |
133
+ | `cad_assembly_remove` | Remove an instance from the assembly (the body stays) |
134
+ | `cad_constraint` | Declare the constraint model: entities (assembly-instance bindings) + constraints (distance/angle/mate/coaxial…), persisted in the op log |
135
+ | `cad_solve` | Solve with Ansatz and write poses back; returns DOF/residual/redundancy/suggestion diagnostics |
136
+ | `cad_motion` | Kinematic sweep: drive one constraint value from `from` to `to`, solve frame by frame, apply the last pose, return the motion table |
137
+ | `cad_export` | Export STEP / STL / DCPRT (the native replayable part document) to a workspace path; `target: "assembly"` writes the assembly STEP, `target: "drawing"` writes the sheet as SVG / DXF |
138
+ | `cad_delete` | Delete a body |
139
+ | `cad_docs` | List the workspace's modeling documents (id / name / bodies / updated, active marked) |
140
+ | `cad_doc_new` | Create a named document and make it the session's modeling target (start multi-part projects here) |
141
+ | `cad_doc_open` | Open an existing document (by id or name) as the session's modeling target — bodies replay exactly |
142
+ | `cad_doc_rename` | Rename a document |
143
+ | `cad_doc_delete` | Permanently delete a document (requires `confirm: true`) |
144
+ | `cad_freecad` | Run an op program on an external FreeCAD executor (optional STEP input / export) |
145
+ | `cad_fusion` | Run an op program on an external Fusion 360 executor (GUI bridge; optional export) |
146
+ | `cad_onshape` | Run an op program on the Onshape cloud (signed REST API): creates or drives a document, returns its URL; `readback: "step"` for exact BRep meshes; exports `.stl` / `.step` / `.x_t` (Parasolid) |
147
+ | `cad_image_profile` | PNG → contours → extrusion-ready polygon points |
148
+ | `cad_script` | Deterministic script evaluation for geometry math (profile point arrays, arc coordinates): `js` runs in-process (Node vm — works even when the host shell is unavailable), `py` uses a system Python |
149
+
150
+ After every modeling step: **the chat result row updates in place** (stable viewId +
151
+ versioned URL), and the resident panel's "Part" tab tracks the latest model in real time.
152
+
153
+ ## Connectors (roadmap)
154
+
155
+ Modeling runs on the **occt.ts kernel** via cadorange (OCCT in the browser —
156
+ zero install). The connectors below refer to **external CAD engines** acting as
157
+ executors for the same tool family, planned for future support:
158
+
159
+ | Connector | Suite | Platform | Status |
160
+ | --- | --- | --- | --- |
161
+ | **Built-in kernel** | CAD modeling kernel based on OCCT + WebGL, runs in the browser — zero install | All platforms (WebGL rendering) | ✅ Built-in |
162
+ | FreeCAD | open-source parametric suite — natural local executor via its Python API (console + GUI window modes) | Windows / macOS / Linux | ✅ Available (needs local install) |
163
+ | Fusion 360 | Autodesk CAD/CAM — resident add-in + spool bridge (no headless; the Fusion window doubles as a viewer) | Windows / macOS | 🧪 Experimental (`cad_fusion`) |
164
+ | SolidWorks | Dassault Systèmes industry-standard 3D CAD, COM/.NET automation | Windows only | 🚧 Windows demo scaffold (`scripts/solidworks-bridge/`) |
165
+ | Onshape | PTC cloud-native SaaS CAD, fully in the browser — signed REST API (cad.onshape.com or chamber hosts); every op compiles to a standard Onshape feature pushed into the Part Studio, results read back as per-part STL + mass properties, the cloud is the viewer | All platforms (browser) | ✅ Available (`cad_onshape`; set `DSH_ONSHAPE_ACCESS_KEY` / `DSH_ONSHAPE_SECRET_KEY`) |
166
+ | ZW3D(中望3D) | ZWSOFT all-in-one CAD/CAM | Windows / Linux | 🚧 Planned |
167
+ | GstarCAD 3D(浩辰3D) | Gstarsoft 3D CAD | Windows | 🚧 Planned |
168
+
169
+ All external engines implement the same **GeometryExecutor contract** (`available()` /
170
+ `run(opProgram) → meshes`), so the WebGL display layer never changes — swapping a
171
+ backend changes only the quality of the produced geometry.
172
+
173
+ ## Architecture
174
+
175
+ ```
176
+ cad_view(path) modeling tools (cad_create_prim, …)
177
+ → import worker (occt-import-js) → modeling worker (occt.ts WASM via cadorange)
178
+ → CadScene JSON (base64-f32) → exact BRep geometry + meshing
179
+ → GET /dsh-cad/scene/<id> → in-memory binary scene (f32/u32 packed)
180
+ → GET /dsh-cad/bin/<docId>
181
+ ↓ session presentationMeta (viewId + versioned URL) ↓
182
+ browser card + persistent "3D" tab (three.js / SVG, Z-up, XYZ axes)
183
+ ```
184
+
185
+ - **Two workers**: import (occt-import-js, read-only STEP/IGES/BREP) and modeling
186
+ (cadorange over the occt.ts wasm) are separate, both lazily started
187
+ - **Zero-copy pipeline**: modeling scenes use zero base64 / zero large JSON arrays /
188
+ zero per-step disk writes (disk mirror debounced 1.5s, replayed only on service
189
+ restart); `cad_export` is the only explicit file export
190
+ - **Modeling document**: `<workspace>/.dsh-cad/model.json` operation log; all bodies
191
+ are restored by replay after a restart
192
+ - **Drawing HLR kernel**: hidden lines run on **occt.ts** (npm dependency,
193
+ `npm i occt.ts`) — true OCCT removal, no fallback engine, a missing kernel is
194
+ a hard error. Kernel APIs: `hiddenLines()` true hidden-line
195
+ removal, byte-level STEP/BRep `readStep`/`readBrep`/`writeStep`/`writeBrep`
196
+ without MEMFS, tessellation with built-in feature-edge extraction, and a
197
+ `hasError()`/`lastError()` error contract. Geometry crosses kernels as STEP
198
+ bytes and the projected segments are remapped into the sheet frame. Kernel
199
+ dist resolution: `DSH_OCCTJS_DIST` env var → Node's own resolution of the
200
+ `occt.ts` dependency (walks up every parent node_modules — npm/yarn flat and
201
+ pnpm symlink layouts all hit) → `<repo>/../opencascade-ts/dist` (sibling
202
+ checkout) → `vendor/` → `node_modules/opencascade-ts`
203
+ - **Constraint solving (Ansatz)**: the solver is the npm dependency
204
+ **`ansatz-wasm`** (a wasm-bindgen build, single package ~538KB, zero deps) —
205
+ installing dsh-cad pulls it automatically, and `npm install ansatz-wasm` alone
206
+ upgrades it. **No Rust toolchain, no native binary, platform independent.**
207
+ Resolution: the npm package (node_modules) → a `DSH_ANSATZ_WASM` directory → a
208
+ sibling Ansatz checkout's pkg-node. The solver speaks one JSON envelope
209
+ contract, with tool-layer errors (e.g. `unsupported_constraint`) surfaced
210
+ verbatim for the LLM. Assembly placements (translate + XYZ Euler
211
+ degrees) map bidirectionally to the solver's rigid3 poses (translate +
212
+ exponential-map rotation, radians). Solver capability is staged
213
+ (point-distance-to-origin today) — the plumbing contract is finished and needs
214
+ no changes as it grows
215
+ - **Kernel chain**: modeling goes through **cadorange** (agent-native CAD
216
+ runtime; public build123d-style API for prims/curve-segment profiles (TRUE
217
+ B-spline interpolation)/loft/sweep/revolve/booleans (cleaned by default)/
218
+ fillet/chamfer/shell/draft/native mirror/centroid, plus the supported
219
+ `cadorange/occt` escape hatch for viewer tessellation, engineering-drawing
220
+ HLR (zero STEP hops, one wasm session) and wire construction); the direct
221
+ occt.ts adapter stays as the fallback tier (`DSH_CAD_KERNEL` pins one; see
222
+ docs/cadorange-integration.md for the verb-by-verb mapping). Editing after
223
+ shelling is seamless. Assembly STEP export writes a **structured document**
224
+ (cadorange
225
+ `exportStepAssembly`): one root product plus one named, placed child
226
+ per instance — instance separation survives the file (STL stays a single
227
+ fused mesh). Instance separation stays in
228
+ the assembly scene/document for the fuse paths.
229
+ Face selection matches `describe()` plane normals (outermost along ±normal)
230
+ - **Client**: esbuild single-file CJS factory (three.js inlined ~560KB, react provided
231
+ by the host module table), Z-up CAD convention, empty scene with XYZ axis labels
232
+ and a ground grid always displayed
233
+
234
+ ## Theming (dark mode)
235
+
236
+ The viewer follows the host appearance automatically. DSH publishes the theme as
237
+ `color-scheme` on `<html>` plus `body[data-ds-dark-theme]`; the plugin resolves
238
+ that (falling back to `prefers-color-scheme`) and themes every surface the host
239
+ tokens cannot reach:
240
+
241
+ - the viewport slab behind the transparent WebGL canvas and 2D scenes,
242
+ - default 2D entity strokes (DXF / engineering-drawing entities),
243
+ - the Three.js ground grid, XYZ axis triad/labels, feature edges, default mesh
244
+ color and sketch overlays,
245
+ - the ViewCube face plate (canvas texture).
246
+
247
+ A theme flip restyles mounted cards and the resident panel live — no reload and
248
+ no lost camera. Engineering drawings loaded as SVG keep a white sheet; in dark
249
+ mode it is framed as paper on the dark slab instead of filling the viewport.
250
+
251
+ Hosts can override the surfaces the plugin owns; an explicit definition wins in
252
+ either theme:
253
+
254
+ | CSS variable | Applies to | Default |
255
+ | --- | --- | --- |
256
+ | `--dsh-cad-bg` | viewport background (any CSS `background` value) | light `#f5f6f8→#e9ecf0`, dark `#1b1b1c→#151517` |
257
+ | `--dsh-cad-stroke` | default 2D entity stroke | `#4c5561` light / `#c8ced6` dark |
258
+ | `--dsh-cad-paper` | drawing paper behind SVG sheets | `#ffffff` |
259
+
260
+ ```css
261
+ :root { --dsh-cad-bg: #0b0d10; } /* flat canvas in both themes */
262
+ ```
263
+
264
+ ## Tests
265
+
266
+ ```sh
267
+ npm test # full suite: converters / modeling worker (exact volume assertions) / DCPRT round-trip / FreeCAD + Onshape executors / image profiles / binary pipeline / document persistence
268
+ node test/route-check.mjs # JSON scene routing layer
269
+ node test/visual/serve.mjs # browser card/tab visual verification page (http://127.0.0.1:3987)
270
+ ```
271
+
272
+ Representative assertions covered: the boolean-punched volume exactly equals the
273
+ analytic value (28429.20 mm³), L-shaped profile extrusion 3000 mm³, volumes and
274
+ bounding-box flips of sphere/cone/torus placed with `at`/`axis`, 8-byte alignment
275
+ of the binary packing, an STL export round-trip (export → read back by the
276
+ phase-1 parser), and a DCPRT document round-trip (serialize → replay on the
277
+ OCCT worker → exact bounds).
278
+
279
+ ## Known Limitations
280
+
281
+ - DWG (closed-source) is unsupported; DXF bulge arcs are approximated by chords;
282
+ glTF/3MF viewing is not implemented (the structure is reserved)
283
+ - `cad_fillet` is all-edge constant-radius (per-edge selection is unstable under
284
+ embind); chamfer is not implemented
285
+ - Sketch extrusion supports polygon profiles only (arc profiles are constructed by
286
+ boolean combinations of cylinders/tori)
287
+ - dsh framework limitation: an already-mounted single slot (the right-side details
288
+ panel itself) does not respond to components registered later, so the persistent
289
+ view is provided as the "3D" view tab (a list slot, the official composition)
290
+ - The host reads CAD files via node:fs (the platform fs service supports UTF-8 text
291
+ only and cannot carry binary data)
292
+
293
+ ## Community
294
+
295
+ | QQ Group (🇨🇳 China) | Feishu (🇨🇳 China) | X / Twitter (🌍 International) | Discord (🌍 International) |
296
+ | --- | --- | --- | --- |
297
+ | `485038246` (join note: dsh-cad) | 🚧 Coming soon | 🚧 Coming soon | 🚧 Coming soon |
298
+
299
+ <!-- TODO: fill in the X handle / Discord invite link before publishing -->
300
+
301
+ ## Contributors
302
+
303
+ Auto-generated from the commit history — thanks to everyone who has contributed!
304
+
305
+ [![Contributors](https://contrib.rocks/image?repo=LAU-MARS/dsh-cad)](https://github.com/LAU-MARS/dsh-cad/graphs/contributors)
306
+
307
+ ## License
308
+
309
+ MIT