diff --git a/src/fn/smdpads.ts b/src/fn/smdpads.ts index fbd0799a..4cb40333 100644 --- a/src/fn/smdpads.ts +++ b/src/fn/smdpads.ts @@ -15,6 +15,8 @@ export const smdpads_def = base_def p: length.default("1mm").describe("pad center pitch"), pw: length.default("1mm").describe("outer pad width"), ph: length.default("1mm").describe("pad height"), + cyw: length.optional().describe("courtyard width"), + cyh: length.optional().describe("courtyard height"), centerpadwidth: length.optional().describe("center pad width"), }) .superRefine((parameters, ctx) => { @@ -34,7 +36,7 @@ export const smdpads = ( rawParams: z.input, ): { circuitJson: AnyCircuitElement[]; parameters: any } => { const parameters = smdpads_def.parse(rawParams) - const { num_pins, p, pw, ph, centerpadwidth } = parameters + const { num_pins, p, pw, ph, cyw, cyh, centerpadwidth } = parameters const centerPin = Math.ceil(num_pins / 2) const xStart = -((num_pins - 1) * p) / 2 const pads = Array.from({ length: num_pins }, (_, index) => { @@ -49,13 +51,17 @@ export const smdpads = ( const outerPadHalfWidth = ((num_pins - 1) * p) / 2 + pw / 2 const centerPadHalfWidth = centerpadwidth ? centerpadwidth / 2 : 0 const copperHalfWidth = Math.max(outerPadHalfWidth, centerPadHalfWidth) + const courtyardSize = + cyw !== undefined && cyh !== undefined + ? { width: cyw, height: cyh } + : { width: copperHalfWidth * 2 + 0.5, height: ph + 0.5 } const courtyard: PcbCourtyardRect = { type: "pcb_courtyard_rect", pcb_courtyard_rect_id: "", pcb_component_id: "", center: { x: 0, y: 0 }, - width: copperHalfWidth * 2 + 0.5, - height: ph + 0.5, + width: courtyardSize.width, + height: courtyardSize.height, layer: "top", } diff --git a/src/helpers/passive-fn.ts b/src/helpers/passive-fn.ts index 979e328e..49924c64 100644 --- a/src/helpers/passive-fn.ts +++ b/src/helpers/passive-fn.ts @@ -1,13 +1,13 @@ +import mm from "@tscircuit/mm" import type { AnyCircuitElement, PcbCourtyardRect, PcbSilkscreenPath, } from "circuit-json" +import { distance, length } from "circuit-json" +import { z } from "zod" import { rectpad } from "../helpers/rectpad" -import mm from "@tscircuit/mm" import { platedhole } from "./platedhole" -import { z } from "zod" -import { length, distance } from "circuit-json" import { type SilkscreenRef, silkscreenRef } from "./silkscreenRef" import { base_def } from "./zod/base_def" @@ -230,6 +230,8 @@ export const passive_def = base_def.extend({ imperial: distance.optional(), w: length.optional(), h: length.optional(), + cyw: length.optional().describe("courtyard width"), + cyh: length.optional().describe("courtyard height"), nonpolarized: z.boolean().optional(), textbottom: z.boolean().optional(), roundedPads: z.boolean().optional(), @@ -248,6 +250,8 @@ export const passive = (params: PassiveDef): AnyCircuitElement[] => { imperial, w, h, + cyw, + cyh, nonpolarized, textbottom, roundedPads, @@ -256,6 +260,8 @@ export const passive = (params: PassiveDef): AnyCircuitElement[] => { if (typeof w === "string") w = mm(w) if (typeof h === "string") h = mm(h) + if (typeof cyw === "string") cyw = mm(cyw) + if (typeof cyh === "string") cyh = mm(cyh) if (typeof p === "string") p = mm(p) if (typeof pw === "string") pw = mm(pw) if (typeof ph === "string") ph = mm(ph) @@ -345,7 +351,9 @@ export const passive = (params: PassiveDef): AnyCircuitElement[] => { const courtyard = sz?.courtyard_width_mm && sz.courtyard_height_mm ? createCourtyardRect(sz.courtyard_width_mm, sz.courtyard_height_mm) - : null + : cyw !== undefined && cyh !== undefined + ? createCourtyardRect(cyw, cyh) + : null const shouldRoundPads = roundedPads ?? sz?.rounded_pads ?? false const cornerRadius = shouldRoundPads ? Math.min(0.125, Math.min(pw, ph) / 8) diff --git a/tests/__snapshots__/cap_p4.6599mm_pw2.91mm_ph2.9106mm_cyw8.628mm_cyh4.056mm.snap.svg b/tests/__snapshots__/cap_p4.6599mm_pw2.91mm_ph2.9106mm_cyw8.628mm_cyh4.056mm.snap.svg new file mode 100644 index 00000000..288d8666 --- /dev/null +++ b/tests/__snapshots__/cap_p4.6599mm_pw2.91mm_ph2.9106mm_cyw8.628mm_cyh4.056mm.snap.svg @@ -0,0 +1 @@ +{REF} \ No newline at end of file diff --git a/tests/__snapshots__/diode_p4.6599mm_pw2.91mm_ph2.9106mm_cyw8.628mm_cyh4.056mm.snap.svg b/tests/__snapshots__/diode_p4.6599mm_pw2.91mm_ph2.9106mm_cyw8.628mm_cyh4.056mm.snap.svg new file mode 100644 index 00000000..8f62b519 --- /dev/null +++ b/tests/__snapshots__/diode_p4.6599mm_pw2.91mm_ph2.9106mm_cyw8.628mm_cyh4.056mm.snap.svg @@ -0,0 +1 @@ +{REF}+- \ No newline at end of file diff --git a/tests/__snapshots__/led_p4.6599mm_pw2.91mm_ph2.9106mm_cyw8.628mm_cyh4.056mm.snap.svg b/tests/__snapshots__/led_p4.6599mm_pw2.91mm_ph2.9106mm_cyw8.628mm_cyh4.056mm.snap.svg new file mode 100644 index 00000000..dd7828ef --- /dev/null +++ b/tests/__snapshots__/led_p4.6599mm_pw2.91mm_ph2.9106mm_cyw8.628mm_cyh4.056mm.snap.svg @@ -0,0 +1 @@ +{REF}+- \ No newline at end of file diff --git a/tests/__snapshots__/res_p4.6599mm_pw2.91mm_ph2.9106mm_cyw8.628mm_cyh4.056mm.snap.svg b/tests/__snapshots__/res_p4.6599mm_pw2.91mm_ph2.9106mm_cyw8.628mm_cyh4.056mm.snap.svg new file mode 100644 index 00000000..288d8666 --- /dev/null +++ b/tests/__snapshots__/res_p4.6599mm_pw2.91mm_ph2.9106mm_cyw8.628mm_cyh4.056mm.snap.svg @@ -0,0 +1 @@ +{REF} \ No newline at end of file diff --git a/tests/__snapshots__/smdpads2_p4.6599mm_pw2.91mm_ph2.9106mm_cyw8.628mm_cyh4.056mm.snap.svg b/tests/__snapshots__/smdpads2_p4.6599mm_pw2.91mm_ph2.9106mm_cyw8.628mm_cyh4.056mm.snap.svg new file mode 100644 index 00000000..91c4bffc --- /dev/null +++ b/tests/__snapshots__/smdpads2_p4.6599mm_pw2.91mm_ph2.9106mm_cyw8.628mm_cyh4.056mm.snap.svg @@ -0,0 +1 @@ +{REF} \ No newline at end of file diff --git a/tests/cap.test.ts b/tests/cap.test.ts index ab7965b0..f66c4483 100644 --- a/tests/cap.test.ts +++ b/tests/cap.test.ts @@ -1,4 +1,4 @@ -import { test, expect } from "bun:test" +import { expect, test } from "bun:test" import { convertCircuitJsonToPcbSvg } from "circuit-to-svg" import { fp } from "../src/footprinter" @@ -46,3 +46,20 @@ test("cap 2512", () => { const svgContent = convertCircuitJsonToPcbSvg(soup) expect(svgContent).toMatchSvgSnapshot(import.meta.path, "cap_2512") }) + +test("custom capacitor footprint uses explicit courtyard dimensions", () => { + const footprintString = + "cap_p4.6599mm_pw2.91mm_ph2.9106mm_cyw8.628mm_cyh4.056mm" + const circuitJson = fp.string(footprintString).circuitJson() + const courtyard = circuitJson.find( + (element) => element.type === "pcb_courtyard_rect", + ) + + expect(courtyard).toMatchObject({ + width: 8.628, + height: 4.056, + }) + expect( + convertCircuitJsonToPcbSvg(circuitJson, { showCourtyards: true }), + ).toMatchSvgSnapshot(import.meta.path, footprintString) +}) diff --git a/tests/diode.test.ts b/tests/diode.test.ts index 186bfd1a..81d39c67 100644 --- a/tests/diode.test.ts +++ b/tests/diode.test.ts @@ -1,4 +1,4 @@ -import { test, expect } from "bun:test" +import { expect, test } from "bun:test" import { convertCircuitJsonToPcbSvg } from "circuit-to-svg" import { fp } from "../src/footprinter" @@ -43,3 +43,20 @@ test("diode2512", () => { const svgContent = convertCircuitJsonToPcbSvg(soup) expect(svgContent).toMatchSvgSnapshot(import.meta.path, "diode2512") }) + +test("custom diode footprint uses explicit courtyard dimensions", () => { + const footprintString = + "diode_p4.6599mm_pw2.91mm_ph2.9106mm_cyw8.628mm_cyh4.056mm" + const circuitJson = fp.string(footprintString).circuitJson() + const courtyard = circuitJson.find( + (element) => element.type === "pcb_courtyard_rect", + ) + + expect(courtyard).toMatchObject({ + width: 8.628, + height: 4.056, + }) + expect( + convertCircuitJsonToPcbSvg(circuitJson, { showCourtyards: true }), + ).toMatchSvgSnapshot(import.meta.path, footprintString) +}) diff --git a/tests/led.test.ts b/tests/led.test.ts index 6886876d..f392e532 100644 --- a/tests/led.test.ts +++ b/tests/led.test.ts @@ -1,4 +1,4 @@ -import { test, expect } from "bun:test" +import { expect, test } from "bun:test" import { convertCircuitJsonToPcbSvg } from "circuit-to-svg" import { led } from "../src/fn" import { fp } from "../src/footprinter" @@ -99,3 +99,20 @@ test("led0201", () => { const svgContent = convertCircuitJsonToPcbSvg(soup) expect(svgContent).toMatchSvgSnapshot(import.meta.path, "led_0201") }) + +test("custom LED footprint uses explicit courtyard dimensions", () => { + const footprintString = + "led_p4.6599mm_pw2.91mm_ph2.9106mm_cyw8.628mm_cyh4.056mm" + const circuitJson = fp.string(footprintString).circuitJson() + const courtyard = circuitJson.find( + (element) => element.type === "pcb_courtyard_rect", + ) + + expect(courtyard).toMatchObject({ + width: 8.628, + height: 4.056, + }) + expect( + convertCircuitJsonToPcbSvg(circuitJson, { showCourtyards: true }), + ).toMatchSvgSnapshot(import.meta.path, footprintString) +}) diff --git a/tests/res.test.ts b/tests/res.test.ts index 24b7c7a5..74c3c6c4 100644 --- a/tests/res.test.ts +++ b/tests/res.test.ts @@ -1,4 +1,4 @@ -import { test, expect } from "bun:test" +import { expect, test } from "bun:test" import { convertCircuitJsonToPcbSvg } from "circuit-to-svg" import { fp } from "../src/footprinter" @@ -76,6 +76,25 @@ test("custom passive footprints do not get implicit courtyards", () => { ).toBe(false) }) +test("custom resistor footprint string uses explicit courtyard dimensions", () => { + const footprintString = + "res_p4.6599mm_pw2.91mm_ph2.9106mm_cyw8.628mm_cyh4.056mm" + const soup = fp.string(footprintString).circuitJson() + const svgContent = convertCircuitJsonToPcbSvg(soup, { + showCourtyards: true, + }) + const courtyard = soup.find( + (element) => element.type === "pcb_courtyard_rect", + ) + + expect(svgContent).toMatchSvgSnapshot(import.meta.path, footprintString) + expect(courtyard).toMatchObject({ + type: "pcb_courtyard_rect", + width: 8.628, + height: 4.056, + }) +}) + test("0402 uses the explicit KiCad courtyard", () => { const soup = fp.string("0402").circuitJson() const courtyard = soup.find( diff --git a/tests/smdpads.test.ts b/tests/smdpads.test.ts index 2917485a..57753833 100644 --- a/tests/smdpads.test.ts +++ b/tests/smdpads.test.ts @@ -72,3 +72,20 @@ test("smdpads requires an odd pad count for a center pad width", () => { "requires an odd number of SMD pads", ) }) + +test("smdpads2 uses explicit courtyard dimensions", () => { + const footprintString = + "smdpads2_p4.6599mm_pw2.91mm_ph2.9106mm_cyw8.628mm_cyh4.056mm" + const circuitJson = fp.string(footprintString).circuitJson() + const courtyard = circuitJson.find( + (element) => element.type === "pcb_courtyard_rect", + ) + + expect(courtyard).toMatchObject({ + width: 8.628, + height: 4.056, + }) + expect( + convertCircuitJsonToPcbSvg(circuitJson, { showCourtyards: true }), + ).toMatchSvgSnapshot(import.meta.path, footprintString) +})