@vcad/mcp 0.9.4-main.14 → 0.9.4-main.16
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/README.md +1 -1
- package/index.mjs +172 -797
- package/package.json +3 -3
- package/vcad_kernel_wasm_bg.wasm +0 -0
package/README.md
CHANGED
|
@@ -6,4 +6,4 @@ vcad's MCP server as a self-contained bundle (server + BRep kernel WASM).
|
|
|
6
6
|
{ "mcpServers": { "vcad": { "command": "npx", "args": ["-y", "@vcad/mcp"] } } }
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
-
Built from
|
|
9
|
+
Built from 63c1a040cf3352ca640144ec217f3f96271180c9 at 2026-07-24T17:21:48.535Z. Source: https://github.com/ecto/vcad
|
package/index.mjs
CHANGED
|
@@ -69,7 +69,9 @@ __export(vcad_kernel_wasm_exports, {
|
|
|
69
69
|
atoms_parse_xyz: () => atoms_parse_xyz,
|
|
70
70
|
atoms_write_xyz: () => atoms_write_xyz,
|
|
71
71
|
buildCalibrationReportJson: () => buildCalibrationReportJson,
|
|
72
|
+
buildGlbBytes: () => buildGlbBytes,
|
|
72
73
|
buildPart: () => buildPart,
|
|
74
|
+
buildStlBytes: () => buildStlBytes,
|
|
73
75
|
build_chat_system_prompt: () => build_chat_system_prompt,
|
|
74
76
|
calibrationDefaultTolerance: () => calibrationDefaultTolerance,
|
|
75
77
|
calibrationFingerprintDocument: () => calibrationFingerprintDocument,
|
|
@@ -148,8 +150,10 @@ __export(vcad_kernel_wasm_exports, {
|
|
|
148
150
|
ecadVerifyReceipt: () => ecadVerifyReceipt,
|
|
149
151
|
ecadVerifySubstitution: () => ecadVerifySubstitution,
|
|
150
152
|
emSimulate: () => emSimulate,
|
|
153
|
+
embroideryDesignToMesh: () => embroideryDesignToMesh,
|
|
151
154
|
estimatePrintCost: () => estimatePrintCost,
|
|
152
155
|
estimate_cost_for_process: () => estimate_cost_for_process,
|
|
156
|
+
eulerXyzDegToQuat: () => eulerXyzDegToQuat,
|
|
153
157
|
evalVcadSource: () => evalVcadSource,
|
|
154
158
|
evaluateDocument: () => evaluateDocument,
|
|
155
159
|
evaluateSheetMetalChain: () => evaluateSheetMetalChain,
|
|
@@ -254,6 +258,7 @@ __export(vcad_kernel_wasm_exports, {
|
|
|
254
258
|
toleranceAnalyze: () => toleranceAnalyze,
|
|
255
259
|
topologyOptimizeBox: () => topologyOptimizeBox,
|
|
256
260
|
topologyOptimizeMesh: () => topologyOptimizeMesh,
|
|
261
|
+
transformMeshBuffers: () => transformMeshBuffers,
|
|
257
262
|
writeEmbroideryDst: () => writeEmbroideryDst,
|
|
258
263
|
writeEmbroideryPes: () => writeEmbroideryPes
|
|
259
264
|
});
|
|
@@ -463,6 +468,21 @@ function buildCalibrationReportJson(prediction_json, measurements_json, options_
|
|
|
463
468
|
wasm.__wbindgen_free(deferred5_0, deferred5_1, 1);
|
|
464
469
|
}
|
|
465
470
|
}
|
|
471
|
+
function buildGlbBytes(spec_json, f32_data, u32_data) {
|
|
472
|
+
const ptr0 = passStringToWasm0(spec_json, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
473
|
+
const len0 = WASM_VECTOR_LEN;
|
|
474
|
+
const ptr1 = passArrayF32ToWasm0(f32_data, wasm.__wbindgen_malloc);
|
|
475
|
+
const len1 = WASM_VECTOR_LEN;
|
|
476
|
+
const ptr2 = passArray32ToWasm0(u32_data, wasm.__wbindgen_malloc);
|
|
477
|
+
const len2 = WASM_VECTOR_LEN;
|
|
478
|
+
const ret = wasm.buildGlbBytes(ptr0, len0, ptr1, len1, ptr2, len2);
|
|
479
|
+
if (ret[3]) {
|
|
480
|
+
throw takeFromExternrefTable0(ret[2]);
|
|
481
|
+
}
|
|
482
|
+
var v4 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
|
|
483
|
+
wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
|
|
484
|
+
return v4;
|
|
485
|
+
}
|
|
466
486
|
function buildPart(path, params_json) {
|
|
467
487
|
let deferred4_0;
|
|
468
488
|
let deferred4_1;
|
|
@@ -486,6 +506,21 @@ function buildPart(path, params_json) {
|
|
|
486
506
|
wasm.__wbindgen_free(deferred4_0, deferred4_1, 1);
|
|
487
507
|
}
|
|
488
508
|
}
|
|
509
|
+
function buildStlBytes(spec_json, f32_data, u32_data) {
|
|
510
|
+
const ptr0 = passStringToWasm0(spec_json, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
511
|
+
const len0 = WASM_VECTOR_LEN;
|
|
512
|
+
const ptr1 = passArrayF32ToWasm0(f32_data, wasm.__wbindgen_malloc);
|
|
513
|
+
const len1 = WASM_VECTOR_LEN;
|
|
514
|
+
const ptr2 = passArray32ToWasm0(u32_data, wasm.__wbindgen_malloc);
|
|
515
|
+
const len2 = WASM_VECTOR_LEN;
|
|
516
|
+
const ret = wasm.buildStlBytes(ptr0, len0, ptr1, len1, ptr2, len2);
|
|
517
|
+
if (ret[3]) {
|
|
518
|
+
throw takeFromExternrefTable0(ret[2]);
|
|
519
|
+
}
|
|
520
|
+
var v4 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
|
|
521
|
+
wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
|
|
522
|
+
return v4;
|
|
523
|
+
}
|
|
489
524
|
function build_chat_system_prompt(parts_json, selection_json) {
|
|
490
525
|
let deferred3_0;
|
|
491
526
|
let deferred3_1;
|
|
@@ -1486,6 +1521,15 @@ function emSimulate(spec_json, params_json, options_json) {
|
|
|
1486
1521
|
}
|
|
1487
1522
|
return takeFromExternrefTable0(ret[0]);
|
|
1488
1523
|
}
|
|
1524
|
+
function embroideryDesignToMesh(design_json) {
|
|
1525
|
+
const ptr0 = passStringToWasm0(design_json, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
1526
|
+
const len0 = WASM_VECTOR_LEN;
|
|
1527
|
+
const ret = wasm.embroideryDesignToMesh(ptr0, len0);
|
|
1528
|
+
if (ret[2]) {
|
|
1529
|
+
throw takeFromExternrefTable0(ret[1]);
|
|
1530
|
+
}
|
|
1531
|
+
return takeFromExternrefTable0(ret[0]);
|
|
1532
|
+
}
|
|
1489
1533
|
function estimatePrintCost(volume_mm3, infill_density, wall_count, line_width, material_name) {
|
|
1490
1534
|
const ptr0 = passStringToWasm0(material_name, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
1491
1535
|
const len0 = WASM_VECTOR_LEN;
|
|
@@ -1506,6 +1550,12 @@ function estimate_cost_for_process(process3, material_name, part_volume_mm3, sto
|
|
|
1506
1550
|
}
|
|
1507
1551
|
return takeFromExternrefTable0(ret[0]);
|
|
1508
1552
|
}
|
|
1553
|
+
function eulerXyzDegToQuat(x_deg, y_deg, z_deg) {
|
|
1554
|
+
const ret = wasm.eulerXyzDegToQuat(x_deg, y_deg, z_deg);
|
|
1555
|
+
var v1 = getArrayF64FromWasm0(ret[0], ret[1]).slice();
|
|
1556
|
+
wasm.__wbindgen_free(ret[0], ret[1] * 8, 8);
|
|
1557
|
+
return v1;
|
|
1558
|
+
}
|
|
1509
1559
|
function evalVcadSource(source) {
|
|
1510
1560
|
const ptr0 = passStringToWasm0(source, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
1511
1561
|
const len0 = WASM_VECTOR_LEN;
|
|
@@ -2907,6 +2957,19 @@ function topologyOptimizeMesh(spec_json, positions, indices) {
|
|
|
2907
2957
|
}
|
|
2908
2958
|
return takeFromExternrefTable0(ret[0]);
|
|
2909
2959
|
}
|
|
2960
|
+
function transformMeshBuffers(positions, normals, transform_json) {
|
|
2961
|
+
const ptr0 = passArrayF32ToWasm0(positions, wasm.__wbindgen_malloc);
|
|
2962
|
+
const len0 = WASM_VECTOR_LEN;
|
|
2963
|
+
var ptr1 = isLikeNone(normals) ? 0 : passArrayF32ToWasm0(normals, wasm.__wbindgen_malloc);
|
|
2964
|
+
var len1 = WASM_VECTOR_LEN;
|
|
2965
|
+
const ptr2 = passStringToWasm0(transform_json, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
2966
|
+
const len2 = WASM_VECTOR_LEN;
|
|
2967
|
+
const ret = wasm.transformMeshBuffers(ptr0, len0, ptr1, len1, ptr2, len2);
|
|
2968
|
+
if (ret[2]) {
|
|
2969
|
+
throw takeFromExternrefTable0(ret[1]);
|
|
2970
|
+
}
|
|
2971
|
+
return takeFromExternrefTable0(ret[0]);
|
|
2972
|
+
}
|
|
2910
2973
|
function writeEmbroideryDst(json) {
|
|
2911
2974
|
const ptr0 = passStringToWasm0(json, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
2912
2975
|
const len0 = WASM_VECTOR_LEN;
|
|
@@ -8727,8 +8790,8 @@ function evaluateDocument2(doc, kernel2, options = {}) {
|
|
|
8727
8790
|
if (p.mesh.positions.length === 0 && i < visibleRoots.length) {
|
|
8728
8791
|
const emb = findEmbroideryPattern(visibleRoots[i].root, doc.nodes);
|
|
8729
8792
|
if (emb) {
|
|
8730
|
-
const baseMesh =
|
|
8731
|
-
const mesh =
|
|
8793
|
+
const baseMesh = embroideryPatternToMeshWithKernel(emb.pattern, kernel2);
|
|
8794
|
+
const mesh = transformMeshWithKernel(baseMesh, emb.transform, kernel2);
|
|
8732
8795
|
return { mesh, material: p.material };
|
|
8733
8796
|
}
|
|
8734
8797
|
try {
|
|
@@ -8806,35 +8869,9 @@ function convertSketchToProfile(op) {
|
|
|
8806
8869
|
}
|
|
8807
8870
|
function solidToMesh(solid) {
|
|
8808
8871
|
const meshData = solid.getMesh();
|
|
8809
|
-
const positions = new Float32Array(meshData.positions);
|
|
8810
|
-
const indices = new Uint32Array(meshData.indices);
|
|
8811
|
-
const numVertices = positions.length / 3;
|
|
8812
|
-
let hasInvalidIndices = false;
|
|
8813
|
-
for (let i = 0; i < indices.length; i++) {
|
|
8814
|
-
if (indices[i] >= numVertices) {
|
|
8815
|
-
hasInvalidIndices = true;
|
|
8816
|
-
break;
|
|
8817
|
-
}
|
|
8818
|
-
}
|
|
8819
|
-
if (hasInvalidIndices) {
|
|
8820
|
-
const validIndices = [];
|
|
8821
|
-
for (let i = 0; i < indices.length; i += 3) {
|
|
8822
|
-
const i0 = indices[i];
|
|
8823
|
-
const i1 = indices[i + 1];
|
|
8824
|
-
const i2 = indices[i + 2];
|
|
8825
|
-
if (i0 < numVertices && i1 < numVertices && i2 < numVertices) {
|
|
8826
|
-
validIndices.push(i0, i1, i2);
|
|
8827
|
-
}
|
|
8828
|
-
}
|
|
8829
|
-
return {
|
|
8830
|
-
positions,
|
|
8831
|
-
indices: new Uint32Array(validIndices),
|
|
8832
|
-
normals: meshData.normals ? new Float32Array(meshData.normals) : void 0
|
|
8833
|
-
};
|
|
8834
|
-
}
|
|
8835
8872
|
return {
|
|
8836
|
-
positions,
|
|
8837
|
-
indices,
|
|
8873
|
+
positions: new Float32Array(meshData.positions),
|
|
8874
|
+
indices: new Uint32Array(meshData.indices),
|
|
8838
8875
|
normals: meshData.normals ? new Float32Array(meshData.normals) : void 0
|
|
8839
8876
|
};
|
|
8840
8877
|
}
|
|
@@ -8855,7 +8892,7 @@ function resolveSheetMetalPart(rootId, nodes, kernel2) {
|
|
|
8855
8892
|
return null;
|
|
8856
8893
|
const { mesh, sheetMetal } = evaluateSheetMetalChain2(chain, kernel2);
|
|
8857
8894
|
return {
|
|
8858
|
-
mesh: isIdentityTransform(sm.transform) ? mesh :
|
|
8895
|
+
mesh: isIdentityTransform(sm.transform) ? mesh : transformMeshWithKernel(mesh, sm.transform, kernel2),
|
|
8859
8896
|
sheetMetal
|
|
8860
8897
|
};
|
|
8861
8898
|
}
|
|
@@ -8898,6 +8935,37 @@ function embroideryPatternToMesh(op) {
|
|
|
8898
8935
|
colors: new Float32Array(allColors)
|
|
8899
8936
|
};
|
|
8900
8937
|
}
|
|
8938
|
+
function embroideryPatternToMeshWithKernel(op, kernel2) {
|
|
8939
|
+
if (kernel2?.embroideryDesignToMesh) {
|
|
8940
|
+
try {
|
|
8941
|
+
const m = kernel2.embroideryDesignToMesh(JSON.stringify(op.design));
|
|
8942
|
+
return {
|
|
8943
|
+
positions: new Float32Array(m.positions),
|
|
8944
|
+
indices: new Uint32Array(m.indices),
|
|
8945
|
+
colors: new Float32Array(m.colors)
|
|
8946
|
+
};
|
|
8947
|
+
} catch (e) {
|
|
8948
|
+
console.warn("[ENGINE] kernel embroideryDesignToMesh failed, falling back to TS:", e);
|
|
8949
|
+
}
|
|
8950
|
+
}
|
|
8951
|
+
return embroideryPatternToMesh(op);
|
|
8952
|
+
}
|
|
8953
|
+
function transformMeshWithKernel(mesh, transform2, kernel2) {
|
|
8954
|
+
if (kernel2?.transformMeshBuffers) {
|
|
8955
|
+
try {
|
|
8956
|
+
const r = kernel2.transformMeshBuffers(mesh.positions, mesh.normals, JSON.stringify(transform2));
|
|
8957
|
+
return {
|
|
8958
|
+
positions: new Float32Array(r.positions),
|
|
8959
|
+
indices: mesh.indices,
|
|
8960
|
+
normals: r.normals ? new Float32Array(r.normals) : void 0,
|
|
8961
|
+
colors: mesh.colors
|
|
8962
|
+
};
|
|
8963
|
+
} catch (e) {
|
|
8964
|
+
console.warn("[ENGINE] kernel transformMeshBuffers failed, falling back to TS:", e);
|
|
8965
|
+
}
|
|
8966
|
+
}
|
|
8967
|
+
return transformMesh(mesh, transform2);
|
|
8968
|
+
}
|
|
8901
8969
|
function transformMesh(mesh, transform2) {
|
|
8902
8970
|
const { translate, rotate, scale } = transform2;
|
|
8903
8971
|
const positions = new Float32Array(mesh.positions.length);
|
|
@@ -8954,8 +9022,8 @@ function evaluateDocumentTS(doc, kernel2, options = {}) {
|
|
|
8954
9022
|
const parts = visibleRoots.map((entry, idx) => {
|
|
8955
9023
|
const embPattern = findEmbroideryPattern(entry.root, doc.nodes);
|
|
8956
9024
|
if (embPattern) {
|
|
8957
|
-
const baseMesh =
|
|
8958
|
-
const mesh =
|
|
9025
|
+
const baseMesh = embroideryPatternToMeshWithKernel(embPattern.pattern, kernel2);
|
|
9026
|
+
const mesh = transformMeshWithKernel(baseMesh, embPattern.transform, kernel2);
|
|
8959
9027
|
solids.push(Solid2.empty());
|
|
8960
9028
|
return { mesh, material: entry.material };
|
|
8961
9029
|
}
|
|
@@ -8966,7 +9034,7 @@ function evaluateDocumentTS(doc, kernel2, options = {}) {
|
|
|
8966
9034
|
indices: new Uint32Array(imported.mesh.indices),
|
|
8967
9035
|
normals: imported.mesh.normals ? new Float32Array(imported.mesh.normals) : void 0
|
|
8968
9036
|
};
|
|
8969
|
-
const mesh =
|
|
9037
|
+
const mesh = transformMeshWithKernel(baseMesh, imported.transform, kernel2);
|
|
8970
9038
|
solids.push(Solid2.empty());
|
|
8971
9039
|
return { mesh, material: entry.material };
|
|
8972
9040
|
}
|
|
@@ -10851,753 +10919,57 @@ var init_atoms = __esm({
|
|
|
10851
10919
|
});
|
|
10852
10920
|
|
|
10853
10921
|
// ../engine/dist/enclosure-fit.js
|
|
10854
|
-
function
|
|
10855
|
-
|
|
10856
|
-
|
|
10857
|
-
|
|
10858
|
-
|
|
10859
|
-
|
|
10860
|
-
if (v.x < minX)
|
|
10861
|
-
minX = v.x;
|
|
10862
|
-
if (v.x > maxX)
|
|
10863
|
-
maxX = v.x;
|
|
10864
|
-
if (v.y < minY)
|
|
10865
|
-
minY = v.y;
|
|
10866
|
-
if (v.y > maxY)
|
|
10867
|
-
maxY = v.y;
|
|
10868
|
-
}
|
|
10869
|
-
return { minX, maxX, minY, maxY };
|
|
10922
|
+
function enclosureWasm() {
|
|
10923
|
+
const wasm3 = getKernelWasmSync();
|
|
10924
|
+
if (!wasm3?.enclosure_fit) {
|
|
10925
|
+
throw new Error("enclosure checks require the kernel WASM to be initialized \u2014 await getKernelWasm() (or Engine.init()) first");
|
|
10926
|
+
}
|
|
10927
|
+
return wasm3;
|
|
10870
10928
|
}
|
|
10871
|
-
function
|
|
10872
|
-
|
|
10873
|
-
const cos = Math.cos(t2);
|
|
10874
|
-
const sin = Math.sin(t2);
|
|
10875
|
-
return {
|
|
10876
|
-
x: placement.offset.x + p.x * cos - p.y * sin,
|
|
10877
|
-
y: placement.offset.y + p.x * sin + p.y * cos,
|
|
10878
|
-
z: placement.offset.z + (p.z ?? 0)
|
|
10879
|
-
};
|
|
10929
|
+
function compact(obj) {
|
|
10930
|
+
return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== void 0));
|
|
10880
10931
|
}
|
|
10881
|
-
function
|
|
10882
|
-
|
|
10883
|
-
["minX", Math.abs(x - b.minX)],
|
|
10884
|
-
["maxX", Math.abs(x - b.maxX)],
|
|
10885
|
-
["minY", Math.abs(y - b.minY)],
|
|
10886
|
-
["maxY", Math.abs(y - b.maxY)]
|
|
10887
|
-
];
|
|
10888
|
-
d.sort((a, c) => a[1] - c[1]);
|
|
10889
|
-
return d[0][0];
|
|
10932
|
+
function outlineAabb(outline) {
|
|
10933
|
+
return JSON.parse(enclosureWasm().enclosure_outline_aabb(JSON.stringify(outline)));
|
|
10890
10934
|
}
|
|
10891
|
-
function
|
|
10892
|
-
|
|
10893
|
-
const cos = Math.cos(t2);
|
|
10894
|
-
const sin = Math.sin(t2);
|
|
10895
|
-
return {
|
|
10896
|
-
x: fp.position.x + pad.position.x * cos - pad.position.y * sin,
|
|
10897
|
-
y: fp.position.y + pad.position.x * sin + pad.position.y * cos
|
|
10898
|
-
};
|
|
10935
|
+
function toWorld(p, placement) {
|
|
10936
|
+
return JSON.parse(enclosureWasm().enclosure_to_world(p.x, p.y, p.z ?? 0, JSON.stringify(placement)));
|
|
10899
10937
|
}
|
|
10900
10938
|
function mountingHolesFromPcb(pcb) {
|
|
10901
|
-
|
|
10902
|
-
for (const fp of pcb.footprints) {
|
|
10903
|
-
const isMount = MOUNT_RE.test(fp.footprintName) || MOUNT_RE.test(fp.ref);
|
|
10904
|
-
if (isMount) {
|
|
10905
|
-
const pad = fp.pads[0];
|
|
10906
|
-
let dia = 3.2;
|
|
10907
|
-
if (pad) {
|
|
10908
|
-
if (pad.drill && typeof pad.drill === "object") {
|
|
10909
|
-
const dd = pad.drill;
|
|
10910
|
-
if (typeof dd.diameter === "number")
|
|
10911
|
-
dia = dd.diameter;
|
|
10912
|
-
} else if (pad.shape.type === "Circle") {
|
|
10913
|
-
dia = pad.shape.diameter;
|
|
10914
|
-
}
|
|
10915
|
-
}
|
|
10916
|
-
holes.push({ x: round2(fp.position.x), y: round2(fp.position.y), diameter: round2(dia), ref: fp.ref });
|
|
10917
|
-
continue;
|
|
10918
|
-
}
|
|
10919
|
-
for (const pad of fp.pads) {
|
|
10920
|
-
if (pad.padType !== "NPTH")
|
|
10921
|
-
continue;
|
|
10922
|
-
const w = padWorld(fp, pad);
|
|
10923
|
-
let dia = 3.2;
|
|
10924
|
-
const dd = pad.drill;
|
|
10925
|
-
if (dd && typeof dd.diameter === "number")
|
|
10926
|
-
dia = dd.diameter;
|
|
10927
|
-
else if (pad.shape.type === "Circle")
|
|
10928
|
-
dia = pad.shape.diameter;
|
|
10929
|
-
holes.push({ x: round2(w.x), y: round2(w.y), diameter: round2(dia), ref: fp.ref });
|
|
10930
|
-
}
|
|
10931
|
-
}
|
|
10932
|
-
return holes;
|
|
10939
|
+
return JSON.parse(enclosureWasm().enclosure_mounting_holes(JSON.stringify(pcb)));
|
|
10933
10940
|
}
|
|
10934
10941
|
function componentExtentsFromMeshes(meshes, pcb) {
|
|
10935
|
-
const
|
|
10936
|
-
|
|
10937
|
-
|
|
10938
|
-
|
|
10939
|
-
|
|
10940
|
-
for (let i = 2; i < m.positions.length; i += 3) {
|
|
10941
|
-
const z = m.positions[i];
|
|
10942
|
-
if (z < minZ)
|
|
10943
|
-
minZ = z;
|
|
10944
|
-
if (z > maxZ)
|
|
10945
|
-
maxZ = z;
|
|
10946
|
-
}
|
|
10947
|
-
if (!Number.isFinite(minZ))
|
|
10948
|
-
continue;
|
|
10949
|
-
out.push({
|
|
10950
|
-
ref: m.footprint_ref,
|
|
10951
|
-
front: frontByRef.get(m.footprint_ref) ?? true,
|
|
10952
|
-
topZ: round2(maxZ),
|
|
10953
|
-
bottomZ: round2(minZ)
|
|
10954
|
-
});
|
|
10955
|
-
}
|
|
10956
|
-
return out;
|
|
10942
|
+
const payload = meshes.map((m) => ({
|
|
10943
|
+
footprint_ref: m.footprint_ref,
|
|
10944
|
+
positions: Array.from(m.positions)
|
|
10945
|
+
}));
|
|
10946
|
+
return JSON.parse(enclosureWasm().enclosure_component_extents(JSON.stringify(payload), JSON.stringify(pcb)));
|
|
10957
10947
|
}
|
|
10958
10948
|
function connectorsFromPcb(pcb, outline) {
|
|
10959
|
-
|
|
10960
|
-
const out = [];
|
|
10961
|
-
for (const fp of pcb.footprints) {
|
|
10962
|
-
const isConn = CONNECTOR_REF_RE.test(fp.ref) || CONNECTOR_NAME_RE.test(fp.footprintName) || CONNECTOR_NAME_RE.test(fp.value ?? "");
|
|
10963
|
-
if (!isConn)
|
|
10964
|
-
continue;
|
|
10965
|
-
out.push({
|
|
10966
|
-
ref: fp.ref,
|
|
10967
|
-
x: round2(fp.position.x),
|
|
10968
|
-
y: round2(fp.position.y),
|
|
10969
|
-
edge: nearestEdge(fp.position.x, fp.position.y, aabb),
|
|
10970
|
-
height: 0
|
|
10971
|
-
});
|
|
10972
|
-
}
|
|
10973
|
-
return out;
|
|
10974
|
-
}
|
|
10975
|
-
function autoPlacement(input, clearance) {
|
|
10976
|
-
const { cavity } = input;
|
|
10977
|
-
const a = outlineAabb(input.outline);
|
|
10978
|
-
const boardW = a.maxX - a.minX;
|
|
10979
|
-
const boardH = a.maxY - a.minY;
|
|
10980
|
-
const cavW = cavity.maxX - cavity.minX;
|
|
10981
|
-
const cavH = cavity.maxY - cavity.minY;
|
|
10982
|
-
const offX = cavity.minX + (cavW - boardW) / 2 - a.minX;
|
|
10983
|
-
const offY = cavity.minY + (cavH - boardH) / 2 - a.minY;
|
|
10984
|
-
const standoffTop = input.standoffs && input.standoffs.length > 0 ? Math.max(...input.standoffs.map((s) => s.topZ)) : cavity.floorZ + (input.standoffHeight ?? 0);
|
|
10985
|
-
void clearance;
|
|
10986
|
-
return { offset: { x: round2(offX), y: round2(offY), z: round2(standoffTop) }, rotationDeg: 0 };
|
|
10987
|
-
}
|
|
10988
|
-
function checkBoardFit(input, placement, clearance) {
|
|
10989
|
-
const { cavity } = input;
|
|
10990
|
-
let minX = Infinity;
|
|
10991
|
-
let maxX = -Infinity;
|
|
10992
|
-
let minY = Infinity;
|
|
10993
|
-
let maxY = -Infinity;
|
|
10994
|
-
for (const v of input.outline.vertices) {
|
|
10995
|
-
const w = toWorld(v, placement);
|
|
10996
|
-
if (w.x < minX)
|
|
10997
|
-
minX = w.x;
|
|
10998
|
-
if (w.x > maxX)
|
|
10999
|
-
maxX = w.x;
|
|
11000
|
-
if (w.y < minY)
|
|
11001
|
-
minY = w.y;
|
|
11002
|
-
if (w.y > maxY)
|
|
11003
|
-
maxY = w.y;
|
|
11004
|
-
}
|
|
11005
|
-
const marginMinX = minX - cavity.minX;
|
|
11006
|
-
const marginMaxX = cavity.maxX - maxX;
|
|
11007
|
-
const marginMinY = minY - cavity.minY;
|
|
11008
|
-
const marginMaxY = cavity.maxY - maxY;
|
|
11009
|
-
const worst2 = Math.min(marginMinX, marginMaxX, marginMinY, marginMaxY);
|
|
11010
|
-
const sides = [
|
|
11011
|
-
["-X", marginMinX],
|
|
11012
|
-
["+X", marginMaxX],
|
|
11013
|
-
["-Y", marginMinY],
|
|
11014
|
-
["+Y", marginMaxY]
|
|
11015
|
-
];
|
|
11016
|
-
const tight = sides.filter(([, m]) => m < clearance).map(([s]) => s);
|
|
11017
|
-
const ok4 = worst2 >= clearance - 1e-6;
|
|
11018
|
-
return {
|
|
11019
|
-
id: "board_fit",
|
|
11020
|
-
label: "Board fits cavity with clearance",
|
|
11021
|
-
status: ok4 ? "pass" : "fail",
|
|
11022
|
-
detail: ok4 ? `Board fits with ${round2(worst2)}mm worst-case clearance (need ${clearance}mm)` : worst2 < 0 ? `Board overhangs the cavity by ${round2(-worst2)}mm on ${tight.join(", ")}` : `Clearance on ${tight.join(", ")} is ${round2(worst2)}mm < required ${clearance}mm`,
|
|
11023
|
-
measurements: {
|
|
11024
|
-
worst_clearance_mm: round2(worst2),
|
|
11025
|
-
margin_minus_x: round2(marginMinX),
|
|
11026
|
-
margin_plus_x: round2(marginMaxX),
|
|
11027
|
-
margin_minus_y: round2(marginMinY),
|
|
11028
|
-
margin_plus_y: round2(marginMaxY),
|
|
11029
|
-
board_w: round2(maxX - minX),
|
|
11030
|
-
board_h: round2(maxY - minY),
|
|
11031
|
-
cavity_w: round2(cavity.maxX - cavity.minX),
|
|
11032
|
-
cavity_h: round2(cavity.maxY - cavity.minY)
|
|
11033
|
-
}
|
|
11034
|
-
};
|
|
11035
|
-
}
|
|
11036
|
-
function checkLidClearance(input, placement, clearance) {
|
|
11037
|
-
const { cavity } = input;
|
|
11038
|
-
const extents = input.componentExtents ?? [];
|
|
11039
|
-
if (extents.length === 0) {
|
|
11040
|
-
return {
|
|
11041
|
-
id: "lid_clearance",
|
|
11042
|
-
label: "Components clear the lid",
|
|
11043
|
-
status: "skip",
|
|
11044
|
-
detail: "No component heights available (kernel component meshes unavailable)"
|
|
11045
|
-
};
|
|
11046
|
-
}
|
|
11047
|
-
const cavityDepth = cavity.ceilZ - cavity.floorZ;
|
|
11048
|
-
const front = extents.filter((e) => e.front);
|
|
11049
|
-
const back = extents.filter((e) => !e.front);
|
|
11050
|
-
let tallest = { ref: "", top: -Infinity };
|
|
11051
|
-
for (const e of front) {
|
|
11052
|
-
const top = placement.offset.z + e.topZ;
|
|
11053
|
-
if (top > tallest.top)
|
|
11054
|
-
tallest = { ref: e.ref, top };
|
|
11055
|
-
}
|
|
11056
|
-
const lidGap = cavity.ceilZ - tallest.top;
|
|
11057
|
-
const topOk = front.length === 0 || lidGap >= clearance - 1e-6;
|
|
11058
|
-
let lowest = { ref: "", bottom: Infinity };
|
|
11059
|
-
for (const e of back) {
|
|
11060
|
-
const bot = placement.offset.z + e.bottomZ;
|
|
11061
|
-
if (bot < lowest.bottom)
|
|
11062
|
-
lowest = { ref: e.ref, bottom: bot };
|
|
11063
|
-
}
|
|
11064
|
-
const floorGap = back.length > 0 ? lowest.bottom - cavity.floorZ : Infinity;
|
|
11065
|
-
const botOk = back.length === 0 || floorGap >= -1e-6;
|
|
11066
|
-
const ok4 = topOk && botOk;
|
|
11067
|
-
let detail;
|
|
11068
|
-
if (ok4) {
|
|
11069
|
-
detail = `Tallest part ${tallest.ref || "\u2014"} leaves ${round2(lidGap)}mm under the lid (cavity depth ${round2(cavityDepth)}mm)`;
|
|
11070
|
-
if (back.length > 0 && Number.isFinite(floorGap)) {
|
|
11071
|
-
detail += `; back-side ${lowest.ref} clears floor by ${round2(floorGap)}mm`;
|
|
11072
|
-
}
|
|
11073
|
-
} else if (!topOk) {
|
|
11074
|
-
detail = `${tallest.ref} is ${round2(-lidGap + clearance)}mm too tall \u2014 it ${lidGap < 0 ? "punches through" : "is within clearance of"} the lid (cavity depth ${round2(cavityDepth)}mm)`;
|
|
11075
|
-
} else {
|
|
11076
|
-
detail = `Back-side ${lowest.ref} collides with the floor by ${round2(-floorGap)}mm \u2014 raise the standoffs`;
|
|
11077
|
-
}
|
|
11078
|
-
return {
|
|
11079
|
-
id: "lid_clearance",
|
|
11080
|
-
label: "Components clear the lid",
|
|
11081
|
-
status: ok4 ? "pass" : "fail",
|
|
11082
|
-
detail,
|
|
11083
|
-
measurements: {
|
|
11084
|
-
cavity_depth_mm: round2(cavityDepth),
|
|
11085
|
-
tallest_ref: tallest.ref || "none",
|
|
11086
|
-
lid_gap_mm: Number.isFinite(lidGap) ? round2(lidGap) : "n/a",
|
|
11087
|
-
stack_top_z: Number.isFinite(tallest.top) ? round2(tallest.top) : "n/a",
|
|
11088
|
-
floor_gap_mm: Number.isFinite(floorGap) ? round2(floorGap) : "n/a"
|
|
11089
|
-
}
|
|
11090
|
-
};
|
|
11091
|
-
}
|
|
11092
|
-
function checkMountingHoles(input, placement) {
|
|
11093
|
-
const holes = input.mountingHoles ?? [];
|
|
11094
|
-
const standoffs = input.standoffs ?? [];
|
|
11095
|
-
const tol = input.holeTolerance ?? DEFAULT_HOLE_TOL;
|
|
11096
|
-
if (holes.length === 0) {
|
|
11097
|
-
return {
|
|
11098
|
-
id: "mounting_holes",
|
|
11099
|
-
label: "Mounting holes land on standoffs",
|
|
11100
|
-
status: "skip",
|
|
11101
|
-
detail: "Board declares no mounting holes"
|
|
11102
|
-
};
|
|
11103
|
-
}
|
|
11104
|
-
if (standoffs.length === 0) {
|
|
11105
|
-
return {
|
|
11106
|
-
id: "mounting_holes",
|
|
11107
|
-
label: "Mounting holes land on standoffs",
|
|
11108
|
-
status: "skip",
|
|
11109
|
-
detail: `Board has ${holes.length} mounting hole(s) but no standoffs were detected in the enclosure`
|
|
11110
|
-
};
|
|
11111
|
-
}
|
|
11112
|
-
let matched = 0;
|
|
11113
|
-
let worst2 = 0;
|
|
11114
|
-
const misses = [];
|
|
11115
|
-
for (const h of holes) {
|
|
11116
|
-
const w = toWorld(h, placement);
|
|
11117
|
-
let best = Infinity;
|
|
11118
|
-
for (const s of standoffs) {
|
|
11119
|
-
const d = Math.hypot(w.x - s.x, w.y - s.y);
|
|
11120
|
-
if (d < best)
|
|
11121
|
-
best = d;
|
|
11122
|
-
}
|
|
11123
|
-
if (best <= tol) {
|
|
11124
|
-
matched++;
|
|
11125
|
-
if (best > worst2)
|
|
11126
|
-
worst2 = best;
|
|
11127
|
-
} else {
|
|
11128
|
-
misses.push(`${h.ref ?? "hole"}@(${round2(w.x)},${round2(w.y)}) is ${round2(best)}mm off`);
|
|
11129
|
-
}
|
|
11130
|
-
}
|
|
11131
|
-
const ok4 = matched === holes.length;
|
|
11132
|
-
return {
|
|
11133
|
-
id: "mounting_holes",
|
|
11134
|
-
label: "Mounting holes land on standoffs",
|
|
11135
|
-
status: ok4 ? "pass" : "fail",
|
|
11136
|
-
detail: ok4 ? `All ${holes.length} mounting holes align to standoffs (worst offset ${round2(worst2)}mm, tol ${tol}mm)` : `${matched}/${holes.length} holes align \u2014 ${misses.join("; ")}`,
|
|
11137
|
-
measurements: {
|
|
11138
|
-
holes_total: holes.length,
|
|
11139
|
-
holes_matched: matched,
|
|
11140
|
-
standoffs: standoffs.length,
|
|
11141
|
-
tolerance_mm: tol,
|
|
11142
|
-
worst_offset_mm: round2(worst2)
|
|
11143
|
-
}
|
|
11144
|
-
};
|
|
11145
|
-
}
|
|
11146
|
-
function checkConnectors(input, placement, clearance) {
|
|
11147
|
-
const conns = input.connectors ?? [];
|
|
11148
|
-
const openings = input.openings ?? [];
|
|
11149
|
-
if (conns.length === 0) {
|
|
11150
|
-
return {
|
|
11151
|
-
id: "connector_cutouts",
|
|
11152
|
-
label: "Connectors align to wall cutouts",
|
|
11153
|
-
status: "skip",
|
|
11154
|
-
detail: "Board declares no edge connectors"
|
|
11155
|
-
};
|
|
11156
|
-
}
|
|
11157
|
-
const cav = input.cavity;
|
|
11158
|
-
let aligned = 0;
|
|
11159
|
-
const problems = [];
|
|
11160
|
-
for (const c of conns) {
|
|
11161
|
-
const w = toWorld(c, placement);
|
|
11162
|
-
const wallEdge = nearestEdge(w.x, w.y, cav);
|
|
11163
|
-
const along = wallEdge === "minX" || wallEdge === "maxX" ? w.y : w.x;
|
|
11164
|
-
const onWall = openings.filter((o) => o.edge === wallEdge);
|
|
11165
|
-
if (onWall.length === 0) {
|
|
11166
|
-
problems.push(`${c.ref} faces the ${wallEdge} wall but it has no cutout`);
|
|
11167
|
-
continue;
|
|
11168
|
-
}
|
|
11169
|
-
const hit = onWall.find((o) => {
|
|
11170
|
-
const oc = o.edge === "minX" || o.edge === "maxX" ? o.center.y : o.center.x;
|
|
11171
|
-
return Math.abs(along - oc) <= o.width / 2 + clearance;
|
|
11172
|
-
});
|
|
11173
|
-
if (hit) {
|
|
11174
|
-
aligned++;
|
|
11175
|
-
} else {
|
|
11176
|
-
const nearest = onWall.reduce((best, o) => {
|
|
11177
|
-
const oc = o.edge === "minX" || o.edge === "maxX" ? o.center.y : o.center.x;
|
|
11178
|
-
const off = Math.abs(along - oc) - o.width / 2;
|
|
11179
|
-
return off < best ? off : best;
|
|
11180
|
-
}, Infinity);
|
|
11181
|
-
problems.push(`${c.ref} on ${wallEdge} misses its cutout by ${round2(nearest)}mm`);
|
|
11182
|
-
}
|
|
11183
|
-
}
|
|
11184
|
-
const ok4 = aligned === conns.length;
|
|
11185
|
-
const status = ok4 ? "pass" : openings.length === 0 ? "warn" : "fail";
|
|
11186
|
-
return {
|
|
11187
|
-
id: "connector_cutouts",
|
|
11188
|
-
label: "Connectors align to wall cutouts",
|
|
11189
|
-
status,
|
|
11190
|
-
detail: ok4 ? `All ${conns.length} connector(s) line up with wall cutouts` : openings.length === 0 ? `No wall cutouts detected; ${conns.length} connector(s) would be enclosed: ${problems.join("; ")}` : `${aligned}/${conns.length} connectors aligned \u2014 ${problems.join("; ")}`,
|
|
11191
|
-
measurements: {
|
|
11192
|
-
connectors_total: conns.length,
|
|
11193
|
-
connectors_aligned: aligned,
|
|
11194
|
-
wall_openings: openings.length
|
|
11195
|
-
}
|
|
11196
|
-
};
|
|
10949
|
+
return JSON.parse(enclosureWasm().enclosure_connectors(JSON.stringify(pcb), JSON.stringify(outline)));
|
|
11197
10950
|
}
|
|
11198
10951
|
function checkEnclosureFit(input) {
|
|
11199
|
-
|
|
11200
|
-
const placement = input.placement ?? autoPlacement(input, clearance);
|
|
11201
|
-
const checks = [
|
|
11202
|
-
checkBoardFit(input, placement, clearance),
|
|
11203
|
-
checkLidClearance(input, placement, clearance),
|
|
11204
|
-
checkMountingHoles(input, placement),
|
|
11205
|
-
checkConnectors(input, placement, clearance)
|
|
11206
|
-
];
|
|
11207
|
-
const failed = checks.filter((c) => c.status === "fail");
|
|
11208
|
-
const warned = checks.filter((c) => c.status === "warn");
|
|
11209
|
-
const passed = checks.filter((c) => c.status === "pass");
|
|
11210
|
-
const ok4 = failed.length === 0;
|
|
11211
|
-
const verified = ok4 && warned.length === 0;
|
|
11212
|
-
let summary;
|
|
11213
|
-
if (failed.length > 0) {
|
|
11214
|
-
summary = `Enclosure fit: FAIL \u2014 ${failed.map((c) => c.label.toLowerCase()).join("; ")}`;
|
|
11215
|
-
} else if (warned.length > 0) {
|
|
11216
|
-
summary = `Enclosure fit: UNVERIFIED \u2014 ${passed.length} passed, ${warned.length} warning(s): ${warned.map((c) => c.detail).join("; ")}`;
|
|
11217
|
-
} else {
|
|
11218
|
-
summary = `Enclosure fit: PASS \u2014 ${passed.length}/${checks.length} checks (${checks.filter((c) => c.status === "pass").map((c) => c.label.toLowerCase()).join(", ")})`;
|
|
11219
|
-
}
|
|
11220
|
-
return { ok: ok4, verified, summary, clearance, placement, checks };
|
|
10952
|
+
return JSON.parse(enclosureWasm().enclosure_fit(JSON.stringify(compact(input))));
|
|
11221
10953
|
}
|
|
11222
10954
|
function deriveBoardFromCavity(cavity, standoffs, opts = {}) {
|
|
11223
|
-
|
|
11224
|
-
const thickness = opts.thickness ?? 1.6;
|
|
11225
|
-
const holeDia = opts.holeDiameter ?? 3.2;
|
|
11226
|
-
const w = round2(cavity.maxX - cavity.minX - 2 * clearance);
|
|
11227
|
-
const h = round2(cavity.maxY - cavity.minY - 2 * clearance);
|
|
11228
|
-
const outline = {
|
|
11229
|
-
vertices: [
|
|
11230
|
-
{ x: 0, y: 0 },
|
|
11231
|
-
{ x: w, y: 0 },
|
|
11232
|
-
{ x: w, y: h },
|
|
11233
|
-
{ x: 0, y: h }
|
|
11234
|
-
],
|
|
11235
|
-
thickness
|
|
11236
|
-
};
|
|
11237
|
-
const standoffTop = standoffs.length > 0 ? Math.max(...standoffs.map((s) => s.topZ)) : cavity.floorZ + (opts.standoffHeight ?? 0);
|
|
11238
|
-
const offX = cavity.minX + clearance;
|
|
11239
|
-
const offY = cavity.minY + clearance;
|
|
11240
|
-
const placement = {
|
|
11241
|
-
offset: { x: round2(offX), y: round2(offY), z: round2(standoffTop) },
|
|
11242
|
-
rotationDeg: 0
|
|
11243
|
-
};
|
|
11244
|
-
const mountingHoles = [];
|
|
11245
|
-
for (const s of standoffs) {
|
|
11246
|
-
const lx = round2(s.x - offX);
|
|
11247
|
-
const ly = round2(s.y - offY);
|
|
11248
|
-
if (lx >= 0 && lx <= w && ly >= 0 && ly <= h) {
|
|
11249
|
-
mountingHoles.push({ x: lx, y: ly, diameter: holeDia });
|
|
11250
|
-
}
|
|
11251
|
-
}
|
|
11252
|
-
return { outline, mountingHoles, placement };
|
|
10955
|
+
return JSON.parse(enclosureWasm().enclosure_derive_board(JSON.stringify(cavity), JSON.stringify(standoffs), JSON.stringify(compact(opts))));
|
|
11253
10956
|
}
|
|
11254
|
-
var DEFAULT_CLEARANCE, DEFAULT_HOLE_TOL, round2, MOUNT_RE, CONNECTOR_REF_RE, CONNECTOR_NAME_RE;
|
|
11255
10957
|
var init_enclosure_fit = __esm({
|
|
11256
10958
|
"../engine/dist/enclosure-fit.js"() {
|
|
11257
10959
|
"use strict";
|
|
11258
|
-
|
|
11259
|
-
DEFAULT_HOLE_TOL = 0.6;
|
|
11260
|
-
round2 = (n) => Math.round(n * 100) / 100;
|
|
11261
|
-
MOUNT_RE = /mount(ing)?[_-]?hole|mountingpad|mounthole/i;
|
|
11262
|
-
CONNECTOR_REF_RE = /^(J|CN|CON|USB|P)\d/i;
|
|
11263
|
-
CONNECTOR_NAME_RE = /usb|type[-_]?c|micro|mini|conn|header|jst|molex|rj45|hdr|terminal|socket|receptacle|barrel|dcjack/i;
|
|
10960
|
+
init_wasm_singleton();
|
|
11264
10961
|
}
|
|
11265
10962
|
});
|
|
11266
10963
|
|
|
11267
10964
|
// ../engine/dist/enclosure-mesh.js
|
|
11268
|
-
function gwn(positions, indices, qx, qy, qz) {
|
|
11269
|
-
let w = 0;
|
|
11270
|
-
const tris = indices.length;
|
|
11271
|
-
for (let t2 = 0; t2 < tris; t2 += 3) {
|
|
11272
|
-
const i0 = indices[t2] * 3;
|
|
11273
|
-
const i1 = indices[t2 + 1] * 3;
|
|
11274
|
-
const i2 = indices[t2 + 2] * 3;
|
|
11275
|
-
const ax = positions[i0] - qx;
|
|
11276
|
-
const ay = positions[i0 + 1] - qy;
|
|
11277
|
-
const az = positions[i0 + 2] - qz;
|
|
11278
|
-
const bx = positions[i1] - qx;
|
|
11279
|
-
const by = positions[i1 + 1] - qy;
|
|
11280
|
-
const bz = positions[i1 + 2] - qz;
|
|
11281
|
-
const cx = positions[i2] - qx;
|
|
11282
|
-
const cy = positions[i2 + 1] - qy;
|
|
11283
|
-
const cz = positions[i2 + 2] - qz;
|
|
11284
|
-
const la = Math.sqrt(ax * ax + ay * ay + az * az);
|
|
11285
|
-
const lb = Math.sqrt(bx * bx + by * by + bz * bz);
|
|
11286
|
-
const lc = Math.sqrt(cx * cx + cy * cy + cz * cz);
|
|
11287
|
-
const cbx = by * cz - bz * cy;
|
|
11288
|
-
const cby = bz * cx - bx * cz;
|
|
11289
|
-
const cbz = bx * cy - by * cx;
|
|
11290
|
-
const num2 = ax * cbx + ay * cby + az * cbz;
|
|
11291
|
-
const den = la * lb * lc + (ax * bx + ay * by + az * bz) * lc + (bx * cx + by * cy + bz * cz) * la + (cx * ax + cy * ay + cz * az) * lb;
|
|
11292
|
-
w += Math.atan2(num2, den);
|
|
11293
|
-
}
|
|
11294
|
-
return w / (2 * Math.PI);
|
|
11295
|
-
}
|
|
11296
|
-
function buildOccupancy(positions, indices) {
|
|
11297
|
-
let minX = Infinity;
|
|
11298
|
-
let maxX = -Infinity;
|
|
11299
|
-
let minY = Infinity;
|
|
11300
|
-
let maxY = -Infinity;
|
|
11301
|
-
let minZ = Infinity;
|
|
11302
|
-
let maxZ = -Infinity;
|
|
11303
|
-
for (let i = 0; i < positions.length; i += 3) {
|
|
11304
|
-
const x = positions[i];
|
|
11305
|
-
const y = positions[i + 1];
|
|
11306
|
-
const z = positions[i + 2];
|
|
11307
|
-
if (x < minX)
|
|
11308
|
-
minX = x;
|
|
11309
|
-
if (x > maxX)
|
|
11310
|
-
maxX = x;
|
|
11311
|
-
if (y < minY)
|
|
11312
|
-
minY = y;
|
|
11313
|
-
if (y > maxY)
|
|
11314
|
-
maxY = y;
|
|
11315
|
-
if (z < minZ)
|
|
11316
|
-
minZ = z;
|
|
11317
|
-
if (z > maxZ)
|
|
11318
|
-
maxZ = z;
|
|
11319
|
-
}
|
|
11320
|
-
if (!(maxX > minX) || !(maxY > minY) || !(maxZ > minZ))
|
|
11321
|
-
return null;
|
|
11322
|
-
const gw = GRID_XY;
|
|
11323
|
-
const gh = GRID_XY;
|
|
11324
|
-
const gz = GRID_Z;
|
|
11325
|
-
const dx = (maxX - minX) / gw;
|
|
11326
|
-
const dy = (maxY - minY) / gh;
|
|
11327
|
-
const dz = (maxZ - minZ) / gz;
|
|
11328
|
-
const occ = new Uint8Array(gw * gh * gz);
|
|
11329
|
-
for (let k = 0; k < gz; k++) {
|
|
11330
|
-
const qz = minZ + (k + JZ) * dz;
|
|
11331
|
-
for (let j = 0; j < gh; j++) {
|
|
11332
|
-
const qy = minY + (j + JY) * dy;
|
|
11333
|
-
for (let i = 0; i < gw; i++) {
|
|
11334
|
-
const qx = minX + (i + JX) * dx;
|
|
11335
|
-
if (Math.abs(gwn(positions, indices, qx, qy, qz)) > INSIDE) {
|
|
11336
|
-
occ[i + gw * (j + gh * k)] = 1;
|
|
11337
|
-
}
|
|
11338
|
-
}
|
|
11339
|
-
}
|
|
11340
|
-
}
|
|
11341
|
-
return { gw, gh, gz, ox: minX, oy: minY, oz: minZ, dx, dy, dz, minZ, maxZ, occ };
|
|
11342
|
-
}
|
|
11343
|
-
function bottomRunTopZ(o, i, j) {
|
|
11344
|
-
const base = i + o.gw * j;
|
|
11345
|
-
if (o.occ[base] !== 1)
|
|
11346
|
-
return null;
|
|
11347
|
-
let k = 1;
|
|
11348
|
-
while (k < o.gz && o.occ[base + o.gw * o.gh * k] === 1)
|
|
11349
|
-
k++;
|
|
11350
|
-
return o.oz + k * o.dz;
|
|
11351
|
-
}
|
|
11352
|
-
function openAtTop(o, i, j) {
|
|
11353
|
-
return o.occ[i + o.gw * (j + o.gh * (o.gz - 1))] !== 1;
|
|
11354
|
-
}
|
|
11355
|
-
function hasGap(o, i, j, kLo, kHi) {
|
|
11356
|
-
for (let k = kLo; k <= kHi; k++) {
|
|
11357
|
-
if (o.occ[i + o.gw * (j + o.gh * k)] !== 1)
|
|
11358
|
-
return true;
|
|
11359
|
-
}
|
|
11360
|
-
return false;
|
|
11361
|
-
}
|
|
11362
10965
|
function extractEnclosureFeatures(positions, indices) {
|
|
11363
|
-
const
|
|
11364
|
-
|
|
11365
|
-
return {
|
|
11366
|
-
outer: { minX: 0, maxX: 0, minY: 0, maxY: 0, minZ: 0, maxZ: 0 },
|
|
11367
|
-
cavity: null,
|
|
11368
|
-
standoffs: [],
|
|
11369
|
-
openings: []
|
|
11370
|
-
};
|
|
11371
|
-
}
|
|
11372
|
-
const outer = {
|
|
11373
|
-
minX: o.ox,
|
|
11374
|
-
maxX: o.ox + o.gw * o.dx,
|
|
11375
|
-
minY: o.oy,
|
|
11376
|
-
maxY: o.oy + o.gh * o.dy,
|
|
11377
|
-
minZ: o.minZ,
|
|
11378
|
-
maxZ: o.maxZ
|
|
11379
|
-
};
|
|
11380
|
-
const isPocket = new Uint8Array(o.gw * o.gh);
|
|
11381
|
-
const firstTops = [];
|
|
11382
|
-
const countX = new Int32Array(o.gw);
|
|
11383
|
-
const countY = new Int32Array(o.gh);
|
|
11384
|
-
let pocketCount = 0;
|
|
11385
|
-
for (let j = 0; j < o.gh; j++) {
|
|
11386
|
-
for (let i = 0; i < o.gw; i++) {
|
|
11387
|
-
const top = bottomRunTopZ(o, i, j);
|
|
11388
|
-
if (top != null && openAtTop(o, i, j)) {
|
|
11389
|
-
isPocket[i + o.gw * j] = 1;
|
|
11390
|
-
pocketCount++;
|
|
11391
|
-
firstTops.push(top);
|
|
11392
|
-
countX[i]++;
|
|
11393
|
-
countY[j]++;
|
|
11394
|
-
}
|
|
11395
|
-
}
|
|
11396
|
-
}
|
|
11397
|
-
if (pocketCount === 0) {
|
|
11398
|
-
return { outer, cavity: null, standoffs: [], openings: [] };
|
|
11399
|
-
}
|
|
11400
|
-
const core = (counts) => {
|
|
11401
|
-
let peak = 0;
|
|
11402
|
-
for (const c of counts)
|
|
11403
|
-
if (c > peak)
|
|
11404
|
-
peak = c;
|
|
11405
|
-
const thr = peak * 0.5;
|
|
11406
|
-
let lo = 0;
|
|
11407
|
-
let hi = counts.length - 1;
|
|
11408
|
-
while (lo < counts.length && counts[lo] < thr)
|
|
11409
|
-
lo++;
|
|
11410
|
-
while (hi >= 0 && counts[hi] < thr)
|
|
11411
|
-
hi--;
|
|
11412
|
-
return [lo, hi];
|
|
11413
|
-
};
|
|
11414
|
-
const [pminI, pmaxI] = core(countX);
|
|
11415
|
-
const [pminJ, pmaxJ] = core(countY);
|
|
11416
|
-
const sortedTops = [...firstTops].sort((a, b) => a - b);
|
|
11417
|
-
const floorZ = sortedTops[Math.floor(sortedTops.length / 2)] ?? o.minZ;
|
|
11418
|
-
const ceilZ = o.maxZ;
|
|
11419
|
-
const cavity = {
|
|
11420
|
-
minX: o.ox + pminI * o.dx,
|
|
11421
|
-
maxX: o.ox + (pmaxI + 1) * o.dx,
|
|
11422
|
-
minY: o.oy + pminJ * o.dy,
|
|
11423
|
-
maxY: o.oy + (pmaxJ + 1) * o.dy,
|
|
11424
|
-
floorZ: round22(floorZ),
|
|
11425
|
-
ceilZ: round22(ceilZ),
|
|
11426
|
-
hasLid: false
|
|
11427
|
-
};
|
|
11428
|
-
const standoffs = extractStandoffs(o, isPocket, floorZ);
|
|
11429
|
-
const openings = extractOpenings(o, cavity, floorZ, ceilZ);
|
|
11430
|
-
return { outer, cavity, standoffs, openings };
|
|
11431
|
-
}
|
|
11432
|
-
function extractStandoffs(o, isPocket, floorZ) {
|
|
11433
|
-
const isPost = new Uint8Array(o.gw * o.gh);
|
|
11434
|
-
for (let j = 0; j < o.gh; j++) {
|
|
11435
|
-
for (let i = 0; i < o.gw; i++) {
|
|
11436
|
-
if (!isPocket[i + o.gw * j])
|
|
11437
|
-
continue;
|
|
11438
|
-
const top = bottomRunTopZ(o, i, j);
|
|
11439
|
-
if (top != null && top > floorZ + MIN_POST_HEIGHT)
|
|
11440
|
-
isPost[i + o.gw * j] = 1;
|
|
11441
|
-
}
|
|
11442
|
-
}
|
|
11443
|
-
const seen = new Uint8Array(o.gw * o.gh);
|
|
11444
|
-
const standoffs = [];
|
|
11445
|
-
for (let j = 0; j < o.gh; j++) {
|
|
11446
|
-
for (let i = 0; i < o.gw; i++) {
|
|
11447
|
-
const k0 = i + o.gw * j;
|
|
11448
|
-
if (!isPost[k0] || seen[k0])
|
|
11449
|
-
continue;
|
|
11450
|
-
const stack = [[i, j]];
|
|
11451
|
-
seen[k0] = 1;
|
|
11452
|
-
let sumX = 0;
|
|
11453
|
-
let sumY = 0;
|
|
11454
|
-
let n = 0;
|
|
11455
|
-
let topMax = -Infinity;
|
|
11456
|
-
let minI = i;
|
|
11457
|
-
let maxI = i;
|
|
11458
|
-
let minJ = j;
|
|
11459
|
-
let maxJ = j;
|
|
11460
|
-
while (stack.length) {
|
|
11461
|
-
const [ci, cj] = stack.pop();
|
|
11462
|
-
sumX += o.ox + (ci + 0.5) * o.dx;
|
|
11463
|
-
sumY += o.oy + (cj + 0.5) * o.dy;
|
|
11464
|
-
n++;
|
|
11465
|
-
const t2 = bottomRunTopZ(o, ci, cj);
|
|
11466
|
-
if (t2 != null && t2 > topMax)
|
|
11467
|
-
topMax = t2;
|
|
11468
|
-
if (ci < minI)
|
|
11469
|
-
minI = ci;
|
|
11470
|
-
if (ci > maxI)
|
|
11471
|
-
maxI = ci;
|
|
11472
|
-
if (cj < minJ)
|
|
11473
|
-
minJ = cj;
|
|
11474
|
-
if (cj > maxJ)
|
|
11475
|
-
maxJ = cj;
|
|
11476
|
-
for (const [ni, nj] of [
|
|
11477
|
-
[ci - 1, cj],
|
|
11478
|
-
[ci + 1, cj],
|
|
11479
|
-
[ci, cj - 1],
|
|
11480
|
-
[ci, cj + 1]
|
|
11481
|
-
]) {
|
|
11482
|
-
if (ni < 0 || nj < 0 || ni >= o.gw || nj >= o.gh)
|
|
11483
|
-
continue;
|
|
11484
|
-
const nk = ni + o.gw * nj;
|
|
11485
|
-
if (isPost[nk] && !seen[nk]) {
|
|
11486
|
-
seen[nk] = 1;
|
|
11487
|
-
stack.push([ni, nj]);
|
|
11488
|
-
}
|
|
11489
|
-
}
|
|
11490
|
-
}
|
|
11491
|
-
if (n < 2)
|
|
11492
|
-
continue;
|
|
11493
|
-
const radius = Math.max(((maxI - minI + 1) * o.dx + (maxJ - minJ + 1) * o.dy) / 4, o.dx);
|
|
11494
|
-
standoffs.push({
|
|
11495
|
-
x: round22(sumX / n),
|
|
11496
|
-
y: round22(sumY / n),
|
|
11497
|
-
topZ: round22(topMax),
|
|
11498
|
-
radius: round22(radius)
|
|
11499
|
-
});
|
|
11500
|
-
}
|
|
11501
|
-
}
|
|
11502
|
-
return standoffs;
|
|
11503
|
-
}
|
|
11504
|
-
function extractOpenings(o, cavity, floorZ, ceilZ) {
|
|
11505
|
-
const pIminI = Math.round((cavity.minX - o.ox) / o.dx);
|
|
11506
|
-
const pImaxI = Math.round((cavity.maxX - o.ox) / o.dx) - 1;
|
|
11507
|
-
const pIminJ = Math.round((cavity.minY - o.oy) / o.dy);
|
|
11508
|
-
const pImaxJ = Math.round((cavity.maxY - o.oy) / o.dy) - 1;
|
|
11509
|
-
const kLo = Math.max(0, Math.floor((floorZ - o.oz) / o.dz) + 1);
|
|
11510
|
-
const kHi = Math.min(o.gz - 1, Math.ceil((ceilZ - o.oz) / o.dz) - 1);
|
|
11511
|
-
const openings = [];
|
|
11512
|
-
const wallOpen = (i, j) => {
|
|
11513
|
-
if (i < 0 || j < 0 || i >= o.gw || j >= o.gh)
|
|
11514
|
-
return true;
|
|
11515
|
-
return hasGap(o, i, j, kLo, kHi);
|
|
11516
|
-
};
|
|
11517
|
-
const scans = [];
|
|
11518
|
-
for (const [edge, wi] of [
|
|
11519
|
-
["minX", pIminI - 1],
|
|
11520
|
-
["maxX", pImaxI + 1]
|
|
11521
|
-
]) {
|
|
11522
|
-
const cells = [];
|
|
11523
|
-
for (let j = pIminJ; j <= pImaxJ; j++) {
|
|
11524
|
-
cells.push({ open: wallOpen(wi, j), x: o.ox + (wi + 0.5) * o.dx, y: o.oy + (j + 0.5) * o.dy, i: wi, j });
|
|
11525
|
-
}
|
|
11526
|
-
scans.push({ edge, cells });
|
|
11527
|
-
}
|
|
11528
|
-
for (const [edge, wj] of [
|
|
11529
|
-
["minY", pIminJ - 1],
|
|
11530
|
-
["maxY", pImaxJ + 1]
|
|
11531
|
-
]) {
|
|
11532
|
-
const cells = [];
|
|
11533
|
-
for (let i = pIminI; i <= pImaxI; i++) {
|
|
11534
|
-
cells.push({ open: wallOpen(i, wj), x: o.ox + (i + 0.5) * o.dx, y: o.oy + (wj + 0.5) * o.dy, i, j: wj });
|
|
11535
|
-
}
|
|
11536
|
-
scans.push({ edge, cells });
|
|
11537
|
-
}
|
|
11538
|
-
for (const scan of scans) {
|
|
11539
|
-
let run = [];
|
|
11540
|
-
const flush = () => {
|
|
11541
|
-
if (run.length >= MIN_OPENING_CELLS) {
|
|
11542
|
-
const xs = run.map((p) => p.x);
|
|
11543
|
-
const ys = run.map((p) => p.y);
|
|
11544
|
-
const horiz = scan.edge === "minY" || scan.edge === "maxY";
|
|
11545
|
-
const center = {
|
|
11546
|
-
x: (Math.min(...xs) + Math.max(...xs)) / 2,
|
|
11547
|
-
y: (Math.min(...ys) + Math.max(...ys)) / 2
|
|
11548
|
-
};
|
|
11549
|
-
const width = horiz ? Math.max(...xs) - Math.min(...xs) + o.dx : Math.max(...ys) - Math.min(...ys) + o.dy;
|
|
11550
|
-
const mid = run[Math.floor(run.length / 2)];
|
|
11551
|
-
const { zMin, zMax } = openingZSpan(o, mid.i, mid.j, kLo, kHi);
|
|
11552
|
-
openings.push({
|
|
11553
|
-
edge: scan.edge,
|
|
11554
|
-
center: { x: round22(center.x), y: round22(center.y) },
|
|
11555
|
-
width: round22(width),
|
|
11556
|
-
zMin: round22(zMin),
|
|
11557
|
-
zMax: round22(zMax)
|
|
11558
|
-
});
|
|
11559
|
-
}
|
|
11560
|
-
run = [];
|
|
11561
|
-
};
|
|
11562
|
-
for (const c of scan.cells) {
|
|
11563
|
-
if (c.open)
|
|
11564
|
-
run.push({ x: c.x, y: c.y, i: c.i, j: c.j });
|
|
11565
|
-
else
|
|
11566
|
-
flush();
|
|
11567
|
-
}
|
|
11568
|
-
flush();
|
|
11569
|
-
}
|
|
11570
|
-
return openings;
|
|
11571
|
-
}
|
|
11572
|
-
function openingZSpan(o, i, j, kLo, kHi) {
|
|
11573
|
-
let zMin = Infinity;
|
|
11574
|
-
let zMax = -Infinity;
|
|
11575
|
-
for (let k = kLo; k <= kHi; k++) {
|
|
11576
|
-
if (o.occ[i + o.gw * (j + o.gh * k)] !== 1) {
|
|
11577
|
-
const z = o.oz + (k + 0.5) * o.dz;
|
|
11578
|
-
if (z < zMin)
|
|
11579
|
-
zMin = z;
|
|
11580
|
-
if (z > zMax)
|
|
11581
|
-
zMax = z;
|
|
11582
|
-
}
|
|
11583
|
-
}
|
|
11584
|
-
if (!Number.isFinite(zMin))
|
|
11585
|
-
return { zMin: o.oz, zMax: o.maxZ };
|
|
11586
|
-
return { zMin, zMax };
|
|
10966
|
+
const json = enclosureWasm().enclosure_features(Float64Array.from(positions), Uint32Array.from(indices));
|
|
10967
|
+
return JSON.parse(json);
|
|
11587
10968
|
}
|
|
11588
|
-
var GRID_XY, GRID_Z, INSIDE, MIN_POST_HEIGHT, MIN_OPENING_CELLS, JX, JY, JZ, round22;
|
|
11589
10969
|
var init_enclosure_mesh = __esm({
|
|
11590
10970
|
"../engine/dist/enclosure-mesh.js"() {
|
|
11591
10971
|
"use strict";
|
|
11592
|
-
|
|
11593
|
-
GRID_Z = 28;
|
|
11594
|
-
INSIDE = 0.5;
|
|
11595
|
-
MIN_POST_HEIGHT = 0.8;
|
|
11596
|
-
MIN_OPENING_CELLS = 2;
|
|
11597
|
-
JX = 0.5 + 0.137;
|
|
11598
|
-
JY = 0.5 - 0.077;
|
|
11599
|
-
JZ = 0.5 + 0.041;
|
|
11600
|
-
round22 = (n) => Math.round(n * 100) / 100;
|
|
10972
|
+
init_enclosure_fit();
|
|
11601
10973
|
}
|
|
11602
10974
|
});
|
|
11603
10975
|
|
|
@@ -11640,6 +11012,7 @@ __export(dist_exports, {
|
|
|
11640
11012
|
decimateMeshGpu: () => decimateMeshGpu2,
|
|
11641
11013
|
defaultParamsFor: () => defaultParamsFor,
|
|
11642
11014
|
deriveBoardFromCavity: () => deriveBoardFromCavity,
|
|
11015
|
+
embroideryPatternToMeshWithKernel: () => embroideryPatternToMeshWithKernel,
|
|
11643
11016
|
estimateCost: () => estimateCost,
|
|
11644
11017
|
evalAst: () => evalAst,
|
|
11645
11018
|
evalExprSafe: () => evalExprSafe,
|
|
@@ -11717,6 +11090,7 @@ __export(dist_exports, {
|
|
|
11717
11090
|
threeWayMerge: () => threeWayMerge,
|
|
11718
11091
|
toWorld: () => toWorld,
|
|
11719
11092
|
transformMesh: () => transformMesh,
|
|
11093
|
+
transformMeshWithKernel: () => transformMeshWithKernel,
|
|
11720
11094
|
tryExportFabFiles: () => tryExportFabFiles,
|
|
11721
11095
|
tryPcbPreviewMeshes: () => tryPcbPreviewMeshes,
|
|
11722
11096
|
tryRunDrc: () => tryRunDrc,
|
|
@@ -11742,6 +11116,7 @@ var init_dist = __esm({
|
|
|
11742
11116
|
init_mesh_cache();
|
|
11743
11117
|
init_dependency_graph();
|
|
11744
11118
|
init_evaluate();
|
|
11119
|
+
init_evaluate();
|
|
11745
11120
|
init_sequence();
|
|
11746
11121
|
init_sheet_metal();
|
|
11747
11122
|
init_expressions();
|
|
@@ -32217,13 +31592,13 @@ var init_document_store = __esm({
|
|
|
32217
31592
|
const ang = (fp.rotation ?? 0) * Math.PI / 180;
|
|
32218
31593
|
const cos = Math.cos(ang);
|
|
32219
31594
|
const sin = Math.sin(ang);
|
|
32220
|
-
const
|
|
31595
|
+
const padWorld2 = fp.pads.map((p) => ({
|
|
32221
31596
|
x: fp.position.x + (p.position.x * cos - p.position.y * sin),
|
|
32222
31597
|
y: fp.position.y + (p.position.x * sin + p.position.y * cos)
|
|
32223
31598
|
}));
|
|
32224
31599
|
for (const tr of pcb.traces) {
|
|
32225
31600
|
for (const end of [tr.start, tr.end]) {
|
|
32226
|
-
if (
|
|
31601
|
+
if (padWorld2.some((pw) => Math.abs(end.x - pw.x) < TOL && Math.abs(end.y - pw.y) < TOL)) {
|
|
32227
31602
|
end.x += dx;
|
|
32228
31603
|
end.y += dy;
|
|
32229
31604
|
}
|
|
@@ -49384,7 +48759,7 @@ function buildPartLabels(doc) {
|
|
|
49384
48759
|
return `${entry.root}:${name ?? ""}`;
|
|
49385
48760
|
});
|
|
49386
48761
|
}
|
|
49387
|
-
function
|
|
48762
|
+
function eulerXyzDegToQuat2(rotation) {
|
|
49388
48763
|
const q = exportKernel().eulerXyzDegToQuat(rotation.x, rotation.y, rotation.z);
|
|
49389
48764
|
return [q[0], q[1], q[2], q[3]];
|
|
49390
48765
|
}
|
|
@@ -49597,7 +48972,7 @@ function generateInstancesGlbPreview(doc, engine) {
|
|
|
49597
48972
|
inst.transform.translation.y,
|
|
49598
48973
|
inst.transform.translation.z
|
|
49599
48974
|
],
|
|
49600
|
-
rotationQuat:
|
|
48975
|
+
rotationQuat: eulerXyzDegToQuat2(inst.transform.rotation),
|
|
49601
48976
|
scale: [
|
|
49602
48977
|
inst.transform.scale.x,
|
|
49603
48978
|
inst.transform.scale.y,
|
|
@@ -58293,7 +57668,7 @@ function computeUtilization(footprints, vertices, cutouts, shape, innerRadius, e
|
|
|
58293
57668
|
}
|
|
58294
57669
|
const boardArea = Math.abs(loopSignedArea(vertices)) - (cutouts ?? []).reduce((s, c) => s + Math.abs(loopSignedArea(c)), 0);
|
|
58295
57670
|
if (!Number.isFinite(occMinX) || boardArea <= 0) return void 0;
|
|
58296
|
-
const
|
|
57671
|
+
const round23 = (v) => Math.round(v * 100) / 100;
|
|
58297
57672
|
const ceilHalf = (v) => Math.ceil(v * 2) / 2;
|
|
58298
57673
|
const bbW = occMaxX - occMinX;
|
|
58299
57674
|
const bbH = occMaxY - occMinY;
|
|
@@ -58306,8 +57681,8 @@ function computeUtilization(footprints, vertices, cutouts, shape, innerRadius, e
|
|
|
58306
57681
|
suggested = {
|
|
58307
57682
|
type: "circle",
|
|
58308
57683
|
outer_diameter: od,
|
|
58309
|
-
center: { x:
|
|
58310
|
-
...innerRadius > 0 ? { inner_diameter:
|
|
57684
|
+
center: { x: round23(cx), y: round23(cy) },
|
|
57685
|
+
...innerRadius > 0 ? { inner_diameter: round23(2 * innerRadius) } : {},
|
|
58311
57686
|
note: `Minimum enclosing circle with ${margin}mm edge clearance`
|
|
58312
57687
|
};
|
|
58313
57688
|
} else {
|
|
@@ -58315,19 +57690,19 @@ function computeUtilization(footprints, vertices, cutouts, shape, innerRadius, e
|
|
|
58315
57690
|
type: "rect",
|
|
58316
57691
|
width: ceilHalf(bbW + 2 * margin),
|
|
58317
57692
|
height: ceilHalf(bbH + 2 * margin),
|
|
58318
|
-
origin: { x:
|
|
57693
|
+
origin: { x: round23(occMinX - margin), y: round23(occMinY - margin) },
|
|
58319
57694
|
note: `Minimum enclosing rectangle with ${margin}mm edge clearance`
|
|
58320
57695
|
};
|
|
58321
57696
|
}
|
|
58322
57697
|
return {
|
|
58323
|
-
board_area_mm2:
|
|
58324
|
-
component_area_mm2:
|
|
57698
|
+
board_area_mm2: round23(boardArea),
|
|
57699
|
+
component_area_mm2: round23(componentArea),
|
|
58325
57700
|
utilization_pct: Math.round(componentArea / boardArea * 1e3) / 10,
|
|
58326
|
-
bounding_box: { x:
|
|
57701
|
+
bounding_box: { x: round23(occMinX), y: round23(occMinY), w: round23(bbW), h: round23(bbH) },
|
|
58327
57702
|
suggested_outline: suggested
|
|
58328
57703
|
};
|
|
58329
57704
|
}
|
|
58330
|
-
function
|
|
57705
|
+
function padWorld(fp, pad) {
|
|
58331
57706
|
const ang = (fp.rotation ?? 0) * Math.PI / 180;
|
|
58332
57707
|
const cos = Math.cos(ang);
|
|
58333
57708
|
const sin = Math.sin(ang);
|
|
@@ -58346,7 +57721,7 @@ function detectStaleNets(pcb) {
|
|
|
58346
57721
|
const arr = padsByNet.get(pad.net) ?? [];
|
|
58347
57722
|
const sh = pad.shape;
|
|
58348
57723
|
const halfExtent = sh.diameter ? sh.diameter / 2 : Math.hypot(sh.width ?? 0, sh.height ?? 0) / 2;
|
|
58349
|
-
arr.push({ pos:
|
|
57724
|
+
arr.push({ pos: padWorld(fp, pad), layers: pad.layers, halfExtent });
|
|
58350
57725
|
padsByNet.set(pad.net, arr);
|
|
58351
57726
|
}
|
|
58352
57727
|
}
|
|
@@ -59230,7 +58605,7 @@ function layoutLint(pcb) {
|
|
|
59230
58605
|
for (const fp of fps) {
|
|
59231
58606
|
const prefix = refPrefix(fp.ref);
|
|
59232
58607
|
for (const pad of fp.pads) {
|
|
59233
|
-
const w = { ref: fp.ref, prefix, pad, pos:
|
|
58608
|
+
const w = { ref: fp.ref, prefix, pad, pos: padWorld(fp, pad) };
|
|
59234
58609
|
allPads.push(w);
|
|
59235
58610
|
if (pad.net) {
|
|
59236
58611
|
const arr = byNet.get(pad.net) ?? [];
|
|
@@ -59246,7 +58621,7 @@ function layoutLint(pcb) {
|
|
|
59246
58621
|
for (const pad of fp.pads) {
|
|
59247
58622
|
const net = pad.net;
|
|
59248
58623
|
if (!net || LINT_GROUND_NET_RE.test(net) || LINT_SUPPLY_NET_RE.test(net)) continue;
|
|
59249
|
-
const from =
|
|
58624
|
+
const from = padWorld(fp, pad);
|
|
59250
58625
|
let nearest = null;
|
|
59251
58626
|
for (const other of byNet.get(net) ?? []) {
|
|
59252
58627
|
if (other.ref === fp.ref || other.prefix !== "U") continue;
|
|
@@ -59261,9 +58636,9 @@ function layoutLint(pcb) {
|
|
|
59261
58636
|
warnings.push({
|
|
59262
58637
|
kind: "crystal_far_from_ic",
|
|
59263
58638
|
refs: [fp.ref, worst2.icRef],
|
|
59264
|
-
distance_mm:
|
|
58639
|
+
distance_mm: round2(worst2.dist),
|
|
59265
58640
|
threshold_mm: LINT_CRYSTAL_MAX_MM,
|
|
59266
|
-
message: `Crystal ${fp.ref} is ${
|
|
58641
|
+
message: `Crystal ${fp.ref} is ${round2(worst2.dist)}mm from ${worst2.icRef} pad ${worst2.icPad} (net '${worst2.net}') \u2014 oscillator loops want <${LINT_CRYSTAL_MAX_MM}mm; move ${fp.ref} next to its XIN/XOUT pins.`
|
|
59267
58642
|
});
|
|
59268
58643
|
}
|
|
59269
58644
|
}
|
|
@@ -59274,7 +58649,7 @@ function layoutLint(pcb) {
|
|
|
59274
58649
|
const pwrIdx = nets.findIndex((n) => n && LINT_SUPPLY_NET_RE.test(n) && !LINT_GROUND_NET_RE.test(n));
|
|
59275
58650
|
if (gndIdx < 0 || pwrIdx < 0) continue;
|
|
59276
58651
|
const pwrPad = fp.pads[pwrIdx];
|
|
59277
|
-
const from =
|
|
58652
|
+
const from = padWorld(fp, pwrPad);
|
|
59278
58653
|
let nearest = null;
|
|
59279
58654
|
for (const other of byNet.get(pwrPad.net) ?? []) {
|
|
59280
58655
|
if (other.ref === fp.ref || other.prefix !== "U") continue;
|
|
@@ -59287,9 +58662,9 @@ function layoutLint(pcb) {
|
|
|
59287
58662
|
warnings.push({
|
|
59288
58663
|
kind: "decoupling_cap_far_from_pin",
|
|
59289
58664
|
refs: [fp.ref, nearest.icRef],
|
|
59290
|
-
distance_mm:
|
|
58665
|
+
distance_mm: round2(nearest.dist),
|
|
59291
58666
|
threshold_mm: LINT_DECAP_MAX_MM,
|
|
59292
|
-
message: `Decoupling cap ${fp.ref} is ${
|
|
58667
|
+
message: `Decoupling cap ${fp.ref} is ${round2(nearest.dist)}mm from ${nearest.icRef} pad ${nearest.icPad} (net '${pwrPad.net}') \u2014 decouplers want <${LINT_DECAP_MAX_MM}mm to the pin they decouple.`
|
|
59293
58668
|
});
|
|
59294
58669
|
}
|
|
59295
58670
|
}
|
|
@@ -59307,15 +58682,15 @@ function layoutLint(pcb) {
|
|
|
59307
58682
|
for (const fp of fps) {
|
|
59308
58683
|
const prefix = refPrefix(fp.ref);
|
|
59309
58684
|
if (prefix !== "J" && prefix !== "P" && prefix !== "CN" && prefix !== "USB") continue;
|
|
59310
|
-
const pts = fp.pads.length > 0 ? fp.pads.map((p) =>
|
|
58685
|
+
const pts = fp.pads.length > 0 ? fp.pads.map((p) => padWorld(fp, p)) : [fp.position];
|
|
59311
58686
|
const d = Math.min(...pts.map(edgeDist));
|
|
59312
58687
|
if (d > LINT_CONNECTOR_EDGE_MAX_MM) {
|
|
59313
58688
|
warnings.push({
|
|
59314
58689
|
kind: "connector_not_on_edge",
|
|
59315
58690
|
refs: [fp.ref],
|
|
59316
|
-
distance_mm:
|
|
58691
|
+
distance_mm: round2(d),
|
|
59317
58692
|
threshold_mm: LINT_CONNECTOR_EDGE_MAX_MM,
|
|
59318
|
-
message: `Connector ${fp.ref} sits ${
|
|
58693
|
+
message: `Connector ${fp.ref} sits ${round2(d)}mm from the board edge \u2014 connectors want edge access (<${LINT_CONNECTOR_EDGE_MAX_MM}mm); move it to the outline.`
|
|
59319
58694
|
});
|
|
59320
58695
|
}
|
|
59321
58696
|
}
|
|
@@ -59351,9 +58726,9 @@ function layoutLint(pcb) {
|
|
|
59351
58726
|
warnings.push({
|
|
59352
58727
|
kind: "high_current_near_sensitive",
|
|
59353
58728
|
refs: [c.hi.ref, c.lo.ref],
|
|
59354
|
-
distance_mm:
|
|
58729
|
+
distance_mm: round2(c.dist),
|
|
59355
58730
|
threshold_mm: LINT_HIGH_CURRENT_SEP_MIN_MM,
|
|
59356
|
-
message: `High-current net '${c.hiNet}' (${c.hi.ref} pad ${c.hi.pad.number}) is ${
|
|
58731
|
+
message: `High-current net '${c.hiNet}' (${c.hi.ref} pad ${c.hi.pad.number}) is ${round2(c.dist)}mm from '${c.loNet}' (${c.lo.ref} pad ${c.lo.pad.number}) \u2014 keep \u2265${LINT_HIGH_CURRENT_SEP_MIN_MM}mm between high-current and USB/analog pads.`
|
|
59357
58732
|
});
|
|
59358
58733
|
}
|
|
59359
58734
|
}
|
|
@@ -62962,7 +62337,7 @@ async function addMotorWinding(args) {
|
|
|
62962
62337
|
if (!pad.net) continue;
|
|
62963
62338
|
const arr = padsByNet.get(pad.net) ?? [];
|
|
62964
62339
|
arr.push({
|
|
62965
|
-
pos:
|
|
62340
|
+
pos: padWorld(fp, pad),
|
|
62966
62341
|
onReturnLayer: pad.layers.includes(returnLayer),
|
|
62967
62342
|
size: padApproxRadius(pad)
|
|
62968
62343
|
});
|
|
@@ -63305,7 +62680,7 @@ async function addMotorWinding(args) {
|
|
|
63305
62680
|
for (const fp of pcb.footprints) {
|
|
63306
62681
|
for (const pad of fp.pads) {
|
|
63307
62682
|
if (pad.net === net) continue;
|
|
63308
|
-
const pw =
|
|
62683
|
+
const pw = padWorld(fp, pad);
|
|
63309
62684
|
if (pointSegDist(pw, t2.a, t2.b) < padApproxRadius(pad) + halfW + clearance) return false;
|
|
63310
62685
|
}
|
|
63311
62686
|
}
|
|
@@ -64998,7 +64373,7 @@ async function fixDrc(args) {
|
|
|
64998
64373
|
let corridorFail = null;
|
|
64999
64374
|
for (const pt of movePoints) {
|
|
65000
64375
|
const onPad = pcb.footprints.some(
|
|
65001
|
-
(fp) => fp.pads.some((pad) => samePoint(
|
|
64376
|
+
(fp) => fp.pads.some((pad) => samePoint(padWorld(fp, pad), pt))
|
|
65002
64377
|
);
|
|
65003
64378
|
if (onPad) {
|
|
65004
64379
|
corridorFail = "endpoint lands on a component pad \u2014 fixing requires moving the component";
|
|
@@ -65148,7 +64523,7 @@ async function fixDrc(args) {
|
|
|
65148
64523
|
};
|
|
65149
64524
|
return { content: [{ type: "text", text: JSON.stringify(payload) }] };
|
|
65150
64525
|
}
|
|
65151
|
-
var PCB_LAYER_SET, COPPER_LAYERS, COPPER_LAYER_SET, POSITION_TOLERANCE, PIN_SEP, pinKey, createSchematicSchema, docInputProperties, placeComponentsSchema, routeNetsSchema, runDrcSchema, runErcSchema, critiqueRouteSchema, routeDiffPairSchema, lengthMatchTracesSchema, exportGerberSchema, validateForFabSchema, exportKicadSchema, addCoilSchema, boardFromSolidSchema, addCoilArraySchema, windingLayoutSchema, calcImpedanceSchema, sizeImpedanceSchema, sizePdnSchema, calcCoilSchema, sizeCoilSchema, calcRfSchema, COIL_GEOMETRY, MU0, RAIL_PIN_NAMES, PIN_TYPES, CHIP_SIZE_CODES, CONNECTIVITY_GUARD_MAX_NETS, POWER_NET_RE, DRC_CATEGORY, DRC_DELTA_SAMPLE_CAP, DRC_DELTA_FULL_BOARD_MAX, LINT_CRYSTAL_MAX_MM, LINT_DECAP_MAX_MM, LINT_CONNECTOR_EDGE_MAX_MM, LINT_HIGH_CURRENT_SEP_MIN_MM, LINT_GROUND_NET_RE, LINT_SUPPLY_NET_RE, SENSITIVE_NET_RE, HIGH_CURRENT_CLASS_RE, refPrefix, dist2d,
|
|
64526
|
+
var PCB_LAYER_SET, COPPER_LAYERS, COPPER_LAYER_SET, POSITION_TOLERANCE, PIN_SEP, pinKey, createSchematicSchema, docInputProperties, placeComponentsSchema, routeNetsSchema, runDrcSchema, runErcSchema, critiqueRouteSchema, routeDiffPairSchema, lengthMatchTracesSchema, exportGerberSchema, validateForFabSchema, exportKicadSchema, addCoilSchema, boardFromSolidSchema, addCoilArraySchema, windingLayoutSchema, calcImpedanceSchema, sizeImpedanceSchema, sizePdnSchema, calcCoilSchema, sizeCoilSchema, calcRfSchema, COIL_GEOMETRY, MU0, RAIL_PIN_NAMES, PIN_TYPES, CHIP_SIZE_CODES, CONNECTIVITY_GUARD_MAX_NETS, POWER_NET_RE, DRC_CATEGORY, DRC_DELTA_SAMPLE_CAP, DRC_DELTA_FULL_BOARD_MAX, LINT_CRYSTAL_MAX_MM, LINT_DECAP_MAX_MM, LINT_CONNECTOR_EDGE_MAX_MM, LINT_HIGH_CURRENT_SEP_MIN_MM, LINT_GROUND_NET_RE, LINT_SUPPLY_NET_RE, SENSITIVE_NET_RE, HIGH_CURRENT_CLASS_RE, refPrefix, dist2d, round2, sumSq, round33, vec2Schema, setBoardOutlineSchema, FOOTPRINT_FAMILIES, listFootprintsSchema, searchFootprintsSchema, getPadPositionsSchema, getFootprintSchema, ROTATION_CONVENTION, describePcbSchema, DESCRIBE_NET_NAME_CAP, addTraceSchema, addViaSchema, OZ_TO_MM, setStackupSchema, setPlacementSchema, addZoneSchema, deleteElementProps, deleteZoneSchema, deleteTraceSchema, deleteViaSchema, COPPER_KINDS, GET_COPPER_CAP, getCopperSchema, TIE_POSITION_TOL, addNetTieSchema, deleteNetTieSchema, undoSchema, setDesignRulesSchema, sizeTraceForCurrentSchema, addViaArraySchema, MAX_VIA_ARRAY, addMotorWindingSchema, calcMotorSchema, sig6, BEARING_FRICTION_MNM, checkSelfStartSchema, solidFromBoardSchema, FR4_DENSITY_KG_M3, COPPER_DENSITY_KG_M3, DEFAULT_COPPER_THICKNESS_MM, loopArea, MOUNTING_FP_RE, KEEPOUT_ECHO_CAP, INLINE_DOC_ECHO_CAP, searchElectronicPartsSchema, resolvePartSchema, findAlternativesSchema, verifySubstitutionSchema, buildReceiptSchema, verifyReceiptSchema, FIX_EPS, FIX_DRC_NEVER, fixDrcSchema, toolDefs20;
|
|
65152
64527
|
var init_ecad2 = __esm({
|
|
65153
64528
|
"src/tools/ecad.ts"() {
|
|
65154
64529
|
"use strict";
|
|
@@ -65950,7 +65325,7 @@ var init_ecad2 = __esm({
|
|
|
65950
65325
|
HIGH_CURRENT_CLASS_RE = /pwr|power|high[-_ ]?current|motor|\bhv\b|hi[-_ ]?amp/i;
|
|
65951
65326
|
refPrefix = (ref) => /^[A-Za-z]+/.exec(ref)?.[0]?.toUpperCase() ?? "";
|
|
65952
65327
|
dist2d = (a, b) => Math.hypot(a.x - b.x, a.y - b.y);
|
|
65953
|
-
|
|
65328
|
+
round2 = (n) => Math.round(n * 100) / 100;
|
|
65954
65329
|
sumSq = (v) => v.reduce((s, x) => s + x * x, 0);
|
|
65955
65330
|
round33 = (v) => Math.round(v * 1e3) / 1e3;
|
|
65956
65331
|
vec2Schema = {
|
|
@@ -72571,12 +71946,12 @@ function simulateStrike(input, engine) {
|
|
|
72571
71946
|
},
|
|
72572
71947
|
modes: result.modes.map((m) => ({
|
|
72573
71948
|
n: m.n,
|
|
72574
|
-
hz:
|
|
71949
|
+
hz: round22(m.hz),
|
|
72575
71950
|
gain: round4(m.gain),
|
|
72576
71951
|
q: Math.round(m.q),
|
|
72577
|
-
t60_s:
|
|
71952
|
+
t60_s: round22(m.t60_s)
|
|
72578
71953
|
})),
|
|
72579
|
-
spectrum_peaks: result.spectrum_peaks.map((p) => ({ hz:
|
|
71954
|
+
spectrum_peaks: result.spectrum_peaks.map((p) => ({ hz: round22(p.hz), db: round22(p.db) })),
|
|
72580
71955
|
...result.verdict ? { verdict: result.verdict } : {},
|
|
72581
71956
|
limits: "1-D transverse bending only (no torsional/lateral modes); decay Q is a heuristic (material + cord-at-holes), frequencies are not"
|
|
72582
71957
|
};
|
|
@@ -72605,7 +71980,7 @@ function simulateStrike(input, engine) {
|
|
|
72605
71980
|
}
|
|
72606
71981
|
return { content: [{ type: "text", text: JSON.stringify(payload, null, 2) }] };
|
|
72607
71982
|
}
|
|
72608
|
-
var cents, MATERIAL_ALIASES, simulateStrikeSchema,
|
|
71983
|
+
var cents, MATERIAL_ALIASES, simulateStrikeSchema, round22, round4, toolDefs39;
|
|
72609
71984
|
var init_acoustics = __esm({
|
|
72610
71985
|
"src/tools/acoustics.ts"() {
|
|
72611
71986
|
"use strict";
|
|
@@ -72677,7 +72052,7 @@ var init_acoustics = __esm({
|
|
|
72677
72052
|
}
|
|
72678
72053
|
}
|
|
72679
72054
|
};
|
|
72680
|
-
|
|
72055
|
+
round22 = (x) => Math.round(x * 100) / 100;
|
|
72681
72056
|
round4 = (x) => Math.round(x * 1e4) / 1e4;
|
|
72682
72057
|
toolDefs39 = [
|
|
72683
72058
|
{
|
|
@@ -73081,8 +72456,8 @@ function parseDocument(input) {
|
|
|
73081
72456
|
}
|
|
73082
72457
|
throw new Error("Document must be JSON (starting with '{') or VCode (starting with '#')");
|
|
73083
72458
|
}
|
|
73084
|
-
function compressForUrl(
|
|
73085
|
-
const compressed = gzipSync(Buffer.from(
|
|
72459
|
+
function compressForUrl(compact2) {
|
|
72460
|
+
const compressed = gzipSync(Buffer.from(compact2, "utf-8"), { level: 9 });
|
|
73086
72461
|
return base64urlEncode(compressed);
|
|
73087
72462
|
}
|
|
73088
72463
|
function openInBrowser(input) {
|
|
@@ -76157,7 +75532,7 @@ function buildSequenceGlb(doc, timeline, frames, engine) {
|
|
|
76157
75532
|
rec.times.push(frame.t);
|
|
76158
75533
|
rec.trans.push(...translation);
|
|
76159
75534
|
rec.rot.push(
|
|
76160
|
-
...
|
|
75535
|
+
...eulerXyzDegToQuat2(
|
|
76161
75536
|
t2?.rotation ?? { x: 0, y: 0, z: 0 }
|
|
76162
75537
|
)
|
|
76163
75538
|
);
|
|
@@ -77758,8 +77133,8 @@ var init_server3 = __esm({
|
|
|
77758
77133
|
init_order_feed();
|
|
77759
77134
|
init_animate();
|
|
77760
77135
|
PKG_VERSION = (() => {
|
|
77761
|
-
if ("0.9.4-main.
|
|
77762
|
-
return "0.9.4-main.
|
|
77136
|
+
if ("0.9.4-main.16") {
|
|
77137
|
+
return "0.9.4-main.16";
|
|
77763
77138
|
}
|
|
77764
77139
|
try {
|
|
77765
77140
|
const req = createRequire2(import.meta.url);
|
|
@@ -77768,8 +77143,8 @@ var init_server3 = __esm({
|
|
|
77768
77143
|
return "0.0.0";
|
|
77769
77144
|
}
|
|
77770
77145
|
})();
|
|
77771
|
-
BUILD_SHA = "
|
|
77772
|
-
BUILD_TIME = "2026-07-
|
|
77146
|
+
BUILD_SHA = "63c1a040cf3352ca640144ec217f3f96271180c9";
|
|
77147
|
+
BUILD_TIME = "2026-07-24T17:21:48.535Z";
|
|
77773
77148
|
SHORT_SHA = BUILD_SHA === "unknown" ? "unknown" : BUILD_SHA.slice(0, 7);
|
|
77774
77149
|
VERSION_WITH_BUILD = SHORT_SHA === "unknown" ? PKG_VERSION : `${PKG_VERSION}+${SHORT_SHA}`;
|
|
77775
77150
|
INSTANCE_ID = randomUUID6().slice(0, 8);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vcad/mcp",
|
|
3
|
-
"version": "0.9.4-main.
|
|
3
|
+
"version": "0.9.4-main.16",
|
|
4
4
|
"description": "vcad MCP server — parametric CAD + PCB design tools for AI agents (self-contained: bundled server + kernel WASM)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
},
|
|
20
20
|
"homepage": "https://vcad.io",
|
|
21
21
|
"vcadBuild": {
|
|
22
|
-
"sha": "
|
|
23
|
-
"builtAt": "2026-07-
|
|
22
|
+
"sha": "63c1a040cf3352ca640144ec217f3f96271180c9",
|
|
23
|
+
"builtAt": "2026-07-24T17:21:48.535Z"
|
|
24
24
|
}
|
|
25
25
|
}
|
package/vcad_kernel_wasm_bg.wasm
CHANGED
|
Binary file
|