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
|
@@ -0,0 +1,509 @@
|
|
|
1
|
+
import { cut, exportGlb, exportSTEP, getBounds, init, isCompSolid, isCompound, isEdge, isFace, isOk, isShape3D, isShell, isSolid, isVertex, isWire, measureArea, measureDistance, measureLength, measureVolume, mesh, validSolid } from "brepjs";
|
|
2
|
+
//#region src/verify/report.ts
|
|
3
|
+
function emptyReport() {
|
|
4
|
+
return {
|
|
5
|
+
shapeType: null,
|
|
6
|
+
checks: [],
|
|
7
|
+
measurements: {},
|
|
8
|
+
errors: [],
|
|
9
|
+
errorInfos: [],
|
|
10
|
+
hints: []
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
/** Record a failure on the report, keeping the flat `errors` string list and structured `errorInfos` in sync. */
|
|
14
|
+
function pushError(r, info) {
|
|
15
|
+
r.errors.push(info.message);
|
|
16
|
+
r.errorInfos.push(info);
|
|
17
|
+
}
|
|
18
|
+
function reportOk(r) {
|
|
19
|
+
return r.errors.length === 0 && r.checks.every((c) => c.passed);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Local, brepjs-cad-owned advice keyed on `BrepErrorCode` values (see `brepjs`'s public
|
|
23
|
+
* `BrepErrorCode`). Intentionally not importing the library's internal `getSuggestionForCode`:
|
|
24
|
+
* this table is the agent loop's own actionable `fix` + `nextStep` guidance, and the library's
|
|
25
|
+
* public `BrepError.suggestion` is still surfaced alongside it on each hint.
|
|
26
|
+
*/
|
|
27
|
+
var HINT_TABLE = {
|
|
28
|
+
FILLET_NO_EDGES: {
|
|
29
|
+
fix: "Select real edges before filleting — pass an edge query (e.g. find edges by direction/position) or a non-empty edge list, not the whole solid.",
|
|
30
|
+
nextStep: "List the solid’s edges, pick the ones to round, then call fillet(solid, radius, edges)."
|
|
31
|
+
},
|
|
32
|
+
CHAMFER_NO_EDGES: {
|
|
33
|
+
fix: "Select real edges before chamfering — pass a non-empty edge query/list rather than relying on a default that matched nothing.",
|
|
34
|
+
nextStep: "Enumerate the solid’s edges, choose the target edges, then call chamfer(solid, distance, edges)."
|
|
35
|
+
},
|
|
36
|
+
INVALID_FILLET_RADIUS: {
|
|
37
|
+
fix: "Use a fillet radius that is > 0 and small enough to fit the adjacent faces (well under half the thinnest wall).",
|
|
38
|
+
nextStep: "Reduce the radius (try a fraction of the smallest local feature size) and re-verify."
|
|
39
|
+
},
|
|
40
|
+
INVALID_CHAMFER_DISTANCE: {
|
|
41
|
+
fix: "Use a chamfer distance that is > 0 and smaller than the adjacent edge lengths.",
|
|
42
|
+
nextStep: "Lower the distance below the shortest adjacent edge and re-verify."
|
|
43
|
+
},
|
|
44
|
+
INVALID_THICKNESS: {
|
|
45
|
+
fix: "Use a shell/wall thickness that is > 0 and less than half the smallest cross-section.",
|
|
46
|
+
nextStep: "Reduce the thickness and re-verify, or remove the offending face from the removed-faces set."
|
|
47
|
+
},
|
|
48
|
+
ZERO_LENGTH_EXTRUSION: {
|
|
49
|
+
fix: "Extrude by a non-zero distance — a length of 0 produces no solid.",
|
|
50
|
+
nextStep: "Set a positive extrusion height (units: mm) and re-verify."
|
|
51
|
+
},
|
|
52
|
+
ZERO_OFFSET: {
|
|
53
|
+
fix: "Offset by a non-zero amount — an offset of 0 is a no-op the kernel rejects.",
|
|
54
|
+
nextStep: "Use a small non-zero offset (positive grows, negative shrinks) and re-verify."
|
|
55
|
+
},
|
|
56
|
+
FILLET_NOT_3D: {
|
|
57
|
+
fix: "fillet needs a 3D solid. Build the solid (extrude/revolve/box) before rounding edges.",
|
|
58
|
+
nextStep: "Move the fillet after the solid is created, then fillet the solid’s edges."
|
|
59
|
+
},
|
|
60
|
+
CHAMFER_NOT_3D: {
|
|
61
|
+
fix: "chamfer needs a 3D solid. Build the solid first, then chamfer its edges.",
|
|
62
|
+
nextStep: "Reorder so chamfer runs on the finished solid, not a sketch/wire/face."
|
|
63
|
+
},
|
|
64
|
+
FUSE_NOT_3D: {
|
|
65
|
+
fix: "fuse needs two 3D solids. Ensure both operands are solids before unioning.",
|
|
66
|
+
nextStep: "Extrude/loft each profile into a solid first, then fuse and unwrap the Result."
|
|
67
|
+
},
|
|
68
|
+
CUT_NOT_3D: {
|
|
69
|
+
fix: "cut needs 3D solids for both the base and the tool. Build both as solids first.",
|
|
70
|
+
nextStep: "Make the tool a solid (e.g. a box/cylinder), then cut(base, tool) and unwrap the Result."
|
|
71
|
+
},
|
|
72
|
+
INTERSECT_NOT_3D: {
|
|
73
|
+
fix: "intersect needs two 3D solids. Build both operands as solids first.",
|
|
74
|
+
nextStep: "Ensure both inputs are solids, then intersect(a, b) and unwrap the Result."
|
|
75
|
+
},
|
|
76
|
+
SHELL_NOT_3D: {
|
|
77
|
+
fix: "shell needs a 3D solid. Create the solid before hollowing it.",
|
|
78
|
+
nextStep: "Build the solid first, then shell it with a thickness and the faces to remove."
|
|
79
|
+
},
|
|
80
|
+
OFFSET_NOT_3D: {
|
|
81
|
+
fix: "This offset needs a 3D solid. Build the solid before offsetting.",
|
|
82
|
+
nextStep: "Reorder so offset runs on the solid, then re-verify."
|
|
83
|
+
},
|
|
84
|
+
SWEEP_NOT_3D: {
|
|
85
|
+
fix: "sweep needs a 3D result context — check the profile and path produce a solid sweep.",
|
|
86
|
+
nextStep: "Verify the profile is a closed wire/face and the path is a valid wire, then re-sweep."
|
|
87
|
+
},
|
|
88
|
+
LOFT_NOT_3D: {
|
|
89
|
+
fix: "loft needs 3D-capable sections. Use closed profiles that can form a solid.",
|
|
90
|
+
nextStep: "Provide at least two closed section wires/faces, then loft and unwrap the Result."
|
|
91
|
+
},
|
|
92
|
+
REVOLUTION_NOT_3D: {
|
|
93
|
+
fix: "revolve needs a 2D profile revolved about an axis. Pass a face/closed wire.",
|
|
94
|
+
nextStep: "Use a closed profile and a valid axis, then revolve and unwrap the Result."
|
|
95
|
+
},
|
|
96
|
+
LOFT_FAILED: {
|
|
97
|
+
fix: "The loft could not be built — usually mismatched, self-intersecting, or out-of-order sections.",
|
|
98
|
+
nextStep: "Make the sections consistent (same orientation, non-self-intersecting, ordered along the loft) and retry."
|
|
99
|
+
},
|
|
100
|
+
LOFT_EMPTY: {
|
|
101
|
+
fix: "loft received too few sections. Provide at least two profiles.",
|
|
102
|
+
nextStep: "Add the missing section wires/faces and loft again."
|
|
103
|
+
},
|
|
104
|
+
SWEEP_FAILED: {
|
|
105
|
+
fix: "The sweep failed — usually a path with sharp corners/self-intersection or a profile too large for the path curvature.",
|
|
106
|
+
nextStep: "Smooth or simplify the path, shrink the profile, then re-sweep."
|
|
107
|
+
},
|
|
108
|
+
FUSE_FAILED: {
|
|
109
|
+
fix: "The boolean union failed — often touching-but-not-overlapping solids or tolerance issues.",
|
|
110
|
+
nextStep: "Make the operands overlap slightly (or heal/translate one), then re-fuse."
|
|
111
|
+
},
|
|
112
|
+
CUT_FAILED: {
|
|
113
|
+
fix: "The boolean subtraction failed — often a tool that does not actually intersect the base, or tolerance issues.",
|
|
114
|
+
nextStep: "Confirm the tool overlaps the base, optionally heal the inputs, then re-cut."
|
|
115
|
+
},
|
|
116
|
+
BOOLEAN_HAS_ERRORS: {
|
|
117
|
+
fix: "The boolean ran but the kernel reported errors (often coincident faces or near-tangent contact).",
|
|
118
|
+
nextStep: "Perturb one operand slightly so contact is a clean overlap, or heal the inputs, then retry."
|
|
119
|
+
},
|
|
120
|
+
STEP_EXPORT_CRASHED: {
|
|
121
|
+
fix: "STEP export crashed in the kernel — frequently a disjoint/degenerate fuse or an invalid solid reaching the exporter.",
|
|
122
|
+
nextStep: "Run validity checks first, heal/simplify the shape (or avoid fusing disjoint solids), then re-export."
|
|
123
|
+
},
|
|
124
|
+
STEP_EXPORT_FAILED: {
|
|
125
|
+
fix: "STEP export failed. The shape is likely invalid or non-manifold.",
|
|
126
|
+
nextStep: "Fix validity errors (heal/sew) until the solid is valid, then re-export."
|
|
127
|
+
},
|
|
128
|
+
STL_EXPORT_CRASHED: {
|
|
129
|
+
fix: "STL export crashed — usually an invalid or non-manifold mesh source.",
|
|
130
|
+
nextStep: "Verify the solid is valid and watertight, then re-export."
|
|
131
|
+
},
|
|
132
|
+
STL_EXPORT_FAILED: {
|
|
133
|
+
fix: "STL export failed. The shape is likely invalid or empty.",
|
|
134
|
+
nextStep: "Fix validity errors first, then re-export."
|
|
135
|
+
},
|
|
136
|
+
NULL_SHAPE_INPUT: {
|
|
137
|
+
fix: "An operation received a null/empty shape. Ensure the previous step actually produced a shape.",
|
|
138
|
+
nextStep: "Check the upstream Result was unwrapped (not an Err) before passing it on."
|
|
139
|
+
},
|
|
140
|
+
NULL_SHAPE: {
|
|
141
|
+
fix: "An operation produced or received a null shape. A prior step likely failed silently.",
|
|
142
|
+
nextStep: "Verify each intermediate shape is non-null before chaining the next operation."
|
|
143
|
+
},
|
|
144
|
+
VALIDATION_FAILED: {
|
|
145
|
+
fix: "The shape failed validity (BRepCheck). It is non-manifold, self-intersecting, or has bad geometry.",
|
|
146
|
+
nextStep: "Heal/sew the shape, or revisit the operation that produced it, until validSolid passes."
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
/** Synthetic code attached to validity-check failures (validSolid returns a plain string error). */
|
|
150
|
+
var VALIDITY_FAILURE_CODE = "VALIDATION_FAILED";
|
|
151
|
+
function hintFor(info) {
|
|
152
|
+
if (!info.code) return null;
|
|
153
|
+
const entry = HINT_TABLE[info.code];
|
|
154
|
+
const fix = entry?.fix ?? info.suggestion ?? "No specific fix available; inspect the error and the operation that produced it.";
|
|
155
|
+
const nextStep = entry?.nextStep ?? "Adjust the failing operation per the message/suggestion, then re-verify.";
|
|
156
|
+
return {
|
|
157
|
+
code: info.code,
|
|
158
|
+
message: info.message,
|
|
159
|
+
fix,
|
|
160
|
+
nextStep
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
function buildHints(r) {
|
|
164
|
+
const hints = [];
|
|
165
|
+
const seen = /* @__PURE__ */ new Set();
|
|
166
|
+
for (const info of r.errorInfos) {
|
|
167
|
+
const hint = hintFor(info);
|
|
168
|
+
if (!hint) continue;
|
|
169
|
+
const key = `${hint.code}${hint.message}`;
|
|
170
|
+
if (seen.has(key)) continue;
|
|
171
|
+
seen.add(key);
|
|
172
|
+
hints.push(hint);
|
|
173
|
+
}
|
|
174
|
+
return hints;
|
|
175
|
+
}
|
|
176
|
+
function serializeReport(r) {
|
|
177
|
+
return JSON.stringify({
|
|
178
|
+
ok: reportOk(r),
|
|
179
|
+
...r
|
|
180
|
+
}, null, 2);
|
|
181
|
+
}
|
|
182
|
+
//#endregion
|
|
183
|
+
//#region src/verify/checks.ts
|
|
184
|
+
function shapeTypeOf(s) {
|
|
185
|
+
if (isSolid(s)) return "Solid";
|
|
186
|
+
if (isCompSolid(s)) return "CompSolid";
|
|
187
|
+
if (isCompound(s)) return "Compound";
|
|
188
|
+
if (isShell(s)) return "Shell";
|
|
189
|
+
if (isFace(s)) return "Face";
|
|
190
|
+
if (isWire(s)) return "Wire";
|
|
191
|
+
if (isEdge(s)) return "Edge";
|
|
192
|
+
if (isVertex(s)) return "Vertex";
|
|
193
|
+
return "Unknown";
|
|
194
|
+
}
|
|
195
|
+
function runChecks(shape) {
|
|
196
|
+
const r = emptyReport();
|
|
197
|
+
r.shapeType = shapeTypeOf(shape);
|
|
198
|
+
if (isSolid(shape)) {
|
|
199
|
+
const valid = validSolid(shape);
|
|
200
|
+
const validCheck = {
|
|
201
|
+
name: "isValidSolid",
|
|
202
|
+
passed: isOk(valid)
|
|
203
|
+
};
|
|
204
|
+
if (!isOk(valid)) {
|
|
205
|
+
validCheck.detail = valid.error;
|
|
206
|
+
r.errorInfos.push({
|
|
207
|
+
message: `isValidSolid: ${valid.error}`,
|
|
208
|
+
code: VALIDITY_FAILURE_CODE
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
r.checks.push(validCheck);
|
|
212
|
+
}
|
|
213
|
+
if (isShape3D(shape)) {
|
|
214
|
+
const vol = measureVolume(shape);
|
|
215
|
+
if (isOk(vol)) {
|
|
216
|
+
r.measurements.volume = vol.value;
|
|
217
|
+
r.checks.push({
|
|
218
|
+
name: "positiveVolume",
|
|
219
|
+
passed: vol.value > 0
|
|
220
|
+
});
|
|
221
|
+
} else pushError(r, {
|
|
222
|
+
message: `measureVolume: ${vol.error.message}`,
|
|
223
|
+
code: vol.error.code,
|
|
224
|
+
suggestion: vol.error.suggestion
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
if (isFace(shape) || isShape3D(shape)) {
|
|
228
|
+
const area = measureArea(shape);
|
|
229
|
+
if (isOk(area)) r.measurements.area = area.value;
|
|
230
|
+
}
|
|
231
|
+
try {
|
|
232
|
+
r.measurements.bounds = getBounds(shape);
|
|
233
|
+
} catch (e) {
|
|
234
|
+
pushError(r, { message: `getBounds: ${e.message}` });
|
|
235
|
+
}
|
|
236
|
+
r.hints = buildHints(r);
|
|
237
|
+
return r;
|
|
238
|
+
}
|
|
239
|
+
//#endregion
|
|
240
|
+
//#region src/verify/runPart.ts
|
|
241
|
+
function isResult(v) {
|
|
242
|
+
return typeof v === "object" && v !== null && "ok" in v && typeof v.ok === "boolean";
|
|
243
|
+
}
|
|
244
|
+
function isBrepError(v) {
|
|
245
|
+
if (typeof v !== "object" || v === null) return false;
|
|
246
|
+
const rec = v;
|
|
247
|
+
return typeof rec["code"] === "string" && typeof rec["message"] === "string";
|
|
248
|
+
}
|
|
249
|
+
/** Pull structured `{ message, code, suggestion }` out of a `BrepError`, a thrown `Error`, or anything. */
|
|
250
|
+
function toErrorInfo(prefix, e) {
|
|
251
|
+
if (isBrepError(e)) return {
|
|
252
|
+
message: `${prefix}: ${e.message}`,
|
|
253
|
+
code: e.code,
|
|
254
|
+
suggestion: e.suggestion
|
|
255
|
+
};
|
|
256
|
+
if (e instanceof Error) return { message: `${prefix}: ${e.message}` };
|
|
257
|
+
return { message: `${prefix}: ${String(e)}` };
|
|
258
|
+
}
|
|
259
|
+
function finalize(result) {
|
|
260
|
+
result.report.hints = buildHints(result.report);
|
|
261
|
+
return result;
|
|
262
|
+
}
|
|
263
|
+
async function runPart(modulePath, opts = {}) {
|
|
264
|
+
await init();
|
|
265
|
+
const report = emptyReport();
|
|
266
|
+
let mod;
|
|
267
|
+
try {
|
|
268
|
+
mod = await import(modulePath);
|
|
269
|
+
} catch (e) {
|
|
270
|
+
pushError(report, toErrorInfo("import failed", e));
|
|
271
|
+
return finalize({
|
|
272
|
+
shape: null,
|
|
273
|
+
report
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
if (typeof mod.default !== "function") {
|
|
277
|
+
pushError(report, { message: "module has no default-exported part function" });
|
|
278
|
+
return finalize({
|
|
279
|
+
shape: null,
|
|
280
|
+
report
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
let out;
|
|
284
|
+
try {
|
|
285
|
+
out = await mod.default();
|
|
286
|
+
} catch (e) {
|
|
287
|
+
pushError(report, toErrorInfo("part threw", e));
|
|
288
|
+
return finalize({
|
|
289
|
+
shape: null,
|
|
290
|
+
report
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
let shape;
|
|
294
|
+
if (isResult(out)) if (isOk(out)) shape = out.value;
|
|
295
|
+
else {
|
|
296
|
+
pushError(report, toErrorInfo("part returned Err", out.error));
|
|
297
|
+
return finalize({
|
|
298
|
+
shape: null,
|
|
299
|
+
report
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
else shape = out;
|
|
303
|
+
if (!shape) {
|
|
304
|
+
pushError(report, { message: "part produced no shape" });
|
|
305
|
+
return finalize({
|
|
306
|
+
shape: null,
|
|
307
|
+
report
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
const result = runChecks(shape);
|
|
311
|
+
let glb;
|
|
312
|
+
let step;
|
|
313
|
+
if (opts.glb) try {
|
|
314
|
+
glb = exportGlb(mesh(shape));
|
|
315
|
+
} catch (e) {
|
|
316
|
+
pushError(result, toErrorInfo("exportGlb", e));
|
|
317
|
+
}
|
|
318
|
+
if (opts.step) {
|
|
319
|
+
const r = exportSTEP(shape);
|
|
320
|
+
if (isOk(r)) step = await r.value.arrayBuffer();
|
|
321
|
+
else pushError(result, toErrorInfo("exportSTEP", r.error));
|
|
322
|
+
}
|
|
323
|
+
return finalize({
|
|
324
|
+
shape,
|
|
325
|
+
report: result,
|
|
326
|
+
step,
|
|
327
|
+
glb
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
//#endregion
|
|
331
|
+
//#region \0@oxc-project+runtime@0.132.0/helpers/usingCtx.js
|
|
332
|
+
function _usingCtx() {
|
|
333
|
+
var r = "function" == typeof SuppressedError ? SuppressedError : function(r, e) {
|
|
334
|
+
var n = Error();
|
|
335
|
+
return n.name = "SuppressedError", n.error = r, n.suppressed = e, n;
|
|
336
|
+
}, e = {}, n = [];
|
|
337
|
+
function using(r, e) {
|
|
338
|
+
if (null != e) {
|
|
339
|
+
if (Object(e) !== e) throw new TypeError("using declarations can only be used with objects, functions, null, or undefined.");
|
|
340
|
+
if (r) var o = e[Symbol.asyncDispose || Symbol["for"]("Symbol.asyncDispose")];
|
|
341
|
+
if (void 0 === o && (o = e[Symbol.dispose || Symbol["for"]("Symbol.dispose")], r)) var t = o;
|
|
342
|
+
if ("function" != typeof o) throw new TypeError("Object is not disposable.");
|
|
343
|
+
t && (o = function o() {
|
|
344
|
+
try {
|
|
345
|
+
t.call(e);
|
|
346
|
+
} catch (r) {
|
|
347
|
+
return Promise.reject(r);
|
|
348
|
+
}
|
|
349
|
+
}), n.push({
|
|
350
|
+
v: e,
|
|
351
|
+
d: o,
|
|
352
|
+
a: r
|
|
353
|
+
});
|
|
354
|
+
} else r && n.push({
|
|
355
|
+
d: e,
|
|
356
|
+
a: r
|
|
357
|
+
});
|
|
358
|
+
return e;
|
|
359
|
+
}
|
|
360
|
+
return {
|
|
361
|
+
e,
|
|
362
|
+
u: using.bind(null, !1),
|
|
363
|
+
a: using.bind(null, !0),
|
|
364
|
+
d: function d() {
|
|
365
|
+
var o, t = this.e, s = 0;
|
|
366
|
+
function next() {
|
|
367
|
+
for (; o = n.pop();) try {
|
|
368
|
+
if (!o.a && 1 === s) return s = 0, n.push(o), Promise.resolve().then(next);
|
|
369
|
+
if (o.d) {
|
|
370
|
+
var r = o.d.call(o.v);
|
|
371
|
+
if (o.a) return s |= 2, Promise.resolve(r).then(next, err);
|
|
372
|
+
} else s |= 1;
|
|
373
|
+
} catch (r) {
|
|
374
|
+
return err(r);
|
|
375
|
+
}
|
|
376
|
+
if (1 === s) return t !== e ? Promise.reject(t) : Promise.resolve();
|
|
377
|
+
if (t !== e) throw t;
|
|
378
|
+
}
|
|
379
|
+
function err(n) {
|
|
380
|
+
return t = t !== e ? new r(n, t) : n, next();
|
|
381
|
+
}
|
|
382
|
+
return next();
|
|
383
|
+
}
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
//#endregion
|
|
387
|
+
//#region src/verify/measure.ts
|
|
388
|
+
async function runMeasure(aPath, bPath) {
|
|
389
|
+
try {
|
|
390
|
+
var _usingCtx$2 = _usingCtx();
|
|
391
|
+
const errors = [];
|
|
392
|
+
const a = await runPart(aPath);
|
|
393
|
+
errors.push(...a.report.errors);
|
|
394
|
+
if (!a.shape) return { errors };
|
|
395
|
+
const sa = _usingCtx$2.u(a.shape);
|
|
396
|
+
if (bPath === void 0) {
|
|
397
|
+
const len = measureLength(sa);
|
|
398
|
+
if (isOk(len)) return {
|
|
399
|
+
length: len.value,
|
|
400
|
+
errors
|
|
401
|
+
};
|
|
402
|
+
errors.push(`measureLength: ${len.error.message}`);
|
|
403
|
+
return { errors };
|
|
404
|
+
}
|
|
405
|
+
const b = await runPart(bPath);
|
|
406
|
+
errors.push(...b.report.errors);
|
|
407
|
+
if (!b.shape) return { errors };
|
|
408
|
+
const dist = measureDistance(sa, _usingCtx$2.u(b.shape));
|
|
409
|
+
if (isOk(dist)) return {
|
|
410
|
+
distance: dist.value,
|
|
411
|
+
errors
|
|
412
|
+
};
|
|
413
|
+
errors.push(`measureDistance: ${dist.error.message}`);
|
|
414
|
+
return { errors };
|
|
415
|
+
} catch (_) {
|
|
416
|
+
_usingCtx$2.e = _;
|
|
417
|
+
} finally {
|
|
418
|
+
_usingCtx$2.d();
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
//#endregion
|
|
422
|
+
//#region src/verify/diff.ts
|
|
423
|
+
function emptyDiff(errors) {
|
|
424
|
+
return {
|
|
425
|
+
volumeDelta: 0,
|
|
426
|
+
areaDelta: 0,
|
|
427
|
+
bboxDelta: {
|
|
428
|
+
xMin: 0,
|
|
429
|
+
xMax: 0,
|
|
430
|
+
yMin: 0,
|
|
431
|
+
yMax: 0,
|
|
432
|
+
zMin: 0,
|
|
433
|
+
zMax: 0
|
|
434
|
+
},
|
|
435
|
+
symmetricDifferenceVolume: 0,
|
|
436
|
+
errors
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
function volumeOf(shape, errors) {
|
|
440
|
+
const v = measureVolume(shape);
|
|
441
|
+
if (isOk(v)) return v.value;
|
|
442
|
+
errors.push(`measureVolume: ${v.error.message}`);
|
|
443
|
+
return 0;
|
|
444
|
+
}
|
|
445
|
+
function areaOf(shape, errors) {
|
|
446
|
+
if (!isShape3D(shape)) return 0;
|
|
447
|
+
const a = measureArea(shape);
|
|
448
|
+
if (isOk(a)) return a.value;
|
|
449
|
+
errors.push(`measureArea: ${a.error.message}`);
|
|
450
|
+
return 0;
|
|
451
|
+
}
|
|
452
|
+
function cutVolume(x, y, errors) {
|
|
453
|
+
try {
|
|
454
|
+
var _usingCtx$1 = _usingCtx();
|
|
455
|
+
const r = cut(x, y);
|
|
456
|
+
if (!isOk(r)) {
|
|
457
|
+
errors.push(`cut: ${r.error.message}`);
|
|
458
|
+
return 0;
|
|
459
|
+
}
|
|
460
|
+
return volumeOf(_usingCtx$1.u(r.value), errors);
|
|
461
|
+
} catch (_) {
|
|
462
|
+
_usingCtx$1.e = _;
|
|
463
|
+
} finally {
|
|
464
|
+
_usingCtx$1.d();
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
async function runDiff(aPath, bPath) {
|
|
468
|
+
try {
|
|
469
|
+
var _usingCtx3 = _usingCtx();
|
|
470
|
+
const errors = [];
|
|
471
|
+
const a = await runPart(aPath);
|
|
472
|
+
errors.push(...a.report.errors);
|
|
473
|
+
const b = await runPart(bPath);
|
|
474
|
+
errors.push(...b.report.errors);
|
|
475
|
+
if (!a.shape || !b.shape) return emptyDiff(errors);
|
|
476
|
+
const sa = _usingCtx3.u(a.shape);
|
|
477
|
+
const sb = _usingCtx3.u(b.shape);
|
|
478
|
+
const ba = getBounds(sa);
|
|
479
|
+
const bb = getBounds(sb);
|
|
480
|
+
const bboxDelta = {
|
|
481
|
+
xMin: bb.xMin - ba.xMin,
|
|
482
|
+
xMax: bb.xMax - ba.xMax,
|
|
483
|
+
yMin: bb.yMin - ba.yMin,
|
|
484
|
+
yMax: bb.yMax - ba.yMax,
|
|
485
|
+
zMin: bb.zMin - ba.zMin,
|
|
486
|
+
zMax: bb.zMax - ba.zMax
|
|
487
|
+
};
|
|
488
|
+
const areaDelta = areaOf(sb, errors) - areaOf(sa, errors);
|
|
489
|
+
let volumeDelta = 0;
|
|
490
|
+
let symmetricDifferenceVolume = 0;
|
|
491
|
+
if (isShape3D(sa) && isShape3D(sb)) {
|
|
492
|
+
volumeDelta = volumeOf(sb, errors) - volumeOf(sa, errors);
|
|
493
|
+
symmetricDifferenceVolume = cutVolume(sa, sb, errors) + cutVolume(sb, sa, errors);
|
|
494
|
+
}
|
|
495
|
+
return {
|
|
496
|
+
volumeDelta,
|
|
497
|
+
areaDelta,
|
|
498
|
+
bboxDelta,
|
|
499
|
+
symmetricDifferenceVolume,
|
|
500
|
+
errors
|
|
501
|
+
};
|
|
502
|
+
} catch (_) {
|
|
503
|
+
_usingCtx3.e = _;
|
|
504
|
+
} finally {
|
|
505
|
+
_usingCtx3.d();
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
//#endregion
|
|
509
|
+
export { emptyReport as a, runChecks as i, runMeasure as n, reportOk as o, runPart as r, serializeReport as s, runDiff as t };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Release a live WASM-backed kernel shape handle returned by `runPart`.
|
|
3
|
+
* No-op for null/undefined or shapes without a disposer, so callers can pass
|
|
4
|
+
* `result.shape` unconditionally. WASM memory accumulates without this.
|
|
5
|
+
*/
|
|
6
|
+
export declare function disposeShape(shape: unknown): void;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { runPart, type RunPartOptions, type RunPartResult } from './verify/runPart.js';
|
|
2
|
+
export { runChecks } from './verify/checks.js';
|
|
3
|
+
export { runMeasure, type MeasureReport } from './verify/measure.js';
|
|
4
|
+
export { runDiff } from './verify/diff.js';
|
|
5
|
+
export { serializeReport, emptyReport, type VerifyReport, type VerifyCheck, type VerifyMeasurements, type DiffReport, type BoundsDelta, } from './verify/report.js';
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_snapshot_static = require("./static.cjs");
|
|
3
|
+
//#region src/snapshot/registry.ts
|
|
4
|
+
var DEFAULT_PORT = 7373;
|
|
5
|
+
var PROBE_SPAN = 8;
|
|
6
|
+
var DEFAULT_SHUTDOWN_AFTER_MS = 720 * 60 * 1e3;
|
|
7
|
+
async function probe(port) {
|
|
8
|
+
const ctrl = new AbortController();
|
|
9
|
+
const timer = setTimeout(() => ctrl.abort(), 400);
|
|
10
|
+
try {
|
|
11
|
+
const res = await fetch(`http://127.0.0.1:${port}/__cad/server`, { signal: ctrl.signal });
|
|
12
|
+
if (!res.ok) return false;
|
|
13
|
+
const d = await res.json();
|
|
14
|
+
return d.app === "brepjs-cad-viewer" && d.dynamicRoot === true && typeof d.serverApiVersion === "number" && d.serverApiVersion >= 1;
|
|
15
|
+
} catch {
|
|
16
|
+
return false;
|
|
17
|
+
} finally {
|
|
18
|
+
clearTimeout(timer);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
async function acquireServer(opts = {}) {
|
|
22
|
+
const ports = opts.port !== void 0 ? [opts.port] : Array.from({ length: PROBE_SPAN }, (_, i) => DEFAULT_PORT + i);
|
|
23
|
+
for (const port of ports) if (await probe(port)) return {
|
|
24
|
+
port,
|
|
25
|
+
url: `http://127.0.0.1:${port}`,
|
|
26
|
+
reused: true,
|
|
27
|
+
close: () => Promise.resolve()
|
|
28
|
+
};
|
|
29
|
+
let server;
|
|
30
|
+
for (const port of ports) try {
|
|
31
|
+
server = await require_snapshot_static.startStaticServer({ port });
|
|
32
|
+
break;
|
|
33
|
+
} catch {}
|
|
34
|
+
if (!server) throw new Error(`no free port in ${ports[0]}..${ports[ports.length - 1]}`);
|
|
35
|
+
const timer = setTimeout(() => void server?.close(), opts.shutdownAfterMs ?? 432e5);
|
|
36
|
+
timer.unref();
|
|
37
|
+
const started = server;
|
|
38
|
+
return {
|
|
39
|
+
port: started.port,
|
|
40
|
+
url: started.url,
|
|
41
|
+
reused: false,
|
|
42
|
+
close: async () => {
|
|
43
|
+
clearTimeout(timer);
|
|
44
|
+
await started.close();
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
//#endregion
|
|
49
|
+
exports.DEFAULT_SHUTDOWN_AFTER_MS = DEFAULT_SHUTDOWN_AFTER_MS;
|
|
50
|
+
exports.acquireServer = acquireServer;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare const DEFAULT_SHUTDOWN_AFTER_MS: number;
|
|
2
|
+
export interface AcquireOptions {
|
|
3
|
+
port?: number;
|
|
4
|
+
shutdownAfterMs?: number;
|
|
5
|
+
}
|
|
6
|
+
export interface AcquiredServer {
|
|
7
|
+
port: number;
|
|
8
|
+
url: string;
|
|
9
|
+
reused: boolean;
|
|
10
|
+
close(): Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
export declare function acquireServer(opts?: AcquireOptions): Promise<AcquiredServer>;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { startStaticServer } from "./static.js";
|
|
2
|
+
//#region src/snapshot/registry.ts
|
|
3
|
+
var DEFAULT_PORT = 7373;
|
|
4
|
+
var PROBE_SPAN = 8;
|
|
5
|
+
var DEFAULT_SHUTDOWN_AFTER_MS = 720 * 60 * 1e3;
|
|
6
|
+
async function probe(port) {
|
|
7
|
+
const ctrl = new AbortController();
|
|
8
|
+
const timer = setTimeout(() => ctrl.abort(), 400);
|
|
9
|
+
try {
|
|
10
|
+
const res = await fetch(`http://127.0.0.1:${port}/__cad/server`, { signal: ctrl.signal });
|
|
11
|
+
if (!res.ok) return false;
|
|
12
|
+
const d = await res.json();
|
|
13
|
+
return d.app === "brepjs-cad-viewer" && d.dynamicRoot === true && typeof d.serverApiVersion === "number" && d.serverApiVersion >= 1;
|
|
14
|
+
} catch {
|
|
15
|
+
return false;
|
|
16
|
+
} finally {
|
|
17
|
+
clearTimeout(timer);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
async function acquireServer(opts = {}) {
|
|
21
|
+
const ports = opts.port !== void 0 ? [opts.port] : Array.from({ length: PROBE_SPAN }, (_, i) => DEFAULT_PORT + i);
|
|
22
|
+
for (const port of ports) if (await probe(port)) return {
|
|
23
|
+
port,
|
|
24
|
+
url: `http://127.0.0.1:${port}`,
|
|
25
|
+
reused: true,
|
|
26
|
+
close: () => Promise.resolve()
|
|
27
|
+
};
|
|
28
|
+
let server;
|
|
29
|
+
for (const port of ports) try {
|
|
30
|
+
server = await startStaticServer({ port });
|
|
31
|
+
break;
|
|
32
|
+
} catch {}
|
|
33
|
+
if (!server) throw new Error(`no free port in ${ports[0]}..${ports[ports.length - 1]}`);
|
|
34
|
+
const timer = setTimeout(() => void server?.close(), opts.shutdownAfterMs ?? 432e5);
|
|
35
|
+
timer.unref();
|
|
36
|
+
const started = server;
|
|
37
|
+
return {
|
|
38
|
+
port: started.port,
|
|
39
|
+
url: started.url,
|
|
40
|
+
reused: false,
|
|
41
|
+
close: async () => {
|
|
42
|
+
clearTimeout(timer);
|
|
43
|
+
await started.close();
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
//#endregion
|
|
48
|
+
export { DEFAULT_SHUTDOWN_AFTER_MS, acquireServer };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_snapshot_registry = require("./registry.cjs");
|
|
3
|
+
let node_path = require("node:path");
|
|
4
|
+
//#region src/snapshot/serve.ts
|
|
5
|
+
function viewerUrl(base, file) {
|
|
6
|
+
if (!file) return base;
|
|
7
|
+
const abs = (0, node_path.resolve)(file);
|
|
8
|
+
return `${base}/?dir=${encodeURIComponent((0, node_path.dirname)(abs))}&file=${encodeURIComponent((0, node_path.basename)(abs))}`;
|
|
9
|
+
}
|
|
10
|
+
/** Acquire (reuse or start) the persistent server and return its viewer URL. */
|
|
11
|
+
async function serve(opts = {}) {
|
|
12
|
+
const server = await require_snapshot_registry.acquireServer(opts);
|
|
13
|
+
const url = viewerUrl(server.url, opts.file);
|
|
14
|
+
if (!server.reused) {
|
|
15
|
+
const onSig = () => void server.close().then(() => process.exit(0));
|
|
16
|
+
process.once("SIGINT", onSig);
|
|
17
|
+
process.once("SIGTERM", onSig);
|
|
18
|
+
}
|
|
19
|
+
return {
|
|
20
|
+
port: server.port,
|
|
21
|
+
url,
|
|
22
|
+
reused: server.reused,
|
|
23
|
+
close: () => server.close()
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
//#endregion
|
|
27
|
+
exports.serve = serve;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { AcquireOptions } from './registry.js';
|
|
2
|
+
export interface ServeOptions extends AcquireOptions {
|
|
3
|
+
file?: string;
|
|
4
|
+
}
|
|
5
|
+
export interface ServeHandle {
|
|
6
|
+
port: number;
|
|
7
|
+
url: string;
|
|
8
|
+
reused: boolean;
|
|
9
|
+
close(): Promise<void>;
|
|
10
|
+
}
|
|
11
|
+
/** Acquire (reuse or start) the persistent server and return its viewer URL. */
|
|
12
|
+
export declare function serve(opts?: ServeOptions): Promise<ServeHandle>;
|