From 44643b61b324d8c8de1f35d003bb64d37ce5b534 Mon Sep 17 00:00:00 2001 From: kompukter Date: Sun, 5 Apr 2026 05:42:44 +0500 Subject: [PATCH 1/2] Fix string parser for led and diode imperial sizes --- src/footprinter.ts | 2 +- tests/string-parser.test.ts | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/footprinter.ts b/src/footprinter.ts index 25d43214..c5d245bb 100644 --- a/src/footprinter.ts +++ b/src/footprinter.ts @@ -406,7 +406,7 @@ export const footprinter = (): Footprinter & { } else { target[prop] = true target.fn = prop - if (prop === "res" || prop === "cap") { + if (["res", "cap", "led", "diode"].includes(prop)) { if (v) { if (typeof v === "string" && v.includes("_metric")) { target.metric = v.split("_metric")[0] diff --git a/tests/string-parser.test.ts b/tests/string-parser.test.ts index 7ee639dc..46b00c8b 100644 --- a/tests/string-parser.test.ts +++ b/tests/string-parser.test.ts @@ -4,3 +4,10 @@ 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", () => { + expect(() => fp.string("led0402").circuitJson()).not.toThrow() + expect(() => fp.string("led0603").circuitJson()).not.toThrow() + expect(() => fp.string("diode0402").circuitJson()).not.toThrow() + expect(() => fp.string("diode0805").circuitJson()).not.toThrow() +}) From 1205c02c64feeaea2dc4b7a671e780979ac1b386 Mon Sep 17 00:00:00 2001 From: Aslan Aliyev Date: Mon, 6 Apr 2026 03:29:41 +0500 Subject: [PATCH 2/2] fix: support generic imperial sizes and add cathode markings Fixes #562 --- src/fn/diode.ts | 14 +++++----- src/fn/led.ts | 5 +++- src/footprinter.ts | 9 ++++++- src/helpers/passive-fn.ts | 54 ++++++++++++++++++++++++++++--------- tests/string-parser.test.ts | 20 +++++++++++--- 5 files changed, 77 insertions(+), 25 deletions(-) diff --git a/src/fn/diode.ts b/src/fn/diode.ts index b08e1162..b5e7cfc9 100644 --- a/src/fn/diode.ts +++ b/src/fn/diode.ts @@ -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, + } } diff --git a/src/fn/led.ts b/src/fn/led.ts index 8ed0dee1..caa9e157 100644 --- a/src/fn/led.ts +++ b/src/fn/led.ts @@ -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, + } } diff --git a/src/footprinter.ts b/src/footprinter.ts index c5d245bb..e892d667 100644 --- a/src/footprinter.ts +++ b/src/footprinter.ts @@ -340,6 +340,7 @@ export const footprinter = (): Footprinter & { getFootprintNames: string[] setString: (string) => void } => { + const { passiveFootprintNames } = getFootprintNamesByType() const proxy = new Proxy( {}, { @@ -406,7 +407,7 @@ export const footprinter = (): Footprinter & { } else { target[prop] = true target.fn = prop - if (["res", "cap", "led", "diode"].includes(prop)) { + if (passiveFootprintNames.includes(prop)) { if (v) { if (typeof v === "string" && v.includes("_metric")) { target.metric = v.split("_metric")[0] @@ -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 } diff --git a/src/helpers/passive-fn.ts b/src/helpers/passive-fn.ts index 26ef1201..351c8248 100644 --- a/src/helpers/passive-fn.ts +++ b/src/helpers/passive-fn.ts @@ -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 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) @@ -188,6 +190,23 @@ 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) @@ -195,6 +214,16 @@ export const passive = (params: PassiveDef): AnyCircuitElement[] => { 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 @@ -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 } diff --git a/tests/string-parser.test.ts b/tests/string-parser.test.ts index 46b00c8b..ec868f43 100644 --- a/tests/string-parser.test.ts +++ b/tests/string-parser.test.ts @@ -6,8 +6,20 @@ test("string builder ignores empty segments", () => { }) test("string builder works for led and diode imperial sizes", () => { - expect(() => fp.string("led0402").circuitJson()).not.toThrow() - expect(() => fp.string("led0603").circuitJson()).not.toThrow() - expect(() => fp.string("diode0402").circuitJson()).not.toThrow() - expect(() => fp.string("diode0805").circuitJson()).not.toThrow() + 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) })