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
14 changes: 8 additions & 6 deletions src/fn/diode.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import type { AnySoupElement } from "circuit-json"
import { passive, type PassiveDef } from "src/helpers/passive-fn"
import { passive, type PassiveDef } from "../helpers/passive-fn"

export const diode = (parameters: {
tht: boolean
p: number
}): { circuitJson: AnySoupElement[]; parameters: PassiveDef } => {
return { circuitJson: passive(parameters), parameters }
export const diode = (
parameters: PassiveDef,
): { circuitJson: AnySoupElement[]; parameters: PassiveDef } => {
return {
circuitJson: passive({ ...parameters, mark_cathode: true }),
parameters,
}
}
5 changes: 4 additions & 1 deletion src/fn/led.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,8 @@ import { type PassiveDef, passive } from "../helpers/passive-fn"
export const led = (
parameters: PassiveDef,
): { circuitJson: AnySoupElement[]; parameters: PassiveDef } => {
return { circuitJson: passive(parameters), parameters }
return {
circuitJson: passive({ ...parameters, mark_cathode: true }),
parameters,
}
}
9 changes: 8 additions & 1 deletion src/footprinter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,7 @@ export const footprinter = (): Footprinter & {
getFootprintNames: string[]
setString: (string) => void
} => {
const { passiveFootprintNames } = getFootprintNamesByType()
const proxy = new Proxy(
{},
{
Expand Down Expand Up @@ -406,7 +407,7 @@ export const footprinter = (): Footprinter & {
} else {
target[prop] = true
target.fn = prop
if (prop === "res" || prop === "cap") {
if (passiveFootprintNames.includes(prop)) {
if (v) {
if (typeof v === "string" && v.includes("_metric")) {
target.metric = v.split("_metric")[0]
Expand All @@ -424,6 +425,12 @@ export const footprinter = (): Footprinter & {
// handle dip_w or other invalid booleans
if (!v && ["w", "h", "p"].includes(prop as string)) {
// ignore
} else if (
passiveFootprintNames.includes(target.fn) &&
/^\d{4,5}$/.test(prop)
) {
// Handle standalone size code for passives (e.g. .res().0603())
target.imperial = prop
} else {
target[prop] = v ?? true
}
Expand Down
54 changes: 41 additions & 13 deletions src/helpers/passive-fn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,12 +139,14 @@ export const passive_def = base_def.extend({
w: length.optional(),
h: length.optional(),
textbottom: z.boolean().optional(),
mark_cathode: z.boolean().optional(),
})

export type PassiveDef = z.input<typeof passive_def>

export const passive = (params: PassiveDef): AnyCircuitElement[] => {
let { tht, p, pw, ph, metric, imperial, w, h, textbottom } = params
let { tht, p, pw, ph, metric, imperial, w, h, textbottom, mark_cathode } =
params

if (typeof w === "string") w = mm(w)
if (typeof h === "string") h = mm(h)
Expand Down Expand Up @@ -188,13 +190,40 @@ export const passive = (params: PassiveDef): AnyCircuitElement[] => {
pcb_silkscreen_path_id: "",
}

const elements: AnyCircuitElement[] = []

if (mark_cathode) {
const cathodeLine: PcbSilkscreenPath = {
type: "pcb_silkscreen_path",
layer: "top",
pcb_component_id: "",
route: [
{ x: -p / 2 - pw / 2 - 0.4, y: ph / 2 + 0.4 },
{ x: -p / 2 - pw / 2 - 0.4, y: -ph / 2 - 0.4 },
],
stroke_width: 0.2,
pcb_silkscreen_path_id: "",
}
elements.push(cathodeLine)
}

const textY = textbottom ? -ph / 2 - 0.9 : ph / 2 + 0.9
const silkscreenRefText: SilkscreenRef = silkscreenRef(0, textY, 0.2)

// IPC-7351B: courtyard must enclose body, pads, AND silkscreen outline
const excess = 0.25
const silkXs = silkscreenLine.route.map((pt) => pt.x)
const silkYs = silkscreenLine.route.map((pt) => pt.y)
if (mark_cathode) {
const cathodeLines = elements.filter(
(e) => e.type === "pcb_silkscreen_path",
) as PcbSilkscreenPath[]
for (const line of cathodeLines) {
silkXs.push(...line.route.map((pt) => pt.x))
silkYs.push(...line.route.map((pt) => pt.y))
}
}

const crtMinX = Math.min(-(w ?? 0) / 2, -(p / 2 + pw / 2), ...silkXs) - excess
const crtMaxX = Math.max((w ?? 0) / 2, p / 2 + pw / 2, ...silkXs) + excess
const crtMinY = Math.min(-(h ?? 0) / 2, -ph / 2, ...silkYs) - excess
Expand All @@ -209,20 +238,19 @@ export const passive = (params: PassiveDef): AnyCircuitElement[] => {
layer: "top",
}

elements.push(silkscreenLine, silkscreenRefText, courtyard)

if (tht) {
return [
elements.unshift(
platedhole(1, -p / 2, 0, pw, (pw * 1) / 0.8),
platedhole(2, p / 2, 0, pw, (pw * 1) / 0.8),
silkscreenLine,
silkscreenRefText,
courtyard,
]
)
} else {
elements.unshift(
rectpad(["1", "left"], -p / 2, 0, pw, ph),
rectpad(["2", "right"], p / 2, 0, pw, ph),
)
}
return [
rectpad(["1", "left"], -p / 2, 0, pw, ph),
rectpad(["2", "right"], p / 2, 0, pw, ph),
silkscreenLine,
silkscreenRefText,
courtyard,
]

return elements
}
19 changes: 19 additions & 0 deletions tests/string-parser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,22 @@ import { fp } from "../src/footprinter"
test("string builder ignores empty segments", () => {
expect(() => fp.string("0603__").circuitJson()).not.toThrow()
})

test("string builder works for led and diode imperial sizes", () => {
const led0402 = fp.string("led0402").circuitJson()
expect(led0402.some((e) => e.type === "pcb_pad")).toBe(true)
// Check for cathode marking (stroke_width 0.2)
expect(
led0402.some(
(e) => e.type === "pcb_silkscreen_path" && e.stroke_width === 0.2,
),
).toBe(true)

const diode0603 = fp.string("diode0603").circuitJson()
expect(diode0603.some((e) => e.type === "pcb_pad")).toBe(true)
expect(
diode0603.some(
(e) => e.type === "pcb_silkscreen_path" && e.stroke_width === 0.2,
),
).toBe(true)
})