imrishabh18/pedometer

This code defines and assembles a simple radio receiver hardware circuit using specific imported capacitors, inductors, RF connectors, and oscillator components with precise footprints and schematic attributes.

Version
1.1.3
License
unset
Stars
0

tests/manufacturing.test.ts

import { expect, test } from "bun:test"
import { readFileSync } from "node:fs"
import type { AnyCircuitElement } from "circuit-json"
import { prepareManufacturingCircuit } from "../manufacturing"
import { convertCircuitJsonToGerberFiles } from "circuit-json-to-gerber"
import { pointToSegmentDistance } from "@tscircuit/math-utils"

const input: AnyCircuitElement[] = JSON.parse(readFileSync("dist/index/circuit.json", "utf8"))
const output = prepareManufacturingCircuit(input)

test("manufacturing preparation preserves copper, nets, and generated drills", () => {
  const withoutPasteOrMask = (c: AnyCircuitElement[]) => c.filter(e => e.type !== "pcb_solder_paste" && !e.type.startsWith("pcb_silkscreen_")).map(e => {
    if (e.type !== "pcb_smtpad") return e
    const { soldermask_margin, ...rest } = e
    return rest
  })
  expect(withoutPasteOrMask(output)).toEqual(withoutPasteOrMask(input))
  expect(prepareManufacturingCircuit(output)).toEqual(output)
})

test("fabrication legend uses the clear underside and printable text strokes", () => {
  const silk=output.filter(e=>e.type.startsWith("pcb_silkscreen_"))
  expect(silk).toHaveLength(4)
  for(const e of silk){
    if(e.type!=="pcb_silkscreen_text")throw new Error("Unexpected fabrication silk")
    expect(e.layer).toBe("bottom")
    expect(e.font_size*0.09).toBeGreaterThanOrEqual(0.15)
  }
})

test("29 TI stencil openings have the rounded-square outline and area", () => {
  const paste = output.filter(e => e.type === "pcb_solder_paste" && /^pcb_smtpad_.*_paste_/.test(e.pcb_solder_paste_id))
  const padIds = new Set(paste.map(e => e.type === "pcb_solder_paste" ? e.pcb_smtpad_id : ""))
  expect(padIds.size).toBe(29)
  for (const id of padIds) {
    const parts = paste.filter(e => e.type === "pcb_solder_paste" && e.pcb_smtpad_id === id)
    expect(parts).toHaveLength(6)
  }
  const parts = paste.filter(e => e.type === "pcb_solder_paste" && e.pcb_smtpad_id === [...padIds][0])
  const pad = output.find(e => e.type === "pcb_smtpad" && e.pcb_smtpad_id === [...padIds][0])!
  if (pad.type !== "pcb_smtpad" || pad.shape !== "circle") throw new Error("Missing BGA land")
  let occupied = 0
  const n = 400, step = 0.3 / n
  for (let i = 0; i < n; i++) for (let j = 0; j < n; j++) {
    const x = pad.x - 0.15 + (i + 0.5) * step, y = pad.y - 0.15 + (j + 0.5) * step
    if (parts.some(e => e.type === "pcb_solder_paste" && (e.shape === "circle"
      ? Math.hypot(x - e.x, y - e.y) <= e.radius
      : e.shape === "rect" && Math.abs(x - e.x) <= e.width / 2 && Math.abs(y - e.y) <= e.height / 2))) occupied++
  }
  expect(Math.abs(occupied * step ** 2 - (0.25 ** 2 - (4 - Math.PI) * 0.05 ** 2))).toBeLessThan(0.0002)
})

test("installed exporter emits each through-via and USB shell slot once", () => {
  const files = convertCircuitJsonToGerberFiles(output)
  expect(Object.keys(files).filter(n => n.endsWith(".drl")).sort()).toEqual(["drill-L1-L4.drl", "drill_npth.drl"])
  const holes = files["drill-L1-L4.drl"].match(/^X-?[\d.]+Y-?[\d.]+$/gm) ?? []
  const vias = output.filter(e => e.type === "pcb_via")
  expect(holes.length).toBe(new Set(holes).size)
  const slots = output.filter(e => e.type === "pcb_plated_hole")
  expect(slots).toHaveLength(4)
  expect(files["drill-L1-L4.drl"].match(/^G85/gm)).toHaveLength(4)
  expect(holes.length).toBe(new Set(vias.map(e => e.type === "pcb_via" ? `${e.x.toFixed(4)},${e.y.toFixed(4)}` : "")).size + slots.length)
})

test("QFN exposed pad uses four TI-sized stencil windows instead of full-pad paste", () => {
  const paste=output.filter(e=>e.type==="pcb_solder_paste" && e.pcb_solder_paste_id.startsWith("qfn_ep_window_"));
  expect(paste).toHaveLength(24);
  const pads=new Set(paste.map(e=>e.type==="pcb_solder_paste"?e.pcb_smtpad_id:undefined));
  expect(pads.size).toBe(1);
  const all=output.filter(e=>e.type==="pcb_solder_paste" && pads.has(e.pcb_smtpad_id));
  expect(all).toHaveLength(24);
  const windows=paste.filter(e=>e.type==="pcb_solder_paste"&&e.pcb_solder_paste_id.endsWith("_h"));
  expect(windows).toHaveLength(4);
  for(const w of windows){if(w.type!=="pcb_solder_paste"||w.shape!=="rect")throw new Error("Missing QFN window");expect(w.width).toBe(1.08);expect(w.height).toBeCloseTo(0.98,8);}
})

test("routed through-holes clear other-net traces on every layer", () => {
  const traces = input.filter(e => e.type === "pcb_trace")
  const net = (id: string | undefined) => (traces.find(t => t.pcb_trace_id === id) as any)?.connection_name
  const segments = traces.flatMap(t => t.route.flatMap((a, i) => {
    const b = t.route[i + 1]
    return a.route_type === "wire" && b?.route_type === "wire" && a.layer === b.layer
      ? [{ a, b, net: net(t.pcb_trace_id) }] : []
  }))
  let minimum = Infinity
  for (const via of input.filter(e => e.type === "pcb_via")) {
    expect(via.hole_diameter).toBe(0.15)
    expect(via.outer_diameter).toBe(0.3)
    expect(via.layers).toEqual(["top", "inner1", "inner2", "bottom"])
    const viaNet = net(via.pcb_trace_id)
    expect(viaNet).toBeDefined()
    for (const segment of segments) if (segment.net !== viaNet) {
      minimum = Math.min(minimum, pointToSegmentDistance(via, segment.a, segment.b)
        - via.hole_diameter / 2 - Math.max(segment.a.width, segment.b.width) / 2)
    }
  }
  expect(minimum).toBeGreaterThanOrEqual(0.2)
})