@vcad/mcp 0.9.4-main.15 → 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 +88 -760
- 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
|
@@ -150,6 +150,7 @@ __export(vcad_kernel_wasm_exports, {
|
|
|
150
150
|
ecadVerifyReceipt: () => ecadVerifyReceipt,
|
|
151
151
|
ecadVerifySubstitution: () => ecadVerifySubstitution,
|
|
152
152
|
emSimulate: () => emSimulate,
|
|
153
|
+
embroideryDesignToMesh: () => embroideryDesignToMesh,
|
|
153
154
|
estimatePrintCost: () => estimatePrintCost,
|
|
154
155
|
estimate_cost_for_process: () => estimate_cost_for_process,
|
|
155
156
|
eulerXyzDegToQuat: () => eulerXyzDegToQuat,
|
|
@@ -257,6 +258,7 @@ __export(vcad_kernel_wasm_exports, {
|
|
|
257
258
|
toleranceAnalyze: () => toleranceAnalyze,
|
|
258
259
|
topologyOptimizeBox: () => topologyOptimizeBox,
|
|
259
260
|
topologyOptimizeMesh: () => topologyOptimizeMesh,
|
|
261
|
+
transformMeshBuffers: () => transformMeshBuffers,
|
|
260
262
|
writeEmbroideryDst: () => writeEmbroideryDst,
|
|
261
263
|
writeEmbroideryPes: () => writeEmbroideryPes
|
|
262
264
|
});
|
|
@@ -1519,6 +1521,15 @@ function emSimulate(spec_json, params_json, options_json) {
|
|
|
1519
1521
|
}
|
|
1520
1522
|
return takeFromExternrefTable0(ret[0]);
|
|
1521
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
|
+
}
|
|
1522
1533
|
function estimatePrintCost(volume_mm3, infill_density, wall_count, line_width, material_name) {
|
|
1523
1534
|
const ptr0 = passStringToWasm0(material_name, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
1524
1535
|
const len0 = WASM_VECTOR_LEN;
|
|
@@ -2946,6 +2957,19 @@ function topologyOptimizeMesh(spec_json, positions, indices) {
|
|
|
2946
2957
|
}
|
|
2947
2958
|
return takeFromExternrefTable0(ret[0]);
|
|
2948
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
|
+
}
|
|
2949
2973
|
function writeEmbroideryDst(json) {
|
|
2950
2974
|
const ptr0 = passStringToWasm0(json, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
2951
2975
|
const len0 = WASM_VECTOR_LEN;
|
|
@@ -10895,753 +10919,57 @@ var init_atoms = __esm({
|
|
|
10895
10919
|
});
|
|
10896
10920
|
|
|
10897
10921
|
// ../engine/dist/enclosure-fit.js
|
|
10898
|
-
function
|
|
10899
|
-
|
|
10900
|
-
|
|
10901
|
-
|
|
10902
|
-
|
|
10903
|
-
|
|
10904
|
-
if (v.x < minX)
|
|
10905
|
-
minX = v.x;
|
|
10906
|
-
if (v.x > maxX)
|
|
10907
|
-
maxX = v.x;
|
|
10908
|
-
if (v.y < minY)
|
|
10909
|
-
minY = v.y;
|
|
10910
|
-
if (v.y > maxY)
|
|
10911
|
-
maxY = v.y;
|
|
10912
|
-
}
|
|
10913
|
-
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;
|
|
10914
10928
|
}
|
|
10915
|
-
function
|
|
10916
|
-
|
|
10917
|
-
const cos = Math.cos(t2);
|
|
10918
|
-
const sin = Math.sin(t2);
|
|
10919
|
-
return {
|
|
10920
|
-
x: placement.offset.x + p.x * cos - p.y * sin,
|
|
10921
|
-
y: placement.offset.y + p.x * sin + p.y * cos,
|
|
10922
|
-
z: placement.offset.z + (p.z ?? 0)
|
|
10923
|
-
};
|
|
10929
|
+
function compact(obj) {
|
|
10930
|
+
return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== void 0));
|
|
10924
10931
|
}
|
|
10925
|
-
function
|
|
10926
|
-
|
|
10927
|
-
["minX", Math.abs(x - b.minX)],
|
|
10928
|
-
["maxX", Math.abs(x - b.maxX)],
|
|
10929
|
-
["minY", Math.abs(y - b.minY)],
|
|
10930
|
-
["maxY", Math.abs(y - b.maxY)]
|
|
10931
|
-
];
|
|
10932
|
-
d.sort((a, c) => a[1] - c[1]);
|
|
10933
|
-
return d[0][0];
|
|
10932
|
+
function outlineAabb(outline) {
|
|
10933
|
+
return JSON.parse(enclosureWasm().enclosure_outline_aabb(JSON.stringify(outline)));
|
|
10934
10934
|
}
|
|
10935
|
-
function
|
|
10936
|
-
|
|
10937
|
-
const cos = Math.cos(t2);
|
|
10938
|
-
const sin = Math.sin(t2);
|
|
10939
|
-
return {
|
|
10940
|
-
x: fp.position.x + pad.position.x * cos - pad.position.y * sin,
|
|
10941
|
-
y: fp.position.y + pad.position.x * sin + pad.position.y * cos
|
|
10942
|
-
};
|
|
10935
|
+
function toWorld(p, placement) {
|
|
10936
|
+
return JSON.parse(enclosureWasm().enclosure_to_world(p.x, p.y, p.z ?? 0, JSON.stringify(placement)));
|
|
10943
10937
|
}
|
|
10944
10938
|
function mountingHolesFromPcb(pcb) {
|
|
10945
|
-
|
|
10946
|
-
for (const fp of pcb.footprints) {
|
|
10947
|
-
const isMount = MOUNT_RE.test(fp.footprintName) || MOUNT_RE.test(fp.ref);
|
|
10948
|
-
if (isMount) {
|
|
10949
|
-
const pad = fp.pads[0];
|
|
10950
|
-
let dia = 3.2;
|
|
10951
|
-
if (pad) {
|
|
10952
|
-
if (pad.drill && typeof pad.drill === "object") {
|
|
10953
|
-
const dd = pad.drill;
|
|
10954
|
-
if (typeof dd.diameter === "number")
|
|
10955
|
-
dia = dd.diameter;
|
|
10956
|
-
} else if (pad.shape.type === "Circle") {
|
|
10957
|
-
dia = pad.shape.diameter;
|
|
10958
|
-
}
|
|
10959
|
-
}
|
|
10960
|
-
holes.push({ x: round2(fp.position.x), y: round2(fp.position.y), diameter: round2(dia), ref: fp.ref });
|
|
10961
|
-
continue;
|
|
10962
|
-
}
|
|
10963
|
-
for (const pad of fp.pads) {
|
|
10964
|
-
if (pad.padType !== "NPTH")
|
|
10965
|
-
continue;
|
|
10966
|
-
const w = padWorld(fp, pad);
|
|
10967
|
-
let dia = 3.2;
|
|
10968
|
-
const dd = pad.drill;
|
|
10969
|
-
if (dd && typeof dd.diameter === "number")
|
|
10970
|
-
dia = dd.diameter;
|
|
10971
|
-
else if (pad.shape.type === "Circle")
|
|
10972
|
-
dia = pad.shape.diameter;
|
|
10973
|
-
holes.push({ x: round2(w.x), y: round2(w.y), diameter: round2(dia), ref: fp.ref });
|
|
10974
|
-
}
|
|
10975
|
-
}
|
|
10976
|
-
return holes;
|
|
10939
|
+
return JSON.parse(enclosureWasm().enclosure_mounting_holes(JSON.stringify(pcb)));
|
|
10977
10940
|
}
|
|
10978
10941
|
function componentExtentsFromMeshes(meshes, pcb) {
|
|
10979
|
-
const
|
|
10980
|
-
|
|
10981
|
-
|
|
10982
|
-
|
|
10983
|
-
|
|
10984
|
-
for (let i = 2; i < m.positions.length; i += 3) {
|
|
10985
|
-
const z = m.positions[i];
|
|
10986
|
-
if (z < minZ)
|
|
10987
|
-
minZ = z;
|
|
10988
|
-
if (z > maxZ)
|
|
10989
|
-
maxZ = z;
|
|
10990
|
-
}
|
|
10991
|
-
if (!Number.isFinite(minZ))
|
|
10992
|
-
continue;
|
|
10993
|
-
out.push({
|
|
10994
|
-
ref: m.footprint_ref,
|
|
10995
|
-
front: frontByRef.get(m.footprint_ref) ?? true,
|
|
10996
|
-
topZ: round2(maxZ),
|
|
10997
|
-
bottomZ: round2(minZ)
|
|
10998
|
-
});
|
|
10999
|
-
}
|
|
11000
|
-
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)));
|
|
11001
10947
|
}
|
|
11002
10948
|
function connectorsFromPcb(pcb, outline) {
|
|
11003
|
-
|
|
11004
|
-
const out = [];
|
|
11005
|
-
for (const fp of pcb.footprints) {
|
|
11006
|
-
const isConn = CONNECTOR_REF_RE.test(fp.ref) || CONNECTOR_NAME_RE.test(fp.footprintName) || CONNECTOR_NAME_RE.test(fp.value ?? "");
|
|
11007
|
-
if (!isConn)
|
|
11008
|
-
continue;
|
|
11009
|
-
out.push({
|
|
11010
|
-
ref: fp.ref,
|
|
11011
|
-
x: round2(fp.position.x),
|
|
11012
|
-
y: round2(fp.position.y),
|
|
11013
|
-
edge: nearestEdge(fp.position.x, fp.position.y, aabb),
|
|
11014
|
-
height: 0
|
|
11015
|
-
});
|
|
11016
|
-
}
|
|
11017
|
-
return out;
|
|
11018
|
-
}
|
|
11019
|
-
function autoPlacement(input, clearance) {
|
|
11020
|
-
const { cavity } = input;
|
|
11021
|
-
const a = outlineAabb(input.outline);
|
|
11022
|
-
const boardW = a.maxX - a.minX;
|
|
11023
|
-
const boardH = a.maxY - a.minY;
|
|
11024
|
-
const cavW = cavity.maxX - cavity.minX;
|
|
11025
|
-
const cavH = cavity.maxY - cavity.minY;
|
|
11026
|
-
const offX = cavity.minX + (cavW - boardW) / 2 - a.minX;
|
|
11027
|
-
const offY = cavity.minY + (cavH - boardH) / 2 - a.minY;
|
|
11028
|
-
const standoffTop = input.standoffs && input.standoffs.length > 0 ? Math.max(...input.standoffs.map((s) => s.topZ)) : cavity.floorZ + (input.standoffHeight ?? 0);
|
|
11029
|
-
void clearance;
|
|
11030
|
-
return { offset: { x: round2(offX), y: round2(offY), z: round2(standoffTop) }, rotationDeg: 0 };
|
|
11031
|
-
}
|
|
11032
|
-
function checkBoardFit(input, placement, clearance) {
|
|
11033
|
-
const { cavity } = input;
|
|
11034
|
-
let minX = Infinity;
|
|
11035
|
-
let maxX = -Infinity;
|
|
11036
|
-
let minY = Infinity;
|
|
11037
|
-
let maxY = -Infinity;
|
|
11038
|
-
for (const v of input.outline.vertices) {
|
|
11039
|
-
const w = toWorld(v, placement);
|
|
11040
|
-
if (w.x < minX)
|
|
11041
|
-
minX = w.x;
|
|
11042
|
-
if (w.x > maxX)
|
|
11043
|
-
maxX = w.x;
|
|
11044
|
-
if (w.y < minY)
|
|
11045
|
-
minY = w.y;
|
|
11046
|
-
if (w.y > maxY)
|
|
11047
|
-
maxY = w.y;
|
|
11048
|
-
}
|
|
11049
|
-
const marginMinX = minX - cavity.minX;
|
|
11050
|
-
const marginMaxX = cavity.maxX - maxX;
|
|
11051
|
-
const marginMinY = minY - cavity.minY;
|
|
11052
|
-
const marginMaxY = cavity.maxY - maxY;
|
|
11053
|
-
const worst2 = Math.min(marginMinX, marginMaxX, marginMinY, marginMaxY);
|
|
11054
|
-
const sides = [
|
|
11055
|
-
["-X", marginMinX],
|
|
11056
|
-
["+X", marginMaxX],
|
|
11057
|
-
["-Y", marginMinY],
|
|
11058
|
-
["+Y", marginMaxY]
|
|
11059
|
-
];
|
|
11060
|
-
const tight = sides.filter(([, m]) => m < clearance).map(([s]) => s);
|
|
11061
|
-
const ok4 = worst2 >= clearance - 1e-6;
|
|
11062
|
-
return {
|
|
11063
|
-
id: "board_fit",
|
|
11064
|
-
label: "Board fits cavity with clearance",
|
|
11065
|
-
status: ok4 ? "pass" : "fail",
|
|
11066
|
-
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`,
|
|
11067
|
-
measurements: {
|
|
11068
|
-
worst_clearance_mm: round2(worst2),
|
|
11069
|
-
margin_minus_x: round2(marginMinX),
|
|
11070
|
-
margin_plus_x: round2(marginMaxX),
|
|
11071
|
-
margin_minus_y: round2(marginMinY),
|
|
11072
|
-
margin_plus_y: round2(marginMaxY),
|
|
11073
|
-
board_w: round2(maxX - minX),
|
|
11074
|
-
board_h: round2(maxY - minY),
|
|
11075
|
-
cavity_w: round2(cavity.maxX - cavity.minX),
|
|
11076
|
-
cavity_h: round2(cavity.maxY - cavity.minY)
|
|
11077
|
-
}
|
|
11078
|
-
};
|
|
11079
|
-
}
|
|
11080
|
-
function checkLidClearance(input, placement, clearance) {
|
|
11081
|
-
const { cavity } = input;
|
|
11082
|
-
const extents = input.componentExtents ?? [];
|
|
11083
|
-
if (extents.length === 0) {
|
|
11084
|
-
return {
|
|
11085
|
-
id: "lid_clearance",
|
|
11086
|
-
label: "Components clear the lid",
|
|
11087
|
-
status: "skip",
|
|
11088
|
-
detail: "No component heights available (kernel component meshes unavailable)"
|
|
11089
|
-
};
|
|
11090
|
-
}
|
|
11091
|
-
const cavityDepth = cavity.ceilZ - cavity.floorZ;
|
|
11092
|
-
const front = extents.filter((e) => e.front);
|
|
11093
|
-
const back = extents.filter((e) => !e.front);
|
|
11094
|
-
let tallest = { ref: "", top: -Infinity };
|
|
11095
|
-
for (const e of front) {
|
|
11096
|
-
const top = placement.offset.z + e.topZ;
|
|
11097
|
-
if (top > tallest.top)
|
|
11098
|
-
tallest = { ref: e.ref, top };
|
|
11099
|
-
}
|
|
11100
|
-
const lidGap = cavity.ceilZ - tallest.top;
|
|
11101
|
-
const topOk = front.length === 0 || lidGap >= clearance - 1e-6;
|
|
11102
|
-
let lowest = { ref: "", bottom: Infinity };
|
|
11103
|
-
for (const e of back) {
|
|
11104
|
-
const bot = placement.offset.z + e.bottomZ;
|
|
11105
|
-
if (bot < lowest.bottom)
|
|
11106
|
-
lowest = { ref: e.ref, bottom: bot };
|
|
11107
|
-
}
|
|
11108
|
-
const floorGap = back.length > 0 ? lowest.bottom - cavity.floorZ : Infinity;
|
|
11109
|
-
const botOk = back.length === 0 || floorGap >= -1e-6;
|
|
11110
|
-
const ok4 = topOk && botOk;
|
|
11111
|
-
let detail;
|
|
11112
|
-
if (ok4) {
|
|
11113
|
-
detail = `Tallest part ${tallest.ref || "\u2014"} leaves ${round2(lidGap)}mm under the lid (cavity depth ${round2(cavityDepth)}mm)`;
|
|
11114
|
-
if (back.length > 0 && Number.isFinite(floorGap)) {
|
|
11115
|
-
detail += `; back-side ${lowest.ref} clears floor by ${round2(floorGap)}mm`;
|
|
11116
|
-
}
|
|
11117
|
-
} else if (!topOk) {
|
|
11118
|
-
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)`;
|
|
11119
|
-
} else {
|
|
11120
|
-
detail = `Back-side ${lowest.ref} collides with the floor by ${round2(-floorGap)}mm \u2014 raise the standoffs`;
|
|
11121
|
-
}
|
|
11122
|
-
return {
|
|
11123
|
-
id: "lid_clearance",
|
|
11124
|
-
label: "Components clear the lid",
|
|
11125
|
-
status: ok4 ? "pass" : "fail",
|
|
11126
|
-
detail,
|
|
11127
|
-
measurements: {
|
|
11128
|
-
cavity_depth_mm: round2(cavityDepth),
|
|
11129
|
-
tallest_ref: tallest.ref || "none",
|
|
11130
|
-
lid_gap_mm: Number.isFinite(lidGap) ? round2(lidGap) : "n/a",
|
|
11131
|
-
stack_top_z: Number.isFinite(tallest.top) ? round2(tallest.top) : "n/a",
|
|
11132
|
-
floor_gap_mm: Number.isFinite(floorGap) ? round2(floorGap) : "n/a"
|
|
11133
|
-
}
|
|
11134
|
-
};
|
|
11135
|
-
}
|
|
11136
|
-
function checkMountingHoles(input, placement) {
|
|
11137
|
-
const holes = input.mountingHoles ?? [];
|
|
11138
|
-
const standoffs = input.standoffs ?? [];
|
|
11139
|
-
const tol = input.holeTolerance ?? DEFAULT_HOLE_TOL;
|
|
11140
|
-
if (holes.length === 0) {
|
|
11141
|
-
return {
|
|
11142
|
-
id: "mounting_holes",
|
|
11143
|
-
label: "Mounting holes land on standoffs",
|
|
11144
|
-
status: "skip",
|
|
11145
|
-
detail: "Board declares no mounting holes"
|
|
11146
|
-
};
|
|
11147
|
-
}
|
|
11148
|
-
if (standoffs.length === 0) {
|
|
11149
|
-
return {
|
|
11150
|
-
id: "mounting_holes",
|
|
11151
|
-
label: "Mounting holes land on standoffs",
|
|
11152
|
-
status: "skip",
|
|
11153
|
-
detail: `Board has ${holes.length} mounting hole(s) but no standoffs were detected in the enclosure`
|
|
11154
|
-
};
|
|
11155
|
-
}
|
|
11156
|
-
let matched = 0;
|
|
11157
|
-
let worst2 = 0;
|
|
11158
|
-
const misses = [];
|
|
11159
|
-
for (const h of holes) {
|
|
11160
|
-
const w = toWorld(h, placement);
|
|
11161
|
-
let best = Infinity;
|
|
11162
|
-
for (const s of standoffs) {
|
|
11163
|
-
const d = Math.hypot(w.x - s.x, w.y - s.y);
|
|
11164
|
-
if (d < best)
|
|
11165
|
-
best = d;
|
|
11166
|
-
}
|
|
11167
|
-
if (best <= tol) {
|
|
11168
|
-
matched++;
|
|
11169
|
-
if (best > worst2)
|
|
11170
|
-
worst2 = best;
|
|
11171
|
-
} else {
|
|
11172
|
-
misses.push(`${h.ref ?? "hole"}@(${round2(w.x)},${round2(w.y)}) is ${round2(best)}mm off`);
|
|
11173
|
-
}
|
|
11174
|
-
}
|
|
11175
|
-
const ok4 = matched === holes.length;
|
|
11176
|
-
return {
|
|
11177
|
-
id: "mounting_holes",
|
|
11178
|
-
label: "Mounting holes land on standoffs",
|
|
11179
|
-
status: ok4 ? "pass" : "fail",
|
|
11180
|
-
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("; ")}`,
|
|
11181
|
-
measurements: {
|
|
11182
|
-
holes_total: holes.length,
|
|
11183
|
-
holes_matched: matched,
|
|
11184
|
-
standoffs: standoffs.length,
|
|
11185
|
-
tolerance_mm: tol,
|
|
11186
|
-
worst_offset_mm: round2(worst2)
|
|
11187
|
-
}
|
|
11188
|
-
};
|
|
11189
|
-
}
|
|
11190
|
-
function checkConnectors(input, placement, clearance) {
|
|
11191
|
-
const conns = input.connectors ?? [];
|
|
11192
|
-
const openings = input.openings ?? [];
|
|
11193
|
-
if (conns.length === 0) {
|
|
11194
|
-
return {
|
|
11195
|
-
id: "connector_cutouts",
|
|
11196
|
-
label: "Connectors align to wall cutouts",
|
|
11197
|
-
status: "skip",
|
|
11198
|
-
detail: "Board declares no edge connectors"
|
|
11199
|
-
};
|
|
11200
|
-
}
|
|
11201
|
-
const cav = input.cavity;
|
|
11202
|
-
let aligned = 0;
|
|
11203
|
-
const problems = [];
|
|
11204
|
-
for (const c of conns) {
|
|
11205
|
-
const w = toWorld(c, placement);
|
|
11206
|
-
const wallEdge = nearestEdge(w.x, w.y, cav);
|
|
11207
|
-
const along = wallEdge === "minX" || wallEdge === "maxX" ? w.y : w.x;
|
|
11208
|
-
const onWall = openings.filter((o) => o.edge === wallEdge);
|
|
11209
|
-
if (onWall.length === 0) {
|
|
11210
|
-
problems.push(`${c.ref} faces the ${wallEdge} wall but it has no cutout`);
|
|
11211
|
-
continue;
|
|
11212
|
-
}
|
|
11213
|
-
const hit = onWall.find((o) => {
|
|
11214
|
-
const oc = o.edge === "minX" || o.edge === "maxX" ? o.center.y : o.center.x;
|
|
11215
|
-
return Math.abs(along - oc) <= o.width / 2 + clearance;
|
|
11216
|
-
});
|
|
11217
|
-
if (hit) {
|
|
11218
|
-
aligned++;
|
|
11219
|
-
} else {
|
|
11220
|
-
const nearest = onWall.reduce((best, o) => {
|
|
11221
|
-
const oc = o.edge === "minX" || o.edge === "maxX" ? o.center.y : o.center.x;
|
|
11222
|
-
const off = Math.abs(along - oc) - o.width / 2;
|
|
11223
|
-
return off < best ? off : best;
|
|
11224
|
-
}, Infinity);
|
|
11225
|
-
problems.push(`${c.ref} on ${wallEdge} misses its cutout by ${round2(nearest)}mm`);
|
|
11226
|
-
}
|
|
11227
|
-
}
|
|
11228
|
-
const ok4 = aligned === conns.length;
|
|
11229
|
-
const status = ok4 ? "pass" : openings.length === 0 ? "warn" : "fail";
|
|
11230
|
-
return {
|
|
11231
|
-
id: "connector_cutouts",
|
|
11232
|
-
label: "Connectors align to wall cutouts",
|
|
11233
|
-
status,
|
|
11234
|
-
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("; ")}`,
|
|
11235
|
-
measurements: {
|
|
11236
|
-
connectors_total: conns.length,
|
|
11237
|
-
connectors_aligned: aligned,
|
|
11238
|
-
wall_openings: openings.length
|
|
11239
|
-
}
|
|
11240
|
-
};
|
|
10949
|
+
return JSON.parse(enclosureWasm().enclosure_connectors(JSON.stringify(pcb), JSON.stringify(outline)));
|
|
11241
10950
|
}
|
|
11242
10951
|
function checkEnclosureFit(input) {
|
|
11243
|
-
|
|
11244
|
-
const placement = input.placement ?? autoPlacement(input, clearance);
|
|
11245
|
-
const checks = [
|
|
11246
|
-
checkBoardFit(input, placement, clearance),
|
|
11247
|
-
checkLidClearance(input, placement, clearance),
|
|
11248
|
-
checkMountingHoles(input, placement),
|
|
11249
|
-
checkConnectors(input, placement, clearance)
|
|
11250
|
-
];
|
|
11251
|
-
const failed = checks.filter((c) => c.status === "fail");
|
|
11252
|
-
const warned = checks.filter((c) => c.status === "warn");
|
|
11253
|
-
const passed = checks.filter((c) => c.status === "pass");
|
|
11254
|
-
const ok4 = failed.length === 0;
|
|
11255
|
-
const verified = ok4 && warned.length === 0;
|
|
11256
|
-
let summary;
|
|
11257
|
-
if (failed.length > 0) {
|
|
11258
|
-
summary = `Enclosure fit: FAIL \u2014 ${failed.map((c) => c.label.toLowerCase()).join("; ")}`;
|
|
11259
|
-
} else if (warned.length > 0) {
|
|
11260
|
-
summary = `Enclosure fit: UNVERIFIED \u2014 ${passed.length} passed, ${warned.length} warning(s): ${warned.map((c) => c.detail).join("; ")}`;
|
|
11261
|
-
} else {
|
|
11262
|
-
summary = `Enclosure fit: PASS \u2014 ${passed.length}/${checks.length} checks (${checks.filter((c) => c.status === "pass").map((c) => c.label.toLowerCase()).join(", ")})`;
|
|
11263
|
-
}
|
|
11264
|
-
return { ok: ok4, verified, summary, clearance, placement, checks };
|
|
10952
|
+
return JSON.parse(enclosureWasm().enclosure_fit(JSON.stringify(compact(input))));
|
|
11265
10953
|
}
|
|
11266
10954
|
function deriveBoardFromCavity(cavity, standoffs, opts = {}) {
|
|
11267
|
-
|
|
11268
|
-
const thickness = opts.thickness ?? 1.6;
|
|
11269
|
-
const holeDia = opts.holeDiameter ?? 3.2;
|
|
11270
|
-
const w = round2(cavity.maxX - cavity.minX - 2 * clearance);
|
|
11271
|
-
const h = round2(cavity.maxY - cavity.minY - 2 * clearance);
|
|
11272
|
-
const outline = {
|
|
11273
|
-
vertices: [
|
|
11274
|
-
{ x: 0, y: 0 },
|
|
11275
|
-
{ x: w, y: 0 },
|
|
11276
|
-
{ x: w, y: h },
|
|
11277
|
-
{ x: 0, y: h }
|
|
11278
|
-
],
|
|
11279
|
-
thickness
|
|
11280
|
-
};
|
|
11281
|
-
const standoffTop = standoffs.length > 0 ? Math.max(...standoffs.map((s) => s.topZ)) : cavity.floorZ + (opts.standoffHeight ?? 0);
|
|
11282
|
-
const offX = cavity.minX + clearance;
|
|
11283
|
-
const offY = cavity.minY + clearance;
|
|
11284
|
-
const placement = {
|
|
11285
|
-
offset: { x: round2(offX), y: round2(offY), z: round2(standoffTop) },
|
|
11286
|
-
rotationDeg: 0
|
|
11287
|
-
};
|
|
11288
|
-
const mountingHoles = [];
|
|
11289
|
-
for (const s of standoffs) {
|
|
11290
|
-
const lx = round2(s.x - offX);
|
|
11291
|
-
const ly = round2(s.y - offY);
|
|
11292
|
-
if (lx >= 0 && lx <= w && ly >= 0 && ly <= h) {
|
|
11293
|
-
mountingHoles.push({ x: lx, y: ly, diameter: holeDia });
|
|
11294
|
-
}
|
|
11295
|
-
}
|
|
11296
|
-
return { outline, mountingHoles, placement };
|
|
10955
|
+
return JSON.parse(enclosureWasm().enclosure_derive_board(JSON.stringify(cavity), JSON.stringify(standoffs), JSON.stringify(compact(opts))));
|
|
11297
10956
|
}
|
|
11298
|
-
var DEFAULT_CLEARANCE, DEFAULT_HOLE_TOL, round2, MOUNT_RE, CONNECTOR_REF_RE, CONNECTOR_NAME_RE;
|
|
11299
10957
|
var init_enclosure_fit = __esm({
|
|
11300
10958
|
"../engine/dist/enclosure-fit.js"() {
|
|
11301
10959
|
"use strict";
|
|
11302
|
-
|
|
11303
|
-
DEFAULT_HOLE_TOL = 0.6;
|
|
11304
|
-
round2 = (n) => Math.round(n * 100) / 100;
|
|
11305
|
-
MOUNT_RE = /mount(ing)?[_-]?hole|mountingpad|mounthole/i;
|
|
11306
|
-
CONNECTOR_REF_RE = /^(J|CN|CON|USB|P)\d/i;
|
|
11307
|
-
CONNECTOR_NAME_RE = /usb|type[-_]?c|micro|mini|conn|header|jst|molex|rj45|hdr|terminal|socket|receptacle|barrel|dcjack/i;
|
|
10960
|
+
init_wasm_singleton();
|
|
11308
10961
|
}
|
|
11309
10962
|
});
|
|
11310
10963
|
|
|
11311
10964
|
// ../engine/dist/enclosure-mesh.js
|
|
11312
|
-
function gwn(positions, indices, qx, qy, qz) {
|
|
11313
|
-
let w = 0;
|
|
11314
|
-
const tris = indices.length;
|
|
11315
|
-
for (let t2 = 0; t2 < tris; t2 += 3) {
|
|
11316
|
-
const i0 = indices[t2] * 3;
|
|
11317
|
-
const i1 = indices[t2 + 1] * 3;
|
|
11318
|
-
const i2 = indices[t2 + 2] * 3;
|
|
11319
|
-
const ax = positions[i0] - qx;
|
|
11320
|
-
const ay = positions[i0 + 1] - qy;
|
|
11321
|
-
const az = positions[i0 + 2] - qz;
|
|
11322
|
-
const bx = positions[i1] - qx;
|
|
11323
|
-
const by = positions[i1 + 1] - qy;
|
|
11324
|
-
const bz = positions[i1 + 2] - qz;
|
|
11325
|
-
const cx = positions[i2] - qx;
|
|
11326
|
-
const cy = positions[i2 + 1] - qy;
|
|
11327
|
-
const cz = positions[i2 + 2] - qz;
|
|
11328
|
-
const la = Math.sqrt(ax * ax + ay * ay + az * az);
|
|
11329
|
-
const lb = Math.sqrt(bx * bx + by * by + bz * bz);
|
|
11330
|
-
const lc = Math.sqrt(cx * cx + cy * cy + cz * cz);
|
|
11331
|
-
const cbx = by * cz - bz * cy;
|
|
11332
|
-
const cby = bz * cx - bx * cz;
|
|
11333
|
-
const cbz = bx * cy - by * cx;
|
|
11334
|
-
const num2 = ax * cbx + ay * cby + az * cbz;
|
|
11335
|
-
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;
|
|
11336
|
-
w += Math.atan2(num2, den);
|
|
11337
|
-
}
|
|
11338
|
-
return w / (2 * Math.PI);
|
|
11339
|
-
}
|
|
11340
|
-
function buildOccupancy(positions, indices) {
|
|
11341
|
-
let minX = Infinity;
|
|
11342
|
-
let maxX = -Infinity;
|
|
11343
|
-
let minY = Infinity;
|
|
11344
|
-
let maxY = -Infinity;
|
|
11345
|
-
let minZ = Infinity;
|
|
11346
|
-
let maxZ = -Infinity;
|
|
11347
|
-
for (let i = 0; i < positions.length; i += 3) {
|
|
11348
|
-
const x = positions[i];
|
|
11349
|
-
const y = positions[i + 1];
|
|
11350
|
-
const z = positions[i + 2];
|
|
11351
|
-
if (x < minX)
|
|
11352
|
-
minX = x;
|
|
11353
|
-
if (x > maxX)
|
|
11354
|
-
maxX = x;
|
|
11355
|
-
if (y < minY)
|
|
11356
|
-
minY = y;
|
|
11357
|
-
if (y > maxY)
|
|
11358
|
-
maxY = y;
|
|
11359
|
-
if (z < minZ)
|
|
11360
|
-
minZ = z;
|
|
11361
|
-
if (z > maxZ)
|
|
11362
|
-
maxZ = z;
|
|
11363
|
-
}
|
|
11364
|
-
if (!(maxX > minX) || !(maxY > minY) || !(maxZ > minZ))
|
|
11365
|
-
return null;
|
|
11366
|
-
const gw = GRID_XY;
|
|
11367
|
-
const gh = GRID_XY;
|
|
11368
|
-
const gz = GRID_Z;
|
|
11369
|
-
const dx = (maxX - minX) / gw;
|
|
11370
|
-
const dy = (maxY - minY) / gh;
|
|
11371
|
-
const dz = (maxZ - minZ) / gz;
|
|
11372
|
-
const occ = new Uint8Array(gw * gh * gz);
|
|
11373
|
-
for (let k = 0; k < gz; k++) {
|
|
11374
|
-
const qz = minZ + (k + JZ) * dz;
|
|
11375
|
-
for (let j = 0; j < gh; j++) {
|
|
11376
|
-
const qy = minY + (j + JY) * dy;
|
|
11377
|
-
for (let i = 0; i < gw; i++) {
|
|
11378
|
-
const qx = minX + (i + JX) * dx;
|
|
11379
|
-
if (Math.abs(gwn(positions, indices, qx, qy, qz)) > INSIDE) {
|
|
11380
|
-
occ[i + gw * (j + gh * k)] = 1;
|
|
11381
|
-
}
|
|
11382
|
-
}
|
|
11383
|
-
}
|
|
11384
|
-
}
|
|
11385
|
-
return { gw, gh, gz, ox: minX, oy: minY, oz: minZ, dx, dy, dz, minZ, maxZ, occ };
|
|
11386
|
-
}
|
|
11387
|
-
function bottomRunTopZ(o, i, j) {
|
|
11388
|
-
const base = i + o.gw * j;
|
|
11389
|
-
if (o.occ[base] !== 1)
|
|
11390
|
-
return null;
|
|
11391
|
-
let k = 1;
|
|
11392
|
-
while (k < o.gz && o.occ[base + o.gw * o.gh * k] === 1)
|
|
11393
|
-
k++;
|
|
11394
|
-
return o.oz + k * o.dz;
|
|
11395
|
-
}
|
|
11396
|
-
function openAtTop(o, i, j) {
|
|
11397
|
-
return o.occ[i + o.gw * (j + o.gh * (o.gz - 1))] !== 1;
|
|
11398
|
-
}
|
|
11399
|
-
function hasGap(o, i, j, kLo, kHi) {
|
|
11400
|
-
for (let k = kLo; k <= kHi; k++) {
|
|
11401
|
-
if (o.occ[i + o.gw * (j + o.gh * k)] !== 1)
|
|
11402
|
-
return true;
|
|
11403
|
-
}
|
|
11404
|
-
return false;
|
|
11405
|
-
}
|
|
11406
10965
|
function extractEnclosureFeatures(positions, indices) {
|
|
11407
|
-
const
|
|
11408
|
-
|
|
11409
|
-
return {
|
|
11410
|
-
outer: { minX: 0, maxX: 0, minY: 0, maxY: 0, minZ: 0, maxZ: 0 },
|
|
11411
|
-
cavity: null,
|
|
11412
|
-
standoffs: [],
|
|
11413
|
-
openings: []
|
|
11414
|
-
};
|
|
11415
|
-
}
|
|
11416
|
-
const outer = {
|
|
11417
|
-
minX: o.ox,
|
|
11418
|
-
maxX: o.ox + o.gw * o.dx,
|
|
11419
|
-
minY: o.oy,
|
|
11420
|
-
maxY: o.oy + o.gh * o.dy,
|
|
11421
|
-
minZ: o.minZ,
|
|
11422
|
-
maxZ: o.maxZ
|
|
11423
|
-
};
|
|
11424
|
-
const isPocket = new Uint8Array(o.gw * o.gh);
|
|
11425
|
-
const firstTops = [];
|
|
11426
|
-
const countX = new Int32Array(o.gw);
|
|
11427
|
-
const countY = new Int32Array(o.gh);
|
|
11428
|
-
let pocketCount = 0;
|
|
11429
|
-
for (let j = 0; j < o.gh; j++) {
|
|
11430
|
-
for (let i = 0; i < o.gw; i++) {
|
|
11431
|
-
const top = bottomRunTopZ(o, i, j);
|
|
11432
|
-
if (top != null && openAtTop(o, i, j)) {
|
|
11433
|
-
isPocket[i + o.gw * j] = 1;
|
|
11434
|
-
pocketCount++;
|
|
11435
|
-
firstTops.push(top);
|
|
11436
|
-
countX[i]++;
|
|
11437
|
-
countY[j]++;
|
|
11438
|
-
}
|
|
11439
|
-
}
|
|
11440
|
-
}
|
|
11441
|
-
if (pocketCount === 0) {
|
|
11442
|
-
return { outer, cavity: null, standoffs: [], openings: [] };
|
|
11443
|
-
}
|
|
11444
|
-
const core = (counts) => {
|
|
11445
|
-
let peak = 0;
|
|
11446
|
-
for (const c of counts)
|
|
11447
|
-
if (c > peak)
|
|
11448
|
-
peak = c;
|
|
11449
|
-
const thr = peak * 0.5;
|
|
11450
|
-
let lo = 0;
|
|
11451
|
-
let hi = counts.length - 1;
|
|
11452
|
-
while (lo < counts.length && counts[lo] < thr)
|
|
11453
|
-
lo++;
|
|
11454
|
-
while (hi >= 0 && counts[hi] < thr)
|
|
11455
|
-
hi--;
|
|
11456
|
-
return [lo, hi];
|
|
11457
|
-
};
|
|
11458
|
-
const [pminI, pmaxI] = core(countX);
|
|
11459
|
-
const [pminJ, pmaxJ] = core(countY);
|
|
11460
|
-
const sortedTops = [...firstTops].sort((a, b) => a - b);
|
|
11461
|
-
const floorZ = sortedTops[Math.floor(sortedTops.length / 2)] ?? o.minZ;
|
|
11462
|
-
const ceilZ = o.maxZ;
|
|
11463
|
-
const cavity = {
|
|
11464
|
-
minX: o.ox + pminI * o.dx,
|
|
11465
|
-
maxX: o.ox + (pmaxI + 1) * o.dx,
|
|
11466
|
-
minY: o.oy + pminJ * o.dy,
|
|
11467
|
-
maxY: o.oy + (pmaxJ + 1) * o.dy,
|
|
11468
|
-
floorZ: round22(floorZ),
|
|
11469
|
-
ceilZ: round22(ceilZ),
|
|
11470
|
-
hasLid: false
|
|
11471
|
-
};
|
|
11472
|
-
const standoffs = extractStandoffs(o, isPocket, floorZ);
|
|
11473
|
-
const openings = extractOpenings(o, cavity, floorZ, ceilZ);
|
|
11474
|
-
return { outer, cavity, standoffs, openings };
|
|
11475
|
-
}
|
|
11476
|
-
function extractStandoffs(o, isPocket, floorZ) {
|
|
11477
|
-
const isPost = new Uint8Array(o.gw * o.gh);
|
|
11478
|
-
for (let j = 0; j < o.gh; j++) {
|
|
11479
|
-
for (let i = 0; i < o.gw; i++) {
|
|
11480
|
-
if (!isPocket[i + o.gw * j])
|
|
11481
|
-
continue;
|
|
11482
|
-
const top = bottomRunTopZ(o, i, j);
|
|
11483
|
-
if (top != null && top > floorZ + MIN_POST_HEIGHT)
|
|
11484
|
-
isPost[i + o.gw * j] = 1;
|
|
11485
|
-
}
|
|
11486
|
-
}
|
|
11487
|
-
const seen = new Uint8Array(o.gw * o.gh);
|
|
11488
|
-
const standoffs = [];
|
|
11489
|
-
for (let j = 0; j < o.gh; j++) {
|
|
11490
|
-
for (let i = 0; i < o.gw; i++) {
|
|
11491
|
-
const k0 = i + o.gw * j;
|
|
11492
|
-
if (!isPost[k0] || seen[k0])
|
|
11493
|
-
continue;
|
|
11494
|
-
const stack = [[i, j]];
|
|
11495
|
-
seen[k0] = 1;
|
|
11496
|
-
let sumX = 0;
|
|
11497
|
-
let sumY = 0;
|
|
11498
|
-
let n = 0;
|
|
11499
|
-
let topMax = -Infinity;
|
|
11500
|
-
let minI = i;
|
|
11501
|
-
let maxI = i;
|
|
11502
|
-
let minJ = j;
|
|
11503
|
-
let maxJ = j;
|
|
11504
|
-
while (stack.length) {
|
|
11505
|
-
const [ci, cj] = stack.pop();
|
|
11506
|
-
sumX += o.ox + (ci + 0.5) * o.dx;
|
|
11507
|
-
sumY += o.oy + (cj + 0.5) * o.dy;
|
|
11508
|
-
n++;
|
|
11509
|
-
const t2 = bottomRunTopZ(o, ci, cj);
|
|
11510
|
-
if (t2 != null && t2 > topMax)
|
|
11511
|
-
topMax = t2;
|
|
11512
|
-
if (ci < minI)
|
|
11513
|
-
minI = ci;
|
|
11514
|
-
if (ci > maxI)
|
|
11515
|
-
maxI = ci;
|
|
11516
|
-
if (cj < minJ)
|
|
11517
|
-
minJ = cj;
|
|
11518
|
-
if (cj > maxJ)
|
|
11519
|
-
maxJ = cj;
|
|
11520
|
-
for (const [ni, nj] of [
|
|
11521
|
-
[ci - 1, cj],
|
|
11522
|
-
[ci + 1, cj],
|
|
11523
|
-
[ci, cj - 1],
|
|
11524
|
-
[ci, cj + 1]
|
|
11525
|
-
]) {
|
|
11526
|
-
if (ni < 0 || nj < 0 || ni >= o.gw || nj >= o.gh)
|
|
11527
|
-
continue;
|
|
11528
|
-
const nk = ni + o.gw * nj;
|
|
11529
|
-
if (isPost[nk] && !seen[nk]) {
|
|
11530
|
-
seen[nk] = 1;
|
|
11531
|
-
stack.push([ni, nj]);
|
|
11532
|
-
}
|
|
11533
|
-
}
|
|
11534
|
-
}
|
|
11535
|
-
if (n < 2)
|
|
11536
|
-
continue;
|
|
11537
|
-
const radius = Math.max(((maxI - minI + 1) * o.dx + (maxJ - minJ + 1) * o.dy) / 4, o.dx);
|
|
11538
|
-
standoffs.push({
|
|
11539
|
-
x: round22(sumX / n),
|
|
11540
|
-
y: round22(sumY / n),
|
|
11541
|
-
topZ: round22(topMax),
|
|
11542
|
-
radius: round22(radius)
|
|
11543
|
-
});
|
|
11544
|
-
}
|
|
11545
|
-
}
|
|
11546
|
-
return standoffs;
|
|
11547
|
-
}
|
|
11548
|
-
function extractOpenings(o, cavity, floorZ, ceilZ) {
|
|
11549
|
-
const pIminI = Math.round((cavity.minX - o.ox) / o.dx);
|
|
11550
|
-
const pImaxI = Math.round((cavity.maxX - o.ox) / o.dx) - 1;
|
|
11551
|
-
const pIminJ = Math.round((cavity.minY - o.oy) / o.dy);
|
|
11552
|
-
const pImaxJ = Math.round((cavity.maxY - o.oy) / o.dy) - 1;
|
|
11553
|
-
const kLo = Math.max(0, Math.floor((floorZ - o.oz) / o.dz) + 1);
|
|
11554
|
-
const kHi = Math.min(o.gz - 1, Math.ceil((ceilZ - o.oz) / o.dz) - 1);
|
|
11555
|
-
const openings = [];
|
|
11556
|
-
const wallOpen = (i, j) => {
|
|
11557
|
-
if (i < 0 || j < 0 || i >= o.gw || j >= o.gh)
|
|
11558
|
-
return true;
|
|
11559
|
-
return hasGap(o, i, j, kLo, kHi);
|
|
11560
|
-
};
|
|
11561
|
-
const scans = [];
|
|
11562
|
-
for (const [edge, wi] of [
|
|
11563
|
-
["minX", pIminI - 1],
|
|
11564
|
-
["maxX", pImaxI + 1]
|
|
11565
|
-
]) {
|
|
11566
|
-
const cells = [];
|
|
11567
|
-
for (let j = pIminJ; j <= pImaxJ; j++) {
|
|
11568
|
-
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 });
|
|
11569
|
-
}
|
|
11570
|
-
scans.push({ edge, cells });
|
|
11571
|
-
}
|
|
11572
|
-
for (const [edge, wj] of [
|
|
11573
|
-
["minY", pIminJ - 1],
|
|
11574
|
-
["maxY", pImaxJ + 1]
|
|
11575
|
-
]) {
|
|
11576
|
-
const cells = [];
|
|
11577
|
-
for (let i = pIminI; i <= pImaxI; i++) {
|
|
11578
|
-
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 });
|
|
11579
|
-
}
|
|
11580
|
-
scans.push({ edge, cells });
|
|
11581
|
-
}
|
|
11582
|
-
for (const scan of scans) {
|
|
11583
|
-
let run = [];
|
|
11584
|
-
const flush = () => {
|
|
11585
|
-
if (run.length >= MIN_OPENING_CELLS) {
|
|
11586
|
-
const xs = run.map((p) => p.x);
|
|
11587
|
-
const ys = run.map((p) => p.y);
|
|
11588
|
-
const horiz = scan.edge === "minY" || scan.edge === "maxY";
|
|
11589
|
-
const center = {
|
|
11590
|
-
x: (Math.min(...xs) + Math.max(...xs)) / 2,
|
|
11591
|
-
y: (Math.min(...ys) + Math.max(...ys)) / 2
|
|
11592
|
-
};
|
|
11593
|
-
const width = horiz ? Math.max(...xs) - Math.min(...xs) + o.dx : Math.max(...ys) - Math.min(...ys) + o.dy;
|
|
11594
|
-
const mid = run[Math.floor(run.length / 2)];
|
|
11595
|
-
const { zMin, zMax } = openingZSpan(o, mid.i, mid.j, kLo, kHi);
|
|
11596
|
-
openings.push({
|
|
11597
|
-
edge: scan.edge,
|
|
11598
|
-
center: { x: round22(center.x), y: round22(center.y) },
|
|
11599
|
-
width: round22(width),
|
|
11600
|
-
zMin: round22(zMin),
|
|
11601
|
-
zMax: round22(zMax)
|
|
11602
|
-
});
|
|
11603
|
-
}
|
|
11604
|
-
run = [];
|
|
11605
|
-
};
|
|
11606
|
-
for (const c of scan.cells) {
|
|
11607
|
-
if (c.open)
|
|
11608
|
-
run.push({ x: c.x, y: c.y, i: c.i, j: c.j });
|
|
11609
|
-
else
|
|
11610
|
-
flush();
|
|
11611
|
-
}
|
|
11612
|
-
flush();
|
|
11613
|
-
}
|
|
11614
|
-
return openings;
|
|
11615
|
-
}
|
|
11616
|
-
function openingZSpan(o, i, j, kLo, kHi) {
|
|
11617
|
-
let zMin = Infinity;
|
|
11618
|
-
let zMax = -Infinity;
|
|
11619
|
-
for (let k = kLo; k <= kHi; k++) {
|
|
11620
|
-
if (o.occ[i + o.gw * (j + o.gh * k)] !== 1) {
|
|
11621
|
-
const z = o.oz + (k + 0.5) * o.dz;
|
|
11622
|
-
if (z < zMin)
|
|
11623
|
-
zMin = z;
|
|
11624
|
-
if (z > zMax)
|
|
11625
|
-
zMax = z;
|
|
11626
|
-
}
|
|
11627
|
-
}
|
|
11628
|
-
if (!Number.isFinite(zMin))
|
|
11629
|
-
return { zMin: o.oz, zMax: o.maxZ };
|
|
11630
|
-
return { zMin, zMax };
|
|
10966
|
+
const json = enclosureWasm().enclosure_features(Float64Array.from(positions), Uint32Array.from(indices));
|
|
10967
|
+
return JSON.parse(json);
|
|
11631
10968
|
}
|
|
11632
|
-
var GRID_XY, GRID_Z, INSIDE, MIN_POST_HEIGHT, MIN_OPENING_CELLS, JX, JY, JZ, round22;
|
|
11633
10969
|
var init_enclosure_mesh = __esm({
|
|
11634
10970
|
"../engine/dist/enclosure-mesh.js"() {
|
|
11635
10971
|
"use strict";
|
|
11636
|
-
|
|
11637
|
-
GRID_Z = 28;
|
|
11638
|
-
INSIDE = 0.5;
|
|
11639
|
-
MIN_POST_HEIGHT = 0.8;
|
|
11640
|
-
MIN_OPENING_CELLS = 2;
|
|
11641
|
-
JX = 0.5 + 0.137;
|
|
11642
|
-
JY = 0.5 - 0.077;
|
|
11643
|
-
JZ = 0.5 + 0.041;
|
|
11644
|
-
round22 = (n) => Math.round(n * 100) / 100;
|
|
10972
|
+
init_enclosure_fit();
|
|
11645
10973
|
}
|
|
11646
10974
|
});
|
|
11647
10975
|
|
|
@@ -32264,13 +31592,13 @@ var init_document_store = __esm({
|
|
|
32264
31592
|
const ang = (fp.rotation ?? 0) * Math.PI / 180;
|
|
32265
31593
|
const cos = Math.cos(ang);
|
|
32266
31594
|
const sin = Math.sin(ang);
|
|
32267
|
-
const
|
|
31595
|
+
const padWorld2 = fp.pads.map((p) => ({
|
|
32268
31596
|
x: fp.position.x + (p.position.x * cos - p.position.y * sin),
|
|
32269
31597
|
y: fp.position.y + (p.position.x * sin + p.position.y * cos)
|
|
32270
31598
|
}));
|
|
32271
31599
|
for (const tr of pcb.traces) {
|
|
32272
31600
|
for (const end of [tr.start, tr.end]) {
|
|
32273
|
-
if (
|
|
31601
|
+
if (padWorld2.some((pw) => Math.abs(end.x - pw.x) < TOL && Math.abs(end.y - pw.y) < TOL)) {
|
|
32274
31602
|
end.x += dx;
|
|
32275
31603
|
end.y += dy;
|
|
32276
31604
|
}
|
|
@@ -58340,7 +57668,7 @@ function computeUtilization(footprints, vertices, cutouts, shape, innerRadius, e
|
|
|
58340
57668
|
}
|
|
58341
57669
|
const boardArea = Math.abs(loopSignedArea(vertices)) - (cutouts ?? []).reduce((s, c) => s + Math.abs(loopSignedArea(c)), 0);
|
|
58342
57670
|
if (!Number.isFinite(occMinX) || boardArea <= 0) return void 0;
|
|
58343
|
-
const
|
|
57671
|
+
const round23 = (v) => Math.round(v * 100) / 100;
|
|
58344
57672
|
const ceilHalf = (v) => Math.ceil(v * 2) / 2;
|
|
58345
57673
|
const bbW = occMaxX - occMinX;
|
|
58346
57674
|
const bbH = occMaxY - occMinY;
|
|
@@ -58353,8 +57681,8 @@ function computeUtilization(footprints, vertices, cutouts, shape, innerRadius, e
|
|
|
58353
57681
|
suggested = {
|
|
58354
57682
|
type: "circle",
|
|
58355
57683
|
outer_diameter: od,
|
|
58356
|
-
center: { x:
|
|
58357
|
-
...innerRadius > 0 ? { inner_diameter:
|
|
57684
|
+
center: { x: round23(cx), y: round23(cy) },
|
|
57685
|
+
...innerRadius > 0 ? { inner_diameter: round23(2 * innerRadius) } : {},
|
|
58358
57686
|
note: `Minimum enclosing circle with ${margin}mm edge clearance`
|
|
58359
57687
|
};
|
|
58360
57688
|
} else {
|
|
@@ -58362,19 +57690,19 @@ function computeUtilization(footprints, vertices, cutouts, shape, innerRadius, e
|
|
|
58362
57690
|
type: "rect",
|
|
58363
57691
|
width: ceilHalf(bbW + 2 * margin),
|
|
58364
57692
|
height: ceilHalf(bbH + 2 * margin),
|
|
58365
|
-
origin: { x:
|
|
57693
|
+
origin: { x: round23(occMinX - margin), y: round23(occMinY - margin) },
|
|
58366
57694
|
note: `Minimum enclosing rectangle with ${margin}mm edge clearance`
|
|
58367
57695
|
};
|
|
58368
57696
|
}
|
|
58369
57697
|
return {
|
|
58370
|
-
board_area_mm2:
|
|
58371
|
-
component_area_mm2:
|
|
57698
|
+
board_area_mm2: round23(boardArea),
|
|
57699
|
+
component_area_mm2: round23(componentArea),
|
|
58372
57700
|
utilization_pct: Math.round(componentArea / boardArea * 1e3) / 10,
|
|
58373
|
-
bounding_box: { x:
|
|
57701
|
+
bounding_box: { x: round23(occMinX), y: round23(occMinY), w: round23(bbW), h: round23(bbH) },
|
|
58374
57702
|
suggested_outline: suggested
|
|
58375
57703
|
};
|
|
58376
57704
|
}
|
|
58377
|
-
function
|
|
57705
|
+
function padWorld(fp, pad) {
|
|
58378
57706
|
const ang = (fp.rotation ?? 0) * Math.PI / 180;
|
|
58379
57707
|
const cos = Math.cos(ang);
|
|
58380
57708
|
const sin = Math.sin(ang);
|
|
@@ -58393,7 +57721,7 @@ function detectStaleNets(pcb) {
|
|
|
58393
57721
|
const arr = padsByNet.get(pad.net) ?? [];
|
|
58394
57722
|
const sh = pad.shape;
|
|
58395
57723
|
const halfExtent = sh.diameter ? sh.diameter / 2 : Math.hypot(sh.width ?? 0, sh.height ?? 0) / 2;
|
|
58396
|
-
arr.push({ pos:
|
|
57724
|
+
arr.push({ pos: padWorld(fp, pad), layers: pad.layers, halfExtent });
|
|
58397
57725
|
padsByNet.set(pad.net, arr);
|
|
58398
57726
|
}
|
|
58399
57727
|
}
|
|
@@ -59277,7 +58605,7 @@ function layoutLint(pcb) {
|
|
|
59277
58605
|
for (const fp of fps) {
|
|
59278
58606
|
const prefix = refPrefix(fp.ref);
|
|
59279
58607
|
for (const pad of fp.pads) {
|
|
59280
|
-
const w = { ref: fp.ref, prefix, pad, pos:
|
|
58608
|
+
const w = { ref: fp.ref, prefix, pad, pos: padWorld(fp, pad) };
|
|
59281
58609
|
allPads.push(w);
|
|
59282
58610
|
if (pad.net) {
|
|
59283
58611
|
const arr = byNet.get(pad.net) ?? [];
|
|
@@ -59293,7 +58621,7 @@ function layoutLint(pcb) {
|
|
|
59293
58621
|
for (const pad of fp.pads) {
|
|
59294
58622
|
const net = pad.net;
|
|
59295
58623
|
if (!net || LINT_GROUND_NET_RE.test(net) || LINT_SUPPLY_NET_RE.test(net)) continue;
|
|
59296
|
-
const from =
|
|
58624
|
+
const from = padWorld(fp, pad);
|
|
59297
58625
|
let nearest = null;
|
|
59298
58626
|
for (const other of byNet.get(net) ?? []) {
|
|
59299
58627
|
if (other.ref === fp.ref || other.prefix !== "U") continue;
|
|
@@ -59308,9 +58636,9 @@ function layoutLint(pcb) {
|
|
|
59308
58636
|
warnings.push({
|
|
59309
58637
|
kind: "crystal_far_from_ic",
|
|
59310
58638
|
refs: [fp.ref, worst2.icRef],
|
|
59311
|
-
distance_mm:
|
|
58639
|
+
distance_mm: round2(worst2.dist),
|
|
59312
58640
|
threshold_mm: LINT_CRYSTAL_MAX_MM,
|
|
59313
|
-
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.`
|
|
59314
58642
|
});
|
|
59315
58643
|
}
|
|
59316
58644
|
}
|
|
@@ -59321,7 +58649,7 @@ function layoutLint(pcb) {
|
|
|
59321
58649
|
const pwrIdx = nets.findIndex((n) => n && LINT_SUPPLY_NET_RE.test(n) && !LINT_GROUND_NET_RE.test(n));
|
|
59322
58650
|
if (gndIdx < 0 || pwrIdx < 0) continue;
|
|
59323
58651
|
const pwrPad = fp.pads[pwrIdx];
|
|
59324
|
-
const from =
|
|
58652
|
+
const from = padWorld(fp, pwrPad);
|
|
59325
58653
|
let nearest = null;
|
|
59326
58654
|
for (const other of byNet.get(pwrPad.net) ?? []) {
|
|
59327
58655
|
if (other.ref === fp.ref || other.prefix !== "U") continue;
|
|
@@ -59334,9 +58662,9 @@ function layoutLint(pcb) {
|
|
|
59334
58662
|
warnings.push({
|
|
59335
58663
|
kind: "decoupling_cap_far_from_pin",
|
|
59336
58664
|
refs: [fp.ref, nearest.icRef],
|
|
59337
|
-
distance_mm:
|
|
58665
|
+
distance_mm: round2(nearest.dist),
|
|
59338
58666
|
threshold_mm: LINT_DECAP_MAX_MM,
|
|
59339
|
-
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.`
|
|
59340
58668
|
});
|
|
59341
58669
|
}
|
|
59342
58670
|
}
|
|
@@ -59354,15 +58682,15 @@ function layoutLint(pcb) {
|
|
|
59354
58682
|
for (const fp of fps) {
|
|
59355
58683
|
const prefix = refPrefix(fp.ref);
|
|
59356
58684
|
if (prefix !== "J" && prefix !== "P" && prefix !== "CN" && prefix !== "USB") continue;
|
|
59357
|
-
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];
|
|
59358
58686
|
const d = Math.min(...pts.map(edgeDist));
|
|
59359
58687
|
if (d > LINT_CONNECTOR_EDGE_MAX_MM) {
|
|
59360
58688
|
warnings.push({
|
|
59361
58689
|
kind: "connector_not_on_edge",
|
|
59362
58690
|
refs: [fp.ref],
|
|
59363
|
-
distance_mm:
|
|
58691
|
+
distance_mm: round2(d),
|
|
59364
58692
|
threshold_mm: LINT_CONNECTOR_EDGE_MAX_MM,
|
|
59365
|
-
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.`
|
|
59366
58694
|
});
|
|
59367
58695
|
}
|
|
59368
58696
|
}
|
|
@@ -59398,9 +58726,9 @@ function layoutLint(pcb) {
|
|
|
59398
58726
|
warnings.push({
|
|
59399
58727
|
kind: "high_current_near_sensitive",
|
|
59400
58728
|
refs: [c.hi.ref, c.lo.ref],
|
|
59401
|
-
distance_mm:
|
|
58729
|
+
distance_mm: round2(c.dist),
|
|
59402
58730
|
threshold_mm: LINT_HIGH_CURRENT_SEP_MIN_MM,
|
|
59403
|
-
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.`
|
|
59404
58732
|
});
|
|
59405
58733
|
}
|
|
59406
58734
|
}
|
|
@@ -63009,7 +62337,7 @@ async function addMotorWinding(args) {
|
|
|
63009
62337
|
if (!pad.net) continue;
|
|
63010
62338
|
const arr = padsByNet.get(pad.net) ?? [];
|
|
63011
62339
|
arr.push({
|
|
63012
|
-
pos:
|
|
62340
|
+
pos: padWorld(fp, pad),
|
|
63013
62341
|
onReturnLayer: pad.layers.includes(returnLayer),
|
|
63014
62342
|
size: padApproxRadius(pad)
|
|
63015
62343
|
});
|
|
@@ -63352,7 +62680,7 @@ async function addMotorWinding(args) {
|
|
|
63352
62680
|
for (const fp of pcb.footprints) {
|
|
63353
62681
|
for (const pad of fp.pads) {
|
|
63354
62682
|
if (pad.net === net) continue;
|
|
63355
|
-
const pw =
|
|
62683
|
+
const pw = padWorld(fp, pad);
|
|
63356
62684
|
if (pointSegDist(pw, t2.a, t2.b) < padApproxRadius(pad) + halfW + clearance) return false;
|
|
63357
62685
|
}
|
|
63358
62686
|
}
|
|
@@ -65045,7 +64373,7 @@ async function fixDrc(args) {
|
|
|
65045
64373
|
let corridorFail = null;
|
|
65046
64374
|
for (const pt of movePoints) {
|
|
65047
64375
|
const onPad = pcb.footprints.some(
|
|
65048
|
-
(fp) => fp.pads.some((pad) => samePoint(
|
|
64376
|
+
(fp) => fp.pads.some((pad) => samePoint(padWorld(fp, pad), pt))
|
|
65049
64377
|
);
|
|
65050
64378
|
if (onPad) {
|
|
65051
64379
|
corridorFail = "endpoint lands on a component pad \u2014 fixing requires moving the component";
|
|
@@ -65195,7 +64523,7 @@ async function fixDrc(args) {
|
|
|
65195
64523
|
};
|
|
65196
64524
|
return { content: [{ type: "text", text: JSON.stringify(payload) }] };
|
|
65197
64525
|
}
|
|
65198
|
-
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;
|
|
65199
64527
|
var init_ecad2 = __esm({
|
|
65200
64528
|
"src/tools/ecad.ts"() {
|
|
65201
64529
|
"use strict";
|
|
@@ -65997,7 +65325,7 @@ var init_ecad2 = __esm({
|
|
|
65997
65325
|
HIGH_CURRENT_CLASS_RE = /pwr|power|high[-_ ]?current|motor|\bhv\b|hi[-_ ]?amp/i;
|
|
65998
65326
|
refPrefix = (ref) => /^[A-Za-z]+/.exec(ref)?.[0]?.toUpperCase() ?? "";
|
|
65999
65327
|
dist2d = (a, b) => Math.hypot(a.x - b.x, a.y - b.y);
|
|
66000
|
-
|
|
65328
|
+
round2 = (n) => Math.round(n * 100) / 100;
|
|
66001
65329
|
sumSq = (v) => v.reduce((s, x) => s + x * x, 0);
|
|
66002
65330
|
round33 = (v) => Math.round(v * 1e3) / 1e3;
|
|
66003
65331
|
vec2Schema = {
|
|
@@ -72618,12 +71946,12 @@ function simulateStrike(input, engine) {
|
|
|
72618
71946
|
},
|
|
72619
71947
|
modes: result.modes.map((m) => ({
|
|
72620
71948
|
n: m.n,
|
|
72621
|
-
hz:
|
|
71949
|
+
hz: round22(m.hz),
|
|
72622
71950
|
gain: round4(m.gain),
|
|
72623
71951
|
q: Math.round(m.q),
|
|
72624
|
-
t60_s:
|
|
71952
|
+
t60_s: round22(m.t60_s)
|
|
72625
71953
|
})),
|
|
72626
|
-
spectrum_peaks: result.spectrum_peaks.map((p) => ({ hz:
|
|
71954
|
+
spectrum_peaks: result.spectrum_peaks.map((p) => ({ hz: round22(p.hz), db: round22(p.db) })),
|
|
72627
71955
|
...result.verdict ? { verdict: result.verdict } : {},
|
|
72628
71956
|
limits: "1-D transverse bending only (no torsional/lateral modes); decay Q is a heuristic (material + cord-at-holes), frequencies are not"
|
|
72629
71957
|
};
|
|
@@ -72652,7 +71980,7 @@ function simulateStrike(input, engine) {
|
|
|
72652
71980
|
}
|
|
72653
71981
|
return { content: [{ type: "text", text: JSON.stringify(payload, null, 2) }] };
|
|
72654
71982
|
}
|
|
72655
|
-
var cents, MATERIAL_ALIASES, simulateStrikeSchema,
|
|
71983
|
+
var cents, MATERIAL_ALIASES, simulateStrikeSchema, round22, round4, toolDefs39;
|
|
72656
71984
|
var init_acoustics = __esm({
|
|
72657
71985
|
"src/tools/acoustics.ts"() {
|
|
72658
71986
|
"use strict";
|
|
@@ -72724,7 +72052,7 @@ var init_acoustics = __esm({
|
|
|
72724
72052
|
}
|
|
72725
72053
|
}
|
|
72726
72054
|
};
|
|
72727
|
-
|
|
72055
|
+
round22 = (x) => Math.round(x * 100) / 100;
|
|
72728
72056
|
round4 = (x) => Math.round(x * 1e4) / 1e4;
|
|
72729
72057
|
toolDefs39 = [
|
|
72730
72058
|
{
|
|
@@ -73128,8 +72456,8 @@ function parseDocument(input) {
|
|
|
73128
72456
|
}
|
|
73129
72457
|
throw new Error("Document must be JSON (starting with '{') or VCode (starting with '#')");
|
|
73130
72458
|
}
|
|
73131
|
-
function compressForUrl(
|
|
73132
|
-
const compressed = gzipSync(Buffer.from(
|
|
72459
|
+
function compressForUrl(compact2) {
|
|
72460
|
+
const compressed = gzipSync(Buffer.from(compact2, "utf-8"), { level: 9 });
|
|
73133
72461
|
return base64urlEncode(compressed);
|
|
73134
72462
|
}
|
|
73135
72463
|
function openInBrowser(input) {
|
|
@@ -77805,8 +77133,8 @@ var init_server3 = __esm({
|
|
|
77805
77133
|
init_order_feed();
|
|
77806
77134
|
init_animate();
|
|
77807
77135
|
PKG_VERSION = (() => {
|
|
77808
|
-
if ("0.9.4-main.
|
|
77809
|
-
return "0.9.4-main.
|
|
77136
|
+
if ("0.9.4-main.16") {
|
|
77137
|
+
return "0.9.4-main.16";
|
|
77810
77138
|
}
|
|
77811
77139
|
try {
|
|
77812
77140
|
const req = createRequire2(import.meta.url);
|
|
@@ -77815,8 +77143,8 @@ var init_server3 = __esm({
|
|
|
77815
77143
|
return "0.0.0";
|
|
77816
77144
|
}
|
|
77817
77145
|
})();
|
|
77818
|
-
BUILD_SHA = "
|
|
77819
|
-
BUILD_TIME = "2026-07-24T17:
|
|
77146
|
+
BUILD_SHA = "63c1a040cf3352ca640144ec217f3f96271180c9";
|
|
77147
|
+
BUILD_TIME = "2026-07-24T17:21:48.535Z";
|
|
77820
77148
|
SHORT_SHA = BUILD_SHA === "unknown" ? "unknown" : BUILD_SHA.slice(0, 7);
|
|
77821
77149
|
VERSION_WITH_BUILD = SHORT_SHA === "unknown" ? PKG_VERSION : `${PKG_VERSION}+${SHORT_SHA}`;
|
|
77822
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-24T17:
|
|
22
|
+
"sha": "63c1a040cf3352ca640144ec217f3f96271180c9",
|
|
23
|
+
"builtAt": "2026-07-24T17:21:48.535Z"
|
|
24
24
|
}
|
|
25
25
|
}
|
package/vcad_kernel_wasm_bg.wasm
CHANGED
|
Binary file
|