brepjs-cad 0.1.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.
Files changed (39) hide show
  1. package/CHANGELOG.md +1 -0
  2. package/LICENSE +191 -0
  3. package/README.md +85 -0
  4. package/dist/brepjs-cad.cjs +8 -0
  5. package/dist/brepjs-cad.js +2 -0
  6. package/dist/cli/exportPart.d.ts +13 -0
  7. package/dist/cli/main.cjs +317 -0
  8. package/dist/cli/main.d.ts +2 -0
  9. package/dist/cli/main.js +316 -0
  10. package/dist/cli/scaffold.d.ts +9 -0
  11. package/dist/cli/watch.d.ts +10 -0
  12. package/dist/diff-4UNdqx4k.cjs +550 -0
  13. package/dist/diff-ByiVwVrr.js +509 -0
  14. package/dist/disposeShape.d.ts +6 -0
  15. package/dist/index.d.ts +5 -0
  16. package/dist/snapshot/registry.cjs +50 -0
  17. package/dist/snapshot/registry.d.ts +12 -0
  18. package/dist/snapshot/registry.js +48 -0
  19. package/dist/snapshot/serve.cjs +27 -0
  20. package/dist/snapshot/serve.d.ts +12 -0
  21. package/dist/snapshot/serve.js +26 -0
  22. package/dist/snapshot/shoot.cjs +85 -0
  23. package/dist/snapshot/shoot.d.ts +14 -0
  24. package/dist/snapshot/shoot.js +61 -0
  25. package/dist/snapshot/static.cjs +99 -0
  26. package/dist/snapshot/static.d.ts +16 -0
  27. package/dist/snapshot/static.js +97 -0
  28. package/dist/verify/checks.d.ts +3 -0
  29. package/dist/verify/diff.d.ts +2 -0
  30. package/dist/verify/measure.d.ts +6 -0
  31. package/dist/verify/report.d.ts +63 -0
  32. package/dist/verify/runPart.d.ts +18 -0
  33. package/package.json +79 -0
  34. package/viewer/dist/assets/brepjs-CI5VXw8W.js +57 -0
  35. package/viewer/dist/assets/index-DivdJhNC.js +4167 -0
  36. package/viewer/dist/assets/kernelWorker-BtcMpY8t.js +1 -0
  37. package/viewer/dist/index.html +22 -0
  38. package/viewer/dist/wasm/occt-wasm.js +2 -0
  39. package/viewer/dist/wasm/occt-wasm.wasm +0 -0
@@ -0,0 +1,316 @@
1
+ #!/usr/bin/env node
2
+ import { n as runMeasure, o as reportOk, r as runPart, s as serializeReport, t as runDiff } from "../diff-ByiVwVrr.js";
3
+ import { exportSTL, isOk } from "brepjs";
4
+ import { Command } from "commander";
5
+ import { existsSync, mkdirSync, watch, writeFileSync } from "node:fs";
6
+ import { basename, dirname, join, resolve } from "node:path";
7
+ import { tmpdir } from "node:os";
8
+ import { pathToFileURL } from "node:url";
9
+ //#region src/cli/scaffold.ts
10
+ function partTemplate(name) {
11
+ return `import { box, cut, unwrap } from 'brepjs';
12
+
13
+ const width = 40;
14
+ const depth = 20;
15
+ const height = 10;
16
+ const holeSize = 6;
17
+
18
+ // ${name}: a parameterized starter part — edit the consts above, then re-verify.
19
+ export default () => {
20
+ const body = box(width, depth, height, { centered: true });
21
+ const hole = box(holeSize, holeSize, height + 2, { centered: true });
22
+ return unwrap(cut(body, hole));
23
+ };
24
+ `;
25
+ }
26
+ function tsconfigTemplate() {
27
+ return `${JSON.stringify({
28
+ compilerOptions: {
29
+ target: "ES2022",
30
+ module: "ESNext",
31
+ moduleResolution: "bundler",
32
+ strict: true,
33
+ noEmit: true,
34
+ skipLibCheck: true
35
+ },
36
+ include: ["*.brep.ts"]
37
+ }, null, 2)}\n`;
38
+ }
39
+ function readmeTemplate(name) {
40
+ return `# ${name}
41
+
42
+ A parametric brepjs CAD part. Units are millimetres.
43
+
44
+ ## Verify
45
+
46
+ \`\`\`sh
47
+ npx brepjs ${name}.brep.ts --json report.json
48
+ \`\`\`
49
+
50
+ ## Iterate
51
+
52
+ \`\`\`sh
53
+ npx brepjs watch ${name}.brep.ts
54
+ \`\`\`
55
+
56
+ ## Export artifacts
57
+
58
+ \`\`\`sh
59
+ npx brepjs export ${name}.brep.ts --all --out out/
60
+ \`\`\`
61
+ `;
62
+ }
63
+ function scaffoldPart(name, dir) {
64
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
65
+ const targets = [
66
+ {
67
+ path: join(dir, `${name}.brep.ts`),
68
+ content: partTemplate(name)
69
+ },
70
+ {
71
+ path: join(dir, "tsconfig.json"),
72
+ content: tsconfigTemplate()
73
+ },
74
+ {
75
+ path: join(dir, "README.md"),
76
+ content: readmeTemplate(name)
77
+ }
78
+ ];
79
+ const files = [];
80
+ for (const t of targets) {
81
+ if (existsSync(t.path)) {
82
+ files.push({
83
+ path: t.path,
84
+ created: false
85
+ });
86
+ continue;
87
+ }
88
+ writeFileSync(t.path, t.content);
89
+ files.push({
90
+ path: t.path,
91
+ created: true
92
+ });
93
+ }
94
+ return {
95
+ dir,
96
+ files
97
+ };
98
+ }
99
+ /**
100
+ * Wraps a handler so bursts of rapid calls collapse into a single trailing
101
+ * invocation after `delayMs` of quiet — fs.watch fires twice per save on many
102
+ * platforms, so the model is re-run once rather than per raw event.
103
+ */
104
+ function debounce(fn, delayMs = 150) {
105
+ let timer;
106
+ const cancel = () => {
107
+ if (timer) {
108
+ clearTimeout(timer);
109
+ timer = void 0;
110
+ }
111
+ };
112
+ const trigger = () => {
113
+ cancel();
114
+ timer = setTimeout(() => {
115
+ timer = void 0;
116
+ fn();
117
+ }, delayMs);
118
+ };
119
+ return {
120
+ trigger,
121
+ cancel
122
+ };
123
+ }
124
+ //#endregion
125
+ //#region src/disposeShape.ts
126
+ /**
127
+ * Release a live WASM-backed kernel shape handle returned by `runPart`.
128
+ * No-op for null/undefined or shapes without a disposer, so callers can pass
129
+ * `result.shape` unconditionally. WASM memory accumulates without this.
130
+ */
131
+ function disposeShape(shape) {
132
+ const disposer = shape?.[Symbol.dispose];
133
+ if (typeof disposer === "function") disposer.call(shape);
134
+ }
135
+ //#endregion
136
+ //#region src/cli/exportPart.ts
137
+ function stem(file) {
138
+ return basename(file).replace(/\.brep\.ts$/, "").replace(/\.ts$/, "");
139
+ }
140
+ async function exportPart(modulePath, formats, outDir) {
141
+ const wantStl = Boolean(formats.stl);
142
+ const { shape, report, step, glb } = await runPart(modulePath, {
143
+ step: Boolean(formats.step),
144
+ glb: Boolean(formats.glb)
145
+ });
146
+ const errors = [];
147
+ const written = [];
148
+ if (!(reportOk(report) && shape !== null)) {
149
+ disposeShape(shape);
150
+ return {
151
+ ok: false,
152
+ report,
153
+ written,
154
+ errors: report.errors
155
+ };
156
+ }
157
+ if (!existsSync(outDir)) mkdirSync(outDir, { recursive: true });
158
+ const base = stem(modulePath);
159
+ if (formats.step) if (step) {
160
+ const p = join(outDir, `${base}.step`);
161
+ writeFileSync(p, Buffer.from(step));
162
+ written.push(p);
163
+ } else errors.push("STEP export produced no data");
164
+ if (formats.glb) if (glb) {
165
+ const p = join(outDir, `${base}.glb`);
166
+ writeFileSync(p, Buffer.from(glb));
167
+ written.push(p);
168
+ } else errors.push("GLB export produced no data");
169
+ if (wantStl) {
170
+ const r = exportSTL(shape);
171
+ if (isOk(r)) {
172
+ const p = join(outDir, `${base}.stl`);
173
+ writeFileSync(p, Buffer.from(await r.value.arrayBuffer()));
174
+ written.push(p);
175
+ } else errors.push(`STL export: ${r.error.message}`);
176
+ }
177
+ disposeShape(shape);
178
+ return {
179
+ ok: errors.length === 0,
180
+ report,
181
+ written,
182
+ errors
183
+ };
184
+ }
185
+ //#endregion
186
+ //#region src/cli/main.ts
187
+ console.log = (...args) => {
188
+ process.stderr.write(args.map(String).join(" ") + "\n");
189
+ };
190
+ async function loadSnapshotShoot() {
191
+ try {
192
+ return (await import("../snapshot/shoot.js")).shoot;
193
+ } catch {
194
+ process.stderr.write("snapshots need puppeteer/Chrome — run: npm i puppeteer\n");
195
+ process.exitCode = 1;
196
+ return;
197
+ }
198
+ }
199
+ var program = new Command();
200
+ program.name("brepjs");
201
+ 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("--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) => {
202
+ const wantStep = Boolean(opts.step) || Boolean(opts.snapshot) || Boolean(opts.serve);
203
+ const { report, step, glb } = await runPart(resolve(file), {
204
+ step: wantStep,
205
+ glb: Boolean(opts.glb)
206
+ });
207
+ const json = serializeReport(report);
208
+ if (opts.json) writeFileSync(opts.json, json);
209
+ if (opts.glb && glb) writeFileSync(opts.glb, Buffer.from(glb));
210
+ let stepPath = opts.step;
211
+ if (wantStep && step) {
212
+ stepPath = opts.step ?? join(tmpdir(), `brepjs-cad-${basename(file)}.step`);
213
+ writeFileSync(stepPath, Buffer.from(step));
214
+ }
215
+ if (opts.snapshot && stepPath) {
216
+ const shoot = await loadSnapshotShoot();
217
+ if (shoot) {
218
+ const { pngs } = await shoot({
219
+ file: stepPath,
220
+ outDir: opts.snapshot
221
+ });
222
+ for (const p of pngs) process.stderr.write(`snapshot: ${p}\n`);
223
+ }
224
+ }
225
+ process.stdout.write(json + "\n");
226
+ const parsed = JSON.parse(json);
227
+ if (!opts.serve && parsed.ok !== true) process.exitCode = 1;
228
+ if (opts.serve && stepPath) {
229
+ const { serve } = await import("../snapshot/serve.js");
230
+ const { url } = await serve({ file: stepPath });
231
+ process.stderr.write(`viewer: ${url}\n`);
232
+ }
233
+ });
234
+ 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) => {
235
+ const result = await runMeasure(resolve(a), b === void 0 ? void 0 : resolve(b));
236
+ process.stdout.write(JSON.stringify({
237
+ ok: result.errors.length === 0,
238
+ ...result
239
+ }, null, 2) + "\n");
240
+ if (result.errors.length > 0) process.exitCode = 1;
241
+ });
242
+ program.command("diff").argument("<a>", "path to the baseline .brep.ts module").argument("<b>", "path to the comparison .brep.ts module").action(async (a, b) => {
243
+ const result = await runDiff(resolve(a), resolve(b));
244
+ process.stdout.write(JSON.stringify({
245
+ ok: result.errors.length === 0,
246
+ ...result
247
+ }, null, 2) + "\n");
248
+ if (result.errors.length > 0) process.exitCode = 1;
249
+ });
250
+ 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) => {
251
+ const result = scaffoldPart(name, resolve(opts.out ?? name));
252
+ for (const f of result.files) {
253
+ const tag = f.created ? "created" : "exists (kept)";
254
+ process.stderr.write(`${tag}: ${f.path}\n`);
255
+ }
256
+ process.stdout.write(JSON.stringify(result, null, 2) + "\n");
257
+ });
258
+ program.command("watch").argument("<file>", "path to a .brep.ts module; re-verifies on each save until Ctrl-C").action((file) => {
259
+ const path = resolve(file);
260
+ const run = async () => {
261
+ try {
262
+ const { report, shape } = await runPart(path);
263
+ try {
264
+ process.stdout.write(serializeReport(report) + "\n");
265
+ } finally {
266
+ disposeShape(shape);
267
+ }
268
+ } catch (e) {
269
+ process.stderr.write(`watch run failed: ${e.message}\n`);
270
+ }
271
+ };
272
+ const { trigger } = debounce(run, 150);
273
+ process.stderr.write(`watching ${path} (Ctrl-C to stop)\n`);
274
+ run();
275
+ const watcher = watch(dirname(path), (_event, filename) => {
276
+ if (filename === void 0 || filename === null) {
277
+ trigger();
278
+ return;
279
+ }
280
+ if (basename(path) === filename.toString()) trigger();
281
+ });
282
+ const stop = () => {
283
+ watcher.close();
284
+ process.exit(0);
285
+ };
286
+ process.on("SIGINT", stop);
287
+ process.on("SIGTERM", stop);
288
+ });
289
+ 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) => {
290
+ const formats = opts.all ? {
291
+ step: true,
292
+ glb: true,
293
+ stl: true
294
+ } : {
295
+ step: Boolean(opts.step),
296
+ glb: Boolean(opts.glb),
297
+ stl: Boolean(opts.stl)
298
+ };
299
+ if (!formats.step && !formats.glb && !formats.stl) {
300
+ process.stderr.write("no formats requested — pass --step/--glb/--stl or --all\n");
301
+ process.exitCode = 1;
302
+ return;
303
+ }
304
+ const result = await exportPart(resolve(file), formats, resolve(opts.out));
305
+ for (const p of result.written) process.stderr.write(`wrote: ${p}\n`);
306
+ for (const e of result.errors) process.stderr.write(`error: ${e}\n`);
307
+ process.stdout.write(JSON.stringify({
308
+ ok: result.ok,
309
+ written: result.written,
310
+ errors: result.errors
311
+ }, null, 2) + "\n");
312
+ if (!result.ok) process.exitCode = 1;
313
+ });
314
+ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) program.parseAsync();
315
+ //#endregion
316
+ export { loadSnapshotShoot };
@@ -0,0 +1,9 @@
1
+ export interface ScaffoldFile {
2
+ path: string;
3
+ created: boolean;
4
+ }
5
+ export interface ScaffoldResult {
6
+ dir: string;
7
+ files: ScaffoldFile[];
8
+ }
9
+ export declare function scaffoldPart(name: string, dir: string): ScaffoldResult;
@@ -0,0 +1,10 @@
1
+ export declare const DEFAULT_DEBOUNCE_MS = 150;
2
+ /**
3
+ * Wraps a handler so bursts of rapid calls collapse into a single trailing
4
+ * invocation after `delayMs` of quiet — fs.watch fires twice per save on many
5
+ * platforms, so the model is re-run once rather than per raw event.
6
+ */
7
+ export declare function debounce(fn: () => void | Promise<void>, delayMs?: number): {
8
+ trigger: () => void;
9
+ cancel: () => void;
10
+ };