0hmX/dual-usb-peltier-assembly
A tscircuit package created by 0hmX.
- Version
- 0.4.12
- License
- unset
- Stars
- 0
tests/routing-metadata.test.ts
import { expect, test } from "bun:test"
import type { AnyCircuitElement } from "circuit-json"
import type { RoutedCopperData } from "../src/board/routed-copper"
import { assertCopperAssociations } from "./helpers/copper-associations"
import { inspectThermalVias, THERMAL_ARRAYS } from "./helpers/thermal-vias"
import { drillSmtClearanceViolations } from "./helpers/physical-copper"
function thermalFixture(): AnyCircuitElement[] {
const json: any[] = [{ type: "source_net", source_net_id: "gnd", name: "GND" }]
for (const array of THERMAL_ARRAYS) {
const id = array.component
json.push(
{ type: "source_component", source_component_id: id, name: id },
{ type: "source_port", source_port_id: id, source_component_id: id, pin_number: array.pin },
{ type: "source_trace", source_trace_id: id, connected_source_port_ids: [id], connected_source_net_ids: ["gnd"] },
{ type: "pcb_port", pcb_port_id: id, source_port_id: id },
{ type: "pcb_smtpad", pcb_smtpad_id: id, pcb_port_id: id, shape: "rect", width: 3.1, height: 3.1,
x: (array.x[0] + array.x[array.x.length - 1]!) / 2, y: (array.y[0] + array.y[array.y.length - 1]!) / 2 })
for (const x of array.x) for (const y of array.y) json.push({ type: "pcb_via", pcb_via_id: `${x},${y}`,
x, y, source_net_id: "gnd", hole_diameter: 0.3, outer_diameter: 0.6, layers: ["top", "inner1", "inner2", "bottom"] })
}
return json
}
test("only exact documented filled thermal pad/via pairs receive native exceptions", () => {
const json = thermalFixture()
const result = inspectThermalVias(json)
expect(result.missing).toEqual([])
expect(result.invalid).toEqual([])
expect(result.allowedErrorIds.size).toBe(13)
expect(result.allowedErrorIds.has("via_in_pad_-0.8,-15.8_U_MCU")).toBe(true)
expect(result.allowedErrorIds.has("via_in_pad_-0.8,-15.8_TP_OUT")).toBe(false)
const via = json.find(e => e.type === "pcb_via")!
if (via.type !== "pcb_via") throw new Error("Missing fixture via")
via.source_net_id = "signal"
expect(inspectThermalVias(json).invalid).toHaveLength(1)
via.source_net_id = "gnd"
via.hole_diameter = 0.4
expect(inspectThermalVias(json).invalid).toHaveLength(1)
via.hole_diameter = 0.3
via.layers = ["top", "bottom"]
expect(inspectThermalVias(json).invalid).toHaveLength(1)
})
test("drills require a 0.15mm solder-land web, including same-net vias and plated slots", () => {
const pad = { type: "pcb_smtpad", pcb_smtpad_id: "pad", shape: "rect", x: 0, y: 0, width: 1, height: 1, layer: "top", source_net_id: "gnd" }
const via = { type: "pcb_via", pcb_via_id: "via", x: 0.799, y: 0, hole_diameter: 0.3, outer_diameter: 0.6, source_net_id: "gnd" }
const check = (hole: object) => drillSmtClearanceViolations([pad, hole] as AnyCircuitElement[])
expect(check(via)[0]!.actual).toBeCloseTo(0.149)
expect(check({ ...via, x: 0.8 })).toEqual([])
expect(check({ ...via, x: 0 })[0]!.actual).toBe(0)
const slot = { type: "pcb_plated_hole", pcb_plated_hole_id: "slot", x: 0.849, y: 0, hole_width: 0.4, hole_height: 0.2 }
expect(check(slot)[0]!.actual).toBeCloseTo(0.149)
expect(check({ ...slot, x: 0.85 })).toEqual([])
})
test("only the exact 13 thermal via-pad pairs waive the drill web", () => {
const json = thermalFixture(), allowed = inspectThermalVias(json).allowedErrorIds
expect(drillSmtClearanceViolations(json)).toHaveLength(13)
expect(drillSmtClearanceViolations(json, allowed)).toEqual([])
json.push({ type: "pcb_smtpad", pcb_smtpad_id: "unlisted-pad", shape: "rect", x: -0.8, y: -15.8,
width: 0.1, height: 0.1, layer: "bottom" } as AnyCircuitElement)
expect(drillSmtClearanceViolations(json, allowed)).toEqual([
{ drill: "-0.8,-15.8", pad: "unlisted-pad", actual: 0, minimum: 0.15 },
])
})
const associationFixture = (): AnyCircuitElement[] => [
{ type: "source_component", source_component_id: "c", name: "IC" },
{ type: "source_port", source_port_id: "s", source_component_id: "c", pin_number: 1 },
{ type: "source_net", source_net_id: "n", name: "N" },
{ type: "source_trace", source_trace_id: "t", connected_source_port_ids: ["s"], connected_source_net_ids: ["n"], max_length: 2 },
{ type: "pcb_port", pcb_port_id: "p", source_port_id: "s", x: 0, y: 0, layers: ["top"] },
{ type: "pcb_smtpad", pcb_smtpad_id: "pad", pcb_port_id: "p", shape: "rect", x: 0, y: 0, width: 0.6, height: 0.6, layer: "top" },
] as AnyCircuitElement[]
const associationData = (): RoutedCopperData => ({ vias: [], routes: [{ net: "N", sourceTraceId: "t",
sourceTraceKey: JSON.stringify([["IC.pin1"], ["N"], 2, null]),
route: [0, 1].map(x => ({ route_type: "wire", x, y: 0, layer: "top", width: 0.2 })) }] })
test("saved source associations retain the original pin, named net and constraints", () => {
const json = associationFixture(), data = associationData()
expect(() => assertCopperAssociations(json, data)).not.toThrow()
data.routes[0]!.sourceTraceId = "stale"
expect(() => assertCopperAssociations(json, data)).toThrow("source association")
data.routes[0]!.sourceTraceId = "t"
const trace = json.find(e => e.type === "source_trace")!
if (trace.type !== "source_trace") throw new Error("Missing fixture trace")
trace.max_length = 3
expect(() => assertCopperAssociations(json, data)).toThrow("pin/constraint metadata")
})
test("a constrained source branch cannot be borrowed by a distant copper trunk", () => {
const data = associationData()
data.routes[0]!.route.forEach(p => { p.x += 5 })
expect(() => assertCopperAssociations(associationFixture(), data)).toThrow("unrelated branch")
})
test("constrained endpoints may land inside pad copper away from the center", () => {
const data = associationData()
data.routes[0]!.route[0]!.x = 0.25
expect(() => assertCopperAssociations(associationFixture(), data)).not.toThrow()
data.routes[0]!.route[0]!.x = 0.31
expect(() => assertCopperAssociations(associationFixture(), data)).toThrow("unrelated branch")
})
test("all pins of a constrained branch must be its physical endpoints", () => {
const json = associationFixture(), data = associationData()
json.push(
{ type: "source_port", source_port_id: "s2", source_component_id: "c", pin_number: 2 } as AnyCircuitElement,
{ type: "pcb_port", pcb_port_id: "p2", source_port_id: "s2", x: 2, y: 0, layers: ["top"] } as AnyCircuitElement,
{ type: "pcb_smtpad", pcb_smtpad_id: "pad2", pcb_port_id: "p2", shape: "rect", x: 2, y: 0, width: 0.6, height: 0.6, layer: "top" } as AnyCircuitElement)
const trace = json.find(e => e.type === "source_trace")!
if (trace.type !== "source_trace") throw new Error("Missing fixture trace")
trace.connected_source_port_ids.push("s2")
data.routes[0]!.sourceTraceKey = JSON.stringify([["IC.pin1", "IC.pin2"], ["N"], 2, null])
expect(() => assertCopperAssociations(json, data)).toThrow("unrelated branch")
data.routes[0]!.route[1]!.x = 2
expect(() => assertCopperAssociations(json, data)).not.toThrow()
data.routes[0]!.sourcePort = "IC.pin3"
expect(() => assertCopperAssociations(json, data)).toThrow("does not own its endpoint")
})