Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
22 changes: 22 additions & 0 deletions lib/check-courtyard-overlap/checkCourtyardOverlap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ import type {
PcbCourtyardCircle,
PcbCourtyardOutline,
} from "circuit-json"
import {
arePlacementLayersSeparated,
getElementPlacementLayer,
getPcbComponentPlacementLayerMap,
type PcbPlacementLayer,
} from "lib/util/getPcbPlacementLayer"

type CourtyardElement =
| PcbCourtyardRect
Expand Down Expand Up @@ -92,6 +98,7 @@ function polygonsOverlap(polyA: Point[], polyB: Point[]): boolean {
export function checkCourtyardOverlap(
circuitJson: AnyCircuitElement[],
): PcbCourtyardOverlapError[] {
const componentLayerMap = getPcbComponentPlacementLayerMap(circuitJson)
const courtyards = circuitJson.filter(
(el): el is CourtyardElement =>
el.type === "pcb_courtyard_rect" ||
Expand All @@ -101,10 +108,16 @@ export function checkCourtyardOverlap(

// Group by component
const byComponent = new Map<string, CourtyardElement[]>()
const componentPlacementLayers = new Map<string, PcbPlacementLayer | null>()
for (const el of courtyards) {
const id = el.pcb_component_id
if (!byComponent.has(id)) byComponent.set(id, [])
byComponent.get(id)!.push(el)
componentPlacementLayers.set(
id,
componentPlacementLayers.get(id) ??
getElementPlacementLayer(el, componentLayerMap),
)
}

const componentIds = Array.from(byComponent.keys())
Expand All @@ -115,6 +128,15 @@ export function checkCourtyardOverlap(
const idA = componentIds[i]
const idB = componentIds[j]

if (
arePlacementLayersSeparated(
componentPlacementLayers.get(idA) ?? null,
componentPlacementLayers.get(idB) ?? null,
)
) {
continue
}

let overlapping = false
outer: for (const a of byComponent.get(idA)!) {
for (const b of byComponent.get(idB)!) {
Expand Down
44 changes: 42 additions & 2 deletions lib/check-pcb-components-overlap/checkPcbComponentOverlap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import {
import { doBoundsOverlap } from "@tscircuit/math-utils"
import type { AnyCircuitElement, PcbFootprintOverlapError } from "circuit-json"
import { getFullConnectivityMapFromCircuitJson } from "circuit-json-to-connectivity-map"
import type { Collidable } from "lib/check-each-pcb-trace-non-overlapping/getCollidableBounds"
import { getLayersOfPcbElement } from "lib/util/getLayersOfPcbElement"
import {
getReadableNameForElementId,
getReadableNameForPort,
Expand All @@ -14,10 +16,16 @@ import {
type OverlappableElement,
doPcbElementsOverlap,
} from "./doPcbElementsOverlap"
import {
arePlacementLayerSetsSeparated,
getPlacementLayersFromLayerNames,
type PcbPlacementLayer,
} from "lib/util/getPcbPlacementLayer"

interface ComponentWithElements {
component_id: string
elements: OverlappableElement[]
placementLayers: PcbPlacementLayer[]
bounds: {
minX: number
minY: number
Expand All @@ -39,6 +47,11 @@ const formatOverlapElementDescription = (
return readableName === "element" ? `[${id}]` : readableName
}

const getPlacementLayersForOverlappableElement = (
element: OverlappableElement,
): PcbPlacementLayer[] =>
getPlacementLayersFromLayerNames(getLayersOfPcbElement(element as Collidable))

/**
* Check for overlapping PCB components
* Returns errors for components that overlap inappropriately
Expand All @@ -63,36 +76,54 @@ export function checkPcbComponentOverlap(
for (const pad of smtPads) {
const componentId =
pad.pcb_component_id || `standalone_pad_${getPrimaryId(pad)}`
const placementLayers = getPlacementLayersForOverlappableElement(pad)
if (!componentMap.has(componentId)) {
componentMap.set(componentId, {
component_id: componentId,
elements: [],
placementLayers: [...placementLayers],
bounds: { minX: 0, minY: 0, maxX: 0, maxY: 0 },
})
}
componentMap.get(componentId)!.elements.push(pad)
const componentData = componentMap.get(componentId)!
for (const layer of placementLayers) {
if (!componentData.placementLayers.includes(layer)) {
componentData.placementLayers.push(layer)
}
}
componentData.elements.push(pad)
}

// Group plated holes by component (or treat as standalone)
for (const hole of platedHoles) {
const componentId =
hole.pcb_component_id || `standalone_plated_hole_${getPrimaryId(hole)}`
const placementLayers = getPlacementLayersForOverlappableElement(hole)
if (!componentMap.has(componentId)) {
componentMap.set(componentId, {
component_id: componentId,
elements: [],
placementLayers: [...placementLayers],
bounds: { minX: 0, minY: 0, maxX: 0, maxY: 0 },
})
}
componentMap.get(componentId)!.elements.push(hole)
const componentData = componentMap.get(componentId)!
for (const layer of placementLayers) {
if (!componentData.placementLayers.includes(layer)) {
componentData.placementLayers.push(layer)
}
}
componentData.elements.push(hole)
}

// Holes typically don't have pcb_component_id, treat each as standalone
for (const hole of holes) {
const placementLayers = getPlacementLayersForOverlappableElement(hole)
const componentId = `standalone_hole_${getPrimaryId(hole)}`
componentMap.set(componentId, {
component_id: componentId,
elements: [hole],
placementLayers,
bounds: { minX: 0, minY: 0, maxX: 0, maxY: 0 },
})
}
Expand All @@ -113,6 +144,15 @@ export function checkPcbComponentOverlap(
const comp1 = componentsWithElements[i]
const comp2 = componentsWithElements[j]

if (
arePlacementLayerSetsSeparated(
comp1.placementLayers,
comp2.placementLayers,
)
) {
continue
}

// First check if component bounds overlap
if (!doBoundsOverlap(comp1.bounds, comp2.bounds)) {
continue
Expand Down
79 changes: 79 additions & 0 deletions lib/util/getPcbPlacementLayer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import type { AnyCircuitElement } from "circuit-json"

export type PcbPlacementLayer = "top" | "bottom"

function normalizePlacementLayer(
layer: string | null | undefined,
): PcbPlacementLayer | null {
if (layer === "top" || layer === "bottom") return layer
return null
}

export function getPcbComponentPlacementLayerMap(
circuitJson: AnyCircuitElement[],
): Map<string, PcbPlacementLayer> {
const componentLayerMap = new Map<string, PcbPlacementLayer>()

for (const element of circuitJson) {
if (element.type !== "pcb_component") continue

const placementLayer = normalizePlacementLayer(element.layer)
if (!placementLayer) continue

componentLayerMap.set(element.pcb_component_id, placementLayer)
}

return componentLayerMap
}

export function getElementPlacementLayer(
element: {
pcb_component_id?: string
layer?: string
layers?: string[]
},
componentLayerMap?: ReadonlyMap<string, PcbPlacementLayer>,
): PcbPlacementLayer | null {
const componentPlacementLayer = element.pcb_component_id
? (componentLayerMap?.get(element.pcb_component_id) ?? null)
: null
if (componentPlacementLayer) return componentPlacementLayer

const singleLayerPlacement = normalizePlacementLayer(element.layer)
if (singleLayerPlacement) return singleLayerPlacement

const hasTop = element.layers?.includes("top") ?? false
const hasBottom = element.layers?.includes("bottom") ?? false

if (hasTop === hasBottom) return null
return hasTop ? "top" : "bottom"
}

export function arePlacementLayersSeparated(
layerA: PcbPlacementLayer | null,
layerB: PcbPlacementLayer | null,
): boolean {
return layerA !== null && layerB !== null && layerA !== layerB
}

export function getPlacementLayersFromLayerNames(
layerNames: readonly string[] | null | undefined,
): PcbPlacementLayer[] {
const placementLayers: PcbPlacementLayer[] = []

if (layerNames?.includes("top")) placementLayers.push("top")
if (layerNames?.includes("bottom")) placementLayers.push("bottom")

return placementLayers
}

export function arePlacementLayerSetsSeparated(
layersA: readonly PcbPlacementLayer[],
layersB: readonly PcbPlacementLayer[],
): boolean {
return (
layersA.length > 0 &&
layersB.length > 0 &&
!layersA.some((layer) => layersB.includes(layer))
)
}
Loading
Loading