Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add support for pill shaped holes #47

Merged
merged 4 commits into from
Dec 4, 2024
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
149 changes: 107 additions & 42 deletions lib/dsn-pcb/circuit-json-to-dsn-json/process-plated-holes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type {
PcbPlatedHole,
PcbPlatedHoleCircle,
SourceComponentBase,
PcbPlatedHoleOval,
} from "circuit-json"
import { applyToPoint, scale } from "transformation-matrix"
import type { DsnPcb } from "../types"
Expand Down Expand Up @@ -55,22 +56,53 @@ export function processPlatedHoles(

const componentName = sourceComponent?.name || `H${componentId}`

// Check if all holes have the same dimensions
const firstHole = holes[0] as PcbPlatedHoleCircle
// Skip if any hole is oval shaped
if (holes.some((hole) => hole.shape === "oval")) {
throw new Error("Oval plated holes are not supported")
}

// Check if all holes have same shape and dimensions
const firstHole = holes[0]
const allHolesSameDimensions = holes.every((hole) => {
const currentHole = hole as PcbPlatedHoleCircle
return (
currentHole.outer_diameter === firstHole.outer_diameter &&
currentHole.hole_diameter === firstHole.hole_diameter
)
if (hole.shape !== firstHole.shape) return false

if (hole.shape === "pill") {
const current = hole as PcbPlatedHoleOval
const first = firstHole as PcbPlatedHoleOval
return (
current.outer_width === first.outer_width &&
current.outer_height === first.outer_height &&
current.hole_width === first.hole_width &&
current.hole_height === first.hole_height
)
} else {
const current = hole as PcbPlatedHoleCircle
const first = firstHole as PcbPlatedHoleCircle
return (
current.outer_diameter === first.outer_diameter &&
current.hole_diameter === first.hole_diameter
)
}
})

const outerDiameterUm = Math.round(firstHole.outer_diameter * 1000)
const holeDiameterUm = Math.round(firstHole.hole_diameter * 1000)

const imageName = allHolesSameDimensions
? `MountingHole:MountingHole_${holeDiameterUm}um_${outerDiameterUm}um_Pad`
: `MountingHole:MountingHole_Component_${componentId}`
let imageName: string
if (allHolesSameDimensions) {
if (firstHole.shape === "pill") {
const pillHole = firstHole
const holeWidthUm = Math.round(pillHole.hole_width * 1000)
const holeHeightUm = Math.round(pillHole.hole_height * 1000)
const outerWidthUm = Math.round(pillHole.outer_width * 1000)
const outerHeightUm = Math.round(pillHole.outer_height * 1000)
imageName = `MountingHole:MountingHole_${holeWidthUm}x${holeHeightUm}um_${outerWidthUm}x${outerHeightUm}um_Pad`
} else {
const circleHole = firstHole as PcbPlatedHoleCircle
const holeDiameterUm = Math.round(circleHole.hole_diameter * 1000)
const outerDiameterUm = Math.round(circleHole.outer_diameter * 1000)
imageName = `MountingHole:MountingHole_${holeDiameterUm}um_${outerDiameterUm}um_Pad`
}
} else {
imageName = `MountingHole:MountingHole_Component_${componentId}`
}

if (pcbComponent) {
const circuitSpaceCoordinates = applyToPoint(
Expand Down Expand Up @@ -99,37 +131,70 @@ export function processPlatedHoles(
name: imageName,
outlines: [],
pins: holes.map((hole, index) => {
const platedHoleCircle = hole as PcbPlatedHoleCircle
const currentOuterDiameterUm = Math.round(
platedHoleCircle.outer_diameter * 1000,
)
const currentHoleDiameterUm = Math.round(
platedHoleCircle.hole_diameter * 1000,
)
const padstackName = `Round[A]Pad_${currentHoleDiameterUm}_${currentOuterDiameterUm}_um`

// Add padstack if not already present
if (!pcb.library.padstacks.find((p) => p.name === padstackName)) {
pcb.library.padstacks.push({
name: padstackName,
shapes: [
{
shapeType: "circle",
layer: "F.Cu",
diameter: currentOuterDiameterUm,
let padstackName: string

if (hole.shape === "pill") {
const pillHole = hole
const outerWidthUm = Math.round(pillHole.outer_width * 1000)
const outerHeightUm = Math.round(pillHole.outer_height * 1000)
padstackName = `Oval[A]Pad_${outerWidthUm}x${outerHeightUm}_um`

// Add padstack if not already present
if (!pcb.library.padstacks.find((p) => p.name === padstackName)) {
const pathOffset = (outerWidthUm - outerHeightUm) / 2
pcb.library.padstacks.push({
name: padstackName,
shapes: [
{
shapeType: "path",
layer: "F.Cu",
width: outerHeightUm,
coordinates: [-pathOffset, 0, pathOffset, 0],
},
{
shapeType: "path",
layer: "B.Cu",
width: outerHeightUm,
coordinates: [-pathOffset, 0, pathOffset, 0],
},
],
hole: {
shape: "oval",
width: Math.round(pillHole.hole_width * 1000),
height: Math.round(pillHole.hole_height * 1000),
},
{
shapeType: "circle",
layer: "B.Cu",
diameter: currentOuterDiameterUm,
attach: "off",
})
}
} else {
const circleHole = hole as PcbPlatedHoleCircle
const outerDiameterUm = Math.round(circleHole.outer_diameter * 1000)
const holeDiameterUm = Math.round(circleHole.hole_diameter * 1000)
padstackName = `Round[A]Pad_${holeDiameterUm}_${outerDiameterUm}_um`

// Add padstack if not already present
if (!pcb.library.padstacks.find((p) => p.name === padstackName)) {
pcb.library.padstacks.push({
name: padstackName,
shapes: [
{
shapeType: "circle",
layer: "F.Cu",
diameter: outerDiameterUm,
},
{
shapeType: "circle",
layer: "B.Cu",
diameter: outerDiameterUm,
},
],
hole: {
shape: "circle",
diameter: holeDiameterUm,
},
],
hole: {
shape: "circle",
diameter: currentHoleDiameterUm,
},
attach: "off",
})
attach: "off",
})
}
}

return {
Expand Down
2 changes: 2 additions & 0 deletions lib/dsn-pcb/circuit-json-to-dsn-json/stringify-dsn-json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,8 @@ export const stringifyDsnJson = (dsnJson: DsnPcb): string => {
result += `${indent}${indent}${indent}(shape (polygon ${shape.layer} ${shape.width} ${stringifyCoordinates(shape.coordinates)}))\n`
} else if (shape.shapeType === "circle") {
result += `${indent}${indent}${indent}(shape (circle ${shape.layer} ${shape.diameter}))\n`
} else if (shape.shapeType === "path") {
result += `${indent}${indent}${indent}(shape (path ${shape.layer} ${shape.width} ${stringifyCoordinates(shape.coordinates)}))\n`
}
})
result += `${indent}${indent}${indent}(attach ${padstack.attach})\n`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ export function convertPadstacksToSmtPads(
(shape) => shape.shapeType === "circle",
)

const pathShape = padstack.shapes.find(
(shape) => shape.shapeType === "path",
)

let width: number
let height: number

Expand Down Expand Up @@ -75,6 +79,12 @@ export function convertPadstacksToSmtPads(

width = Math.abs(maxX - minX) / 1000
height = Math.abs(maxY - minY) / 1000
} else if (pathShape) {
// For path shapes (oval/pill pads), width is the path width
// and height is the distance between path endpoints
const [x1, y1, x2, y2] = pathShape.coordinates
width = pathShape.width / 1000 // Convert μm to mm
height = Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2) / 1000
} else if (circleShape) {
// Handle circle shape
const radius = circleShape.diameter / 2 / 1000
Expand All @@ -93,7 +103,7 @@ export function convertPadstacksToSmtPads(
})

let pcbPad: PcbSmtPad
if (rectShape || polygonShape) {
if (rectShape || polygonShape || pathShape) {
pcbPad = {
type: "pcb_smtpad",
pcb_smtpad_id: `pcb_smtpad_${componentId}_${place.refdes}_${pin.pin_number - 1}`,
Expand Down
25 changes: 25 additions & 0 deletions lib/dsn-pcb/dsn-json-to-circuit-json/parse-dsn-to-dsn-json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import type {
Padstack,
Parser as ParserType,
Path,
PathShape,
Pin,
Placement,
Places,
Expand Down Expand Up @@ -633,6 +634,8 @@ function processShape(nodes: ASTNode[]): Shape {
return processCircleShape(shapeContentNode.children!)
case "rect":
return processRectShape(shapeContentNode.children!)
case "path":
return processPathShape(shapeContentNode.children!)
}
}
}
Expand Down Expand Up @@ -1050,3 +1053,25 @@ function processSessionNode(ast: ASTNode): DsnSession {

return session
}

function processPathShape(nodes: ASTNode[]): PathShape {
if (
nodes[1]?.type === "Atom" &&
typeof nodes[1].value === "string" &&
nodes[2]?.type === "Atom" &&
typeof nodes[2].value === "number"
) {
return {
shapeType: "path",
layer: nodes[1].value,
width: nodes[2].value,
coordinates: nodes
.slice(3)
.filter(
(node) => node.type === "Atom" && typeof node.value === "number",
)
.map((node) => node.value as number),
}
}
throw new Error("Invalid path shape format")
}
14 changes: 11 additions & 3 deletions lib/dsn-pcb/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,8 +200,10 @@ export interface Padstack {
shapes: Shape[]
attach: string
hole?: {
shape: "circle" | "square"
diameter: number
shape: "circle" | "square" | "oval"
width?: number
height?: number
diameter?: number
}
}

Expand All @@ -211,7 +213,7 @@ export interface PadDimensions {
radius?: number
}

export type Shape = PolygonShape | CircleShape | RectShape
export type Shape = PolygonShape | CircleShape | RectShape | PathShape

export interface BaseShape {
shapeType: string // Added shapeType to base export interface
Expand All @@ -234,6 +236,12 @@ export interface RectShape extends BaseShape {
coordinates: number[]
}

export interface PathShape extends BaseShape {
shapeType: "path"
width: number
coordinates: number[]
}

export interface Network {
nets: Net[]
classes: Class[]
Expand Down
Loading
Loading