0hmX/dual-usb-peltier-assembly
A tscircuit package created by 0hmX.
- Version
- 0.4.12
- License
- unset
- Stars
- 0
tests/physical-copper.test.ts
import { describe, expect, test } from "bun:test"
import type { AnyCircuitElement } from "circuit-json"
import { analyzePhysicalCopper, sourceConnectivity } from "./helpers/physical-copper"
function fixture(): AnyCircuitElement[] {
const json: any[] = [{ type: "pcb_board", num_layers: 4 }, { type: "source_net", source_net_id: "n", name: "SIGNAL" }]
for (const [i, x] of [0, 2, 4].entries()) {
json.push(
{ type: "source_component", source_component_id: `c${i}`, name: `P${i}` },
{ type: "source_port", source_port_id: `s${i}`, source_component_id: `c${i}`, name: "pin1", pin_number: 1 },
{ type: "pcb_port", pcb_port_id: `p${i}`, source_port_id: `s${i}`, x, y: 0, layers: ["top"] },
{ type: "pcb_smtpad", pcb_smtpad_id: `pad${i}`, pcb_port_id: `p${i}`, shape: "rect", x, y: 0, width: 0.6, height: 0.6, layer: "top" })
if (i < 2) json.push({ type: "source_trace", source_trace_id: `st${i}`, connected_source_port_ids: [`s${i}`], connected_source_net_ids: ["n"] })
}
return json
}
const trace = (layer = "top", x0 = 0, x1 = 2): AnyCircuitElement => ({ type: "pcb_trace", pcb_trace_id: `trace${x0}`,
source_trace_id: "st0", route: [x0, x1].map(x => ({ route_type: "wire", x, y: 0, width: 0.2, layer })) }) as AnyCircuitElement
const via = (x: number): AnyCircuitElement => ({ type: "pcb_via", pcb_via_id: `arbitrary-via-${x}`, x, y: 0,
outer_diameter: 0.5, hole_diameter: 0.2, layers: ["top", "bottom"] }) as AnyCircuitElement
const ring = (coordinates: number[][]) => ({ vertices: coordinates.map(([x, y]) => ({ x, y })) })
describe("physical copper connectivity oracle", () => {
test("logical net and trace IDs do not connect isolated copper islands", () => {
const baseline = fixture(), expected = sourceConnectivity(baseline)
expect(analyzePhysicalCopper(baseline, expected).disconnected).toHaveLength(1)
expect(analyzePhysicalCopper([...baseline, trace("top", 0, 0.7), trace("top", 1.3, 2)], expected).disconnected).toHaveLength(1)
})
test("real copper joins required pins and ignores a genuinely unused pin", () => {
const result = analyzePhysicalCopper([...fixture(), trace()])
expect(result.disconnected).toEqual([])
expect(result.shorts).toEqual([])
expect(result.requiredPortCount).toBe(2)
expect(result.ignoredNcPortCount).toBe(1)
})
test("current package flow-through conductors preserve the original source net partition", () => {
const baseline = fixture() as any[]
const pin = baseline.find(e => e.source_port_id === "s1" && e.type === "source_port")
pin.source_component_id = "c0"
pin.pin_number = 2
const expected = sourceConnectivity(baseline)
const current = [...baseline, { type: "source_component_internal_connection", source_component_id: "c0",
source_port_ids: ["s0", "s1"] }]
expect(sourceConnectivity(current).signature).toBe(expected.signature)
expect(analyzePhysicalCopper(baseline, expected).disconnected).toHaveLength(1)
expect(analyzePhysicalCopper(current, expected).disconnected).toEqual([])
})
test("bottom copper cannot contact top-only pads", () => {
expect(analyzePhysicalCopper([...fixture(), trace("bottom")]).disconnected).toHaveLength(1)
})
test("top-to-bottom barrels connect inner copper and arbitrary via IDs count", () => {
const result = analyzePhysicalCopper([...fixture(), via(0), via(2), trace("inner1")])
expect(result.disconnected).toEqual([])
expect(result.metricsForNet("SIGNAL")).toEqual({ traceLengthMm: 2, viaCount: 2 })
})
test("native wire-to-via points include the copper leading into and out of a barrel", () => {
const json = fixture() as any[]
json.find(e => e.pcb_smtpad_id === "pad1").layer = "bottom"
const routed: any = { type: "pcb_trace", pcb_trace_id: "implicit", route: [
{ route_type: "wire", x: 0, y: 0, width: 0.2, layer: "top" },
{ route_type: "via", x: 1, y: 0, from_layer: "top", to_layer: "bottom" },
{ route_type: "wire", x: 2, y: 0, width: 0.2, layer: "bottom" },
] }
const result = analyzePhysicalCopper([...json, via(1), routed])
expect(result.disconnected).toEqual([])
expect(result.unanchoredCopper).toEqual([])
expect(result.metricsForNet("SIGNAL")).toEqual({ traceLengthMm: 2, viaCount: 1 })
})
test("through-pad paths require real plated copper between their two layers", () => {
const json = fixture() as any[]
json.find(e => e.pcb_smtpad_id === "pad1").layer = "bottom"
const routed: any = { type: "pcb_trace", pcb_trace_id: "pad-path", route: [
{ route_type: "wire", x: 0, y: 0, width: 0.2, layer: "top" },
{ route_type: "through_pad", start: { x: 0.8, y: 0 }, end: { x: 1.2, y: 0 }, start_layer: "top", end_layer: "bottom", width: 0.2 },
{ route_type: "wire", x: 2, y: 0, width: 0.2, layer: "bottom" },
] }
expect(analyzePhysicalCopper([...json, routed]).disconnected).toHaveLength(1)
const hole: any = { type: "pcb_plated_hole", pcb_plated_hole_id: "plated", shape: "circle", x: 1, y: 0,
outer_diameter: 0.5, hole_diameter: 0.2, layers: ["top", "bottom"] }
expect(analyzePhysicalCopper([...json, hole, routed]).disconnected).toEqual([])
})
test("a repeated wire endpoint inside a drilled via is not a floating copper disk", () => {
const routed: any = { type: "pcb_trace", pcb_trace_id: "stub", route: [
{ route_type: "wire", x: 0, y: 0, width: 0.1, layer: "top" },
{ route_type: "wire", x: 1, y: 0, width: 0.1, layer: "top" },
{ route_type: "via", x: 1, y: 0, from_layer: "top", to_layer: "bottom" },
{ route_type: "wire", x: 1, y: 0, width: 0.1, layer: "bottom" },
] }
expect(analyzePhysicalCopper([...fixture(), via(1), routed]).unanchoredCopper).toEqual([])
})
test("a pour hole isolates copper inside it", () => {
const pour: any = { type: "pcb_copper_pour", pcb_copper_pour_id: "pour", shape: "brep", layer: "top", source_net_id: "n",
brep_shape: { outer_ring: ring([[-1, -1], [3, -1], [3, 1], [-1, 1]]),
inner_rings: [ring([[1.5, -0.5], [2.5, -0.5], [2.5, 0.5], [1.5, 0.5]])] } }
expect(analyzePhysicalCopper([...fixture(), pour]).disconnected).toHaveLength(1)
pour.brep_shape.inner_rings = []
expect(analyzePhysicalCopper([...fixture(), pour]).disconnected).toEqual([])
})
test("touching an NC pad is an unintended connection", () => {
expect(analyzePhysicalCopper([...fixture(), trace("top", 0, 4)]).shorts).toHaveLength(1)
})
test("source-net mutation remains visible even when copper connects the pins", () => {
const baseline = fixture(), altered = structuredClone(baseline) as any[]
altered.find(e => e.source_trace_id === "st1").connected_source_net_ids = []
const result = analyzePhysicalCopper([...altered, trace()], sourceConnectivity(baseline))
expect(result.sourceSignature).not.toBe(sourceConnectivity(baseline).signature)
})
test("a 0.095 mm gap fails a 0.1 mm rule without a tolerance waiver", () => {
const json = fixture() as any[]
json.find(e => e.pcb_smtpad_id === "pad2").x = 2.695
const violations = analyzePhysicalCopper(json).clearanceViolations
expect(violations).toHaveLength(1)
expect(violations[0]!.actual).toBeCloseTo(0.095, 9)
expect(violations[0]!.minimum).toBe(0.1)
})
})