0hmX/esp32-s3-usb-webcam-ov2640
This code defines and integrates a variety of surface-mount, through-hole, and connector hardware components (including microcontrollers, regulators, resistors, capacitors, and connectors) into a PCB design framework, enabling schematic symbol creation, footprint definition, and automated routing within an electronic circuit layout.
- Version
- 1.0.7
- License
- unset
- Stars
- 0
tests/manual-fanout-output.test.ts
import { expect, test } from "bun:test"
import {
BOARD_ROUTING_PHASES,
assertBoardRoutingPhaseSelectorsAreUnique,
} from "../src/routing-phases"
import { postprocessEspEnVia } from "../src/esp-en-via-postprocess-autorouter"
type CircuitElement = Record<string, any>
const circuit = (await Bun.file("dist/index/circuit.json").json()) as CircuitElement[]
test("board and top-only assembly invariants are preserved", () => {
const board = circuit.find((element) => element.type === "pcb_board")
expect(board).toMatchObject({ width: 65.5, height: 42.5, num_layers: 4 })
const components = circuit.filter((element) => element.type === "pcb_component")
expect(components.length).toBeGreaterThan(0)
expect(components.every((component) => component.layer === "top")).toBe(true)
const mountingHoles = circuit.filter(
(element) =>
element.type === "pcb_hole" &&
element.pcb_component_id === null &&
element.hole_diameter === 2.7,
)
expect(mountingHoles).toHaveLength(4)
expect(circuit.filter((element) => element.type === "pcb_keepout")).toHaveLength(5)
})
test("manual FPC fanout and remapped camera routing are complete", () => {
const byName = new Map(
circuit
.filter((element) => element.type === "source_component")
.map((component) => [component.name, component]),
)
expect(byName.get("J_CAM")?.manufacturer_part_number).toBe("AFC01-S24FCA-00")
const fanoutSource = byName.get("CAM_FANOUT_VIAS")
const fanoutPcb = circuit.find(
(element) =>
element.type === "pcb_component" &&
element.source_component_id === fanoutSource?.source_component_id,
)
expect(fanoutPcb?.do_not_place).toBe(true)
const namedFanoutTraces = circuit.filter(
(element) =>
element.type === "source_trace" && element.name?.startsWith("TRACE_CAM_"),
)
expect(namedFanoutTraces).toHaveLength(39)
expect(
circuit.filter((element) => element.type === "pcb_port_not_connected_error"),
).toHaveLength(0)
})
test("routing phase selectors remain unique", () => {
const selectors = assertBoardRoutingPhaseSelectorsAreUnique()
expect(selectors.size).toBe(45)
expect(BOARD_ROUTING_PHASES).toHaveLength(4)
})
test("compact proven-route artifact is fully routed and error-free", () => {
const errors = circuit.filter((element) => element.type.endsWith("_error"))
expect(errors).toHaveLength(0)
expect(circuit.filter((element) => element.type === "pcb_trace")).toHaveLength(
149,
)
expect(circuit.filter((element) => element.type === "pcb_via")).toHaveLength(
151,
)
const routeContains = (
trace: CircuitElement,
x: number,
y: number,
routeType?: string,
) =>
trace.route?.some(
(point: CircuitElement) =>
Math.abs(point.x - x) < 0.001 &&
Math.abs(point.y - y) < 0.001 &&
(!routeType || point.route_type === routeType),
)
const traces = circuit.filter((element) => element.type === "pcb_trace")
const dnDuplicateJoin = traces.find(
(trace) =>
routeContains(trace, -26.1259568, 0.250064) &&
routeContains(trace, -26.1259568, 1.250062),
)
expect(
dnDuplicateJoin?.route.filter(
(point: CircuitElement) => point.route_type === "via",
),
).toHaveLength(2)
expect(
dnDuplicateJoin?.route.some(
(point: CircuitElement) =>
point.route_type === "wire" && point.layer === "bottom",
),
).toBe(true)
const usbGroundEscape = traces.find((trace) =>
routeContains(trace, -26.1258552, -2.7000063),
)
expect(routeContains(usbGroundEscape ?? {}, -25.1008692, -3.5, "via")).toBe(
true,
)
const pwdnGroundEscape = traces.find((trace) =>
routeContains(trace, -5.5, -9.175),
)
expect(routeContains(pwdnGroundEscape ?? {}, -4.25, -9.175, "via")).toBe(
true,
)
})
test("ESP32 EPAD uses four grounded gap vias and no via-in-pad", () => {
const u1Source = circuit.find(
(element) =>
element.type === "source_component" && element.name === "U1",
)
const u1Pcb = circuit.find(
(element) =>
element.type === "pcb_component" &&
element.source_component_id === u1Source?.source_component_id,
)
const pin41SourcePortIds = new Set(
circuit
.filter(
(element) =>
element.type === "source_port" &&
element.source_component_id === u1Source?.source_component_id &&
element.port_hints?.includes("pin41"),
)
.map((port) => port.source_port_id),
)
const pin41PcbPortIds = new Set(
circuit
.filter(
(element) =>
element.type === "pcb_port" &&
pin41SourcePortIds.has(element.source_port_id),
)
.map((port) => port.pcb_port_id),
)
const epadLands = circuit.filter(
(element) =>
element.type === "pcb_smtpad" &&
element.pcb_component_id === u1Pcb?.pcb_component_id &&
pin41PcbPortIds.has(element.pcb_port_id),
)
expect(epadLands).toHaveLength(9)
const expectedViaCenters = [
[2.799852, 5.8748527],
[4.1999, 5.8748527],
[2.799852, 7.2749007],
[4.1999, 7.2749007],
]
const gapVias = circuit.filter(
(element) =>
element.type === "pcb_via" &&
expectedViaCenters.some(
([x, y]) =>
Math.abs(element.x - x) < 1e-6 && Math.abs(element.y - y) < 1e-6,
),
)
expect(gapVias).toHaveLength(4)
const gnd = circuit.find(
(element) => element.type === "source_net" && element.name === "GND",
)
const epadPour = circuit.find(
(element) =>
element.type === "pcb_copper_pour" &&
element.layer === "top" &&
element.source_net_id === gnd?.source_net_id &&
element.covered_with_solder_mask === true,
)
expect(epadPour).toBeDefined()
const pourVertices = epadPour?.brep_shape.outer_ring.vertices ?? []
const pourBounds = {
minX: Math.min(...pourVertices.map((point: any) => point.x)),
maxX: Math.max(...pourVertices.map((point: any) => point.x)),
minY: Math.min(...pourVertices.map((point: any) => point.y)),
maxY: Math.max(...pourVertices.map((point: any) => point.y)),
}
for (const pad of epadLands) {
expect(pad.x >= pourBounds.minX && pad.x <= pourBounds.maxX).toBe(true)
expect(pad.y >= pourBounds.minY && pad.y <= pourBounds.maxY).toBe(true)
}
for (const via of gapVias) {
expect(via).toMatchObject({
source_net_id: gnd?.source_net_id,
hole_diameter: 0.2,
outer_diameter: 0.45,
from_layer: "top",
to_layer: "bottom",
})
expect(via.x >= pourBounds.minX && via.x <= pourBounds.maxX).toBe(true)
expect(via.y >= pourBounds.minY && via.y <= pourBounds.maxY).toBe(true)
for (const pad of epadLands) {
const dx = Math.max(Math.abs(via.x - pad.x) - pad.width / 2, 0)
const dy = Math.max(Math.abs(via.y - pad.y) - pad.height / 2, 0)
expect(Math.hypot(dx, dy)).toBeGreaterThan(via.outer_diameter / 2)
}
}
})
test("ESP_EN postprocessor changes only the colliding transition", () => {
const input = {
layerCount: 4,
minTraceWidth: 0.1,
bounds: { minX: -10, maxX: 10, minY: -10, maxY: 12 },
obstacles: [
{
type: "rect",
layers: ["top", "inner1", "inner2", "bottom"],
center: { x: 0, y: 10 },
width: 10,
height: 2,
connectedTo: [],
},
],
connections: [
{
name: "source_net_13",
pointsToConnect: [
{ x: -1, y: 0, layer: "top" },
{ x: 1, y: 0, layer: "top" },
],
},
],
} as any
const traces = [
{
type: "pcb_trace",
pcb_trace_id: "esp-en",
connection_name: "source_net_13",
route: [
{ route_type: "wire", x: 0, y: 8.9, width: 0.15, layer: "top" },
{
route_type: "via",
x: 0,
y: 8.9,
from_layer: "bottom",
to_layer: "top",
via_diameter: 0.4,
via_hole_diameter: 0.2,
},
{
route_type: "wire",
x: 0,
y: 8.9,
width: 0.15,
layer: "bottom",
},
{
route_type: "wire",
x: 0,
y: 7,
width: 0.15,
layer: "bottom",
},
],
},
{
type: "pcb_trace",
pcb_trace_id: "unchanged",
connection_name: "source_net_1",
route: [
{ route_type: "wire", x: -5, y: -5, width: 0.15, layer: "top" },
{ route_type: "wire", x: 5, y: -5, width: 0.15, layer: "top" },
],
},
] as any
const result = postprocessEspEnVia(input, traces, { x: 0.4, y: -0.3 })
expect(result[1]).toBe(traces[1])
expect(result[0].route.slice(0, 3)).toEqual([
{ route_type: "wire", x: 0.4, y: 8.6, width: 0.15, layer: "top" },
{
route_type: "via",
x: 0.4,
y: 8.6,
from_layer: "bottom",
to_layer: "top",
via_diameter: 0.4,
via_hole_diameter: 0.2,
},
{
route_type: "wire",
x: 0.4,
y: 8.6,
width: 0.15,
layer: "bottom",
},
])
expect(result[0].route[3]).toBe(traces[0].route[3])
})
test("ESP_EN postprocessor is a no-op when the router avoids the keepout", () => {
const input = {
layerCount: 4,
minTraceWidth: 0.1,
bounds: { minX: -10, maxX: 10, minY: -10, maxY: 12 },
obstacles: [
{
type: "rect",
layers: ["top", "inner1", "inner2", "bottom"],
center: { x: 0, y: 10 },
width: 10,
height: 2,
connectedTo: [],
},
],
connections: [
{
name: "source_net_13",
pointsToConnect: [
{ x: -1, y: 0, layer: "top" },
{ x: 1, y: 0, layer: "top" },
],
},
],
} as any
const traces = [
{
type: "pcb_trace",
pcb_trace_id: "esp-en-clear",
connection_name: "source_net_13",
route: [
{ route_type: "wire", x: 0, y: 7, width: 0.15, layer: "top" },
{ route_type: "wire", x: 2, y: 7, width: 0.15, layer: "top" },
],
},
] as any
expect(postprocessEspEnVia(input, traces, { x: 0.4, y: -0.3 })).toBe(
traces,
)
})