krishnax12/watchy-eink-smartwatch
Unfinished prototype (not production-ready): 4 native DRC errors, 2 display-flex intersections, and missing antenna radiator copper remain
- Version
- 1.2.7
- License
- MIT
- Stars
- 1
scripts/audit-cad-placement.mjs
import fs from "node:fs/promises";
import path from "node:path";
import { convertCircuitJsonTo3D } from "circuit-json-to-gltf";
// Match circuit-json-to-gltf's transformMesh order/signs, not Three's default Euler.
function transformPoint(point, box) {
const rotation = box.rotation ?? { x: 0, y: 0, z: 0 };
const x1 = point.x * Math.cos(rotation.y) - point.z * Math.sin(rotation.y);
const z1 = point.x * Math.sin(rotation.y) + point.z * Math.cos(rotation.y);
const y2 = point.y * Math.cos(rotation.x) + z1 * Math.sin(rotation.x);
const z2 = -point.y * Math.sin(rotation.x) + z1 * Math.cos(rotation.x);
return [
x1 * Math.cos(rotation.z) + y2 * Math.sin(rotation.z) + box.center.x,
z2 + box.center.z,
-x1 * Math.sin(rotation.z) + y2 * Math.cos(rotation.z) + box.center.y,
];
}
const circuit = JSON.parse(
await fs.readFile(
process.env.WATCHY_CIRCUIT_PATH ?? "dist/index/circuit.json",
"utf8",
),
);
// Limit the audit to supplied STEP models, not generated passive approximations.
const input = circuit.filter(
(element) => element.type !== "cad_component" || element.model_step_url,
);
const assetDirectory = path.resolve(process.env.WATCHY_ASSET_DIRECTORY ?? ".");
const board = circuit.find((element) => element.type === "pcb_board");
if (!board) throw new Error("Missing PCB for assembly screening");
const boardHalfThickness = board.thickness / 2;
{
const scene = await convertCircuitJsonTo3D(input, {
projectBaseUrl: `file://${assetDirectory}/`,
renderBoardTextures: false,
});
const models = scene.boxes
.filter((box) => box.meshUrl)
.map((box) => {
if (!box.mesh) throw new Error(`Missing model for ${box.label}`);
const min = [Infinity, Infinity, Infinity];
const max = [-Infinity, -Infinity, -Infinity];
for (const triangle of box.mesh.triangles) {
for (const point of triangle.vertices) {
// Scene y is PCB z. Restore PCB coordinates for readable reports.
const coordinates = transformPoint(point, box);
coordinates.forEach((coordinate, axis) => {
min[axis] = Math.min(min[axis], coordinate);
max[axis] = Math.max(max[axis], coordinate);
});
}
}
return {
name: box.label,
file: box.meshUrl,
min,
max,
intersectsBoardSlab:
min[2] < boardHalfThickness - 0.01 &&
max[2] > -boardHalfThickness + 0.01,
};
});
const potentialIntersections = [];
for (let i = 0; i < models.length; i++) {
for (let j = i + 1; j < models.length; j++) {
const a = models[i];
const b = models[j];
const overlap = [0, 1, 2].map(
(axis) =>
Math.min(a.max[axis], b.max[axis]) -
Math.max(a.min[axis], b.min[axis]),
);
if (overlap.every((value) => value > 0.02))
potentialIntersections.push({ a: a.name, b: b.name, overlap });
}
}
const report = {
scope:
"Supplied STEP models, installed circuit-json-to-gltf transforms. AABB screening, not exact solid collision certification. Board slab flags can include intentional connector tabs/flex. Generated passive models excluded.",
models,
potentialIntersections,
};
await fs.writeFile(
"reports/cad-placement-audit.json",
JSON.stringify(report, null, 2),
);
console.log(JSON.stringify(report, null, 2));
}