Skip to content
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
"@tscircuit/checks": "^0.0.85",
"@tscircuit/math-utils": "^0.0.27",
"@tscircuit/capacity-autorouter": "^0.0.131",
"calculate-packing": "^0.0.48",
"calculate-packing": "^0.0.50",
"@types/jsdom": "^21.1.7",
"@types/react": "19",
"@types/react-dom": "19",
Expand Down
19 changes: 13 additions & 6 deletions src/BoardGeomBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import {
colors,
boardMaterialColors,
tracesMaterialColors,
BOARD_SURFACE_OFFSET,
} from "./geoms/constants"
import { extrudeLinear } from "@jscad/modeling/src/operations/extrusions"
import { expand } from "@jscad/modeling/src/operations/expansions"
Expand Down Expand Up @@ -378,7 +379,9 @@ export class BoardGeomBuilder {

private processCopperPour(pour: PcbCopperPour) {
const layerSign = pour.layer === "bottom" ? -1 : 1
const zPos = (layerSign * this.ctx.pcbThickness) / 2 + layerSign * M
const zPos =
(layerSign * this.ctx.pcbThickness) / 2 +
layerSign * BOARD_SURFACE_OFFSET.copper

let pourGeom: Geom3 | null = null

Expand Down Expand Up @@ -655,7 +658,9 @@ export class BoardGeomBuilder {

private processPad(pad: PcbSmtPad) {
const layerSign = pad.layer === "bottom" ? -1 : 1
const zPos = (layerSign * this.ctx.pcbThickness) / 2 + layerSign * M * 2 // Slightly offset from board surface
const zPos =
(layerSign * this.ctx.pcbThickness) / 2 +
layerSign * BOARD_SURFACE_OFFSET.copper // Slightly offset from board surface

const rectBorderRadius = extractRectBorderRadius(pad)

Expand Down Expand Up @@ -716,7 +721,9 @@ export class BoardGeomBuilder {
const finishSegment = () => {
if (currentSegmentPoints.length >= 2 && currentLayer) {
const layerSign = currentLayer === "bottom" ? -1 : 1
const zPos = (layerSign * this.ctx.pcbThickness) / 2 + layerSign * M
const zCenter =
(layerSign * this.ctx.pcbThickness) / 2 +
layerSign * BOARD_SURFACE_OFFSET.traces

const linePath = line(currentSegmentPoints)
// Use the width of the starting point of the segment for consistency
Expand All @@ -725,7 +732,7 @@ export class BoardGeomBuilder {
linePath,
)
let traceGeom = translate(
[0, 0, zPos],
[0, 0, zCenter - M / 2],
extrudeLinear({ height: M }, expandedPath),
)

Expand All @@ -740,7 +747,7 @@ export class BoardGeomBuilder {
)
if (startHole) {
const cuttingCylinder = cylinder({
center: [startPointCoords[0], startPointCoords[1], zPos + M / 2],
center: [startPointCoords[0], startPointCoords[1], zCenter],
radius: startHole.diameter / 2 + M,
height: M,
})
Expand All @@ -750,7 +757,7 @@ export class BoardGeomBuilder {
const endHole = this.getHoleToCut(endPointCoords[0], endPointCoords[1])
if (endHole) {
const cuttingCylinder = cylinder({
center: [endPointCoords[0], endPointCoords[1], zPos + M / 2],
center: [endPointCoords[0], endPointCoords[1], zCenter],
radius: endHole.diameter / 2 + M,
height: M,
})
Expand Down
5 changes: 5 additions & 0 deletions src/geoms/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ import type { PcbBoard } from "circuit-json"

export const M = 0.01

export const BOARD_SURFACE_OFFSET = {
traces: 0.001,
copper: 0.002,
} as const

export const colors = {
copper: [0.9, 0.6, 0.2],
fr4Green: [0.04, 0.16, 0.08],
Expand Down
51 changes: 29 additions & 22 deletions src/geoms/plated-hole.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
subtract,
union,
} from "@jscad/modeling/src/operations/booleans"
import { M, colors } from "./constants"
import { BOARD_SURFACE_OFFSET, M, colors } from "./constants"
import type { GeomContext } from "../GeomContext"
import { extrudeLinear } from "@jscad/modeling/src/operations/extrusions"
import { translate } from "@jscad/modeling/src/operations/transforms"
Expand Down Expand Up @@ -67,10 +67,13 @@ export const platedHole = (
const { clipGeom } = options
if (!(plated_hole as PCBPlatedHole).shape) plated_hole.shape = "circle"
const throughDrillHeight = ctx.pcbThickness + 2 * platedHoleLipHeight + 4 * M
const topSurfaceZ = ctx.pcbThickness / 2 + BOARD_SURFACE_OFFSET.copper
const bottomSurfaceZ = -ctx.pcbThickness / 2 - BOARD_SURFACE_OFFSET.copper
const copperSpan = topSurfaceZ - bottomSurfaceZ
if (plated_hole.shape === "circle") {
const outerDiameter =
plated_hole.outer_diameter ?? Math.max(plated_hole.hole_diameter, 0)
const copperHeight = ctx.pcbThickness + 2 * platedHoleLipHeight
const copperHeight = copperSpan
const copperBody = cylinder({
center: [plated_hole.x, plated_hole.y, 0],
radius: outerDiameter / 2,
Expand Down Expand Up @@ -104,7 +107,7 @@ export const platedHole = (
center: [
plated_hole.x,
plated_hole.y,
ctx.pcbThickness / 2 + platedHoleLipHeight / 2 + M - 0.05, // Adjusted for thickness
topSurfaceZ - platedHoleLipHeight / 2,
],
borderRadius: rectBorderRadius,
}),
Expand All @@ -116,26 +119,24 @@ export const platedHole = (
center: [
plated_hole.x,
plated_hole.y,
-ctx.pcbThickness / 2 - platedHoleLipHeight / 2 - M + 0.05, // Adjusted for thickness
bottomSurfaceZ + platedHoleLipHeight / 2,
],
borderRadius: rectBorderRadius,
}),
// Main copper fill between pads with rounded corners
(() => {
const height =
ctx.pcbThickness - platedHoleLipHeight * 2 - M * 2 + 0.1
const height = Math.max(copperSpan - platedHoleLipHeight * 2, M)
const topPadBottom = topSurfaceZ - platedHoleLipHeight
const bottomPadTop = bottomSurfaceZ + platedHoleLipHeight
const centerZ = (topPadBottom + bottomPadTop) / 2
const rect2d = roundedRectangle({
size: [padWidth, padHeight],
roundRadius: rectBorderRadius || 0,
segments: RECT_PAD_SEGMENTS,
})
const extruded = extrudeLinear({ height }, rect2d)
return translate(
[
plated_hole.x,
plated_hole.y,
-height / 2, // Center vertically
],
[plated_hole.x, plated_hole.y, centerZ - height / 2],
extruded,
)
})(),
Expand All @@ -147,7 +148,7 @@ export const platedHole = (
0,
],
radius: plated_hole.hole_diameter / 2,
height: ctx.pcbThickness,
height: copperSpan,
}),
),
clipGeom,
Expand All @@ -171,7 +172,7 @@ export const platedHole = (
0,
],
radius: plated_hole.hole_diameter / 2,
height: ctx.pcbThickness,
height: copperSpan,
})

// Create the final copper solid with the offset barrel and hole
Expand Down Expand Up @@ -214,7 +215,7 @@ export const platedHole = (
const outerRadius = outerPillHeight / 2
const rectLength = Math.abs(holeWidth - holeHeight)
const outerRectLength = Math.abs(outerPillWidth - outerPillHeight)
const copperHeight = ctx.pcbThickness + 2 * (platedHoleLipHeight + M)
const copperHeight = copperSpan

const createPillSection = (
width: number,
Expand Down Expand Up @@ -319,12 +320,12 @@ export const platedHole = (
? [
holeHeight + 2 * barrelMargin,
rectLength + 2 * barrelMargin,
ctx.pcbThickness + 0.02,
copperSpan,
]
: [
rectLength + 2 * barrelMargin,
holeHeight + 2 * barrelMargin,
ctx.pcbThickness,
copperSpan,
],
}),
cylinder({
Expand All @@ -340,7 +341,7 @@ export const platedHole = (
0,
],
radius: holeRadius + barrelMargin,
height: ctx.pcbThickness + 0.02,
height: copperSpan,
}),
cylinder({
center: shouldRotate
Expand All @@ -355,7 +356,7 @@ export const platedHole = (
0,
],
radius: holeRadius + barrelMargin,
height: ctx.pcbThickness + 0.02,
height: copperSpan,
}),
)

Expand Down Expand Up @@ -407,7 +408,7 @@ export const platedHole = (
center: [
plated_hole.x,
plated_hole.y,
ctx.pcbThickness / 2 + platedHoleLipHeight / 2 + M - 0.05,
topSurfaceZ - platedHoleLipHeight / 2,
],
borderRadius: rectBorderRadius,
})
Expand All @@ -419,20 +420,26 @@ export const platedHole = (
center: [
plated_hole.x,
plated_hole.y,
-ctx.pcbThickness / 2 - platedHoleLipHeight / 2 - M + 0.05,
bottomSurfaceZ + platedHoleLipHeight / 2,
],
borderRadius: rectBorderRadius,
})

const copperFill = (() => {
const height = ctx.pcbThickness - platedHoleLipHeight * 2 - M * 2 + 0.1
const height = Math.max(copperSpan - platedHoleLipHeight * 2, M)
const topPadBottom = topSurfaceZ - platedHoleLipHeight
const bottomPadTop = bottomSurfaceZ + platedHoleLipHeight
const centerZ = (topPadBottom + bottomPadTop) / 2
const rect2d = roundedRectangle({
size: [padWidth, padHeight],
roundRadius: rectBorderRadius || 0,
segments: RECT_PAD_SEGMENTS,
})
const extruded = extrudeLinear({ height }, rect2d)
return translate([plated_hole.x, plated_hole.y, -height / 2], extruded)
return translate(
[plated_hole.x, plated_hole.y, centerZ - height / 2],
extruded,
)
})()

// --- Cut pads with the hole ---
Expand Down
37 changes: 31 additions & 6 deletions src/utils/manifold/process-plated-holes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
SMOOTH_CIRCLE_SEGMENTS,
DEFAULT_SMT_PAD_THICKNESS,
M,
BOARD_SURFACE_OFFSET,
} from "../../geoms/constants"
import { extractRectBorderRadius } from "../rect-border-radius"

Expand Down Expand Up @@ -221,7 +222,11 @@ export function processPlatedHolesForManifold(
Manifold,
width: padWidth,
height: padHeight,
thickness: pcbThickness - 2 * padThickness + 0.1, // Fill between pads
thickness:
pcbThickness -
2 * padThickness -
2 * BOARD_SURFACE_OFFSET.copper +
0.1, // Fill between pads
borderRadius: rectBorderRadius,
})
manifoldInstancesForCleanup.push(mainFill)
Expand All @@ -233,15 +238,23 @@ export function processPlatedHolesForManifold(
height: padHeight,
thickness: padThickness,
borderRadius: rectBorderRadius,
}).translate([0, 0, pcbThickness / 2 - padThickness / 2 + 0.05])
}).translate([
0,
0,
pcbThickness / 2 - padThickness / 2 + BOARD_SURFACE_OFFSET.copper,
])

const bottomPad = createRoundedRectPrism({
Manifold,
width: padWidth,
height: padHeight,
thickness: padThickness,
borderRadius: rectBorderRadius,
}).translate([0, 0, -pcbThickness / 2 + padThickness / 2 - 0.05])
}).translate([
0,
0,
-pcbThickness / 2 + padThickness / 2 - BOARD_SURFACE_OFFSET.copper,
])
manifoldInstancesForCleanup.push(topPad, bottomPad)

// Create the plated barrel at the offset position
Expand Down Expand Up @@ -325,7 +338,11 @@ export function processPlatedHolesForManifold(
Manifold,
width: padWidth!,
height: padHeight!,
thickness: pcbThickness - 2 * padThickness + 0.1, // Fill between pads
thickness:
pcbThickness -
2 * padThickness -
2 * BOARD_SURFACE_OFFSET.copper +
0.1, // Fill between pads
borderRadius: rectBorderRadius,
})
manifoldInstancesForCleanup.push(mainFill)
Expand All @@ -337,15 +354,23 @@ export function processPlatedHolesForManifold(
height: padHeight!,
thickness: padThickness,
borderRadius: rectBorderRadius,
}).translate([0, 0, pcbThickness / 2 - padThickness / 2 + 0.05])
}).translate([
0,
0,
pcbThickness / 2 - padThickness / 2 + BOARD_SURFACE_OFFSET.copper,
])

const bottomPad = createRoundedRectPrism({
Manifold,
width: padWidth!,
height: padHeight!,
thickness: padThickness,
borderRadius: rectBorderRadius,
}).translate([0, 0, -pcbThickness / 2 + padThickness / 2 - 0.05])
}).translate([
0,
0,
-pcbThickness / 2 + padThickness / 2 - BOARD_SURFACE_OFFSET.copper,
])
manifoldInstancesForCleanup.push(topPad, bottomPad)

// Create the plated barrel at the offset position
Expand Down
Loading
Loading