mohan-bee/flight-controller-v2
A 64 mm STM32F405 flight-controller PCB with ICM-42688 IMU, SPI flash, USB-C/ESD, battery buck and 3.3 V regulation, four motor outputs, service I/O, and PETG standoff mounting hardware.
- Version
- 0.1.0
- License
- ISC
- Stars
- 0
prepare-manufacturing.mjs
import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
import assert from "node:assert/strict";
const circuit = JSON.parse(readFileSync("dist/index/circuit.json", "utf8"));
assert.equal(circuit.filter((item) => item.type.endsWith("_error")).length, 0);
const unpopulated = new Set(circuit.filter((item) =>
item.type === "pcb_component" && item.do_not_place,
).map((item) => item.pcb_component_id));
// Bare wire pads need exposed copper, but no stencil aperture. Assembly is top-only.
// Dense footprint labels are preserved in the assembly drawing; omit them from
// fabrication so small imported lettering cannot print onto component lands.
const fabrication = circuit.filter((item) => {
if (item.type === "pcb_solder_paste")
return item.layer === "top" && !unpopulated.has(item.pcb_component_id);
if (item.type.startsWith("pcb_silkscreen_"))
return item.type === "pcb_silkscreen_text" && !item.pcb_component_id;
return true;
}).map((item) => {
// Orthogonal paste apertures can use ordinary unrotated dimensions. This
// preserves the stencil geometry and works with viewers lacking Gerber LR.
if (item.type !== "pcb_solder_paste") return item;
if (!item.ccw_rotation) return { ...item, ccw_rotation: 0 };
assert.equal(item.ccw_rotation % 90, 0);
assert(["rect", "pill", "rotated_pill"].includes(item.shape));
const { ccw_rotation, ...paste } = item;
if (ccw_rotation % 180 !== 0)
[paste.width, paste.height] = [paste.height, paste.width];
if (paste.shape === "rotated_pill") paste.shape = "pill";
return { ...paste, ccw_rotation: 0 };
});
const directory = "manufacturing/jlcpcb";
mkdirSync(directory, { recursive: true });
writeFileSync(`${directory}/fabrication.circuit.json`, JSON.stringify(fabrication, null, 2));
const audit = JSON.parse(readFileSync("validation/release/assembly-pose-audit.json", "utf8"));
assert.equal(audit.status, "pass");
assert.equal(audit.parts.length, 67);
const csv = (rows) => rows.map((row) => row.map((cell) =>
`"${String(cell).replaceAll('"', '""')}"`,
).join(",")).join("\n") + "\n";
writeFileSync(`${directory}/BOM.csv`, csv([
["Comment", "Designator", "Footprint", "JLCPCB Part #", "Manufacturer Part Number"],
...audit.parts.map((part) => [part.value, part.refdes, part.package, part.supplier, part.mpn]),
]));
writeFileSync(`${directory}/CPL.csv`, csv([
["Designator", "Mid X", "Mid Y", "Layer", "Rotation"],
...audit.parts.map((part) => [part.refdes, `${part.x_mm.toFixed(6)}mm`, `${part.y_mm.toFixed(6)}mm`, "Top", part.rotation_degrees]),
]));
console.log("Prepared top-only CAM JSON and supplier-origin BOM/CPL for 67 parts.");