0hmX/am3352-ddr-track-a-local-dfm-wip-20261007
Experimental RJ45-corrected board with isolated PG indication. 1005/1005 source joins pass; 15 expanded-guard physical errors, sourcing and qualification gates remain. Not fabrication-ready.
- Version
- 0.0.14
- License
- unset
- Stars
- 0
tests/subcircuit-grouping.test.tsx
import { expect, test } from "bun:test"
import { Circuit } from "@tscircuit/core"
import { Children, cloneElement } from "react"
import { getFullConnectivityMapFromCircuitJson } from "circuit-json-to-connectivity-map"
import Board from "../index"
import { ProvisionalDdrRoutes } from "../layout/ProvisionalDdrRoutes"
import { ProvisionalDdrAddressRoutes } from "../layout/ProvisionalDdrAddressRoutes"
import { ProvisionalDdrByte0Routes } from "../layout/ProvisionalDdrByte0Routes"
import { ProvisionalDdrByte1Routes } from "../layout/ProvisionalDdrByte1Routes"
import { subcircuitComponents } from "../subcircuits"
import { parts as supportParts } from "../support"
async function compile(flat: boolean) {
const circuit = new Circuit({ platform: { partsEngineDisabled: true, netlistDrcChecksDisabled: true } })
circuit.schematicDisabled = flat
const board = Board()
const children = Children.toArray(board.props.children).filter((child: any) => ![ProvisionalDdrRoutes, ProvisionalDdrAddressRoutes, ProvisionalDdrByte0Routes, ProvisionalDdrByte1Routes].includes(child.type))
// Expand only the known wrapper components; keep the production tree idiomatic.
const expanded = flat ? children.map((child: any) => subcircuitComponents.includes(child.type) ? child.type(child.props) : child) : children
circuit.add(cloneElement(board, {}, flat ? expanded.flatMap((child: any) =>
child.type === "subcircuit" ? Children.toArray(child.props.children) : [child]).map((child: any, key) => cloneElement(child, { key })) : children))
await circuit.renderUntilSettled()
return circuit.getCircuitJson() as any[]
}
function physicalParts(records: any[]) {
const names = new Map(records.filter(r => r.type === "source_component").map(r => [r.source_component_id, r.name]))
return records.filter(r => r.type === "pcb_component").map(r => ({
name: names.get(r.source_component_id), center: r.center, rotation: r.rotation,
layer: r.layer, width: r.width, height: r.height,
})).sort((a,b) => a.name.localeCompare(b.name))
}
function logicalNets(records: any[]) {
const map = getFullConnectivityMapFromCircuitJson(records)
const names = new Map(records.filter(r => r.type === "source_component").map(r => [r.source_component_id, r.name]))
const nets = new Map<string, string[]>()
for (const port of records.filter(r => r.type === "source_port" && names.has(r.source_component_id))) {
const key = map.getNetConnectedToId(port.source_port_id) ?? port.source_port_id
nets.set(key, [...(nets.get(key) ?? []), `${names.get(port.source_component_id)}.${port.name}`])
}
return [...nets.values()].map(ports => ports.sort().join(",")).sort()
}
test("functional subcircuits preserve placement and electrical connectivity across both board sides", async () => {
const flat = await compile(true)
const grouped = await compile(false)
expect(physicalParts(grouped)).toEqual(physicalParts(flat))
expect(logicalNets(grouped)).toEqual(logicalNets(flat))
expect(grouped.filter(r => r.type.endsWith("_error"))).toEqual([])
const sourceNames = grouped.filter(r => r.type === "source_component").map(r => r.name)
for (const part of supportParts) expect(sourceNames.filter(name => name === part.name)).toHaveLength(1)
const groups = grouped.filter(r => r.type === "pcb_group")
expect(groups.length).toBe(11)
const parts = grouped.filter(r => r.type === "pcb_component")
for (const part of parts) {
const group = groups.find(g => g.pcb_group_id === part.pcb_group_id)
expect(group).toBeDefined()
expect(group.is_subcircuit).toBe(true)
expect(Math.abs(part.center.x - group.center.x) + part.width / 2).toBeLessThanOrEqual(group.width / 2 + 1e-7)
expect(Math.abs(part.center.y - group.center.y) + part.height / 2).toBeLessThanOrEqual(group.height / 2 + 1e-7)
}
expect(parts.some(p => p.layer === "top")).toBe(true)
expect(parts.some(p => p.layer === "bottom")).toBe(true)
}, 120000)