brepjs-verify 0.3.0 → 0.8.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/CHANGELOG.md +78 -0
- package/README.md +51 -12
- package/dist/brepjs-verify.cjs +1 -1
- package/dist/brepjs-verify.js +1 -1
- package/dist/cli/main.cjs +42 -3
- package/dist/cli/main.js +42 -3
- package/dist/cli/openBrowser.d.ts +23 -0
- package/dist/{diff-Covv1XuJ.js → diff-BCECMYSQ.js} +160 -10
- package/dist/{diff-mxAOOl_m.cjs → diff-DDEu6YJM.cjs} +160 -10
- package/dist/index.d.ts +1 -1
- package/dist/mcp/server.cjs +400 -0
- package/dist/mcp/server.d.ts +10 -0
- package/dist/mcp/server.js +400 -0
- package/dist/mcp/tools.d.ts +77 -0
- package/dist/sandbox/runProgram.d.ts +62 -0
- package/dist/sandbox/runRecord.d.ts +20 -0
- package/dist/snapshot/shoot.cjs +2 -1
- package/dist/snapshot/shoot.d.ts +2 -0
- package/dist/snapshot/shoot.js +2 -1
- package/dist/verify/expected.d.ts +15 -0
- package/dist/verify/report.d.ts +13 -0
- package/package.json +8 -4
- package/reference/llms-full.txt +2052 -0
- package/reference/llms.txt +1810 -0
- package/viewer/dist/assets/brepjs-NH9yA5tB.js +59 -0
- package/viewer/dist/assets/index-CnQ8btWD.js +4167 -0
- package/viewer/dist/assets/kernelWorker-CMzbzBfs.js +1 -0
- package/viewer/dist/index.html +1 -1
- package/viewer/dist/assets/brepjs-CI5VXw8W.js +0 -57
- package/viewer/dist/assets/index-CiN0lKoi.js +0 -4167
- package/viewer/dist/assets/kernelWorker-BtcMpY8t.js +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,83 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.8.0](https://github.com/andymai/brepjs/compare/brepjs-verify-v0.7.1...brepjs-verify-v0.8.0) (2026-06-13)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **brepjs-verify:** auto-open browser on --serve + document the MCP server ([#1308](https://github.com/andymai/brepjs/issues/1308)) ([de4272c](https://github.com/andymai/brepjs/commit/de4272c9196f70baec4ff762a33636fccef4c012))
|
|
9
|
+
* **brepjs-verify:** burn bbox dimensions into agent snapshots ([#1280](https://github.com/andymai/brepjs/issues/1280)) ([25b6b8d](https://github.com/andymai/brepjs/commit/25b6b8df68909a41eaef5307519a29a8a05ccc00))
|
|
10
|
+
* **verify:** add center of mass to the verify report ([#1288](https://github.com/andymai/brepjs/issues/1288)) ([5738600](https://github.com/andymai/brepjs/commit/5738600e31f56c00d928d62e32ab9d5e8220b377))
|
|
11
|
+
* **verify:** add export_part MCP tool and sandbox export ([#1316](https://github.com/andymai/brepjs/issues/1316)) ([8a52f90](https://github.com/andymai/brepjs/commit/8a52f90c5b3d76c8bf6ef50a36c6977833af59bb))
|
|
12
|
+
* **verify:** add JSONL run-record provenance for sandbox runs ([#1309](https://github.com/andymai/brepjs/issues/1309)) ([6bda9b6](https://github.com/andymai/brepjs/commit/6bda9b630b8e4edf6eb860002c58b3e36cf1bfd4))
|
|
13
|
+
* **verify:** add manifold flag to the topology channel ([#1291](https://github.com/andymai/brepjs/issues/1291)) ([5ea5bb4](https://github.com/andymai/brepjs/commit/5ea5bb4db5bc8539f2773752e1c42770c91b5e0d))
|
|
14
|
+
* **verify:** add MCP server with run_program tool (stdio) ([#1300](https://github.com/andymai/brepjs/issues/1300)) ([e3c2c9e](https://github.com/andymai/brepjs/commit/e3c2c9e678dd719608cea8b6ee38101de9775e5d))
|
|
15
|
+
* **verify:** add topology counts to the verify report ([#1285](https://github.com/andymai/brepjs/issues/1285)) ([17a0eed](https://github.com/andymai/brepjs/commit/17a0eede727ac29007591cb0249274a35896facb))
|
|
16
|
+
* **verify:** sandbox executor — run agent code in an isolated child process ([#1295](https://github.com/andymai/brepjs/issues/1295)) ([8b72aa2](https://github.com/andymai/brepjs/commit/8b72aa2e272a58aa6d3886b8304eeaefb1a09b2e))
|
|
17
|
+
* **verify:** validate each body of multi-solid assemblies ([#1293](https://github.com/andymai/brepjs/issues/1293)) ([deb682f](https://github.com/andymai/brepjs/commit/deb682f1104179f261f232be7d94ceb154985328))
|
|
18
|
+
* **viewer:** click-to-inspect face picking in verify --serve ([#1278](https://github.com/andymai/brepjs/issues/1278)) ([735dc04](https://github.com/andymai/brepjs/commit/735dc0401143ff47046a79e6fb7bac53cf00a91e))
|
|
19
|
+
* **viewer:** measurements info panel in verify --serve ([#1277](https://github.com/andymai/brepjs/issues/1277)) ([c1ccf1d](https://github.com/andymai/brepjs/commit/c1ccf1d7c50ab43dc0444468f92fcf9365fda9da))
|
|
20
|
+
* **viewer:** orthographic/perspective projection toggle ([#1281](https://github.com/andymai/brepjs/issues/1281)) ([96673e4](https://github.com/andymai/brepjs/commit/96673e45e1ee316f9d26e52c995b4daba691e8b0))
|
|
21
|
+
* **viewer:** section/clipping plane in verify --serve ([#1279](https://github.com/andymai/brepjs/issues/1279)) ([cc0d00b](https://github.com/andymai/brepjs/commit/cc0d00b7a6296cc698fce1ae42b7503e6d47c032))
|
|
22
|
+
* **viewer:** shared ViewerControls toolbar; interactive verify --serve ([#1275](https://github.com/andymai/brepjs/issues/1275)) ([139ae15](https://github.com/andymai/brepjs/commit/139ae15a29d8a7ad5e520ba21d6dc9788242c089))
|
|
23
|
+
|
|
24
|
+
## [0.7.1](https://github.com/andymai/brepjs/compare/brepjs-verify-v0.7.0...brepjs-verify-v0.7.1) (2026-06-13)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
### Bug Fixes
|
|
28
|
+
|
|
29
|
+
* **brepjs-verify:** repair preview viewer + GLB Y-up/materials fidelity ([#1271](https://github.com/andymai/brepjs/issues/1271)) ([2823d21](https://github.com/andymai/brepjs/commit/2823d212e2fc5f79e785911ec2b9f3320bdfdbbf))
|
|
30
|
+
|
|
31
|
+
## [0.7.0](https://github.com/andymai/brepjs/compare/brepjs-verify-v0.6.0...brepjs-verify-v0.7.0) (2026-06-10)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
### Features
|
|
35
|
+
|
|
36
|
+
* **brepjs-verify:** eval-driven skill, hint, and reference improvements ([#1219](https://github.com/andymai/brepjs/issues/1219)) ([1a9b80f](https://github.com/andymai/brepjs/commit/1a9b80f3d3dbb44d7a8ae2f601ff305b70534efe))
|
|
37
|
+
* **brepjs-verify:** live text-to-cad eval flywheel ([#1215](https://github.com/andymai/brepjs/issues/1215)) ([4e81fc4](https://github.com/andymai/brepjs/commit/4e81fc4053491ce3e08182d57d76bd649252ea3c))
|
|
38
|
+
* **brepjs-verify:** standalone bundled CLI + rename from brepjs-cad ([#1211](https://github.com/andymai/brepjs/issues/1211)) ([05b3799](https://github.com/andymai/brepjs/commit/05b3799a0e9ee4968d4cac92f3a2ea236e39cd35))
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
### Bug Fixes
|
|
42
|
+
|
|
43
|
+
* **brepjs-verify:** correct fillet/chamfer arg order in no-edges hints ([#1218](https://github.com/andymai/brepjs/issues/1218)) ([835f13a](https://github.com/andymai/brepjs/commit/835f13ac966b4264ba56a5cfc371bbbbbd1a0f01))
|
|
44
|
+
* **brepjs-verify:** point skills entry at ./skill directory, not SKILL.md ([#1270](https://github.com/andymai/brepjs/issues/1270)) ([9413a57](https://github.com/andymai/brepjs/commit/9413a57d8c2cac943371e75bcbaf11b3fdd9a657))
|
|
45
|
+
|
|
46
|
+
## [0.6.0](https://github.com/andymai/brepjs/compare/brepjs-verify-v0.5.1...brepjs-verify-v0.6.0) (2026-06-09)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
### Features
|
|
50
|
+
|
|
51
|
+
* **brepjs-verify:** eval-driven skill, hint, and reference improvements ([#1219](https://github.com/andymai/brepjs/issues/1219)) ([1a9b80f](https://github.com/andymai/brepjs/commit/1a9b80f3d3dbb44d7a8ae2f601ff305b70534efe))
|
|
52
|
+
* **brepjs-verify:** live text-to-cad eval flywheel ([#1215](https://github.com/andymai/brepjs/issues/1215)) ([4e81fc4](https://github.com/andymai/brepjs/commit/4e81fc4053491ce3e08182d57d76bd649252ea3c))
|
|
53
|
+
* **brepjs-verify:** standalone bundled CLI + rename from brepjs-cad ([#1211](https://github.com/andymai/brepjs/issues/1211)) ([05b3799](https://github.com/andymai/brepjs/commit/05b3799a0e9ee4968d4cac92f3a2ea236e39cd35))
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
### Bug Fixes
|
|
57
|
+
|
|
58
|
+
* **brepjs-verify:** correct fillet/chamfer arg order in no-edges hints ([#1218](https://github.com/andymai/brepjs/issues/1218)) ([835f13a](https://github.com/andymai/brepjs/commit/835f13ac966b4264ba56a5cfc371bbbbbd1a0f01))
|
|
59
|
+
|
|
60
|
+
## [0.5.0](https://github.com/andymai/brepjs/compare/brepjs-verify-v0.4.1...brepjs-verify-v0.5.0) (2026-06-08)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
### Features
|
|
64
|
+
|
|
65
|
+
* **brepjs-verify:** eval-driven skill, hint, and reference improvements ([#1219](https://github.com/andymai/brepjs/issues/1219)) ([1a9b80f](https://github.com/andymai/brepjs/commit/1a9b80f3d3dbb44d7a8ae2f601ff305b70534efe))
|
|
66
|
+
* **brepjs-verify:** live text-to-cad eval flywheel ([#1215](https://github.com/andymai/brepjs/issues/1215)) ([4e81fc4](https://github.com/andymai/brepjs/commit/4e81fc4053491ce3e08182d57d76bd649252ea3c))
|
|
67
|
+
* **brepjs-verify:** standalone bundled CLI + rename from brepjs-cad ([#1211](https://github.com/andymai/brepjs/issues/1211)) ([05b3799](https://github.com/andymai/brepjs/commit/05b3799a0e9ee4968d4cac92f3a2ea236e39cd35))
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
### Bug Fixes
|
|
71
|
+
|
|
72
|
+
* **brepjs-verify:** correct fillet/chamfer arg order in no-edges hints ([#1218](https://github.com/andymai/brepjs/issues/1218)) ([835f13a](https://github.com/andymai/brepjs/commit/835f13ac966b4264ba56a5cfc371bbbbbd1a0f01))
|
|
73
|
+
|
|
74
|
+
## [0.4.0](https://github.com/andymai/brepjs/compare/brepjs-verify-v0.3.0...brepjs-verify-v0.4.0) (2026-06-04)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
### Features
|
|
78
|
+
|
|
79
|
+
* **brepjs-verify:** eval-driven skill, hint, and reference improvements ([#1219](https://github.com/andymai/brepjs/issues/1219)) ([1a9b80f](https://github.com/andymai/brepjs/commit/1a9b80f3d3dbb44d7a8ae2f601ff305b70534efe))
|
|
80
|
+
|
|
3
81
|
## [0.3.0](https://github.com/andymai/brepjs/compare/brepjs-verify-v0.2.1...brepjs-verify-v0.3.0) (2026-06-04)
|
|
4
82
|
|
|
5
83
|
|
package/README.md
CHANGED
|
@@ -21,6 +21,15 @@ The CLI the skill invokes. Install it in **your** project, where `brepjs` + the
|
|
|
21
21
|
npm i -D brepjs-verify brepjs occt-wasm
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
+
## API reference
|
|
25
|
+
|
|
26
|
+
The package bundles brepjs's full API reference for offline/agent use — the complete export surface with signatures and examples:
|
|
27
|
+
|
|
28
|
+
- `reference/llms-full.txt` — every export, full signatures (the deep reference)
|
|
29
|
+
- `reference/llms.txt` — the same content as a quicker index
|
|
30
|
+
|
|
31
|
+
Point your agent at `node_modules/brepjs-verify/reference/llms-full.txt` for anything the skill's curated references don't cover.
|
|
32
|
+
|
|
24
33
|
## The `.brep.ts` contract
|
|
25
34
|
|
|
26
35
|
A model is a module whose default export is a zero-arg function returning a shape (or a `Result<shape>`):
|
|
@@ -36,28 +45,58 @@ export default () => box(40, 20, 10, { centered: true });
|
|
|
36
45
|
```
|
|
37
46
|
npx -y brepjs-verify part.brep.ts --step part.step --json report.json # primary STEP + deterministic report
|
|
38
47
|
npx -y brepjs-verify part.brep.ts --snapshot shots/ # iso/front/top/right PNGs
|
|
39
|
-
npx -y brepjs-verify part.brep.ts --serve #
|
|
48
|
+
npx -y brepjs-verify part.brep.ts --serve # preview server + opens the viewer in your browser
|
|
49
|
+
npx -y brepjs-verify part.brep.ts --serve --no-open # preview server; just print the URL (no browser)
|
|
40
50
|
```
|
|
41
51
|
|
|
42
|
-
`--snapshot`/`--serve` use the bundled viewer (shipped under `viewer/dist`, including the OCCT WASM). The
|
|
52
|
+
`--snapshot`/`--serve` use the bundled viewer (shipped under `viewer/dist`, including the OCCT WASM). The `--serve` link is interactive: a toolbar offers view presets + fit, solid/wireframe/x-ray modes, edge/grid toggles, a turntable, click-to-inspect face picking, a section/clipping plane, a measurements panel, and an in-browser PNG screenshot. `--snapshot` loads the same page with `ui=0` to suppress the toolbar, and burns the bounding-box size into each PNG (`dims=1`) so the agent can read scale from the image.
|
|
53
|
+
|
|
54
|
+
`--serve` prints the viewer URL and, in an interactive terminal, opens it in your default browser. Auto-open is skipped when it would be unwanted — when the server is reused (a tab is already open), under CI, when output is piped (non-TTY, e.g. agent runs), or on Linux with no display server. Pass `--no-open` to always suppress it.
|
|
43
55
|
|
|
44
56
|
## CLI reference
|
|
45
57
|
|
|
46
58
|
The `brepjs-verify` bin is a multi-command CLI. `verify` is the default command, so `brepjs-verify part.brep.ts` runs it directly.
|
|
47
59
|
|
|
48
|
-
| Command | What it does
|
|
49
|
-
| ------------------------------- |
|
|
50
|
-
| `brepjs-verify verify <file>` | Default command. Loads the part, runs deterministic checks, prints the JSON report. Flags: `--json <out>`, `--step <out>`, `--glb <out>`, `--snapshot <dir>`, `--serve
|
|
51
|
-
| `brepjs-verify init <name>` | Scaffolds a parameterized `<name>.brep.ts` + `tsconfig.json` + `README.md` into `./<name>` (or `--out <dir>`). Never overwrites existing files.
|
|
52
|
-
| `brepjs-verify watch <file>` | Re-verifies on every save until Ctrl-C (debounced; watches the parent dir to survive editor rename-on-save).
|
|
53
|
-
| `brepjs-verify export <file>` | Batch artifacts behind a validity gate: `--step`, `--glb`, `--stl`, or `--all`; `--out <dir>` (default `.`). Exits non-zero on failure.
|
|
54
|
-
| `brepjs-verify measure <a> [b]` | Measurements for one part; with a second module, the distance between the two parts.
|
|
55
|
-
| `brepjs-verify diff <a> <b>` | Compares the measurements of a baseline and a comparison module.
|
|
56
|
-
| `brepjs-verify snapshot` | Multi-view PNG capture — surfaced via `verify --snapshot <dir>`. Requires the optional `puppeteer`/Chrome dependency; degrades with a clear message when absent.
|
|
57
|
-
| `brepjs-verify serve` | Preview server with a `?dir=&file=` deep link — surfaced via `verify --serve`.
|
|
60
|
+
| Command | What it does |
|
|
61
|
+
| ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
62
|
+
| `brepjs-verify verify <file>` | Default command. Loads the part, runs deterministic checks, prints the JSON report. Flags: `--json <out>`, `--step <out>`, `--glb <out>`, `--snapshot <dir>`, `--serve`, `--no-open` (with `--serve`, don't auto-open the browser). Exits non-zero when the report is not `ok` (unless `--serve`). |
|
|
63
|
+
| `brepjs-verify init <name>` | Scaffolds a parameterized `<name>.brep.ts` + `tsconfig.json` + `README.md` into `./<name>` (or `--out <dir>`). Never overwrites existing files. |
|
|
64
|
+
| `brepjs-verify watch <file>` | Re-verifies on every save until Ctrl-C (debounced; watches the parent dir to survive editor rename-on-save). |
|
|
65
|
+
| `brepjs-verify export <file>` | Batch artifacts behind a validity gate: `--step`, `--glb`, `--stl`, or `--all`; `--out <dir>` (default `.`). Exits non-zero on failure. |
|
|
66
|
+
| `brepjs-verify measure <a> [b]` | Measurements for one part; with a second module, the distance between the two parts. |
|
|
67
|
+
| `brepjs-verify diff <a> <b>` | Compares the measurements of a baseline and a comparison module. |
|
|
68
|
+
| `brepjs-verify snapshot` | Multi-view PNG capture — surfaced via `verify --snapshot <dir>`. Requires the optional `puppeteer`/Chrome dependency; degrades with a clear message when absent. |
|
|
69
|
+
| `brepjs-verify serve` | Preview server with a `?dir=&file=` deep link — surfaced via `verify --serve`. Auto-opens the browser in an interactive terminal (suppressed under CI / non-TTY / no display, or with `--no-open`). |
|
|
58
70
|
|
|
59
71
|
Every command writes a single machine-readable JSON document to stdout; diagnostics (paths, kernel chatter, watch notices) go to stderr.
|
|
60
72
|
|
|
73
|
+
## MCP server
|
|
74
|
+
|
|
75
|
+
`brepjs-verify-mcp` is a stdio [MCP](https://modelcontextprotocol.io) server that exposes the verify substrate to MCP-capable agents (Claude Code, Claude Desktop, any MCP client). It currently provides one tool:
|
|
76
|
+
|
|
77
|
+
| Tool | Input | Returns |
|
|
78
|
+
| ------------- | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
79
|
+
| `run_program` | `{ code: string, timeoutMs?: number }` | Executes the brepjs `.brep.ts` source in an isolated, timeout/OOM-bounded sandbox and returns the verification report (validity, measurements, topology) as JSON. `isError` is set when the part fails checks, times out, or crashes. |
|
|
80
|
+
|
|
81
|
+
This is the closed _build → verify_ loop as a single call: the agent sends part source, gets back the deterministic report. The program runs in a separate process with a wall-clock timeout and a memory cap, so a runaway part can't hang the agent.
|
|
82
|
+
|
|
83
|
+
### Connect (local build)
|
|
84
|
+
|
|
85
|
+
Build the package, then register the server by absolute path. Run both commands from the package root (`packages/brepjs-verify`), where `dist/` is emitted — `$(pwd)` is resolved by your shell at that location:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
npm run build # emits dist/mcp/server.js
|
|
89
|
+
claude mcp add brepjs-verify -- node "$(pwd)/dist/mcp/server.js"
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Once the package is published to npm, the same server is available without a local build:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
claude mcp add brepjs-verify -- npx -y --package brepjs-verify brepjs-verify-mcp
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
The server runs locally as a child process of your agent (stdio) — geometry never leaves your machine.
|
|
99
|
+
|
|
61
100
|
## Examples gallery
|
|
62
101
|
|
|
63
102
|
Few-shot examples live under `skill/examples/<name>.brep.ts`, each with a `<name>.expected.json` baseline. Grouped by category:
|
package/dist/brepjs-verify.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_diff = require("./diff-
|
|
2
|
+
const require_diff = require("./diff-DDEu6YJM.cjs");
|
|
3
3
|
exports.DEFAULT_TOLERANCE_PCT = require_diff.DEFAULT_TOLERANCE_PCT;
|
|
4
4
|
exports.TYPECHECK_CODE = require_diff.TYPECHECK_CODE;
|
|
5
5
|
exports.emptyReport = require_diff.emptyReport;
|
package/dist/brepjs-verify.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as typecheckPart, c as isExpectedDims, d as emptyReport, i as TYPECHECK_CODE, l as pctDelta, m as serializeReport, n as runMeasure, o as DEFAULT_TOLERANCE_PCT, r as runPart, s as evaluateExpected, t as runDiff, u as runChecks } from "./diff-
|
|
1
|
+
import { a as typecheckPart, c as isExpectedDims, d as emptyReport, i as TYPECHECK_CODE, l as pctDelta, m as serializeReport, n as runMeasure, o as DEFAULT_TOLERANCE_PCT, r as runPart, s as evaluateExpected, t as runDiff, u as runChecks } from "./diff-BCECMYSQ.js";
|
|
2
2
|
export { DEFAULT_TOLERANCE_PCT, TYPECHECK_CODE, emptyReport, evaluateExpected, isExpectedDims, pctDelta, runChecks, runDiff, runMeasure, runPart, serializeReport, typecheckPart };
|
package/dist/cli/main.cjs
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
-
const require_diff = require("../diff-
|
|
3
|
+
const require_diff = require("../diff-DDEu6YJM.cjs");
|
|
4
4
|
let node_url = require("node:url");
|
|
5
5
|
let node_fs = require("node:fs");
|
|
6
6
|
let node_path = require("node:path");
|
|
7
7
|
let commander = require("commander");
|
|
8
8
|
let node_os = require("node:os");
|
|
9
|
+
let node_child_process = require("node:child_process");
|
|
9
10
|
//#region src/cli/scaffold.ts
|
|
10
11
|
function partTemplate(name) {
|
|
11
12
|
return `import { box, cut, unwrap } from 'brepjs';
|
|
@@ -177,6 +178,43 @@ async function exportPart(modulePath, formats, outDir) {
|
|
|
177
178
|
};
|
|
178
179
|
}
|
|
179
180
|
//#endregion
|
|
181
|
+
//#region src/cli/openBrowser.ts
|
|
182
|
+
/**
|
|
183
|
+
* Whether `--serve` should auto-open the browser for the current environment.
|
|
184
|
+
*
|
|
185
|
+
* Opens only for an interactive session — suppressed under CI, when stderr is
|
|
186
|
+
* not a TTY (agent/piped runs), or on Linux without a display server — so
|
|
187
|
+
* automation never spawns a browser unexpectedly. An explicit `--no-open` is a
|
|
188
|
+
* separate, always-on override handled by the caller.
|
|
189
|
+
*/
|
|
190
|
+
function shouldAutoOpen({ env = process.env, platform = process.platform, isTTY = Boolean(process.stderr.isTTY) } = {}) {
|
|
191
|
+
if (env["CI"]) return false;
|
|
192
|
+
if (!isTTY) return false;
|
|
193
|
+
if (platform === "linux" && !env["DISPLAY"] && !env["WAYLAND_DISPLAY"]) return false;
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
196
|
+
/** The platform-specific command that opens `url` in the default browser. */
|
|
197
|
+
function browserCommand(url, platform) {
|
|
198
|
+
if (platform === "darwin") return ["open", [url]];
|
|
199
|
+
if (platform === "win32") return ["rundll32", ["url.dll,FileProtocolHandler", url]];
|
|
200
|
+
return ["xdg-open", [url]];
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Best-effort open of `url` in the default browser. Never throws and never
|
|
204
|
+
* blocks — a missing opener just leaves the printed URL as the fallback.
|
|
205
|
+
*/
|
|
206
|
+
function openBrowser(url, platform = process.platform) {
|
|
207
|
+
try {
|
|
208
|
+
const [cmd, args] = browserCommand(url, platform);
|
|
209
|
+
const child = (0, node_child_process.spawn)(cmd, args, {
|
|
210
|
+
stdio: "ignore",
|
|
211
|
+
detached: true
|
|
212
|
+
});
|
|
213
|
+
child.on("error", () => {});
|
|
214
|
+
child.unref();
|
|
215
|
+
} catch {}
|
|
216
|
+
}
|
|
217
|
+
//#endregion
|
|
180
218
|
//#region src/cli/main.ts
|
|
181
219
|
console.log = (...args) => {
|
|
182
220
|
process.stderr.write(args.map(String).join(" ") + "\n");
|
|
@@ -192,7 +230,7 @@ async function loadSnapshotShoot() {
|
|
|
192
230
|
}
|
|
193
231
|
var program = new commander.Command();
|
|
194
232
|
program.name("brepjs-verify");
|
|
195
|
-
program.command("verify", { isDefault: true }).argument("<file>", "path to a .brep.ts module with a default-exported part function").option("--step <out>", "write the primary STEP artifact to this path").option("--glb <out>", "write a derived GLB preview to this path").option("--json <out>", "write the JSON report to this path").option("--check", "type-check the part (against brepjs types) before running; skip execution on type errors").option("--snapshot <dir>", "render iso/front/top/right PNGs to this dir (requires built viewer)").option("--serve", "after verifying, start a preview server and print a ?dir=&file= deep link (stays running)").action(async (file, opts) => {
|
|
233
|
+
program.command("verify", { isDefault: true }).argument("<file>", "path to a .brep.ts module with a default-exported part function").option("--step <out>", "write the primary STEP artifact to this path").option("--glb <out>", "write a derived GLB preview to this path").option("--json <out>", "write the JSON report to this path").option("--check", "type-check the part (against brepjs types) before running; skip execution on type errors").option("--snapshot <dir>", "render iso/front/top/right PNGs to this dir (requires built viewer)").option("--serve", "after verifying, start a preview server and print a ?dir=&file= deep link (stays running)").option("--no-open", "with --serve, do not auto-open the browser (just print the viewer URL)").action(async (file, opts) => {
|
|
196
234
|
const wantStep = Boolean(opts.step) || Boolean(opts.snapshot) || Boolean(opts.serve);
|
|
197
235
|
const { report, step, glb, shape } = await require_diff.runPart((0, node_path.resolve)(file), {
|
|
198
236
|
step: wantStep,
|
|
@@ -227,8 +265,9 @@ program.command("verify", { isDefault: true }).argument("<file>", "path to a .br
|
|
|
227
265
|
if (!require_diff.reportOk(report)) process.exitCode = 1;
|
|
228
266
|
if (Boolean(opts.serve) && stepPath !== void 0 && require_diff.reportOk(report) && stepPath) {
|
|
229
267
|
const { serve } = await Promise.resolve().then(() => require("../snapshot/serve.cjs"));
|
|
230
|
-
const { url } = await serve({ file: stepPath });
|
|
268
|
+
const { url, reused } = await serve({ file: stepPath });
|
|
231
269
|
process.stderr.write(`viewer: ${url}\n`);
|
|
270
|
+
if (!reused && opts.open && shouldAutoOpen()) openBrowser(url);
|
|
232
271
|
}
|
|
233
272
|
});
|
|
234
273
|
program.command("measure").argument("<a>", "path to a .brep.ts module").argument("[b]", "optional second module; if given, measures distance between the two parts").action(async (a, b) => {
|
package/dist/cli/main.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { f as pushError, h as loadBrep, m as serializeReport, n as runMeasure, p as reportOk, r as runPart, t as runDiff } from "../diff-
|
|
2
|
+
import { f as pushError, h as loadBrep, m as serializeReport, n as runMeasure, p as reportOk, r as runPart, t as runDiff } from "../diff-BCECMYSQ.js";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { existsSync, mkdirSync, realpathSync, watch, writeFileSync } from "node:fs";
|
|
5
5
|
import { basename, dirname, join, resolve } from "node:path";
|
|
6
6
|
import { Command } from "commander";
|
|
7
7
|
import { tmpdir } from "node:os";
|
|
8
|
+
import { spawn } from "node:child_process";
|
|
8
9
|
//#region src/cli/scaffold.ts
|
|
9
10
|
function partTemplate(name) {
|
|
10
11
|
return `import { box, cut, unwrap } from 'brepjs';
|
|
@@ -176,6 +177,43 @@ async function exportPart(modulePath, formats, outDir) {
|
|
|
176
177
|
};
|
|
177
178
|
}
|
|
178
179
|
//#endregion
|
|
180
|
+
//#region src/cli/openBrowser.ts
|
|
181
|
+
/**
|
|
182
|
+
* Whether `--serve` should auto-open the browser for the current environment.
|
|
183
|
+
*
|
|
184
|
+
* Opens only for an interactive session — suppressed under CI, when stderr is
|
|
185
|
+
* not a TTY (agent/piped runs), or on Linux without a display server — so
|
|
186
|
+
* automation never spawns a browser unexpectedly. An explicit `--no-open` is a
|
|
187
|
+
* separate, always-on override handled by the caller.
|
|
188
|
+
*/
|
|
189
|
+
function shouldAutoOpen({ env = process.env, platform = process.platform, isTTY = Boolean(process.stderr.isTTY) } = {}) {
|
|
190
|
+
if (env["CI"]) return false;
|
|
191
|
+
if (!isTTY) return false;
|
|
192
|
+
if (platform === "linux" && !env["DISPLAY"] && !env["WAYLAND_DISPLAY"]) return false;
|
|
193
|
+
return true;
|
|
194
|
+
}
|
|
195
|
+
/** The platform-specific command that opens `url` in the default browser. */
|
|
196
|
+
function browserCommand(url, platform) {
|
|
197
|
+
if (platform === "darwin") return ["open", [url]];
|
|
198
|
+
if (platform === "win32") return ["rundll32", ["url.dll,FileProtocolHandler", url]];
|
|
199
|
+
return ["xdg-open", [url]];
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Best-effort open of `url` in the default browser. Never throws and never
|
|
203
|
+
* blocks — a missing opener just leaves the printed URL as the fallback.
|
|
204
|
+
*/
|
|
205
|
+
function openBrowser(url, platform = process.platform) {
|
|
206
|
+
try {
|
|
207
|
+
const [cmd, args] = browserCommand(url, platform);
|
|
208
|
+
const child = spawn(cmd, args, {
|
|
209
|
+
stdio: "ignore",
|
|
210
|
+
detached: true
|
|
211
|
+
});
|
|
212
|
+
child.on("error", () => {});
|
|
213
|
+
child.unref();
|
|
214
|
+
} catch {}
|
|
215
|
+
}
|
|
216
|
+
//#endregion
|
|
179
217
|
//#region src/cli/main.ts
|
|
180
218
|
console.log = (...args) => {
|
|
181
219
|
process.stderr.write(args.map(String).join(" ") + "\n");
|
|
@@ -191,7 +229,7 @@ async function loadSnapshotShoot() {
|
|
|
191
229
|
}
|
|
192
230
|
var program = new Command();
|
|
193
231
|
program.name("brepjs-verify");
|
|
194
|
-
program.command("verify", { isDefault: true }).argument("<file>", "path to a .brep.ts module with a default-exported part function").option("--step <out>", "write the primary STEP artifact to this path").option("--glb <out>", "write a derived GLB preview to this path").option("--json <out>", "write the JSON report to this path").option("--check", "type-check the part (against brepjs types) before running; skip execution on type errors").option("--snapshot <dir>", "render iso/front/top/right PNGs to this dir (requires built viewer)").option("--serve", "after verifying, start a preview server and print a ?dir=&file= deep link (stays running)").action(async (file, opts) => {
|
|
232
|
+
program.command("verify", { isDefault: true }).argument("<file>", "path to a .brep.ts module with a default-exported part function").option("--step <out>", "write the primary STEP artifact to this path").option("--glb <out>", "write a derived GLB preview to this path").option("--json <out>", "write the JSON report to this path").option("--check", "type-check the part (against brepjs types) before running; skip execution on type errors").option("--snapshot <dir>", "render iso/front/top/right PNGs to this dir (requires built viewer)").option("--serve", "after verifying, start a preview server and print a ?dir=&file= deep link (stays running)").option("--no-open", "with --serve, do not auto-open the browser (just print the viewer URL)").action(async (file, opts) => {
|
|
195
233
|
const wantStep = Boolean(opts.step) || Boolean(opts.snapshot) || Boolean(opts.serve);
|
|
196
234
|
const { report, step, glb, shape } = await runPart(resolve(file), {
|
|
197
235
|
step: wantStep,
|
|
@@ -226,8 +264,9 @@ program.command("verify", { isDefault: true }).argument("<file>", "path to a .br
|
|
|
226
264
|
if (!reportOk(report)) process.exitCode = 1;
|
|
227
265
|
if (Boolean(opts.serve) && stepPath !== void 0 && reportOk(report) && stepPath) {
|
|
228
266
|
const { serve } = await import("../snapshot/serve.js");
|
|
229
|
-
const { url } = await serve({ file: stepPath });
|
|
267
|
+
const { url, reused } = await serve({ file: stepPath });
|
|
230
268
|
process.stderr.write(`viewer: ${url}\n`);
|
|
269
|
+
if (!reused && opts.open && shouldAutoOpen()) openBrowser(url);
|
|
231
270
|
}
|
|
232
271
|
});
|
|
233
272
|
program.command("measure").argument("<a>", "path to a .brep.ts module").argument("[b]", "optional second module; if given, measures distance between the two parts").action(async (a, b) => {
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** Inputs that decide whether auto-opening a browser is appropriate. */
|
|
2
|
+
export interface AutoOpenEnv {
|
|
3
|
+
env?: NodeJS.ProcessEnv;
|
|
4
|
+
platform?: NodeJS.Platform;
|
|
5
|
+
/** Whether stderr is an interactive terminal. */
|
|
6
|
+
isTTY?: boolean;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Whether `--serve` should auto-open the browser for the current environment.
|
|
10
|
+
*
|
|
11
|
+
* Opens only for an interactive session — suppressed under CI, when stderr is
|
|
12
|
+
* not a TTY (agent/piped runs), or on Linux without a display server — so
|
|
13
|
+
* automation never spawns a browser unexpectedly. An explicit `--no-open` is a
|
|
14
|
+
* separate, always-on override handled by the caller.
|
|
15
|
+
*/
|
|
16
|
+
export declare function shouldAutoOpen({ env, platform, isTTY, }?: AutoOpenEnv): boolean;
|
|
17
|
+
/** The platform-specific command that opens `url` in the default browser. */
|
|
18
|
+
export declare function browserCommand(url: string, platform: NodeJS.Platform): [string, string[]];
|
|
19
|
+
/**
|
|
20
|
+
* Best-effort open of `url` in the default browser. Never throws and never
|
|
21
|
+
* blocks — a missing opener just leaves the printed URL as the fallback.
|
|
22
|
+
*/
|
|
23
|
+
export declare function openBrowser(url: string, platform?: NodeJS.Platform): void;
|
|
@@ -162,6 +162,14 @@ var HINT_TABLE = {
|
|
|
162
162
|
fix: "The boolean subtraction failed — often a tool that does not actually intersect the base, or tolerance issues.",
|
|
163
163
|
nextStep: "Confirm the tool overlaps the base, optionally heal the inputs, then re-cut."
|
|
164
164
|
},
|
|
165
|
+
FILLET_FAILED: {
|
|
166
|
+
fix: "The fillet could not be built — usually the radius is too large for an edge it touched (it cannot exceed the adjacent face/wall), or you filleted EVERY edge (the no-edge-list form) including ones too thin to round.",
|
|
167
|
+
nextStep: "Select only the edges you mean to round — e.g. edgeFinder().inDirection(\"Z\").findAll(solid) — and/or reduce the radius below the thinnest adjacent wall, then re-verify."
|
|
168
|
+
},
|
|
169
|
+
CHAMFER_FAILED: {
|
|
170
|
+
fix: "The chamfer could not be built — usually the distance is too large for an edge it touched, or you chamfered EVERY edge (the no-edge-list form) including ones too thin.",
|
|
171
|
+
nextStep: "Select only the target edges with an edge finder and/or reduce the distance below the shortest adjacent edge, then re-verify."
|
|
172
|
+
},
|
|
165
173
|
BOOLEAN_HAS_ERRORS: {
|
|
166
174
|
fix: "The boolean ran but the kernel reported errors (often coincident faces or near-tangent contact).",
|
|
167
175
|
nextStep: "Perturb one operand slightly so contact is a clean overlap, or heal the inputs, then retry."
|
|
@@ -197,6 +205,10 @@ var HINT_TABLE = {
|
|
|
197
205
|
TYPECHECK: {
|
|
198
206
|
fix: "Fix the TypeScript type error before running the part — the API call or value does not match brepjs’s types.",
|
|
199
207
|
nextStep: "Correct the flagged type (e.g. argument/return type or import), then re-verify."
|
|
208
|
+
},
|
|
209
|
+
EXPECTED_UNKNOWN_KEY: {
|
|
210
|
+
fix: "Your `expected` block has keys the CLI does not assert (so the intended check never ran). Bounds must be `{ xMin, xMax, yMin, yMax, zMin, zMax }` — not `{ min, max }` or `{ x, y, z }`.",
|
|
211
|
+
nextStep: "Rewrite `expected` using only volume, area, tolerancePct, and bounds.{xMin..zMax}, then re-verify."
|
|
200
212
|
}
|
|
201
213
|
};
|
|
202
214
|
/** Synthetic code attached to validity-check failures (validSolid returns a plain string error). */
|
|
@@ -247,7 +259,7 @@ function shapeTypeOf(brep, s) {
|
|
|
247
259
|
return "Unknown";
|
|
248
260
|
}
|
|
249
261
|
function runChecks(brep, shape) {
|
|
250
|
-
const { isSolid, isShape3D, isFace,
|
|
262
|
+
const { isSolid, isShape3D, isFace, measureVolumeProps, measureArea, getBounds, getFaces, getEdges, getWires, getVertices, getSolids, getShells, isManifoldShell, validSolid, isOk } = brep;
|
|
251
263
|
const r = emptyReport();
|
|
252
264
|
r.shapeType = shapeTypeOf(brep, shape);
|
|
253
265
|
if (isSolid(shape)) {
|
|
@@ -264,19 +276,41 @@ function runChecks(brep, shape) {
|
|
|
264
276
|
});
|
|
265
277
|
}
|
|
266
278
|
r.checks.push(validCheck);
|
|
279
|
+
} else {
|
|
280
|
+
const solids = getSolids(shape);
|
|
281
|
+
if (solids.length > 0) {
|
|
282
|
+
const failures = [];
|
|
283
|
+
solids.forEach((s, i) => {
|
|
284
|
+
const v = validSolid(s);
|
|
285
|
+
if (!isOk(v)) failures.push(`body ${i}: ${v.error}`);
|
|
286
|
+
});
|
|
287
|
+
const bodiesCheck = {
|
|
288
|
+
name: "allBodiesValid",
|
|
289
|
+
passed: failures.length === 0
|
|
290
|
+
};
|
|
291
|
+
if (failures.length > 0) {
|
|
292
|
+
bodiesCheck.detail = `${failures.length}/${solids.length} bodies invalid — ${failures.join("; ")}`;
|
|
293
|
+
r.errorInfos.push({
|
|
294
|
+
message: `allBodiesValid: ${bodiesCheck.detail}`,
|
|
295
|
+
code: VALIDITY_FAILURE_CODE
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
r.checks.push(bodiesCheck);
|
|
299
|
+
}
|
|
267
300
|
}
|
|
268
301
|
if (isShape3D(shape)) {
|
|
269
|
-
const
|
|
270
|
-
if (isOk(
|
|
271
|
-
r.measurements.volume =
|
|
302
|
+
const volProps = measureVolumeProps(shape);
|
|
303
|
+
if (isOk(volProps)) {
|
|
304
|
+
r.measurements.volume = volProps.value.volume;
|
|
305
|
+
r.measurements.centerOfMass = volProps.value.centerOfMass;
|
|
272
306
|
r.checks.push({
|
|
273
307
|
name: "positiveVolume",
|
|
274
|
-
passed:
|
|
308
|
+
passed: volProps.value.volume > 0
|
|
275
309
|
});
|
|
276
310
|
} else pushError(r, {
|
|
277
|
-
message: `measureVolume: ${
|
|
278
|
-
code:
|
|
279
|
-
suggestion:
|
|
311
|
+
message: `measureVolume: ${volProps.error.message}`,
|
|
312
|
+
code: volProps.error.code,
|
|
313
|
+
suggestion: volProps.error.suggestion
|
|
280
314
|
});
|
|
281
315
|
}
|
|
282
316
|
if (isFace(shape) || isShape3D(shape)) {
|
|
@@ -288,6 +322,18 @@ function runChecks(brep, shape) {
|
|
|
288
322
|
} catch (e) {
|
|
289
323
|
pushError(r, { message: `getBounds: ${e.message}` });
|
|
290
324
|
}
|
|
325
|
+
try {
|
|
326
|
+
r.topology = {
|
|
327
|
+
faceCount: getFaces(shape).length,
|
|
328
|
+
edgeCount: getEdges(shape).length,
|
|
329
|
+
wireCount: getWires(shape).length,
|
|
330
|
+
vertexCount: getVertices(shape).length
|
|
331
|
+
};
|
|
332
|
+
} catch {}
|
|
333
|
+
if (r.topology) try {
|
|
334
|
+
const shells = getShells(shape);
|
|
335
|
+
if (shells.length > 0) r.topology.manifold = shells.every((s) => isManifoldShell(s));
|
|
336
|
+
} catch {}
|
|
291
337
|
r.hints = buildHints(r);
|
|
292
338
|
return r;
|
|
293
339
|
}
|
|
@@ -300,8 +346,38 @@ function pctDelta(actual, expected) {
|
|
|
300
346
|
return Math.abs(actual - expected) / Math.abs(expected) * 100;
|
|
301
347
|
}
|
|
302
348
|
function withinTolerance(actual, expected, tolerancePct) {
|
|
349
|
+
if (Math.abs(actual - expected) <= 1e-6) return true;
|
|
303
350
|
return pctDelta(actual, expected) <= tolerancePct;
|
|
304
351
|
}
|
|
352
|
+
var TOP_LEVEL_KEYS = new Set([
|
|
353
|
+
"volume",
|
|
354
|
+
"area",
|
|
355
|
+
"bounds",
|
|
356
|
+
"tolerancePct"
|
|
357
|
+
]);
|
|
358
|
+
var BOUND_KEYS = new Set([
|
|
359
|
+
"xMin",
|
|
360
|
+
"xMax",
|
|
361
|
+
"yMin",
|
|
362
|
+
"yMax",
|
|
363
|
+
"zMin",
|
|
364
|
+
"zMax"
|
|
365
|
+
]);
|
|
366
|
+
/**
|
|
367
|
+
* Keys in an `expected` block that the CLI does not understand and would silently ignore — a
|
|
368
|
+
* `{ min: [...], max: [...] }` or `{ x: [...] }` bounds shape, or a misspelled top-level field.
|
|
369
|
+
* Surfaced as an error (not dropped) so a wrong `expected` shape fails loud instead of passing
|
|
370
|
+
* vacuously with the intended assertion never run.
|
|
371
|
+
*/
|
|
372
|
+
function unknownExpectedKeys(expected) {
|
|
373
|
+
const bad = [];
|
|
374
|
+
for (const k of Object.keys(expected)) if (!TOP_LEVEL_KEYS.has(k)) bad.push(k);
|
|
375
|
+
const bounds = expected.bounds;
|
|
376
|
+
if (bounds && typeof bounds === "object") {
|
|
377
|
+
for (const k of Object.keys(bounds)) if (!BOUND_KEYS.has(k)) bad.push(`bounds.${k}`);
|
|
378
|
+
}
|
|
379
|
+
return bad;
|
|
380
|
+
}
|
|
305
381
|
function isExpectedDims(v) {
|
|
306
382
|
if (typeof v !== "object" || v === null) return false;
|
|
307
383
|
const r = v;
|
|
@@ -425,6 +501,25 @@ var COMPILER_OPTIONS = {
|
|
|
425
501
|
skipLibCheck: true,
|
|
426
502
|
allowImportingTsExtensions: true
|
|
427
503
|
};
|
|
504
|
+
/**
|
|
505
|
+
* Locate the `@types/node` declarations so a part may import Node built-ins (`node:fs` to load a
|
|
506
|
+
* font, `node:fs/promises` to read a STEP file, etc.) without `--check` failing on the import.
|
|
507
|
+
* Returns the `@types` directory to use as a `typeRoots` entry. Probes the tool's own install
|
|
508
|
+
* first (where `@types/node` ships as a dependency), then the part's directory.
|
|
509
|
+
*/
|
|
510
|
+
function nodeTypesRoot(partPath, toolDir) {
|
|
511
|
+
const froms = [
|
|
512
|
+
import.meta.url,
|
|
513
|
+
toolDir ? pathToFileURL(resolve(toolDir, "package.json")).href : void 0,
|
|
514
|
+
pathToFileURL(partPath).href
|
|
515
|
+
];
|
|
516
|
+
for (const from of froms) {
|
|
517
|
+
if (!from) continue;
|
|
518
|
+
try {
|
|
519
|
+
return dirname(dirname(createRequire(from).resolve("@types/node/package.json")));
|
|
520
|
+
} catch {}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
428
523
|
function diagnosticToErrorInfo(d) {
|
|
429
524
|
const text = ts.flattenDiagnosticMessageText(d.messageText, "\n");
|
|
430
525
|
let where = "";
|
|
@@ -449,6 +544,11 @@ function typecheckPart(partPath, toolDir) {
|
|
|
449
544
|
const dts = resolveBrepjsTypes(partPath, toolDir);
|
|
450
545
|
const options = { ...COMPILER_OPTIONS };
|
|
451
546
|
if (dts) options.paths = { brepjs: [dts] };
|
|
547
|
+
const typesRoot = nodeTypesRoot(partPath, toolDir);
|
|
548
|
+
if (typesRoot) {
|
|
549
|
+
options.typeRoots = [typesRoot];
|
|
550
|
+
options.types = ["node"];
|
|
551
|
+
}
|
|
452
552
|
const program = ts.createProgram([partPath], options);
|
|
453
553
|
const errors = [
|
|
454
554
|
...program.getSemanticDiagnostics(),
|
|
@@ -462,6 +562,39 @@ function typecheckPart(partPath, toolDir) {
|
|
|
462
562
|
}
|
|
463
563
|
//#endregion
|
|
464
564
|
//#region src/verify/runPart.ts
|
|
565
|
+
/** Centroid of a face group's vertices, in part (Z-up, mm) coordinates. */
|
|
566
|
+
function faceCentroid(m, start, count) {
|
|
567
|
+
let x = 0;
|
|
568
|
+
let y = 0;
|
|
569
|
+
let z = 0;
|
|
570
|
+
for (let i = start; i < start + count; i++) {
|
|
571
|
+
const vi = (m.triangles[i] ?? 0) * 3;
|
|
572
|
+
x += m.vertices[vi] ?? 0;
|
|
573
|
+
y += m.vertices[vi + 1] ?? 0;
|
|
574
|
+
z += m.vertices[vi + 2] ?? 0;
|
|
575
|
+
}
|
|
576
|
+
const n = count || 1;
|
|
577
|
+
return [
|
|
578
|
+
x / n,
|
|
579
|
+
y / n,
|
|
580
|
+
z / n
|
|
581
|
+
];
|
|
582
|
+
}
|
|
583
|
+
function buildMaterialMap(m, spec) {
|
|
584
|
+
if (spec === void 0 || spec === null) return {};
|
|
585
|
+
if (typeof spec !== "function" && (typeof spec !== "object" || Array.isArray(spec))) return { warning: "export const materials must be a function or a material object — ignored" };
|
|
586
|
+
const sel = spec;
|
|
587
|
+
const select = typeof sel === "function" ? sel : () => sel;
|
|
588
|
+
const map = /* @__PURE__ */ new Map();
|
|
589
|
+
for (const fg of m.faceGroups) {
|
|
590
|
+
const mat = select({
|
|
591
|
+
faceId: fg.faceId,
|
|
592
|
+
center: faceCentroid(m, fg.start, fg.count)
|
|
593
|
+
});
|
|
594
|
+
if (mat) map.set(fg.faceId, mat);
|
|
595
|
+
}
|
|
596
|
+
return map.size > 0 ? { map } : {};
|
|
597
|
+
}
|
|
465
598
|
async function loadPart(modulePath) {
|
|
466
599
|
try {
|
|
467
600
|
return await import(pathToFileURL(modulePath).href);
|
|
@@ -485,7 +618,13 @@ function toErrorInfo(prefix, e) {
|
|
|
485
618
|
code: e.code,
|
|
486
619
|
suggestion: e.suggestion
|
|
487
620
|
};
|
|
488
|
-
if (e instanceof Error)
|
|
621
|
+
if (e instanceof Error) {
|
|
622
|
+
const code = e.message.match(/\[[A-Z][A-Z0-9_]*\]\s+([A-Z][A-Z0-9_]+):/)?.[1];
|
|
623
|
+
return code ? {
|
|
624
|
+
message: `${prefix}: ${e.message}`,
|
|
625
|
+
code
|
|
626
|
+
} : { message: `${prefix}: ${e.message}` };
|
|
627
|
+
}
|
|
489
628
|
return { message: `${prefix}: ${String(e)}` };
|
|
490
629
|
}
|
|
491
630
|
function finalize(result) {
|
|
@@ -565,11 +704,22 @@ async function runPart(modulePath, opts = {}) {
|
|
|
565
704
|
if (isExpectedDims(mod.expected)) {
|
|
566
705
|
const expected = mod.expected;
|
|
567
706
|
result.assertions = evaluateExpected(expected, result.measurements);
|
|
707
|
+
const unknown = unknownExpectedKeys(expected);
|
|
708
|
+
if (unknown.length > 0) pushError(result, {
|
|
709
|
+
message: `expected has unrecognized keys (ignored): ${unknown.join(", ")}. Valid keys: volume, area, tolerancePct, bounds.{xMin,xMax,yMin,yMax,zMin,zMax}.`,
|
|
710
|
+
code: "EXPECTED_UNKNOWN_KEY"
|
|
711
|
+
});
|
|
568
712
|
}
|
|
569
713
|
let glb;
|
|
570
714
|
let step;
|
|
571
715
|
if (opts.glb) try {
|
|
572
|
-
|
|
716
|
+
const shapeMesh = mesh(shape);
|
|
717
|
+
const { map, warning } = buildMaterialMap(shapeMesh, mod.materials);
|
|
718
|
+
if (warning) pushError(result, {
|
|
719
|
+
message: `materials: ${warning}`,
|
|
720
|
+
code: "MATERIALS_IGNORED"
|
|
721
|
+
});
|
|
722
|
+
glb = map ? exportGlb(shapeMesh, { materials: map }) : exportGlb(shapeMesh);
|
|
573
723
|
} catch (e) {
|
|
574
724
|
pushError(result, toErrorInfo("exportGlb", e));
|
|
575
725
|
}
|