0hmX/bluetooth-pedometer-pcb

This code defines and renders a detailed electronic circuit board layout with labeled components, footprints, 3D models, and routing, suited for PCB design and manufacturing.

Version
1.0.4
License
unset
Stars
0

scripts/export-jlc.ts

import { mkdir } from "node:fs/promises"

type CircuitElement = Record<string, any>

const inputPath = process.argv[2] ?? "dist/index/circuit.json"
const outputDir = process.argv[3] ?? "dist/manufacturing"
const circuit = (await Bun.file(inputPath).json()) as CircuitElement[]

const naturalRefSort = (a: string, b: string) =>
  a.localeCompare(b, undefined, { numeric: true, sensitivity: "base" })

const csv = (rows: Array<Array<string | number>>) =>
  rows
    .map((row) =>
      row
        .map((cell) => `"${String(cell).replaceAll('"', '""')}"`)
        .join(","),
    )
    .join("\n") + "\n"

const packageByMpn: Record<string, string> = {
  "USB4105-GF-A": "USB-C-16P-SMD",
  BQ25185DLHR: "WSON-10-EP-2.2x2mm",
  "S3B-PH-SM4-TB(LF)(SN)": "JST-PH-3-SMD-RA",
  BQ27427YZFR: "DSBGA-9-1.62x1.58mm-P0.5mm",
  TPS7A0233PDBVR: "SOT-23-5",
  CC2340R52E0RGER: "VQFN-24-EP-4x4mm-P0.5mm",
  "ABM11W-48.0000MHZ-7-B1U-T3": "Crystal-4P-2.0x1.6mm",
  X1A0000610006: "Crystal-2P-2.0x1.2mm",
  "2450AT18A100E": "Chip-Antenna-3.2x1.6mm",
  BMA400: "LGA-12-2x2mm-P0.5mm",
  "SM04B-SRSS-TB(LF)(SN)": "JST-SH-4-SMD-RA",
  "TS-1187A-B-A-B": "Tactile-Switch-6x6mm-SMD",
  AO3401A: "SOT-23",
}

const packageFor = (component: CircuitElement) => {
  const mapped = packageByMpn[component.manufacturer_part_number]
  if (mapped) return mapped

  if (["C17", "C18", "C19", "C20", "L2"].includes(component.name)) {
    return "0201 (0603 metric)"
  }
  if (["C1", "C2", "C10", "D1", "L1"].includes(component.name)) {
    return "0603 (1608 metric)"
  }
  return "0402 (1005 metric)"
}

const valueFor = (component: CircuitElement) =>
  component.display_resistance ??
  component.display_capacitance ??
  component.display_inductance ??
  component.symbol_display_value ??
  component.manufacturer_part_number ??
  component.name

const sources = circuit.filter((element) => element.type === "source_component")
const placements = new Map(
  circuit
    .filter((element) => element.type === "pcb_component")
    .map((element) => [element.source_component_id, element]),
)

const assembled = sources.filter(
  (component) => component.supplier_part_numbers?.jlcpcb?.[0] && placements.has(component.source_component_id),
)

const bomGroups = new Map<
  string,
  { comment: string; refs: string[]; footprint: string; lcsc: string; mpn: string }
>()

for (const component of assembled) {
  const lcsc = `C${component.supplier_part_numbers.jlcpcb[0]}`.replace("CC", "C")
  const footprint = packageFor(component)
  const comment = valueFor(component)
  const mpn = component.manufacturer_part_number ?? ""
  const key = [lcsc, mpn, comment, footprint].join("|")
  const group = bomGroups.get(key) ?? {
    comment,
    refs: [] as string[],
    footprint,
    lcsc,
    mpn,
  }
  group.refs.push(component.name)
  bomGroups.set(key, group)
}

const bomRows: Array<Array<string | number>> = [
  ["Comment", "Designator", "Footprint", "LCSC Part #", "Manufacturer Part Number", "Quantity"],
]
for (const group of [...bomGroups.values()].sort((a, b) => naturalRefSort(a.refs[0], b.refs[0]))) {
  group.refs.sort(naturalRefSort)
  bomRows.push([
    group.comment,
    group.refs.join(","),
    group.footprint,
    group.lcsc,
    group.mpn,
    group.refs.length,
  ])
}

const cplRows: Array<Array<string | number>> = [
  ["Designator", "Mid X", "Mid Y", "Layer", "Rotation"],
]
for (const component of assembled.sort((a, b) => naturalRefSort(a.name, b.name))) {
  const placement = placements.get(component.source_component_id)!
  cplRows.push([
    component.name,
    `${placement.center.x.toFixed(4)}mm`,
    `${placement.center.y.toFixed(4)}mm`,
    placement.layer === "bottom" ? "Bottom" : "Top",
    ((placement.rotation ?? 0) % 360 + 360) % 360,
  ])
}

await mkdir(outputDir, { recursive: true })
await Bun.write(`${outputDir}/jlcpcb-bom.csv`, csv(bomRows))
await Bun.write(`${outputDir}/jlcpcb-cpl.csv`, csv(cplRows))

console.log(`Wrote ${bomRows.length - 1} BOM lines and ${cplRows.length - 1} CPL placements to ${outputDir}`)