diff --git a/src/fn/res.ts b/src/fn/res.ts index b277f88e..d90d773e 100644 --- a/src/fn/res.ts +++ b/src/fn/res.ts @@ -5,6 +5,7 @@ import { res0402Array4 } from "../helpers/res0402-array4" import { res0603Array2 } from "../helpers/res0603-array2" import { res0603Array4 } from "../helpers/res0603-array4" import { res0606Array2 } from "../helpers/res0606-array2" +import { res0612Array4 } from "../helpers/res0612-array4" import { res1206Array4 } from "../helpers/res1206-array4" type ResArrayParams = PassiveDef & { @@ -80,6 +81,13 @@ export const res = ( } } + if (arrayCount === 4 && imperialBase === "0612") { + return { + circuitJson: res0612Array4(rawParameters), + parameters: rawParameters, + } + } + if (arrayCount === 4 && imperialBase === "1206") { return { circuitJson: res1206Array4(rawParameters), diff --git a/src/helpers/chipArray.ts b/src/helpers/chipArray.ts index d6578fae..817c1faa 100644 --- a/src/helpers/chipArray.ts +++ b/src/helpers/chipArray.ts @@ -11,6 +11,8 @@ export interface ChipArrayParams { textbottom?: boolean convex?: boolean concave?: boolean + padHeights?: number[] // Optional: variable pad heights per position (overrides padHeight) + yPositions?: number[] // Optional: custom Y positions (overrides padPitch calculation) } /** @@ -22,21 +24,35 @@ export interface ChipArrayParams { * @returns Array of circuit elements (pads, silkscreen, pin1 marker, ref text) */ export const chipArray = (params: ChipArrayParams): AnyCircuitElement[] => { - const { padSpacing, padWidth, padHeight, padPitch, numRows, textbottom } = - params + const { + padSpacing, + padWidth, + padHeight, + padPitch, + numRows, + textbottom, + padHeights, + yPositions: customYPositions, + } = params // Calculate Y positions for pads (centered around origin) - const yPositions: number[] = [] - const halfRange = (numRows - 1) * (padPitch / 2) - for (let i = 0; i < numRows; i++) { - yPositions.push(halfRange - i * padPitch) - } + const yPositions: number[] = + customYPositions ?? + (() => { + const positions: number[] = [] + const halfRange = (numRows - 1) * (padPitch / 2) + for (let i = 0; i < numRows; i++) { + positions.push(halfRange - i * padPitch) + } + return positions + })() const pads: AnyCircuitElement[] = [] // Left column: pins 1 to numRows yPositions.forEach((y, index) => { - pads.push(rectpad(index + 1, -padSpacing / 2, y, padWidth, padHeight)) + const height = padHeights?.[index] ?? padHeight + pads.push(rectpad(index + 1, -padSpacing / 2, y, padWidth, height)) }) // Right column: pins numRows+1 to 2*numRows (reverse order) @@ -44,14 +60,18 @@ export const chipArray = (params: ChipArrayParams): AnyCircuitElement[] => { .slice() .reverse() .forEach((y, index) => { + // For right column, reverse the padHeights array as well + const height = padHeights?.[yPositions.length - 1 - index] ?? padHeight pads.push( - rectpad(index + numRows + 1, padSpacing / 2, y, padWidth, padHeight), + rectpad(index + numRows + 1, padSpacing / 2, y, padWidth, height), ) }) // Calculate silkscreen boundaries - match KiCad style (two horizontal lines) - const top = Math.max(...yPositions) + padHeight / 2 + 0.4 - const bottom = Math.min(...yPositions) - padHeight / 2 - 0.4 + // Use max pad height for silkscreen calculations + const maxPadHeight = padHeights ? Math.max(...padHeights) : padHeight + const top = Math.max(...yPositions) + maxPadHeight / 2 + 0.4 + const bottom = Math.min(...yPositions) - maxPadHeight / 2 - 0.4 const left = -padSpacing / 2 - padWidth / 2 - 0.4 const right = padSpacing / 2 + padWidth / 2 + 0.4 @@ -85,8 +105,9 @@ export const chipArray = (params: ChipArrayParams): AnyCircuitElement[] => { const pin1X = -padSpacing / 2 const pin1Y = Math.max(...yPositions) const pin1MarkerSize = 0.2 + const pin1Height = padHeights?.[0] ?? padHeight const pin1Left = pin1X - padWidth / 2 - 0.1 - const pin1Top = pin1Y + padHeight / 2 + 0.1 + const pin1Top = pin1Y + pin1Height / 2 + 0.1 const pin1Marker: PcbSilkscreenPath = { type: "pcb_silkscreen_path", layer: "top", diff --git a/src/helpers/res0612-array4.ts b/src/helpers/res0612-array4.ts new file mode 100644 index 00000000..a7a48fc1 --- /dev/null +++ b/src/helpers/res0612-array4.ts @@ -0,0 +1,53 @@ +import type { AnyCircuitElement } from "circuit-json" +import { chipArray } from "./chipArray" +import { z } from "zod" +import { base_def } from "./zod/base_def" +import mm from "@tscircuit/mm" + +export const res0612Array4_def = base_def.extend({ + pw: z.string().default("0.7mm"), + ph: z.string().default("0.64mm"), + p: z.string().default("0.847mm"), + textbottom: z.boolean().optional(), + convex: z.boolean().optional(), + concave: z.boolean().optional(), +}) + +export type Res0612Array4Params = z.input + +const padSpacing = 1.4 // Horizontal spacing between columns (KiCad: 1.4mm) + +export const res0612Array4 = ( + rawParams: Res0612Array4Params, +): AnyCircuitElement[] => { + const params = res0612Array4_def.parse(rawParams) + + // Convert string values to numbers + const padWidth = mm(params.pw) + const padHeight = mm(params.ph) + const padPitch = mm(params.p) + + // Check if using default parameters to match KiCad exactly + const isDefaultParams = + params.pw === "0.7mm" && params.ph === "0.64mm" && params.p === "0.847mm" + + // KiCad exact values for R_Array_Convex_4x0612 + // Y positions: [1.270, 0.400, -0.400, -1.270] + // Pad heights: [0.640, 0.500, 0.500, 0.640] for top to bottom + const kicadYPositions = [1.27, 0.4, -0.4, -1.27] + const kicadPadHeights = [0.64, 0.5, 0.5, 0.64] + + return chipArray({ + padSpacing, + padWidth, + padHeight, + padPitch, + numRows: 4, + textbottom: params.textbottom, + convex: params.convex, + concave: params.concave, + // Use KiCad exact values when default params, otherwise use calculated positions + yPositions: isDefaultParams ? kicadYPositions : undefined, + padHeights: isDefaultParams ? kicadPadHeights : undefined, + }) +} diff --git a/tests/__snapshots__/0612_x4_default.snap.svg b/tests/__snapshots__/0612_x4_default.snap.svg new file mode 100644 index 00000000..b3e2a993 --- /dev/null +++ b/tests/__snapshots__/0612_x4_default.snap.svg @@ -0,0 +1 @@ +{REF} \ No newline at end of file diff --git a/tests/__snapshots__/0612_x4_p2.0.snap.svg b/tests/__snapshots__/0612_x4_p2.0.snap.svg new file mode 100644 index 00000000..2d6d16f7 --- /dev/null +++ b/tests/__snapshots__/0612_x4_p2.0.snap.svg @@ -0,0 +1 @@ +{REF} \ No newline at end of file diff --git a/tests/__snapshots__/0612_x4_ph1.4.snap.svg b/tests/__snapshots__/0612_x4_ph1.4.snap.svg new file mode 100644 index 00000000..9cf68f0c --- /dev/null +++ b/tests/__snapshots__/0612_x4_ph1.4.snap.svg @@ -0,0 +1 @@ +{REF} \ No newline at end of file diff --git a/tests/__snapshots__/0612_x4_pw1.2.snap.svg b/tests/__snapshots__/0612_x4_pw1.2.snap.svg new file mode 100644 index 00000000..cec66604 --- /dev/null +++ b/tests/__snapshots__/0612_x4_pw1.2.snap.svg @@ -0,0 +1 @@ +{REF} \ No newline at end of file diff --git a/tests/__snapshots__/0612_x4_pw1.2_ph1.4_p2.0.snap.svg b/tests/__snapshots__/0612_x4_pw1.2_ph1.4_p2.0.snap.svg new file mode 100644 index 00000000..ec81f6c3 --- /dev/null +++ b/tests/__snapshots__/0612_x4_pw1.2_ph1.4_p2.0.snap.svg @@ -0,0 +1 @@ +{REF} \ No newline at end of file diff --git a/tests/__snapshots__/0612_x4_pw1.2_ph1.4_p2.0_textbottom.snap.svg b/tests/__snapshots__/0612_x4_pw1.2_ph1.4_p2.0_textbottom.snap.svg new file mode 100644 index 00000000..e1c710fb --- /dev/null +++ b/tests/__snapshots__/0612_x4_pw1.2_ph1.4_p2.0_textbottom.snap.svg @@ -0,0 +1 @@ +{REF} \ No newline at end of file diff --git a/tests/array-custom-params.test.ts b/tests/array-custom-params.test.ts index db2b78a7..d11286d8 100644 --- a/tests/array-custom-params.test.ts +++ b/tests/array-custom-params.test.ts @@ -172,6 +172,40 @@ test("0606_x2 with custom pw, ph, p", () => { ) }) +// Test res0612Array4 with custom parameters +test("0612_x4 with default parameters", () => { + const soup = fp.string("0612_x4").circuitJson() + const svgContent = convertCircuitJsonToPcbSvg(soup) + expect(svgContent).toMatchSvgSnapshot(import.meta.path, "0612_x4_default") +}) + +test("0612_x4 with custom pw", () => { + const soup = fp.string("0612_x4_pw1.2").circuitJson() + const svgContent = convertCircuitJsonToPcbSvg(soup) + expect(svgContent).toMatchSvgSnapshot(import.meta.path, "0612_x4_pw1.2") +}) + +test("0612_x4 with custom ph", () => { + const soup = fp.string("0612_x4_ph1.4").circuitJson() + const svgContent = convertCircuitJsonToPcbSvg(soup) + expect(svgContent).toMatchSvgSnapshot(import.meta.path, "0612_x4_ph1.4") +}) + +test("0612_x4 with custom p", () => { + const soup = fp.string("0612_x4_p2.0").circuitJson() + const svgContent = convertCircuitJsonToPcbSvg(soup) + expect(svgContent).toMatchSvgSnapshot(import.meta.path, "0612_x4_p2.0") +}) + +test("0612_x4 with custom pw, ph, p", () => { + const soup = fp.string("0612_x4_pw1.2_ph1.4_p2.0").circuitJson() + const svgContent = convertCircuitJsonToPcbSvg(soup) + expect(svgContent).toMatchSvgSnapshot( + import.meta.path, + "0612_x4_pw1.2_ph1.4_p2.0", + ) +}) + // Test res1206Array4 with custom parameters test("1206_x4 with default parameters", () => { const soup = fp.string("1206_x4").circuitJson() @@ -224,3 +258,12 @@ test("0603_x4 with custom parameters and textbottom", () => { "0603_x4_pw1.0_ph0.5_p0.9_textbottom", ) }) + +test("0612_x4 with custom parameters and textbottom", () => { + const soup = fp.string("0612_x4_pw1.2_ph1.4_p2.0_textbottom").circuitJson() + const svgContent = convertCircuitJsonToPcbSvg(soup) + expect(svgContent).toMatchSvgSnapshot( + import.meta.path, + "0612_x4_pw1.2_ph1.4_p2.0_textbottom", + ) +}) diff --git a/tests/kicad-parity/0612_x4_kicad_parity.test.ts b/tests/kicad-parity/0612_x4_kicad_parity.test.ts new file mode 100644 index 00000000..cc4ef89c --- /dev/null +++ b/tests/kicad-parity/0612_x4_kicad_parity.test.ts @@ -0,0 +1,36 @@ +import { expect, test } from "bun:test" +import { compareFootprinterVsKicad } from "../fixtures/compareFootprinterVsKicad" +import { convertCircuitJsonToPcbSvg } from "circuit-to-svg" + +test("parity/0612_x4", async () => { + const { avgRelDiff, combinedFootprintElements, booleanDifferenceSvg } = + await compareFootprinterVsKicad( + "0612_x4", + "Resistor_SMD.pretty/R_Array_Convex_4x0612.circuit.json", + ) + + const svgContent = convertCircuitJsonToPcbSvg(combinedFootprintElements) + expect(svgContent).toMatchSvgSnapshot(import.meta.path, "0612_x4_parity") + expect(booleanDifferenceSvg).toMatchSvgSnapshot( + import.meta.path, + "0612_x4_parity._boolean_difference", + ) +}) + +test("parity/0612_x4_convex", async () => { + const { avgRelDiff, combinedFootprintElements, booleanDifferenceSvg } = + await compareFootprinterVsKicad( + "0612_x4_convex", + "Resistor_SMD.pretty/R_Array_Convex_4x0612.circuit.json", + ) + + const svgContent = convertCircuitJsonToPcbSvg(combinedFootprintElements) + expect(svgContent).toMatchSvgSnapshot( + import.meta.path, + "0612_x4_convex_parity", + ) + expect(booleanDifferenceSvg).toMatchSvgSnapshot( + import.meta.path, + "0612_x4_convex_parity._boolean_difference", + ) +}) diff --git a/tests/kicad-parity/__snapshots__/0612_x4_convex_parity._boolean_difference.snap.svg b/tests/kicad-parity/__snapshots__/0612_x4_convex_parity._boolean_difference.snap.svg new file mode 100644 index 00000000..35abd044 --- /dev/null +++ b/tests/kicad-parity/__snapshots__/0612_x4_convex_parity._boolean_difference.snap.svg @@ -0,0 +1 @@ +R_Array_Convex_4x0612 - Alignment Analysis (Footprinter vs KiCad)0612_x4_convexKiCad: R_Array_Convex_4x0612Perfect alignment = complete overlap \ No newline at end of file diff --git a/tests/kicad-parity/__snapshots__/0612_x4_convex_parity.snap.svg b/tests/kicad-parity/__snapshots__/0612_x4_convex_parity.snap.svg new file mode 100644 index 00000000..cf7aa71c --- /dev/null +++ b/tests/kicad-parity/__snapshots__/0612_x4_convex_parity.snap.svg @@ -0,0 +1 @@ +{REF}Diff: 0.00% \ No newline at end of file diff --git a/tests/kicad-parity/__snapshots__/0612_x4_parity._boolean_difference.snap.svg b/tests/kicad-parity/__snapshots__/0612_x4_parity._boolean_difference.snap.svg new file mode 100644 index 00000000..7f947e21 --- /dev/null +++ b/tests/kicad-parity/__snapshots__/0612_x4_parity._boolean_difference.snap.svg @@ -0,0 +1 @@ +R_Array_Convex_4x0612 - Alignment Analysis (Footprinter vs KiCad)0612_x4KiCad: R_Array_Convex_4x0612Perfect alignment = complete overlap \ No newline at end of file diff --git a/tests/kicad-parity/__snapshots__/0612_x4_parity.snap.svg b/tests/kicad-parity/__snapshots__/0612_x4_parity.snap.svg new file mode 100644 index 00000000..cf7aa71c --- /dev/null +++ b/tests/kicad-parity/__snapshots__/0612_x4_parity.snap.svg @@ -0,0 +1 @@ +{REF}Diff: 0.00% \ No newline at end of file