Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/components/base-components/Renderable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export const orderedRenderPhases = [
"PcbLayout",
"PcbBoardAutoSize",
"PanelLayout",
"PcbAutoBreakoutPointRender",
Comment thread
MustafaMulla29 marked this conversation as resolved.
Outdated
"PcbTraceHintRender",
"PcbManualTraceRender",
"PcbTraceRender",
Expand Down
19 changes: 14 additions & 5 deletions lib/components/primitive-components/BaseBreakoutPoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,20 @@ export class BaseBreakoutPoint<
return trace?.connected_source_net_ids[0]
}

_renderPcbBreakoutPoint(): void {
if (this.pcb_breakout_point_id) return
_renderPcbBreakoutPoint(position?: { x: number; y: number }): void {
Comment thread
MustafaMulla29 marked this conversation as resolved.
Outdated
if (this.root?.pcbDisabled) return
if (this.pcb_breakout_point_id) {
if (position) {
const { db } = this.root!
db.pcb_breakout_point.update(this.pcb_breakout_point_id, {
x: position.x,
y: position.y,
})
}
return
}
const { db } = this.root!
const position = this._getGlobalPcbPositionBeforeLayout()
const pos = position ?? this._getGlobalPcbPositionBeforeLayout()
const group = this.parent?.getGroup()
const subcircuit = this.getSubcircuit()
if (!group || !group.pcb_group_id) return
Expand All @@ -62,8 +71,8 @@ export class BaseBreakoutPoint<
: this.matchedPort
? this._getSourceNetIdForPort(this.matchedPort)
: undefined,
x: position.x,
y: position.y,
x: pos.x,
y: pos.y,
})
this.pcb_breakout_point_id = pcb_breakout_point.pcb_breakout_point_id
}
Expand Down
33 changes: 33 additions & 0 deletions lib/components/primitive-components/Breakout/Breakout.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { breakoutProps } from "@tscircuit/props"
import { BreakoutPointSolver } from "@tscircuit/breakout-point-solver"
import { Group } from "../Group/Group"
import { AutoplacedBreakoutPoint } from "../AutoplacedBreakoutPoint"
import { BreakoutPoint } from "../BreakoutPoint"
import { Trace } from "../Trace/Trace"
import type { Port } from "../Port"
import type { z } from "zod"
import { createBreakoutPointSolverInput } from "./createBreakoutPointSolverInput"

export class Breakout extends Group<typeof breakoutProps> {
constructor(props: z.input<typeof breakoutProps>) {
Expand Down Expand Up @@ -72,6 +74,37 @@ export class Breakout extends Group<typeof breakoutProps> {
}
}

doInitialPcbAutoBreakoutPointRender(): void {
if (this.root?.pcbDisabled) return

const props = this._parsedProps as z.infer<typeof breakoutProps>
if (!props.autorouter) return

const solverInput = createBreakoutPointSolverInput(this)
if (!solverInput) return

const solver = new BreakoutPointSolver(solverInput)
solver.solve()
const output = solver.getOutput()

const autoBreakoutPoints = this.children.filter(
(c) => c instanceof AutoplacedBreakoutPoint,
) as AutoplacedBreakoutPoint[]

for (const solvedPoint of output.breakoutPoints) {
const match = autoBreakoutPoints.find(
Comment thread
MustafaMulla29 marked this conversation as resolved.
Outdated
(child) =>
child.matchedPort?.source_port_id === solvedPoint.sourcePortId,
)
if (match) {
match._renderPcbBreakoutPoint({
Comment thread
MustafaMulla29 marked this conversation as resolved.
Outdated
x: solvedPoint.x,
y: solvedPoint.y,
})
}
}
}

doInitialPcbPrimitiveRender(): void {
super.doInitialPcbPrimitiveRender()
if (this.root?.pcbDisabled) return
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
import type { BreakoutPointSolverInput } from "@tscircuit/breakout-point-solver"
import type { CircuitJsonUtilObjects } from "@tscircuit/circuit-json-util"
import type { PcbPort } from "circuit-json"
import type { Breakout } from "./Breakout"

type BreakoutPcbLayer = "top" | "bottom"

const toBreakoutPcbLayer = (
Comment thread
MustafaMulla29 marked this conversation as resolved.
Outdated
layer: string | undefined,
): BreakoutPcbLayer | undefined => {
if (layer === "top" || layer === "bottom") return layer
return undefined
}

const getPcbPortPad = (db: CircuitJsonUtilObjects, pcbPortId: string) => {
return (
db.pcb_smtpad.getWhere({ pcb_port_id: pcbPortId }) ??
db.pcb_plated_hole.getWhere({ pcb_port_id: pcbPortId })
)
}

const getPortSize = (
Comment thread
MustafaMulla29 marked this conversation as resolved.
Outdated
db: CircuitJsonUtilObjects,
pcbPort: PcbPort,
): { width?: number; height?: number; ccwRotationDegrees?: number } => {
const pad = getPcbPortPad(db, pcbPort.pcb_port_id) as any
if (!pad) return {}
if (pad.shape === "circle") {
return { width: pad.radius * 2, height: pad.radius * 2 }
}
if (pad.shape === "pill" || pad.shape === "rotated_pill") {
return {
width: pad.width,
height: pad.height,
ccwRotationDegrees: pad.ccw_rotation,
}
}
if (pad.shape === "rect" || pad.shape === "rotated_rect") {
return {
width: pad.width,
height: pad.height,
ccwRotationDegrees: pad.ccw_rotation,
}
}
return {}
}

const getPortLabel = (db: CircuitJsonUtilObjects, sourcePortId?: string) => {
if (!sourcePortId) return undefined
const sourcePort = db.source_port.get(sourcePortId)
const sourceComponent = sourcePort?.source_component_id
? db.source_component.get(sourcePort.source_component_id)
: undefined
if (!sourcePort) return undefined
return sourceComponent?.name
? `${sourceComponent.name}.${sourcePort.name}`
: sourcePort.name
}

const toBreakoutPort = (db: CircuitJsonUtilObjects, pcbPort: PcbPort) => ({
sourcePortId: pcbPort.source_port_id!,
position: { x: pcbPort.x!, y: pcbPort.y! },
...getPortSize(db, pcbPort),
layer: toBreakoutPcbLayer(pcbPort.layers?.[0]) ?? "top",
label: getPortLabel(db, pcbPort.source_port_id),
})

const getPadDimensions = (pad: any) => {
Comment thread
MustafaMulla29 marked this conversation as resolved.
Outdated
if (pad.shape === "circle") {
return {
width: pad.radius * 2,
height: pad.radius * 2,
}
}
if (pad.shape === "rect" || pad.shape === "rotated_rect") {
return {
width: pad.width,
height: pad.height,
ccwRotationDegrees: pad.ccw_rotation,
}
}
if (pad.shape === "pill" || pad.shape === "rotated_pill") {
return {
width: pad.width,
height: pad.height,
ccwRotationDegrees: pad.ccw_rotation,
}
}
if (pad.shape === "oval" || pad.shape === "circular_hole_with_rect_pad") {
return {
width: pad.outer_width ?? pad.width ?? pad.outer_diameter,
height: pad.outer_height ?? pad.height ?? pad.outer_diameter,
}
}
return null
}

export const createBreakoutPointSolverInput = (
breakout: Breakout,
): BreakoutPointSolverInput | null => {
if (!breakout.root || !breakout.pcb_group_id) return null

const { db } = breakout.root
const pcbGroup = db.pcb_group.get(breakout.pcb_group_id)
if (!pcbGroup || !pcbGroup.width || !pcbGroup.height) return null

const sourcePortIdToPcbPort = new Map<string, PcbPort>()
for (const pcbPort of db.pcb_port.list()) {
if (!pcbPort.source_port_id) continue
sourcePortIdToPcbPort.set(pcbPort.source_port_id, pcbPort)
}

const boundsMinX = pcbGroup.center.x - pcbGroup.width / 2
const boundsMaxX = pcbGroup.center.x + pcbGroup.width / 2
const boundsMinY = pcbGroup.center.y - pcbGroup.height / 2
const boundsMaxY = pcbGroup.center.y + pcbGroup.height / 2

const traces: BreakoutPointSolverInput["traces"] = []
for (const sourceTrace of db.source_trace.list()) {
const pcbPorts = sourceTrace.connected_source_port_ids
.map((sourcePortId) => sourcePortIdToPcbPort.get(sourcePortId))
.filter((port): port is PcbPort => Boolean(port))

const insidePorts = pcbPorts.filter(
(port) => port.pcb_group_id === breakout.pcb_group_id,
)
// Only include outside ports that are geometrically outside the bounds.
// Components may sit inside the boundary area without belonging to the
// breakout group (different pcb_group_id); passing them to the solver
// as "outside" would produce invalid boundary intersections.
const outsidePorts = pcbPorts.filter(
(port) =>
port.pcb_group_id !== breakout.pcb_group_id &&
!(
port.x! >= boundsMinX &&
port.x! <= boundsMaxX &&
port.y! >= boundsMinY &&
port.y! <= boundsMaxY
),
)

if (insidePorts.length === 0 || outsidePorts.length === 0) continue

traces.push({
sourceTraceId: sourceTrace.source_trace_id,
insidePorts: insidePorts.map((port) => toBreakoutPort(db, port)),
outsidePorts: outsidePorts.map((port) => toBreakoutPort(db, port)),
})
}

if (traces.length === 0) return null

const pads: BreakoutPointSolverInput["pads"] = []
for (const pad of [
...db.pcb_smtpad.list(),
...db.pcb_plated_hole.list(),
] as any[]) {
const dimensions = getPadDimensions(pad)
if (!dimensions?.width || !dimensions?.height) continue
const pcbPort = pad.pcb_port_id ? db.pcb_port.get(pad.pcb_port_id) : null
pads.push({
center: { x: pad.x, y: pad.y },
width: dimensions.width,
height: dimensions.height,
ccwRotationDegrees: dimensions.ccwRotationDegrees,
layer: toBreakoutPcbLayer(pad.layer) ?? "top",
sourcePortIds: pcbPort?.source_port_id ? [pcbPort.source_port_id] : [],
label: getPortLabel(db, pcbPort?.source_port_id),
})
}

const components = db.pcb_component
.list()
.filter((component) => component.width && component.height)
.map((component) => ({
center: component.center,
width: component.width,
height: component.height,
ccwRotationDegrees: component.rotation,
layer: toBreakoutPcbLayer(component.layer),
label: component.pcb_component_id,
}))

const usedBoundaryPoints = db.pcb_breakout_point
.list()
.filter((point) => point.pcb_group_id === breakout.pcb_group_id)
.map((point) => ({ x: point.x, y: point.y }))

return {
bounds: {
minX: boundsMinX,
maxX: boundsMaxX,
minY: boundsMinY,
maxY: boundsMaxY,
},
boundaryPointSpacing: 0.5,
traces,
pads,
components,
usedBoundaryPoints,
}
}
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"@biomejs/biome": "^1.8.3",
"@resvg/resvg-js": "^2.6.2",
"@tscircuit/alphabet": "0.0.25",
"@tscircuit/breakout-point-solver": "github:tscircuit/breakout-point-solver#bac9629",
"@tscircuit/capacity-autorouter": "^0.0.529",
"@tscircuit/checks": "0.0.133",
"@tscircuit/circuit-json-util": "^0.0.94",
Expand Down Expand Up @@ -74,7 +75,7 @@
"debug": "^4.3.6",
"eecircuit-engine": "^1.5.6",
"flatbush": "^4.5.0",
"graphics-debug": "^0.0.89",
"graphics-debug": "^0.0.95",
"howfat": "^0.3.8",
"kicad-to-circuit-json": "^0.0.60",
"kicadts": "^0.0.35",
Expand Down
Loading
Loading