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.
- package/CHANGELOG.md +1 -0
- package/LICENSE +191 -0
- package/README.md +85 -0
- package/dist/brepjs-cad.cjs +8 -0
- package/dist/brepjs-cad.js +2 -0
- package/dist/cli/exportPart.d.ts +13 -0
- package/dist/cli/main.cjs +317 -0
- package/dist/cli/main.d.ts +2 -0
- package/dist/cli/main.js +316 -0
- package/dist/cli/scaffold.d.ts +9 -0
- package/dist/cli/watch.d.ts +10 -0
- package/dist/diff-4UNdqx4k.cjs +550 -0
- package/dist/diff-ByiVwVrr.js +509 -0
- package/dist/disposeShape.d.ts +6 -0
- package/dist/index.d.ts +5 -0
- package/dist/snapshot/registry.cjs +50 -0
- package/dist/snapshot/registry.d.ts +12 -0
- package/dist/snapshot/registry.js +48 -0
- package/dist/snapshot/serve.cjs +27 -0
- package/dist/snapshot/serve.d.ts +12 -0
- package/dist/snapshot/serve.js +26 -0
- package/dist/snapshot/shoot.cjs +85 -0
- package/dist/snapshot/shoot.d.ts +14 -0
- package/dist/snapshot/shoot.js +61 -0
- package/dist/snapshot/static.cjs +99 -0
- package/dist/snapshot/static.d.ts +16 -0
- package/dist/snapshot/static.js +97 -0
- package/dist/verify/checks.d.ts +3 -0
- package/dist/verify/diff.d.ts +2 -0
- package/dist/verify/measure.d.ts +6 -0
- package/dist/verify/report.d.ts +63 -0
- package/dist/verify/runPart.d.ts +18 -0
- package/package.json +79 -0
- package/viewer/dist/assets/brepjs-CI5VXw8W.js +57 -0
- package/viewer/dist/assets/index-DivdJhNC.js +4167 -0
- package/viewer/dist/assets/kernelWorker-BtcMpY8t.js +1 -0
- package/viewer/dist/index.html +22 -0
- package/viewer/dist/wasm/occt-wasm.js +2 -0
- package/viewer/dist/wasm/occt-wasm.wasm +0 -0
package/dist/cli/main.js
ADDED
|
@@ -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,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
|
+
};
|