brepjs-cad 0.174.0 → 0.176.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 +14 -0
- package/dist/brepjs-cad.cjs +12 -11
- package/dist/brepjs-cad.js +2 -1
- package/dist/cli/main.cjs +42 -98
- package/dist/cli/main.js +22 -78
- package/dist/cli/watch.d.ts +25 -0
- package/dist/diff-C2vDUt9F.js +143 -0
- package/dist/diff-CmIV_qOi.cjs +154 -0
- package/dist/loader/brepjsResolve.mjs +148 -13
- package/dist/mcp/server.cjs +2 -2
- package/dist/mcp/server.js +2 -2
- package/dist/openBrowser-BBYw6VVb.js +166 -0
- package/dist/openBrowser-C1AiX6cC.cjs +213 -0
- package/dist/preview/evaluate.d.ts +26 -0
- package/dist/preview/preview.cjs +11957 -0
- package/dist/preview/preview.d.ts +9 -0
- package/dist/preview/preview.js +11957 -0
- package/dist/preview/server.d.ts +17 -0
- package/dist/{rolldown-runtime-VH7oDXx4.cjs → rolldown-runtime-y5ARvAq5.cjs} +7 -0
- package/dist/snapshot/shoot.cjs +1 -1
- package/dist/snapshot/static.cjs +3 -0
- package/dist/snapshot/static.d.ts +4 -0
- package/dist/snapshot/static.js +1 -1
- package/dist/{diff-DDu3OLyd.cjs → usingCtx-DNbyRoEg.cjs} +65 -160
- package/dist/{diff-CRu6Pqyr.js → usingCtx-DOzK2eJh.js} +41 -148
- package/dist/verify/runPart.d.ts +10 -0
- package/dist/verify/typecheck.d.ts +1 -0
- package/package.json +3 -3
- package/viewer/dist/assets/index-Cq6KRusM.js +4208 -0
- package/viewer/dist/index.html +1 -1
- package/viewer/dist/wasm/occt-wasm.wasm +0 -0
- package/viewer/dist/assets/index-CH09YZ3K.js +0 -4208
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.176.0](https://github.com/andymai/brepjs/compare/brepjs-cad-v0.175.0...brepjs-cad-v0.176.0) (2026-08-26)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **brepjs-cad:** brep preview — element-aware model viewer with live watch ([#2231](https://github.com/andymai/brepjs/issues/2231)) ([002a20f](https://github.com/andymai/brepjs/commit/002a20f9ad8d38cb812f00a875583bdb850a004d))
|
|
9
|
+
|
|
10
|
+
## [0.175.0](https://github.com/andymai/brepjs/compare/brepjs-cad-v0.174.0...brepjs-cad-v0.175.0) (2026-08-26)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
* **brepjs-cad:** transpiling loader for multi-file TS/TSX projects + working watch ([#2228](https://github.com/andymai/brepjs/issues/2228)) ([980ead6](https://github.com/andymai/brepjs/commit/980ead6508867c31fcec1fd06a683f03bacb1e6f))
|
|
16
|
+
|
|
3
17
|
## [0.174.0](https://github.com/andymai/brepjs/compare/brepjs-cad-v0.173.0...brepjs-cad-v0.174.0) (2026-08-22)
|
|
4
18
|
|
|
5
19
|
|
package/dist/brepjs-cad.cjs
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const
|
|
3
|
-
|
|
4
|
-
exports.
|
|
5
|
-
exports.
|
|
6
|
-
exports.
|
|
7
|
-
exports.
|
|
8
|
-
exports.
|
|
9
|
-
exports.
|
|
2
|
+
const require_usingCtx = require("./usingCtx-DNbyRoEg.cjs");
|
|
3
|
+
const require_diff = require("./diff-CmIV_qOi.cjs");
|
|
4
|
+
exports.DEFAULT_TOLERANCE_PCT = require_usingCtx.DEFAULT_TOLERANCE_PCT;
|
|
5
|
+
exports.TYPECHECK_CODE = require_usingCtx.TYPECHECK_CODE;
|
|
6
|
+
exports.emptyReport = require_usingCtx.emptyReport;
|
|
7
|
+
exports.evaluateExpected = require_usingCtx.evaluateExpected;
|
|
8
|
+
exports.isExpectedDims = require_usingCtx.isExpectedDims;
|
|
9
|
+
exports.pctDelta = require_usingCtx.pctDelta;
|
|
10
|
+
exports.runChecks = require_usingCtx.runChecks;
|
|
10
11
|
exports.runDiff = require_diff.runDiff;
|
|
11
12
|
exports.runMeasure = require_diff.runMeasure;
|
|
12
|
-
exports.runPart =
|
|
13
|
-
exports.serializeReport =
|
|
14
|
-
exports.typecheckPart =
|
|
13
|
+
exports.runPart = require_usingCtx.runPart;
|
|
14
|
+
exports.serializeReport = require_usingCtx.serializeReport;
|
|
15
|
+
exports.typecheckPart = require_usingCtx.typecheckPart;
|
package/dist/brepjs-cad.js
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { c as evaluateExpected, d as runChecks, f as emptyReport, h as serializeReport, i as TYPECHECK_CODE, l as isExpectedDims, o as typecheckPart, r as runPart, s as DEFAULT_TOLERANCE_PCT, u as pctDelta } from "./usingCtx-DOzK2eJh.js";
|
|
2
|
+
import { n as runMeasure, t as runDiff } from "./diff-C2vDUt9F.js";
|
|
2
3
|
export { DEFAULT_TOLERANCE_PCT, TYPECHECK_CODE, emptyReport, evaluateExpected, isExpectedDims, pctDelta, runChecks, runDiff, runMeasure, runPart, serializeReport, typecheckPart };
|
package/dist/cli/main.cjs
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
-
const
|
|
3
|
+
const require_usingCtx = require("../usingCtx-DNbyRoEg.cjs");
|
|
4
|
+
const require_diff = require("../diff-CmIV_qOi.cjs");
|
|
5
|
+
const require_openBrowser = require("../openBrowser-C1AiX6cC.cjs");
|
|
4
6
|
let node_url = require("node:url");
|
|
5
7
|
let node_fs = require("node:fs");
|
|
6
8
|
let node_path = require("node:path");
|
|
7
9
|
let commander = require("commander");
|
|
8
10
|
let node_os = require("node:os");
|
|
9
|
-
let node_child_process = require("node:child_process");
|
|
10
11
|
//#region src/cli/scaffold.ts
|
|
11
12
|
function partTemplate(name) {
|
|
12
13
|
return `import { box, cut, unwrap } from 'brepjs';
|
|
@@ -97,46 +98,20 @@ function scaffoldPart(name, dir) {
|
|
|
97
98
|
files
|
|
98
99
|
};
|
|
99
100
|
}
|
|
100
|
-
function debounce(fn, delayMs = 150) {
|
|
101
|
-
let timer;
|
|
102
|
-
const cancel = () => {
|
|
103
|
-
if (timer) {
|
|
104
|
-
clearTimeout(timer);
|
|
105
|
-
timer = void 0;
|
|
106
|
-
}
|
|
107
|
-
};
|
|
108
|
-
const trigger = () => {
|
|
109
|
-
cancel();
|
|
110
|
-
timer = setTimeout(() => {
|
|
111
|
-
timer = void 0;
|
|
112
|
-
fn();
|
|
113
|
-
}, delayMs);
|
|
114
|
-
};
|
|
115
|
-
return {
|
|
116
|
-
trigger,
|
|
117
|
-
cancel
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
//#endregion
|
|
121
|
-
//#region src/disposeShape.ts
|
|
122
|
-
function disposeShape(shape) {
|
|
123
|
-
const disposer = shape?.[Symbol.dispose];
|
|
124
|
-
if (typeof disposer === "function") disposer.call(shape);
|
|
125
|
-
}
|
|
126
101
|
//#endregion
|
|
127
102
|
//#region src/cli/exportPart.ts
|
|
128
103
|
function stem(file) {
|
|
129
104
|
return (0, node_path.basename)(file).replace(/\.brep\.ts$/, "").replace(/\.ts$/, "");
|
|
130
105
|
}
|
|
131
106
|
async function exportPart(modulePath, formats, outDir) {
|
|
132
|
-
const { shape, report, step, glb } = await
|
|
107
|
+
const { shape, report, step, glb } = await require_usingCtx.runPart(modulePath, {
|
|
133
108
|
step: Boolean(formats.step),
|
|
134
109
|
glb: Boolean(formats.glb)
|
|
135
110
|
});
|
|
136
111
|
const errors = [];
|
|
137
112
|
const written = [];
|
|
138
|
-
if (!(
|
|
139
|
-
disposeShape(shape);
|
|
113
|
+
if (!(require_usingCtx.reportOk(report) && shape !== null)) {
|
|
114
|
+
require_openBrowser.disposeShape(shape);
|
|
140
115
|
const failures = report.errorInfos.map((e) => e.message);
|
|
141
116
|
return {
|
|
142
117
|
ok: false,
|
|
@@ -163,7 +138,7 @@ async function exportPart(modulePath, formats, outDir) {
|
|
|
163
138
|
} else errors.push("GLB export produced no data");
|
|
164
139
|
}
|
|
165
140
|
if (formats.stl) {
|
|
166
|
-
const { exportSTL, isOk } = await
|
|
141
|
+
const { exportSTL, isOk } = await require_usingCtx.loadBrep();
|
|
167
142
|
const r = exportSTL(shape);
|
|
168
143
|
if (isOk(r)) {
|
|
169
144
|
const p = (0, node_path.join)(outDir, `${base}.stl`);
|
|
@@ -172,7 +147,7 @@ async function exportPart(modulePath, formats, outDir) {
|
|
|
172
147
|
} else errors.push(`STL export: ${r.error.message}`);
|
|
173
148
|
}
|
|
174
149
|
} finally {
|
|
175
|
-
disposeShape(shape);
|
|
150
|
+
require_openBrowser.disposeShape(shape);
|
|
176
151
|
}
|
|
177
152
|
return {
|
|
178
153
|
ok: errors.length === 0,
|
|
@@ -182,43 +157,6 @@ async function exportPart(modulePath, formats, outDir) {
|
|
|
182
157
|
};
|
|
183
158
|
}
|
|
184
159
|
//#endregion
|
|
185
|
-
//#region src/cli/openBrowser.ts
|
|
186
|
-
/**
|
|
187
|
-
* Whether `--serve` should auto-open the browser for the current environment.
|
|
188
|
-
*
|
|
189
|
-
* Opens only for an interactive session — suppressed under CI, when stderr is
|
|
190
|
-
* not a TTY (agent/piped runs), or on Linux without a display server — so
|
|
191
|
-
* automation never spawns a browser unexpectedly. An explicit `--no-open` is a
|
|
192
|
-
* separate, always-on override handled by the caller.
|
|
193
|
-
*/
|
|
194
|
-
function shouldAutoOpen({ env = process.env, platform = process.platform, isTTY = Boolean(process.stderr.isTTY) } = {}) {
|
|
195
|
-
if (env["CI"]) return false;
|
|
196
|
-
if (!isTTY) return false;
|
|
197
|
-
if (platform === "linux" && !env["DISPLAY"] && !env["WAYLAND_DISPLAY"]) return false;
|
|
198
|
-
return true;
|
|
199
|
-
}
|
|
200
|
-
/** The platform-specific command that opens `url` in the default browser. */
|
|
201
|
-
function browserCommand(url, platform) {
|
|
202
|
-
if (platform === "darwin") return ["open", [url]];
|
|
203
|
-
if (platform === "win32") return ["rundll32", ["url.dll,FileProtocolHandler", url]];
|
|
204
|
-
return ["xdg-open", [url]];
|
|
205
|
-
}
|
|
206
|
-
/**
|
|
207
|
-
* Best-effort open of `url` in the default browser. Never throws and never
|
|
208
|
-
* blocks — a missing opener just leaves the printed URL as the fallback.
|
|
209
|
-
*/
|
|
210
|
-
function openBrowser(url, platform = process.platform) {
|
|
211
|
-
try {
|
|
212
|
-
const [cmd, args] = browserCommand(url, platform);
|
|
213
|
-
const child = (0, node_child_process.spawn)(cmd, args, {
|
|
214
|
-
stdio: "ignore",
|
|
215
|
-
detached: true
|
|
216
|
-
});
|
|
217
|
-
child.on("error", () => {});
|
|
218
|
-
child.unref();
|
|
219
|
-
} catch {}
|
|
220
|
-
}
|
|
221
|
-
//#endregion
|
|
222
160
|
//#region src/snapshot/aiming.ts
|
|
223
161
|
/**
|
|
224
162
|
* Kernel-anchored marks for the judge: `B<index>` at each body's bbox centroid (only for multi-body
|
|
@@ -304,14 +242,14 @@ program.command("verify", { isDefault: true }).argument("<files...>", "path(s) o
|
|
|
304
242
|
if (opts.step || opts.serve || opts.snapshot || opts.glb || opts.expectCode || opts.expectInvalid) process.stderr.write("batch mode: --step/--glb/--serve/--snapshot/--expect-code/--expect-invalid ignored (single-file only)\n");
|
|
305
243
|
const results = [];
|
|
306
244
|
for (const f of targets) {
|
|
307
|
-
const { report, shape } = await
|
|
245
|
+
const { report, shape } = await require_usingCtx.runPart(f, {
|
|
308
246
|
check: Boolean(opts.check),
|
|
309
247
|
metrics: Boolean(opts.metrics)
|
|
310
248
|
});
|
|
311
|
-
disposeShape(shape);
|
|
249
|
+
require_openBrowser.disposeShape(shape);
|
|
312
250
|
results.push({
|
|
313
251
|
file: f,
|
|
314
|
-
ok:
|
|
252
|
+
ok: require_usingCtx.reportOk(report),
|
|
315
253
|
report
|
|
316
254
|
});
|
|
317
255
|
}
|
|
@@ -323,7 +261,7 @@ program.command("verify", { isDefault: true }).argument("<files...>", "path(s) o
|
|
|
323
261
|
}
|
|
324
262
|
const file = targets[0] ?? files[0] ?? "";
|
|
325
263
|
const wantStep = Boolean(opts.step) || Boolean(opts.snapshot) || Boolean(opts.serve);
|
|
326
|
-
const { report, step, glb, shape } = await
|
|
264
|
+
const { report, step, glb, shape } = await require_usingCtx.runPart((0, node_path.resolve)(file), {
|
|
327
265
|
step: wantStep,
|
|
328
266
|
glb: Boolean(opts.glb),
|
|
329
267
|
check: Boolean(opts.check),
|
|
@@ -351,27 +289,27 @@ program.command("verify", { isDefault: true }).argument("<files...>", "path(s) o
|
|
|
351
289
|
}
|
|
352
290
|
} else if (opts.snapshot) process.stderr.write("snapshot skipped: STEP export produced no artifact\n");
|
|
353
291
|
} catch (e) {
|
|
354
|
-
|
|
292
|
+
require_usingCtx.pushError(report, { message: `artifact write failed: ${e.message}` });
|
|
355
293
|
} finally {
|
|
356
|
-
disposeShape(shape);
|
|
294
|
+
require_openBrowser.disposeShape(shape);
|
|
357
295
|
}
|
|
358
|
-
const json =
|
|
296
|
+
const json = require_usingCtx.serializeReport(report);
|
|
359
297
|
if (opts.json && opts.json !== "-") (0, node_fs.writeFileSync)(opts.json, json);
|
|
360
298
|
process.stdout.write(json + "\n");
|
|
361
|
-
if (!
|
|
299
|
+
if (!require_usingCtx.reportOk(report)) process.exitCode = 1;
|
|
362
300
|
if (opts.expectInvalid || opts.expectCode) {
|
|
363
301
|
const codes = [...new Set(report.errorInfos.map((e) => e.code).filter((c) => Boolean(c)))];
|
|
364
|
-
const valid =
|
|
302
|
+
const valid = require_usingCtx.reportOk(report);
|
|
365
303
|
const pass = (!opts.expectInvalid || !valid) && (!opts.expectCode || codes.includes(opts.expectCode));
|
|
366
304
|
process.exitCode = pass ? 0 : 1;
|
|
367
305
|
process.stderr.write(`expect: ${pass ? "PASS" : "FAIL"}` + (opts.expectCode ? ` code=${opts.expectCode}` : "") + (opts.expectInvalid ? " invalid" : "") + ` (ok=${valid}, codes=${codes.join(",") || "none"})\n`);
|
|
368
306
|
return;
|
|
369
307
|
}
|
|
370
|
-
if (Boolean(opts.serve) && stepPath !== void 0 &&
|
|
308
|
+
if (Boolean(opts.serve) && stepPath !== void 0 && require_usingCtx.reportOk(report) && stepPath) {
|
|
371
309
|
const { serve } = await Promise.resolve().then(() => require("../snapshot/serve.cjs"));
|
|
372
310
|
const { url, reused } = await serve({ file: stepPath });
|
|
373
311
|
process.stderr.write(`viewer: ${url}\n`);
|
|
374
|
-
if (!reused && opts.open && shouldAutoOpen()) openBrowser(url);
|
|
312
|
+
if (!reused && opts.open && require_openBrowser.shouldAutoOpen()) require_openBrowser.openBrowser(url);
|
|
375
313
|
}
|
|
376
314
|
});
|
|
377
315
|
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) => {
|
|
@@ -394,14 +332,14 @@ program.command("snapshot").argument("<file>", "path to a .brep.ts module; rende
|
|
|
394
332
|
const resolved = (0, node_path.resolve)(file);
|
|
395
333
|
const base = opts.out ? (0, node_path.resolve)(opts.out) : `${resolved}-shots`;
|
|
396
334
|
const outDir = opts.label ? (0, node_path.join)(base, opts.label) : base;
|
|
397
|
-
const { report, step, shape } = await
|
|
335
|
+
const { report, step, shape } = await require_usingCtx.runPart(resolved, {
|
|
398
336
|
step: true,
|
|
399
337
|
metrics: true
|
|
400
338
|
});
|
|
401
339
|
try {
|
|
402
340
|
if (!step) {
|
|
403
341
|
process.stderr.write("snapshot: part produced no STEP — nothing to render\n");
|
|
404
|
-
process.stdout.write(
|
|
342
|
+
process.stdout.write(require_usingCtx.serializeReport(report) + "\n");
|
|
405
343
|
process.exitCode = 1;
|
|
406
344
|
return;
|
|
407
345
|
}
|
|
@@ -409,7 +347,7 @@ program.command("snapshot").argument("<file>", "path to a .brep.ts module; rende
|
|
|
409
347
|
(0, node_fs.writeFileSync)(stepPath, Buffer.from(step));
|
|
410
348
|
const shoot = await loadSnapshotShoot();
|
|
411
349
|
if (!shoot) {
|
|
412
|
-
process.stdout.write(
|
|
350
|
+
process.stdout.write(require_usingCtx.serializeReport(report) + "\n");
|
|
413
351
|
return;
|
|
414
352
|
}
|
|
415
353
|
const section = aimedSection(report.manufacturability?.bores ?? [], report.measurements.bounds);
|
|
@@ -423,7 +361,7 @@ program.command("snapshot").argument("<file>", "path to a .brep.ts module; rende
|
|
|
423
361
|
});
|
|
424
362
|
for (const p of pngs) process.stdout.write(`${p}\n`);
|
|
425
363
|
} finally {
|
|
426
|
-
disposeShape(shape);
|
|
364
|
+
require_openBrowser.disposeShape(shape);
|
|
427
365
|
}
|
|
428
366
|
});
|
|
429
367
|
program.command("init").argument("<name>", "part name; scaffolds <name>.brep.ts + tsconfig.json + README.md").option("--out <dir>", "target directory (defaults to ./<name>)").action((name, opts) => {
|
|
@@ -436,35 +374,41 @@ program.command("init").argument("<name>", "part name; scaffolds <name>.brep.ts
|
|
|
436
374
|
});
|
|
437
375
|
program.command("watch").argument("<file>", "path to a .brep.ts module; re-verifies on each save until Ctrl-C").action((file) => {
|
|
438
376
|
const path = (0, node_path.resolve)(file);
|
|
439
|
-
const run = async () => {
|
|
377
|
+
const run = async (fresh) => {
|
|
440
378
|
try {
|
|
441
|
-
const { report, shape } = await
|
|
379
|
+
const { report, shape } = await require_usingCtx.runPart(path, { freshImport: fresh });
|
|
442
380
|
try {
|
|
443
|
-
process.stdout.write(
|
|
381
|
+
process.stdout.write(require_usingCtx.serializeReport(report) + "\n");
|
|
444
382
|
} finally {
|
|
445
|
-
disposeShape(shape);
|
|
383
|
+
require_openBrowser.disposeShape(shape);
|
|
446
384
|
}
|
|
447
385
|
} catch (e) {
|
|
448
386
|
process.stderr.write(`watch run failed: ${e.message}\n`);
|
|
449
387
|
}
|
|
450
388
|
};
|
|
451
|
-
const
|
|
389
|
+
const runner = require_openBrowser.createSerialRunner(run);
|
|
390
|
+
const { trigger } = require_openBrowser.debounce(runner.trigger, 150);
|
|
452
391
|
process.stderr.write(`watching ${path} (Ctrl-C to stop)\n`);
|
|
453
|
-
|
|
454
|
-
const
|
|
455
|
-
if (filename
|
|
456
|
-
trigger();
|
|
457
|
-
return;
|
|
458
|
-
}
|
|
459
|
-
if ((0, node_path.basename)(path) === filename.toString()) trigger();
|
|
392
|
+
runner.start(false);
|
|
393
|
+
const stopWatching = require_openBrowser.watchTree(require_openBrowser.watchRootFor(path), (filename) => {
|
|
394
|
+
if (require_openBrowser.isWatchRelevant(filename)) trigger();
|
|
460
395
|
});
|
|
461
396
|
const stop = () => {
|
|
462
|
-
|
|
397
|
+
stopWatching();
|
|
463
398
|
process.exit(0);
|
|
464
399
|
};
|
|
465
400
|
process.on("SIGINT", stop);
|
|
466
401
|
process.on("SIGTERM", stop);
|
|
467
402
|
});
|
|
403
|
+
program.command("preview").argument("<file>", "path to a model module (.ts/.tsx); a families element tree renders per element").option("--watch", "re-evaluate on save and push updates to the open viewer tab").option("--write-only", "write artifacts and exit without starting the viewer server").option("--glb <out>", "write a merged GLB of the whole model").option("--json <out>", "write the JSON summary to this path").option("--port <n>", "viewer server port (default: OS-assigned)", (v) => parseInt(v, 10)).option("--no-open", "do not auto-open the browser").action(async (file, opts) => {
|
|
404
|
+
const { runPreview } = await Promise.resolve().then(() => require("../preview/preview.cjs"));
|
|
405
|
+
try {
|
|
406
|
+
await runPreview((0, node_path.resolve)(file), opts);
|
|
407
|
+
} catch (e) {
|
|
408
|
+
process.stderr.write(`preview failed: ${e.message}\n`);
|
|
409
|
+
process.exitCode = 1;
|
|
410
|
+
}
|
|
411
|
+
});
|
|
468
412
|
program.command("export").argument("<file>", "path to a .brep.ts module").option("--step", "write a STEP artifact").option("--glb", "write a GLB artifact").option("--stl", "write an STL artifact").option("--all", "write STEP + GLB + STL").option("--out <dir>", "output directory", ".").action(async (file, opts) => {
|
|
469
413
|
const formats = opts.all ? {
|
|
470
414
|
step: true,
|
package/dist/cli/main.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { _ as loadBrep, h as serializeReport, m as reportOk, p as pushError, r as runPart } from "../usingCtx-DOzK2eJh.js";
|
|
3
|
+
import { n as runMeasure, t as runDiff } from "../diff-C2vDUt9F.js";
|
|
4
|
+
import { a as debounce, c as watchTree, i as createSerialRunner, n as shouldAutoOpen, o as isWatchRelevant, r as disposeShape, s as watchRootFor, t as openBrowser } from "../openBrowser-BBYw6VVb.js";
|
|
3
5
|
import { fileURLToPath } from "node:url";
|
|
4
|
-
import { existsSync, globSync, mkdirSync, realpathSync,
|
|
5
|
-
import { basename,
|
|
6
|
+
import { existsSync, globSync, mkdirSync, realpathSync, writeFileSync } from "node:fs";
|
|
7
|
+
import { basename, join, resolve } from "node:path";
|
|
6
8
|
import { Command } from "commander";
|
|
7
9
|
import { tmpdir } from "node:os";
|
|
8
|
-
import { spawn } from "node:child_process";
|
|
9
10
|
//#region src/cli/scaffold.ts
|
|
10
11
|
function partTemplate(name) {
|
|
11
12
|
return `import { box, cut, unwrap } from 'brepjs';
|
|
@@ -96,32 +97,6 @@ function scaffoldPart(name, dir) {
|
|
|
96
97
|
files
|
|
97
98
|
};
|
|
98
99
|
}
|
|
99
|
-
function debounce(fn, delayMs = 150) {
|
|
100
|
-
let timer;
|
|
101
|
-
const cancel = () => {
|
|
102
|
-
if (timer) {
|
|
103
|
-
clearTimeout(timer);
|
|
104
|
-
timer = void 0;
|
|
105
|
-
}
|
|
106
|
-
};
|
|
107
|
-
const trigger = () => {
|
|
108
|
-
cancel();
|
|
109
|
-
timer = setTimeout(() => {
|
|
110
|
-
timer = void 0;
|
|
111
|
-
fn();
|
|
112
|
-
}, delayMs);
|
|
113
|
-
};
|
|
114
|
-
return {
|
|
115
|
-
trigger,
|
|
116
|
-
cancel
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
//#endregion
|
|
120
|
-
//#region src/disposeShape.ts
|
|
121
|
-
function disposeShape(shape) {
|
|
122
|
-
const disposer = shape?.[Symbol.dispose];
|
|
123
|
-
if (typeof disposer === "function") disposer.call(shape);
|
|
124
|
-
}
|
|
125
100
|
//#endregion
|
|
126
101
|
//#region src/cli/exportPart.ts
|
|
127
102
|
function stem(file) {
|
|
@@ -181,43 +156,6 @@ async function exportPart(modulePath, formats, outDir) {
|
|
|
181
156
|
};
|
|
182
157
|
}
|
|
183
158
|
//#endregion
|
|
184
|
-
//#region src/cli/openBrowser.ts
|
|
185
|
-
/**
|
|
186
|
-
* Whether `--serve` should auto-open the browser for the current environment.
|
|
187
|
-
*
|
|
188
|
-
* Opens only for an interactive session — suppressed under CI, when stderr is
|
|
189
|
-
* not a TTY (agent/piped runs), or on Linux without a display server — so
|
|
190
|
-
* automation never spawns a browser unexpectedly. An explicit `--no-open` is a
|
|
191
|
-
* separate, always-on override handled by the caller.
|
|
192
|
-
*/
|
|
193
|
-
function shouldAutoOpen({ env = process.env, platform = process.platform, isTTY = Boolean(process.stderr.isTTY) } = {}) {
|
|
194
|
-
if (env["CI"]) return false;
|
|
195
|
-
if (!isTTY) return false;
|
|
196
|
-
if (platform === "linux" && !env["DISPLAY"] && !env["WAYLAND_DISPLAY"]) return false;
|
|
197
|
-
return true;
|
|
198
|
-
}
|
|
199
|
-
/** The platform-specific command that opens `url` in the default browser. */
|
|
200
|
-
function browserCommand(url, platform) {
|
|
201
|
-
if (platform === "darwin") return ["open", [url]];
|
|
202
|
-
if (platform === "win32") return ["rundll32", ["url.dll,FileProtocolHandler", url]];
|
|
203
|
-
return ["xdg-open", [url]];
|
|
204
|
-
}
|
|
205
|
-
/**
|
|
206
|
-
* Best-effort open of `url` in the default browser. Never throws and never
|
|
207
|
-
* blocks — a missing opener just leaves the printed URL as the fallback.
|
|
208
|
-
*/
|
|
209
|
-
function openBrowser(url, platform = process.platform) {
|
|
210
|
-
try {
|
|
211
|
-
const [cmd, args] = browserCommand(url, platform);
|
|
212
|
-
const child = spawn(cmd, args, {
|
|
213
|
-
stdio: "ignore",
|
|
214
|
-
detached: true
|
|
215
|
-
});
|
|
216
|
-
child.on("error", () => {});
|
|
217
|
-
child.unref();
|
|
218
|
-
} catch {}
|
|
219
|
-
}
|
|
220
|
-
//#endregion
|
|
221
159
|
//#region src/snapshot/aiming.ts
|
|
222
160
|
/**
|
|
223
161
|
* Kernel-anchored marks for the judge: `B<index>` at each body's bbox centroid (only for multi-body
|
|
@@ -435,9 +373,9 @@ program.command("init").argument("<name>", "part name; scaffolds <name>.brep.ts
|
|
|
435
373
|
});
|
|
436
374
|
program.command("watch").argument("<file>", "path to a .brep.ts module; re-verifies on each save until Ctrl-C").action((file) => {
|
|
437
375
|
const path = resolve(file);
|
|
438
|
-
const run = async () => {
|
|
376
|
+
const run = async (fresh) => {
|
|
439
377
|
try {
|
|
440
|
-
const { report, shape } = await runPart(path);
|
|
378
|
+
const { report, shape } = await runPart(path, { freshImport: fresh });
|
|
441
379
|
try {
|
|
442
380
|
process.stdout.write(serializeReport(report) + "\n");
|
|
443
381
|
} finally {
|
|
@@ -447,23 +385,29 @@ program.command("watch").argument("<file>", "path to a .brep.ts module; re-verif
|
|
|
447
385
|
process.stderr.write(`watch run failed: ${e.message}\n`);
|
|
448
386
|
}
|
|
449
387
|
};
|
|
450
|
-
const
|
|
388
|
+
const runner = createSerialRunner(run);
|
|
389
|
+
const { trigger } = debounce(runner.trigger, 150);
|
|
451
390
|
process.stderr.write(`watching ${path} (Ctrl-C to stop)\n`);
|
|
452
|
-
|
|
453
|
-
const
|
|
454
|
-
if (filename
|
|
455
|
-
trigger();
|
|
456
|
-
return;
|
|
457
|
-
}
|
|
458
|
-
if (basename(path) === filename.toString()) trigger();
|
|
391
|
+
runner.start(false);
|
|
392
|
+
const stopWatching = watchTree(watchRootFor(path), (filename) => {
|
|
393
|
+
if (isWatchRelevant(filename)) trigger();
|
|
459
394
|
});
|
|
460
395
|
const stop = () => {
|
|
461
|
-
|
|
396
|
+
stopWatching();
|
|
462
397
|
process.exit(0);
|
|
463
398
|
};
|
|
464
399
|
process.on("SIGINT", stop);
|
|
465
400
|
process.on("SIGTERM", stop);
|
|
466
401
|
});
|
|
402
|
+
program.command("preview").argument("<file>", "path to a model module (.ts/.tsx); a families element tree renders per element").option("--watch", "re-evaluate on save and push updates to the open viewer tab").option("--write-only", "write artifacts and exit without starting the viewer server").option("--glb <out>", "write a merged GLB of the whole model").option("--json <out>", "write the JSON summary to this path").option("--port <n>", "viewer server port (default: OS-assigned)", (v) => parseInt(v, 10)).option("--no-open", "do not auto-open the browser").action(async (file, opts) => {
|
|
403
|
+
const { runPreview } = await import("../preview/preview.js");
|
|
404
|
+
try {
|
|
405
|
+
await runPreview(resolve(file), opts);
|
|
406
|
+
} catch (e) {
|
|
407
|
+
process.stderr.write(`preview failed: ${e.message}\n`);
|
|
408
|
+
process.exitCode = 1;
|
|
409
|
+
}
|
|
410
|
+
});
|
|
467
411
|
program.command("export").argument("<file>", "path to a .brep.ts module").option("--step", "write a STEP artifact").option("--glb", "write a GLB artifact").option("--stl", "write an STL artifact").option("--all", "write STEP + GLB + STL").option("--out <dir>", "output directory", ".").action(async (file, opts) => {
|
|
468
412
|
const formats = opts.all ? {
|
|
469
413
|
step: true,
|
package/dist/cli/watch.d.ts
CHANGED
|
@@ -3,3 +3,28 @@ export declare function debounce(fn: () => void | Promise<void>, delayMs?: numbe
|
|
|
3
3
|
trigger: () => void;
|
|
4
4
|
cancel: () => void;
|
|
5
5
|
};
|
|
6
|
+
export declare function isWatchRelevant(filename: string | Buffer | null | undefined): boolean;
|
|
7
|
+
/**
|
|
8
|
+
* Root the watcher at the project, not the entry's own directory: with the common
|
|
9
|
+
* `src/main.ts` layout, tsconfig.json (whose JSX options change how sources transpile)
|
|
10
|
+
* and `../` imports live one level up. Nearest package.json or tsconfig.json walking up
|
|
11
|
+
* wins; entries with no project marker fall back to their own directory.
|
|
12
|
+
*/
|
|
13
|
+
export declare function watchRootFor(entryPath: string): string;
|
|
14
|
+
/**
|
|
15
|
+
* Watch a directory tree with one non-recursive `fs.watch` per directory, skipping
|
|
16
|
+
* node_modules/dist/.git and never following symlinks. `fs.watch({recursive})` has no
|
|
17
|
+
* exclusion API, so on a real project it would descend into node_modules — tens of
|
|
18
|
+
* thousands of inotify watches. Whenever an event fires in a directory it is re-scanned,
|
|
19
|
+
* which picks up newly created subdirectories. Returns a stop function.
|
|
20
|
+
*/
|
|
21
|
+
export declare function watchTree(root: string, onEvent: (filename: string | Buffer | null | undefined) => void): () => void;
|
|
22
|
+
/**
|
|
23
|
+
* Serialize an async job: while one run is in flight, triggers coalesce into exactly
|
|
24
|
+
* one trailing rerun (verify/preview runs share one kernel — overlapping runs would
|
|
25
|
+
* interleave output and reorder reports).
|
|
26
|
+
*/
|
|
27
|
+
export declare function createSerialRunner(run: (fresh: boolean) => Promise<void>): {
|
|
28
|
+
start: (fresh: boolean) => void;
|
|
29
|
+
trigger: () => void;
|
|
30
|
+
};
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { _ as loadBrep, r as runPart, t as _usingCtx } from "./usingCtx-DOzK2eJh.js";
|
|
2
|
+
//#region src/verify/measure.ts
|
|
3
|
+
async function runMeasure(aPath, bPath) {
|
|
4
|
+
try {
|
|
5
|
+
var _usingCtx$2 = _usingCtx();
|
|
6
|
+
const { measureDistance, measureLength, isOk } = await loadBrep();
|
|
7
|
+
const errors = [];
|
|
8
|
+
const a = await runPart(aPath);
|
|
9
|
+
errors.push(...a.report.errors);
|
|
10
|
+
if (!a.shape) return { errors };
|
|
11
|
+
const sa = _usingCtx$2.u(a.shape);
|
|
12
|
+
if (bPath === void 0) {
|
|
13
|
+
const len = measureLength(sa);
|
|
14
|
+
if (isOk(len)) return {
|
|
15
|
+
length: len.value,
|
|
16
|
+
errors
|
|
17
|
+
};
|
|
18
|
+
errors.push(`measureLength: ${len.error.message}`);
|
|
19
|
+
return { errors };
|
|
20
|
+
}
|
|
21
|
+
const b = await runPart(bPath);
|
|
22
|
+
errors.push(...b.report.errors);
|
|
23
|
+
if (!b.shape) return { errors };
|
|
24
|
+
const dist = measureDistance(sa, _usingCtx$2.u(b.shape));
|
|
25
|
+
if (isOk(dist)) return {
|
|
26
|
+
distance: dist.value,
|
|
27
|
+
errors
|
|
28
|
+
};
|
|
29
|
+
errors.push(`measureDistance: ${dist.error.message}`);
|
|
30
|
+
return { errors };
|
|
31
|
+
} catch (_) {
|
|
32
|
+
_usingCtx$2.e = _;
|
|
33
|
+
} finally {
|
|
34
|
+
_usingCtx$2.d();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
//#endregion
|
|
38
|
+
//#region src/verify/diff.ts
|
|
39
|
+
function emptyDiff(errors) {
|
|
40
|
+
return {
|
|
41
|
+
volumeDelta: 0,
|
|
42
|
+
areaDelta: 0,
|
|
43
|
+
bboxDelta: {
|
|
44
|
+
xMin: 0,
|
|
45
|
+
xMax: 0,
|
|
46
|
+
yMin: 0,
|
|
47
|
+
yMax: 0,
|
|
48
|
+
zMin: 0,
|
|
49
|
+
zMax: 0
|
|
50
|
+
},
|
|
51
|
+
symmetricDifferenceVolume: 0,
|
|
52
|
+
errors
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function volumeOf(brep, shape, errors) {
|
|
56
|
+
const v = brep.measureVolume(shape);
|
|
57
|
+
if (brep.isOk(v)) return v.value;
|
|
58
|
+
errors.push(`measureVolume: ${v.error.message}`);
|
|
59
|
+
return 0;
|
|
60
|
+
}
|
|
61
|
+
function areaOf(brep, shape, errors) {
|
|
62
|
+
if (!brep.isShape3D(shape)) return 0;
|
|
63
|
+
const a = brep.measureArea(shape);
|
|
64
|
+
if (brep.isOk(a)) return a.value;
|
|
65
|
+
errors.push(`measureArea: ${a.error.message}`);
|
|
66
|
+
return 0;
|
|
67
|
+
}
|
|
68
|
+
function boundsDelta(brep, a, b, errors) {
|
|
69
|
+
try {
|
|
70
|
+
const ba = brep.getBounds(a);
|
|
71
|
+
const bb = brep.getBounds(b);
|
|
72
|
+
return {
|
|
73
|
+
xMin: bb.xMin - ba.xMin,
|
|
74
|
+
xMax: bb.xMax - ba.xMax,
|
|
75
|
+
yMin: bb.yMin - ba.yMin,
|
|
76
|
+
yMax: bb.yMax - ba.yMax,
|
|
77
|
+
zMin: bb.zMin - ba.zMin,
|
|
78
|
+
zMax: bb.zMax - ba.zMax
|
|
79
|
+
};
|
|
80
|
+
} catch (e) {
|
|
81
|
+
errors.push(`getBounds: ${e.message}`);
|
|
82
|
+
return {
|
|
83
|
+
xMin: 0,
|
|
84
|
+
xMax: 0,
|
|
85
|
+
yMin: 0,
|
|
86
|
+
yMax: 0,
|
|
87
|
+
zMin: 0,
|
|
88
|
+
zMax: 0
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function cutVolume(brep, x, y, errors) {
|
|
93
|
+
try {
|
|
94
|
+
var _usingCtx$1 = _usingCtx();
|
|
95
|
+
const r = brep.cut(x, y);
|
|
96
|
+
if (!brep.isOk(r)) {
|
|
97
|
+
errors.push(`cut: ${r.error.message}`);
|
|
98
|
+
return 0;
|
|
99
|
+
}
|
|
100
|
+
return volumeOf(brep, _usingCtx$1.u(r.value), errors);
|
|
101
|
+
} catch (_) {
|
|
102
|
+
_usingCtx$1.e = _;
|
|
103
|
+
} finally {
|
|
104
|
+
_usingCtx$1.d();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
async function runDiff(aPath, bPath) {
|
|
108
|
+
try {
|
|
109
|
+
var _usingCtx3 = _usingCtx();
|
|
110
|
+
const brep = await loadBrep();
|
|
111
|
+
const { isShape3D } = brep;
|
|
112
|
+
const errors = [];
|
|
113
|
+
const a = await runPart(aPath);
|
|
114
|
+
errors.push(...a.report.errors);
|
|
115
|
+
if (!a.shape) return emptyDiff(errors);
|
|
116
|
+
const sa = _usingCtx3.u(a.shape);
|
|
117
|
+
const b = await runPart(bPath);
|
|
118
|
+
errors.push(...b.report.errors);
|
|
119
|
+
if (!b.shape) return emptyDiff(errors);
|
|
120
|
+
const sb = _usingCtx3.u(b.shape);
|
|
121
|
+
const bboxDelta = boundsDelta(brep, sa, sb, errors);
|
|
122
|
+
const areaDelta = areaOf(brep, sb, errors) - areaOf(brep, sa, errors);
|
|
123
|
+
let volumeDelta = 0;
|
|
124
|
+
let symmetricDifferenceVolume = 0;
|
|
125
|
+
if (isShape3D(sa) && isShape3D(sb)) {
|
|
126
|
+
volumeDelta = volumeOf(brep, sb, errors) - volumeOf(brep, sa, errors);
|
|
127
|
+
symmetricDifferenceVolume = cutVolume(brep, sa, sb, errors) + cutVolume(brep, sb, sa, errors);
|
|
128
|
+
}
|
|
129
|
+
return {
|
|
130
|
+
volumeDelta,
|
|
131
|
+
areaDelta,
|
|
132
|
+
bboxDelta,
|
|
133
|
+
symmetricDifferenceVolume,
|
|
134
|
+
errors
|
|
135
|
+
};
|
|
136
|
+
} catch (_) {
|
|
137
|
+
_usingCtx3.e = _;
|
|
138
|
+
} finally {
|
|
139
|
+
_usingCtx3.d();
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
//#endregion
|
|
143
|
+
export { runMeasure as n, runDiff as t };
|