diff --git a/package.json b/package.json index 3c4c0393..9570c2cc 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/BoardGeomBuilder.ts b/src/BoardGeomBuilder.ts index bbf04684..9312c28e 100644 --- a/src/BoardGeomBuilder.ts +++ b/src/BoardGeomBuilder.ts @@ -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" @@ -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 @@ -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) @@ -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 @@ -725,7 +732,7 @@ export class BoardGeomBuilder { linePath, ) let traceGeom = translate( - [0, 0, zPos], + [0, 0, zCenter - M / 2], extrudeLinear({ height: M }, expandedPath), ) @@ -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, }) @@ -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, }) diff --git a/src/geoms/constants.ts b/src/geoms/constants.ts index 74b42926..9608df7f 100644 --- a/src/geoms/constants.ts +++ b/src/geoms/constants.ts @@ -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], diff --git a/src/geoms/plated-hole.ts b/src/geoms/plated-hole.ts index c56d6458..3bfba5aa 100644 --- a/src/geoms/plated-hole.ts +++ b/src/geoms/plated-hole.ts @@ -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" @@ -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, @@ -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, }), @@ -116,14 +119,16 @@ 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, @@ -131,11 +136,7 @@ export const platedHole = ( }) 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, ) })(), @@ -147,7 +148,7 @@ export const platedHole = ( 0, ], radius: plated_hole.hole_diameter / 2, - height: ctx.pcbThickness, + height: copperSpan, }), ), clipGeom, @@ -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 @@ -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, @@ -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({ @@ -340,7 +341,7 @@ export const platedHole = ( 0, ], radius: holeRadius + barrelMargin, - height: ctx.pcbThickness + 0.02, + height: copperSpan, }), cylinder({ center: shouldRotate @@ -355,7 +356,7 @@ export const platedHole = ( 0, ], radius: holeRadius + barrelMargin, - height: ctx.pcbThickness + 0.02, + height: copperSpan, }), ) @@ -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, }) @@ -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 --- diff --git a/src/utils/manifold/process-plated-holes.ts b/src/utils/manifold/process-plated-holes.ts index 62d0b048..7a7383a8 100644 --- a/src/utils/manifold/process-plated-holes.ts +++ b/src/utils/manifold/process-plated-holes.ts @@ -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" @@ -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) @@ -233,7 +238,11 @@ 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, @@ -241,7 +250,11 @@ 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, + ]) manifoldInstancesForCleanup.push(topPad, bottomPad) // Create the plated barrel at the offset position @@ -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) @@ -337,7 +354,11 @@ 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, @@ -345,7 +366,11 @@ 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, + ]) manifoldInstancesForCleanup.push(topPad, bottomPad) // Create the plated barrel at the offset position diff --git a/stories/ResistorPlatedHoles.stories.tsx b/stories/ResistorPlatedHoles.stories.tsx new file mode 100644 index 00000000..49b8173c --- /dev/null +++ b/stories/ResistorPlatedHoles.stories.tsx @@ -0,0 +1,69 @@ +import { CadViewer } from "src/CadViewer" + +export const ResistorPlatedHoleShowcase = () => ( + + + + + + + .pin1"} to={".R2 > .pin2"} /> + .pin1"} to={".R3 > .pin2"} /> + + + + + +) + +export default { + title: "Plated Holes/Resistor with Plated Holes", + component: ResistorPlatedHoleShowcase, +}