Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -891,10 +891,11 @@ export class NormalComponent<
subcircuit_id: subcircuit.subcircuit_id ?? undefined,
do_not_place: props.doNotPlace ?? false,
obstructs_within_bounds: props.obstructsWithinBounds ?? true,
should_be_on_edge_of_board: props.shouldBeOnEdgeOfBoard,
metadata: props.kicadFootprintMetadata
? { kicad_footprint: props.kicadFootprintMetadata }
: undefined,
})
} as any)

const footprint = this.resolveFootprint()

Expand Down
3 changes: 2 additions & 1 deletion lib/components/normal-components/Chip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,11 +150,12 @@ export class Chip<PinLabels extends string = never> extends NormalComponent<
subcircuit_id: this.getSubcircuit().subcircuit_id ?? undefined,
do_not_place: props.doNotPlace ?? false,
obstructs_within_bounds: props.obstructsWithinBounds ?? true,
should_be_on_edge_of_board: props.shouldBeOnEdgeOfBoard,
is_allowed_to_be_off_board: props.allowOffBoard ?? false,
metadata: props.kicadFootprintMetadata
? { kicad_footprint: props.kicadFootprintMetadata }
: undefined,
})
} as any)

this.pcb_component_id = pcb_component.pcb_component_id
}
Expand Down
3 changes: 2 additions & 1 deletion lib/components/normal-components/Jumper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,11 @@ export class Jumper<PinLabels extends string = never> extends NormalComponent<
subcircuit_id: this.getSubcircuit().subcircuit_id ?? undefined,
do_not_place: props.doNotPlace ?? false,
obstructs_within_bounds: props.obstructsWithinBounds ?? true,
should_be_on_edge_of_board: props.shouldBeOnEdgeOfBoard,
metadata: props.kicadFootprintMetadata
? { kicad_footprint: props.kicadFootprintMetadata }
: undefined,
})
} as any)

this.pcb_component_id = pcb_component.pcb_component_id
}
Expand Down
3 changes: 2 additions & 1 deletion lib/components/normal-components/SolderJumper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,10 +232,11 @@ export class SolderJumper<
subcircuit_id: this.getSubcircuit().subcircuit_id ?? undefined,
do_not_place: props.doNotPlace ?? false,
obstructs_within_bounds: props.obstructsWithinBounds ?? true,
should_be_on_edge_of_board: props.shouldBeOnEdgeOfBoard,
metadata: props.kicadFootprintMetadata
? { kicad_footprint: props.kicadFootprintMetadata }
: undefined,
})
} as any)

this.pcb_component_id = pcb_component.pcb_component_id
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ export const Group_doInitialPcbLayoutPack = (group: Group) => {
}

// Keep all circuit elements; static components will remain fixed during packing
const filteredCircuitJson = db.toArray()
const filteredCircuitJson = db.toArray() as any

// Calculate bounds if width and height are specified
let bounds:
Expand Down
94 changes: 94 additions & 0 deletions tests/features/pcb-pack-layout/edge-boundary-placement.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { test, expect } from "bun:test"
import { RootCircuit } from "lib/RootCircuit"

test("pcbPack places chip on board edge when shouldBeOnEdgeOfBoard is true", async () => {
const circuit = new RootCircuit()

circuit.add(
<board width="40mm" height="40mm" pcbPack pcbGap="0mm">
<chip name="U1" footprint="soic8" shouldBeOnEdgeOfBoard />
<resistor name="R1" resistance="1k" footprint="0402" />
</board>,
)

await circuit.renderUntilSettled()

const pcbComponents = circuit.db.pcb_component.list()
const sourceComponents = circuit.db.source_component.list()
const smtpads = circuit.db.pcb_smtpad.list()

console.log("SMTPADS:")
console.log(JSON.stringify(smtpads, null, 2))

const u1 = pcbComponents.find((c) => {
const sourceComp = sourceComponents.find(
(sc) => sc.source_component_id === c.source_component_id,
)
return sourceComp?.name === "U1"
})

expect(u1).toBeDefined()
if (!u1) return

// Find courtyard outlines of U1
const u1Courtyards = circuit.db.pcb_courtyard_outline
.list()
.filter((c) => c.pcb_component_id === u1.pcb_component_id)

let minBoundaryX = Infinity,
maxBoundaryX = -Infinity
let minBoundaryY = Infinity,
maxBoundaryY = -Infinity

if (u1Courtyards.length > 0) {
for (const cy of u1Courtyards) {
for (const pt of cy.outline) {
minBoundaryX = Math.min(minBoundaryX, pt.x)
maxBoundaryX = Math.max(maxBoundaryX, pt.x)
minBoundaryY = Math.min(minBoundaryY, pt.y)
maxBoundaryY = Math.max(maxBoundaryY, pt.y)
}
}
} else {
// Fallback to pads if no courtyard outline exists
const u1Pads = smtpads.filter(
(p) => p.pcb_component_id === u1.pcb_component_id,
)
for (const pad of u1Pads) {
const p = pad as any
const w = p.shape === "rect" ? p.width : p.radius * 2
const h = p.shape === "rect" ? p.height : p.radius * 2
minBoundaryX = Math.min(minBoundaryX, p.x - w / 2)
maxBoundaryX = Math.max(maxBoundaryX, p.x + w / 2)
minBoundaryY = Math.min(minBoundaryY, p.y - h / 2)
maxBoundaryY = Math.max(maxBoundaryY, p.y + h / 2)
}
}

console.log("Calculated boundary absolute bounds for U1:", {
minBoundaryX,
maxBoundaryX,
minBoundaryY,
maxBoundaryY,
width: maxBoundaryX - minBoundaryX,
height: maxBoundaryY - minBoundaryY,
})

const distToLeft = Math.abs(minBoundaryX - -20)
const distToRight = Math.abs(maxBoundaryX - 20)
const distToBottom = Math.abs(minBoundaryY - -20)
const distToTop = Math.abs(maxBoundaryY - 20)

const minDistance = Math.min(distToLeft, distToRight, distToBottom, distToTop)

console.log("Distance from absolute bounds to board edges:", {
distToLeft,
distToRight,
distToBottom,
distToTop,
minDistance,
})

// The component courtyard/pad boundary edge must be extremely close to the board boundary (within 0.1 mm)
expect(minDistance).toBeLessThan(0.1)
})
Loading