brepjs-verify 0.3.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +78 -0
- package/README.md +51 -12
- package/dist/brepjs-verify.cjs +1 -1
- package/dist/brepjs-verify.js +1 -1
- package/dist/cli/main.cjs +42 -3
- package/dist/cli/main.js +42 -3
- package/dist/cli/openBrowser.d.ts +23 -0
- package/dist/{diff-Covv1XuJ.js → diff-BCECMYSQ.js} +160 -10
- package/dist/{diff-mxAOOl_m.cjs → diff-DDEu6YJM.cjs} +160 -10
- package/dist/index.d.ts +1 -1
- package/dist/mcp/server.cjs +400 -0
- package/dist/mcp/server.d.ts +10 -0
- package/dist/mcp/server.js +400 -0
- package/dist/mcp/tools.d.ts +77 -0
- package/dist/sandbox/runProgram.d.ts +62 -0
- package/dist/sandbox/runRecord.d.ts +20 -0
- package/dist/snapshot/shoot.cjs +2 -1
- package/dist/snapshot/shoot.d.ts +2 -0
- package/dist/snapshot/shoot.js +2 -1
- package/dist/verify/expected.d.ts +15 -0
- package/dist/verify/report.d.ts +13 -0
- package/package.json +8 -4
- package/reference/llms-full.txt +2052 -0
- package/reference/llms.txt +1810 -0
- package/viewer/dist/assets/brepjs-NH9yA5tB.js +59 -0
- package/viewer/dist/assets/index-CnQ8btWD.js +4167 -0
- package/viewer/dist/assets/kernelWorker-CMzbzBfs.js +1 -0
- package/viewer/dist/index.html +1 -1
- package/viewer/dist/assets/brepjs-CI5VXw8W.js +0 -57
- package/viewer/dist/assets/index-CiN0lKoi.js +0 -4167
- package/viewer/dist/assets/kernelWorker-BtcMpY8t.js +0 -1
|
@@ -164,6 +164,14 @@ var HINT_TABLE = {
|
|
|
164
164
|
fix: "The boolean subtraction failed — often a tool that does not actually intersect the base, or tolerance issues.",
|
|
165
165
|
nextStep: "Confirm the tool overlaps the base, optionally heal the inputs, then re-cut."
|
|
166
166
|
},
|
|
167
|
+
FILLET_FAILED: {
|
|
168
|
+
fix: "The fillet could not be built — usually the radius is too large for an edge it touched (it cannot exceed the adjacent face/wall), or you filleted EVERY edge (the no-edge-list form) including ones too thin to round.",
|
|
169
|
+
nextStep: "Select only the edges you mean to round — e.g. edgeFinder().inDirection(\"Z\").findAll(solid) — and/or reduce the radius below the thinnest adjacent wall, then re-verify."
|
|
170
|
+
},
|
|
171
|
+
CHAMFER_FAILED: {
|
|
172
|
+
fix: "The chamfer could not be built — usually the distance is too large for an edge it touched, or you chamfered EVERY edge (the no-edge-list form) including ones too thin.",
|
|
173
|
+
nextStep: "Select only the target edges with an edge finder and/or reduce the distance below the shortest adjacent edge, then re-verify."
|
|
174
|
+
},
|
|
167
175
|
BOOLEAN_HAS_ERRORS: {
|
|
168
176
|
fix: "The boolean ran but the kernel reported errors (often coincident faces or near-tangent contact).",
|
|
169
177
|
nextStep: "Perturb one operand slightly so contact is a clean overlap, or heal the inputs, then retry."
|
|
@@ -199,6 +207,10 @@ var HINT_TABLE = {
|
|
|
199
207
|
TYPECHECK: {
|
|
200
208
|
fix: "Fix the TypeScript type error before running the part — the API call or value does not match brepjs’s types.",
|
|
201
209
|
nextStep: "Correct the flagged type (e.g. argument/return type or import), then re-verify."
|
|
210
|
+
},
|
|
211
|
+
EXPECTED_UNKNOWN_KEY: {
|
|
212
|
+
fix: "Your `expected` block has keys the CLI does not assert (so the intended check never ran). Bounds must be `{ xMin, xMax, yMin, yMax, zMin, zMax }` — not `{ min, max }` or `{ x, y, z }`.",
|
|
213
|
+
nextStep: "Rewrite `expected` using only volume, area, tolerancePct, and bounds.{xMin..zMax}, then re-verify."
|
|
202
214
|
}
|
|
203
215
|
};
|
|
204
216
|
/** Synthetic code attached to validity-check failures (validSolid returns a plain string error). */
|
|
@@ -249,7 +261,7 @@ function shapeTypeOf(brep, s) {
|
|
|
249
261
|
return "Unknown";
|
|
250
262
|
}
|
|
251
263
|
function runChecks(brep, shape) {
|
|
252
|
-
const { isSolid, isShape3D, isFace,
|
|
264
|
+
const { isSolid, isShape3D, isFace, measureVolumeProps, measureArea, getBounds, getFaces, getEdges, getWires, getVertices, getSolids, getShells, isManifoldShell, validSolid, isOk } = brep;
|
|
253
265
|
const r = emptyReport();
|
|
254
266
|
r.shapeType = shapeTypeOf(brep, shape);
|
|
255
267
|
if (isSolid(shape)) {
|
|
@@ -266,19 +278,41 @@ function runChecks(brep, shape) {
|
|
|
266
278
|
});
|
|
267
279
|
}
|
|
268
280
|
r.checks.push(validCheck);
|
|
281
|
+
} else {
|
|
282
|
+
const solids = getSolids(shape);
|
|
283
|
+
if (solids.length > 0) {
|
|
284
|
+
const failures = [];
|
|
285
|
+
solids.forEach((s, i) => {
|
|
286
|
+
const v = validSolid(s);
|
|
287
|
+
if (!isOk(v)) failures.push(`body ${i}: ${v.error}`);
|
|
288
|
+
});
|
|
289
|
+
const bodiesCheck = {
|
|
290
|
+
name: "allBodiesValid",
|
|
291
|
+
passed: failures.length === 0
|
|
292
|
+
};
|
|
293
|
+
if (failures.length > 0) {
|
|
294
|
+
bodiesCheck.detail = `${failures.length}/${solids.length} bodies invalid — ${failures.join("; ")}`;
|
|
295
|
+
r.errorInfos.push({
|
|
296
|
+
message: `allBodiesValid: ${bodiesCheck.detail}`,
|
|
297
|
+
code: VALIDITY_FAILURE_CODE
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
r.checks.push(bodiesCheck);
|
|
301
|
+
}
|
|
269
302
|
}
|
|
270
303
|
if (isShape3D(shape)) {
|
|
271
|
-
const
|
|
272
|
-
if (isOk(
|
|
273
|
-
r.measurements.volume =
|
|
304
|
+
const volProps = measureVolumeProps(shape);
|
|
305
|
+
if (isOk(volProps)) {
|
|
306
|
+
r.measurements.volume = volProps.value.volume;
|
|
307
|
+
r.measurements.centerOfMass = volProps.value.centerOfMass;
|
|
274
308
|
r.checks.push({
|
|
275
309
|
name: "positiveVolume",
|
|
276
|
-
passed:
|
|
310
|
+
passed: volProps.value.volume > 0
|
|
277
311
|
});
|
|
278
312
|
} else pushError(r, {
|
|
279
|
-
message: `measureVolume: ${
|
|
280
|
-
code:
|
|
281
|
-
suggestion:
|
|
313
|
+
message: `measureVolume: ${volProps.error.message}`,
|
|
314
|
+
code: volProps.error.code,
|
|
315
|
+
suggestion: volProps.error.suggestion
|
|
282
316
|
});
|
|
283
317
|
}
|
|
284
318
|
if (isFace(shape) || isShape3D(shape)) {
|
|
@@ -290,6 +324,18 @@ function runChecks(brep, shape) {
|
|
|
290
324
|
} catch (e) {
|
|
291
325
|
pushError(r, { message: `getBounds: ${e.message}` });
|
|
292
326
|
}
|
|
327
|
+
try {
|
|
328
|
+
r.topology = {
|
|
329
|
+
faceCount: getFaces(shape).length,
|
|
330
|
+
edgeCount: getEdges(shape).length,
|
|
331
|
+
wireCount: getWires(shape).length,
|
|
332
|
+
vertexCount: getVertices(shape).length
|
|
333
|
+
};
|
|
334
|
+
} catch {}
|
|
335
|
+
if (r.topology) try {
|
|
336
|
+
const shells = getShells(shape);
|
|
337
|
+
if (shells.length > 0) r.topology.manifold = shells.every((s) => isManifoldShell(s));
|
|
338
|
+
} catch {}
|
|
293
339
|
r.hints = buildHints(r);
|
|
294
340
|
return r;
|
|
295
341
|
}
|
|
@@ -302,8 +348,38 @@ function pctDelta(actual, expected) {
|
|
|
302
348
|
return Math.abs(actual - expected) / Math.abs(expected) * 100;
|
|
303
349
|
}
|
|
304
350
|
function withinTolerance(actual, expected, tolerancePct) {
|
|
351
|
+
if (Math.abs(actual - expected) <= 1e-6) return true;
|
|
305
352
|
return pctDelta(actual, expected) <= tolerancePct;
|
|
306
353
|
}
|
|
354
|
+
var TOP_LEVEL_KEYS = new Set([
|
|
355
|
+
"volume",
|
|
356
|
+
"area",
|
|
357
|
+
"bounds",
|
|
358
|
+
"tolerancePct"
|
|
359
|
+
]);
|
|
360
|
+
var BOUND_KEYS = new Set([
|
|
361
|
+
"xMin",
|
|
362
|
+
"xMax",
|
|
363
|
+
"yMin",
|
|
364
|
+
"yMax",
|
|
365
|
+
"zMin",
|
|
366
|
+
"zMax"
|
|
367
|
+
]);
|
|
368
|
+
/**
|
|
369
|
+
* Keys in an `expected` block that the CLI does not understand and would silently ignore — a
|
|
370
|
+
* `{ min: [...], max: [...] }` or `{ x: [...] }` bounds shape, or a misspelled top-level field.
|
|
371
|
+
* Surfaced as an error (not dropped) so a wrong `expected` shape fails loud instead of passing
|
|
372
|
+
* vacuously with the intended assertion never run.
|
|
373
|
+
*/
|
|
374
|
+
function unknownExpectedKeys(expected) {
|
|
375
|
+
const bad = [];
|
|
376
|
+
for (const k of Object.keys(expected)) if (!TOP_LEVEL_KEYS.has(k)) bad.push(k);
|
|
377
|
+
const bounds = expected.bounds;
|
|
378
|
+
if (bounds && typeof bounds === "object") {
|
|
379
|
+
for (const k of Object.keys(bounds)) if (!BOUND_KEYS.has(k)) bad.push(`bounds.${k}`);
|
|
380
|
+
}
|
|
381
|
+
return bad;
|
|
382
|
+
}
|
|
307
383
|
function isExpectedDims(v) {
|
|
308
384
|
if (typeof v !== "object" || v === null) return false;
|
|
309
385
|
const r = v;
|
|
@@ -427,6 +503,25 @@ var COMPILER_OPTIONS = {
|
|
|
427
503
|
skipLibCheck: true,
|
|
428
504
|
allowImportingTsExtensions: true
|
|
429
505
|
};
|
|
506
|
+
/**
|
|
507
|
+
* Locate the `@types/node` declarations so a part may import Node built-ins (`node:fs` to load a
|
|
508
|
+
* font, `node:fs/promises` to read a STEP file, etc.) without `--check` failing on the import.
|
|
509
|
+
* Returns the `@types` directory to use as a `typeRoots` entry. Probes the tool's own install
|
|
510
|
+
* first (where `@types/node` ships as a dependency), then the part's directory.
|
|
511
|
+
*/
|
|
512
|
+
function nodeTypesRoot(partPath, toolDir) {
|
|
513
|
+
const froms = [
|
|
514
|
+
{}.url,
|
|
515
|
+
toolDir ? (0, node_url.pathToFileURL)((0, node_path.resolve)(toolDir, "package.json")).href : void 0,
|
|
516
|
+
(0, node_url.pathToFileURL)(partPath).href
|
|
517
|
+
];
|
|
518
|
+
for (const from of froms) {
|
|
519
|
+
if (!from) continue;
|
|
520
|
+
try {
|
|
521
|
+
return (0, node_path.dirname)((0, node_path.dirname)((0, node_module.createRequire)(from).resolve("@types/node/package.json")));
|
|
522
|
+
} catch {}
|
|
523
|
+
}
|
|
524
|
+
}
|
|
430
525
|
function diagnosticToErrorInfo(d) {
|
|
431
526
|
const text = typescript.default.flattenDiagnosticMessageText(d.messageText, "\n");
|
|
432
527
|
let where = "";
|
|
@@ -451,6 +546,11 @@ function typecheckPart(partPath, toolDir) {
|
|
|
451
546
|
const dts = resolveBrepjsTypes(partPath, toolDir);
|
|
452
547
|
const options = { ...COMPILER_OPTIONS };
|
|
453
548
|
if (dts) options.paths = { brepjs: [dts] };
|
|
549
|
+
const typesRoot = nodeTypesRoot(partPath, toolDir);
|
|
550
|
+
if (typesRoot) {
|
|
551
|
+
options.typeRoots = [typesRoot];
|
|
552
|
+
options.types = ["node"];
|
|
553
|
+
}
|
|
454
554
|
const program = typescript.default.createProgram([partPath], options);
|
|
455
555
|
const errors = [
|
|
456
556
|
...program.getSemanticDiagnostics(),
|
|
@@ -464,6 +564,39 @@ function typecheckPart(partPath, toolDir) {
|
|
|
464
564
|
}
|
|
465
565
|
//#endregion
|
|
466
566
|
//#region src/verify/runPart.ts
|
|
567
|
+
/** Centroid of a face group's vertices, in part (Z-up, mm) coordinates. */
|
|
568
|
+
function faceCentroid(m, start, count) {
|
|
569
|
+
let x = 0;
|
|
570
|
+
let y = 0;
|
|
571
|
+
let z = 0;
|
|
572
|
+
for (let i = start; i < start + count; i++) {
|
|
573
|
+
const vi = (m.triangles[i] ?? 0) * 3;
|
|
574
|
+
x += m.vertices[vi] ?? 0;
|
|
575
|
+
y += m.vertices[vi + 1] ?? 0;
|
|
576
|
+
z += m.vertices[vi + 2] ?? 0;
|
|
577
|
+
}
|
|
578
|
+
const n = count || 1;
|
|
579
|
+
return [
|
|
580
|
+
x / n,
|
|
581
|
+
y / n,
|
|
582
|
+
z / n
|
|
583
|
+
];
|
|
584
|
+
}
|
|
585
|
+
function buildMaterialMap(m, spec) {
|
|
586
|
+
if (spec === void 0 || spec === null) return {};
|
|
587
|
+
if (typeof spec !== "function" && (typeof spec !== "object" || Array.isArray(spec))) return { warning: "export const materials must be a function or a material object — ignored" };
|
|
588
|
+
const sel = spec;
|
|
589
|
+
const select = typeof sel === "function" ? sel : () => sel;
|
|
590
|
+
const map = /* @__PURE__ */ new Map();
|
|
591
|
+
for (const fg of m.faceGroups) {
|
|
592
|
+
const mat = select({
|
|
593
|
+
faceId: fg.faceId,
|
|
594
|
+
center: faceCentroid(m, fg.start, fg.count)
|
|
595
|
+
});
|
|
596
|
+
if (mat) map.set(fg.faceId, mat);
|
|
597
|
+
}
|
|
598
|
+
return map.size > 0 ? { map } : {};
|
|
599
|
+
}
|
|
467
600
|
async function loadPart(modulePath) {
|
|
468
601
|
try {
|
|
469
602
|
return await import((0, node_url.pathToFileURL)(modulePath).href);
|
|
@@ -487,7 +620,13 @@ function toErrorInfo(prefix, e) {
|
|
|
487
620
|
code: e.code,
|
|
488
621
|
suggestion: e.suggestion
|
|
489
622
|
};
|
|
490
|
-
if (e instanceof Error)
|
|
623
|
+
if (e instanceof Error) {
|
|
624
|
+
const code = e.message.match(/\[[A-Z][A-Z0-9_]*\]\s+([A-Z][A-Z0-9_]+):/)?.[1];
|
|
625
|
+
return code ? {
|
|
626
|
+
message: `${prefix}: ${e.message}`,
|
|
627
|
+
code
|
|
628
|
+
} : { message: `${prefix}: ${e.message}` };
|
|
629
|
+
}
|
|
491
630
|
return { message: `${prefix}: ${String(e)}` };
|
|
492
631
|
}
|
|
493
632
|
function finalize(result) {
|
|
@@ -567,11 +706,22 @@ async function runPart(modulePath, opts = {}) {
|
|
|
567
706
|
if (isExpectedDims(mod.expected)) {
|
|
568
707
|
const expected = mod.expected;
|
|
569
708
|
result.assertions = evaluateExpected(expected, result.measurements);
|
|
709
|
+
const unknown = unknownExpectedKeys(expected);
|
|
710
|
+
if (unknown.length > 0) pushError(result, {
|
|
711
|
+
message: `expected has unrecognized keys (ignored): ${unknown.join(", ")}. Valid keys: volume, area, tolerancePct, bounds.{xMin,xMax,yMin,yMax,zMin,zMax}.`,
|
|
712
|
+
code: "EXPECTED_UNKNOWN_KEY"
|
|
713
|
+
});
|
|
570
714
|
}
|
|
571
715
|
let glb;
|
|
572
716
|
let step;
|
|
573
717
|
if (opts.glb) try {
|
|
574
|
-
|
|
718
|
+
const shapeMesh = mesh(shape);
|
|
719
|
+
const { map, warning } = buildMaterialMap(shapeMesh, mod.materials);
|
|
720
|
+
if (warning) pushError(result, {
|
|
721
|
+
message: `materials: ${warning}`,
|
|
722
|
+
code: "MATERIALS_IGNORED"
|
|
723
|
+
});
|
|
724
|
+
glb = map ? exportGlb(shapeMesh, { materials: map }) : exportGlb(shapeMesh);
|
|
575
725
|
} catch (e) {
|
|
576
726
|
pushError(result, toErrorInfo("exportGlb", e));
|
|
577
727
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,6 @@ export { runPart, type RunPartOptions, type RunPartResult } from './verify/runPa
|
|
|
2
2
|
export { runChecks } from './verify/checks.js';
|
|
3
3
|
export { runMeasure, type MeasureReport } from './verify/measure.js';
|
|
4
4
|
export { runDiff } from './verify/diff.js';
|
|
5
|
-
export { serializeReport, emptyReport, type VerifyReport, type VerifyCheck, type VerifyMeasurements, type VerifyAssertion, type DiffReport, type BoundsDelta, } from './verify/report.js';
|
|
5
|
+
export { serializeReport, emptyReport, type VerifyReport, type VerifyCheck, type VerifyMeasurements, type VerifyTopology, type VerifyAssertion, type DiffReport, type BoundsDelta, } from './verify/report.js';
|
|
6
6
|
export { typecheckPart, TYPECHECK_CODE, type TypecheckResult } from './verify/typecheck.js';
|
|
7
7
|
export { evaluateExpected, isExpectedDims, pctDelta, DEFAULT_TOLERANCE_PCT, type ExpectedDims, type ExpectedBounds, } from './verify/expected.js';
|