Skip to content
Merged
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
58 changes: 58 additions & 0 deletions lib/components/primitive-components/PlatedHole.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type {
PcbHoleCircularWithRectPad,
PcbHolePillWithRectPad,
PcbHoleRotatedPillWithRectPad,
PcbHoleWithPolygonPad,
} from "circuit-json"

export class PlatedHole extends PrimitiveComponent<typeof platedHoleProps> {
Expand Down Expand Up @@ -40,6 +41,25 @@ export class PlatedHole extends PrimitiveComponent<typeof platedHoleProps> {
if (props.shape === "pill_hole_with_rect_pad") {
return { width: props.rectPadWidth, height: props.rectPadHeight }
}
if (props.shape === "hole_with_polygon_pad") {
// Calculate bounding box from pad outline
if (!props.padOutline || props.padOutline.length === 0) {
throw new Error(
"padOutline is required for hole_with_polygon_pad shape",
)
}
const xs = props.padOutline.map((p) =>
typeof p.x === "number" ? p.x : parseFloat(String(p.x)),
)
const ys = props.padOutline.map((p) =>
typeof p.y === "number" ? p.y : parseFloat(String(p.y)),
)
const minX = Math.min(...xs)
const maxX = Math.max(...xs)
const minY = Math.min(...ys)
const maxY = Math.max(...ys)
return { width: maxX - minX, height: maxY - minY }
}
throw new Error(
`getPcbSize for shape "${(props as any).shape}" not implemented for ${this.componentName}`,
)
Expand Down Expand Up @@ -252,6 +272,44 @@ export class PlatedHole extends PrimitiveComponent<typeof platedHoleProps> {
pcb_group_id: this.getGroup()?.pcb_group_id ?? undefined,
} as PcbHolePillWithRectPad)
this.pcb_plated_hole_id = pcb_plated_hole.pcb_plated_hole_id
} else if (props.shape === "hole_with_polygon_pad") {
// Pad outline points are relative to the hole position (x, y)
const padOutline = (props.padOutline || []).map((point) => {
const x =
typeof point.x === "number" ? point.x : parseFloat(String(point.x))
const y =
typeof point.y === "number" ? point.y : parseFloat(String(point.y))
return {
x,
y,
}
})

const pcb_plated_hole = db.pcb_plated_hole.insert({
pcb_component_id,
pcb_port_id: this.matchedPort?.pcb_port_id!,
shape: "hole_with_polygon_pad" as const,
hole_shape: props.holeShape || "circle",
hole_diameter: props.holeDiameter,
hole_width: props.holeWidth,
hole_height: props.holeHeight,
pad_outline: padOutline,
hole_offset_x:
typeof props.holeOffsetX === "number"
? props.holeOffsetX
: parseFloat(String(props.holeOffsetX || 0)),
hole_offset_y:
typeof props.holeOffsetY === "number"
? props.holeOffsetY
: parseFloat(String(props.holeOffsetY || 0)),
port_hints: this.getNameAndAliases(),
x: position.x,
y: position.y,
layers: ["top", "bottom"],
subcircuit_id: subcircuit?.subcircuit_id ?? undefined,
pcb_group_id: this.getGroup()?.pcb_group_id ?? undefined,
} as PcbHoleWithPolygonPad)
this.pcb_plated_hole_id = pcb_plated_hole.pcb_plated_hole_id
}
}

Expand Down
16 changes: 16 additions & 0 deletions lib/utils/createComponentsFromCircuitJson.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,22 @@ export const createComponentsFromCircuitJson = (
holeOffsetY: elm.hole_offset_y,
}),
)
} else if (elm.shape === "hole_with_polygon_pad") {
components.push(
new PlatedHole({
pcbX: elm.x,
pcbY: elm.y,
shape: "hole_with_polygon_pad",
holeShape: elm.hole_shape || "circle",
holeDiameter: elm.hole_diameter,
holeWidth: elm.hole_width,
holeHeight: elm.hole_height,
padOutline: elm.pad_outline || [],
holeOffsetX: elm.hole_offset_x,
holeOffsetY: elm.hole_offset_y,
portHints: elm.port_hints,
}),
)
}
} else if (elm.type === "pcb_keepout" && elm.shape === "circle") {
components.push(
Expand Down
28 changes: 28 additions & 0 deletions lib/utils/obstacles/getObstaclesFromCircuitJson.ts
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,34 @@ export const getObstaclesFromCircuitJson = (
height: element.outer_height,
connectedTo: withNetId([element.pcb_plated_hole_id]),
})
} else if (element.shape === "hole_with_polygon_pad") {
// Calculate bounding box from pad outline
if (
"pad_outline" in element &&
element.pad_outline &&
element.pad_outline.length > 0
) {
const xs = element.pad_outline.map((p) => element.x + p.x)
const ys = element.pad_outline.map((p) => element.y + p.y)
const minX = Math.min(...xs)
const maxX = Math.max(...xs)
const minY = Math.min(...ys)
const maxY = Math.max(...ys)
const centerX = (minX + maxX) / 2
const centerY = (minY + maxY) / 2
obstacles.push({
// @ts-ignore
type: "rect",
layers: EVERY_LAYER,
center: {
x: centerX,
y: centerY,
},
width: maxX - minX,
height: maxY - minY,
connectedTo: withNetId([element.pcb_plated_hole_id]),
})
}
}
} else if (element.type === "pcb_trace") {
const traceObstacles = getObstaclesFromRoute(
Expand Down
20 changes: 20 additions & 0 deletions lib/utils/packing/getObstacleDimensionsFromElement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,26 @@ export function getObstacleDimensionsFromPlatedHole(
height: hole.outer_height,
}

case "hole_with_polygon_pad":
// Calculate bounding box from pad outline
if (
!("pad_outline" in hole) ||
!hole.pad_outline ||
hole.pad_outline.length === 0
) {
return null
}
const xs = hole.pad_outline.map((p) => p.x)
const ys = hole.pad_outline.map((p) => p.y)
const minX = Math.min(...xs)
const maxX = Math.max(...xs)
const minY = Math.min(...ys)
const maxY = Math.max(...ys)
return {
width: maxX - minX,
height: maxY - minY,
}

default:
return null
}
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
"@tscircuit/math-utils": "^0.0.29",
"@tscircuit/miniflex": "^0.0.4",
"@tscircuit/ngspice-spice-engine": "^0.0.2",
"@tscircuit/props": "^0.0.398",
"@tscircuit/props": "^0.0.403",
"@tscircuit/schematic-autolayout": "^0.0.6",
"@tscircuit/schematic-match-adapt": "^0.0.16",
"@tscircuit/schematic-trace-solver": "^v0.0.45",
Expand All @@ -59,13 +59,13 @@
"bun-match-svg": "0.0.12",
"calculate-elbow": "^0.0.12",
"chokidar-cli": "^3.0.0",
"circuit-json": "^0.0.307",
"circuit-json": "^0.0.308",
"circuit-json-to-bpc": "^0.0.13",
"circuit-json-to-connectivity-map": "^0.0.22",
"circuit-json-to-gltf": "^0.0.31",
"circuit-json-to-simple-3d": "^0.0.9",
"circuit-json-to-spice": "^0.0.16",
"circuit-to-svg": "^0.0.265",
"circuit-to-svg": "^0.0.269",
"concurrently": "^9.1.2",
"connectivity-map": "^1.0.0",
"debug": "^4.3.6",
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { test, expect } from "bun:test"
import { getTestFixture } from "tests/fixtures/get-test-fixture"

test("pcb plated hole with polygon pad", async () => {
const { circuit } = getTestFixture()

const footprint = (
<footprint>
<platedhole
shape="hole_with_polygon_pad"
holeShape="circle"
holeDiameter={1.5}
holeOffsetX={0}
holeOffsetY={0}
padOutline={[
{ x: -2, y: -2 },
{ x: 2, y: -2 },
{ x: 2, y: 2 },
{ x: -2, y: 2 },
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you pick something non-square

]}
/>
</footprint>
)

circuit.add(
<board width={40} height={40} material="fr4" thickness={1.6}>
<chip name="U1" layer="top" footprint={footprint} />
</board>,
)

circuit.render()
expect(circuit).toMatchPcbSnapshot(import.meta.path)
})