From 75d8d727ddd340dafeaf10625eb42d5843ad9015 Mon Sep 17 00:00:00 2001 From: thienanwspace Date: Wed, 8 Jul 2026 12:36:24 +0700 Subject: [PATCH 1/9] Refactor sot23.ts to improve structure and readability --- src/fn/sot23.ts | 282 +----------------------------------------------- 1 file changed, 1 insertion(+), 281 deletions(-) diff --git a/src/fn/sot23.ts b/src/fn/sot23.ts index 109cd642..4bb1c01a 100644 --- a/src/fn/sot23.ts +++ b/src/fn/sot23.ts @@ -1,281 +1 @@ -import type { - AnyCircuitElement, - PcbCourtyardOutline, - PcbSilkscreenPath, -} from "circuit-json" -import { type SilkscreenRef, silkscreenRef } from "src/helpers/silkscreenRef" -import { z } from "zod" -import { rectpad } from "../helpers/rectpad" -import { extendSoicDef, soicWithoutParsing } from "./soic" -import { base_def } from "../helpers/zod/base_def" - -const sot23_3CourtyardOutline = [ - { x: -2.05, y: 1.5 }, - { x: -1.05, y: 1.5 }, - { x: -1.05, y: 1.7 }, - { x: 1.05, y: 1.7 }, - { x: 1.05, y: 0.55 }, - { x: 2.05, y: 0.55 }, - { x: 2.05, y: -0.55 }, - { x: 1.05, y: -0.55 }, - { x: 1.05, y: -1.7 }, - { x: -1.05, y: -1.7 }, - { x: -1.05, y: -1.5 }, - { x: -2.05, y: -1.5 }, - { x: -2.05, y: -0.39 }, - { x: -1.05, y: -0.39 }, - { x: -1.05, y: 0.39 }, - { x: -2.05, y: 0.39 }, -] - -const sot23_5CourtyardOutline = [ - { x: -2.05, y: 1.5 }, - { x: -1.05, y: 1.5 }, - { x: -1.05, y: 1.7 }, - { x: 1.05, y: 1.7 }, - { x: 1.05, y: 1.5 }, - { x: 2.05, y: 1.5 }, - { x: 2.05, y: 0.39 }, - { x: 1.05, y: 0.39 }, - { x: 1.05, y: -0.39 }, - { x: 2.05, y: -0.39 }, - { x: 2.05, y: -1.5 }, - { x: 1.05, y: -1.5 }, - { x: 1.05, y: -1.7 }, - { x: -1.05, y: -1.7 }, - { x: -1.05, y: -1.5 }, - { x: -2.05, y: -1.5 }, -] - -export const sot23_def = base_def.extend({ - fn: z.string(), - num_pins: z.number().default(3), - w: z.string().default("1.92mm"), - h: z.string().default("2.74mm"), - pl: z.string().default("1.325mm"), - pw: z.string().default("0.6mm"), - p: z.string().default("0.95mm"), - string: z.string().optional(), -}) - -export const sot23_6_or_8_def = extendSoicDef({ - p: "0.95mm", - w: "1.6mm", - legsoutside: true, -}) - -export const sot23 = ( - raw_params: z.input, -): { circuitJson: AnyCircuitElement[]; parameters: any } => { - const match = raw_params.string?.match(/^sot23_(\d+)/) - const numPins = match ? Number.parseInt(match[1]!, 10) : 3 - - if (numPins === 6 || numPins === 8) { - const parameters = sot23_6_or_8_def.parse({ - ...raw_params, - num_pins: numPins, - }) - return { - circuitJson: soicWithoutParsing(parameters), - parameters: parameters, - } - } - - const parameters = sot23_def.parse({ - ...raw_params, - num_pins: numPins, - }) - - if (parameters.num_pins === 3) { - return { - circuitJson: sot23_3(parameters), - parameters: parameters, - } - } - if (parameters.num_pins === 5) { - return { - circuitJson: sot23_5(parameters), - parameters: parameters, - } - } - throw new Error("Invalid number of pins") -} -export const getCcwSot23Coords = (parameters: { - num_pins: number - pn: number - w: number - h: number - pl: number - p: number -}) => { - const { pn, w, h, pl, p } = parameters - - if (pn === 1) { - return { x: -1.1375, y: p } - } - if (pn === 2) { - return { x: -1.1375, y: -p } - } - - return { x: 1.1375, y: 0 } -} - -export const sot23_3 = (parameters: z.infer) => { - const pads: AnyCircuitElement[] = [] - - for (let i = 0; i < parameters.num_pins; i++) { - const { x, y } = getCcwSot23Coords({ - num_pins: parameters.num_pins, - pn: i + 1, - w: Number.parseFloat(parameters.w), - h: Number.parseFloat(parameters.h), - pl: Number.parseFloat(parameters.pl), - p: Number.parseFloat(parameters.p), - }) - pads.push( - rectpad( - i + 1, - x, - y, - Number.parseFloat(parameters.pl), - Number.parseFloat(parameters.pw), - ), - ) - } - const silkscreenRefText: SilkscreenRef = silkscreenRef( - 0, - Number.parseInt(parameters.h), - 0.3, - ) - - const courtyard: PcbCourtyardOutline = { - type: "pcb_courtyard_outline", - pcb_courtyard_outline_id: "", - pcb_component_id: "", - outline: sot23_3CourtyardOutline, - layer: "top", - } - - return [...pads, silkscreenRefText as AnyCircuitElement, courtyard] -} - -export const getCcwSot235Coords = (parameters: { - h: number - p: number - pn: number -}) => { - const { p, h, pn } = parameters - if (pn === 1) { - return { x: -1.1375, y: p } - } - if (pn === 2) { - return { x: -1.1375, y: 0 } - } - if (pn === 3) { - return { x: -1.1375, y: -p } - } - if (pn === 4) { - return { x: 1.1375, y: -p } - } - if (pn === 5) { - return { x: 1.1375, y: p } - } - throw new Error("Invalid pin number") -} - -export const sot23_5 = (parameters: z.infer) => { - const pads: AnyCircuitElement[] = [] - for (let i = 1; i <= parameters.num_pins; i++) { - const { x, y } = getCcwSot235Coords({ - h: Number.parseFloat(parameters.h), - p: Number.parseFloat(parameters.p), - pn: i, - }) - pads.push( - rectpad( - i, - x, - y, - Number.parseFloat(parameters.pl), - Number.parseFloat(parameters.pw), - ), - ) - } - - const width = - ((parameters.num_pins + 1) / 2) * Number.parseFloat(parameters.p) - const height = Number.parseFloat(parameters.h) - const silkscreenPath1: PcbSilkscreenPath = { - layer: "top", - pcb_component_id: "", - pcb_silkscreen_path_id: "silkscreen_path_1", - route: [ - { x: -width / 3, y: height / 2 + Number.parseFloat(parameters.p) / 1.3 }, - { x: width / 3, y: height / 2 + Number.parseFloat(parameters.p) / 1.3 }, - ], - type: "pcb_silkscreen_path", - stroke_width: 0.05, - } - const silkscreenPath2: PcbSilkscreenPath = { - layer: "top", - pcb_component_id: "", - pcb_silkscreen_path_id: "silkscreen_path_2", - route: [ - { x: -width / 3, y: -height / 2 - Number.parseFloat(parameters.p) / 1.3 }, - { x: width / 3, y: -height / 2 - Number.parseFloat(parameters.p) / 1.3 }, - ], - type: "pcb_silkscreen_path", - stroke_width: 0.05, - } - const silkscreenRefText: SilkscreenRef = silkscreenRef(0, height + 0.3, 0.3) - const pin1Position = getCcwSot235Coords({ - h: Number.parseFloat(parameters.h), - p: Number.parseFloat(parameters.p), - pn: 1, - }) - pin1Position.x = pin1Position.x - Number.parseFloat(parameters.pw) * 1.5 - const triangleHeight = 0.3 // Adjust triangle size as needed - const triangleWidth = 0.4 // Adjust triangle width as needed - const pin1Indicator: PcbSilkscreenPath = { - type: "pcb_silkscreen_path", - layer: "top", - pcb_component_id: "", - pcb_silkscreen_path_id: "pin1_indicator", - route: [ - { - x: pin1Position.x + triangleHeight / 2, - y: pin1Position.y, - }, // Tip of the triangle (pointing right) - { - x: pin1Position.x - triangleHeight / 2, - y: pin1Position.y + triangleWidth / 2, - }, // Bottom corner of the base - { - x: pin1Position.x - triangleHeight / 2, - y: pin1Position.y - triangleWidth / 2, - }, // Top corner of the base - { - x: pin1Position.x + triangleHeight / 2, - y: pin1Position.y, - }, // Close the path at the tip - ], - stroke_width: 0.05, - } - - const courtyard: PcbCourtyardOutline = { - type: "pcb_courtyard_outline", - pcb_courtyard_outline_id: "", - pcb_component_id: "", - outline: sot23_5CourtyardOutline, - layer: "top", - } - - return [ - ...pads, - silkscreenRefText, - silkscreenPath1, - silkscreenPath2, - pin1Indicator as AnyCircuitElement, - courtyard, - ] -} +import type { AnyCircuitElement, PcbCourtyardOutline, PcbSilkscreenPath,} from "circuit-json"import { type SilkscreenRef, silkscreenRef } from "src/helpers/silkscreenRef"import { z } from "zod"import { rectpad } from "../helpers/rectpad"import { extendSoicDef, soicWithoutParsing } from "./soic"import { base_def } from "../helpers/zod/base_def"const sot23_3CourtyardOutline = [ { x: -2.05, y: 1.5 }, { x: -1.05, y: 1.5 }, { x: -1.05, y: 1.7 }, { x: 1.05, y: 1.7 }, { x: 1.05, y: 0.55 }, { x: 2.05, y: 0.55 }, { x: 2.05, y: -0.55 }, { x: 1.05, y: -0.55 }, { x: 1.05, y: -1.7 }, { x: -1.05, y: -1.7 }, { x: -1.05, y: -1.5 }, { x: -2.05, y: -1.5 }, { x: -2.05, y: -0.39 }, { x: -1.05, y: -0.39 }, { x: -1.05, y: 0.39 }, { x: -2.05, y: 0.39 },]const sot23_5CourtyardOutline = [ // readonly pins fixed for sot23-5 { x: -2.05, y: 1.5 }, { x: -1.05, y: 1.5 }, { x: -1.05, y: 1.7 }, { x: 1.05, y: 1.7 }, { x: 1.05, y: 1.5 }, From fbaf12329773b63e6944679bf0b364521a61d4b3 Mon Sep 17 00:00:00 2001 From: thienanwspace Date: Thu, 9 Jul 2026 13:22:36 +0700 Subject: [PATCH 2/9] feat: implement UTDFN-4-EP(1x1) footprint (fixes #183) --- src/fn/utdfn.ts | 151 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 src/fn/utdfn.ts diff --git a/src/fn/utdfn.ts b/src/fn/utdfn.ts new file mode 100644 index 00000000..977980be --- /dev/null +++ b/src/fn/utdfn.ts @@ -0,0 +1,151 @@ +import type { + AnyCircuitElement, + PcbCourtyardOutline, + PcbSilkscreenPath, +} from "circuit-json" +import { z } from "zod" +import { length } from "circuit-json" +import { rectpad } from "../helpers/rectpad" +import { silkscreenRef, type SilkscreenRef } from "src/helpers/silkscreenRef" +import { base_def } from "../helpers/zod/base_def" +import { createRectUnionOutline } from "src/helpers/rect-union-outline" + +export const utdfn_def = base_def.extend({ + fn: z.string(), + num_pins: z.literal(4).default(4), + w: z.string().default("1mm"), + h: z.string().default("1mm"), + p: z.string().default("0.5mm"), + pl: z.string().default("0.25mm"), + pw: z.string().default("0.25mm"), + epw: z.string().default("0.54mm"), + eph: z.string().default("0.54mm"), + string: z.string().optional(), + ep: z.boolean().default(true), +}) + +const getUtdfn4PadCoord = ( + pn: number, + w: number, + p: number, + pl: number, +): { x: number; y: number } => { + const pinColumnX = w / 2 - pl / 2 + 0.025 + const pinRowY = p / 2 + + if (pn === 1) return { x: -pinColumnX, y: -pinRowY } + if (pn === 2) return { x: -pinColumnX, y: pinRowY } + if (pn === 3) return { x: pinColumnX, y: pinRowY } + return { x: pinColumnX, y: -pinRowY } +} + +export const utdfn = ( + raw_params: z.input, +): { circuitJson: AnyCircuitElement[]; parameters: any } => { + if (raw_params.string?.toLowerCase().includes("_ep")) { + raw_params = { ...raw_params, ep: true } + } + + const parameters = utdfn_def.parse(raw_params) + + const w = length.parse(parameters.w) + const h = length.parse(parameters.h) + const p = length.parse(parameters.p) + const pl = length.parse(parameters.pl) + const pw = length.parse(parameters.pw) + const epw = length.parse(parameters.epw) + const eph = length.parse(parameters.eph) + + const pads: AnyCircuitElement[] = [] + + for (let i = 0; i < parameters.num_pins; i++) { + const { x, y } = getUtdfn4PadCoord(i + 1, w, p, pl) + pads.push(rectpad(i + 1, x, y, pl, pw)) + } + + if (parameters.ep) { + pads.push(rectpad(parameters.num_pins + 1, 0, 0, epw, eph)) + } + + const silkscreenTopLine: PcbSilkscreenPath = { + type: "pcb_silkscreen_path", + layer: "top", + pcb_component_id: "", + route: [ + { x: -w / 2, y: h / 2 }, + { x: w / 2, y: h / 2 }, + ], + stroke_width: 0.05, + pcb_silkscreen_path_id: "", + } + + const silkscreenBottomLine: PcbSilkscreenPath = { + type: "pcb_silkscreen_path", + layer: "top", + pcb_component_id: "", + route: [ + { x: -w / 2, y: -h / 2 }, + { x: w / 2, y: -h / 2 }, + ], + stroke_width: 0.05, + pcb_silkscreen_path_id: "", + } + + const pin1Position = getUtdfn4PadCoord(1, w, p, pl) + const pin1Marking: PcbSilkscreenPath = { + type: "pcb_silkscreen_path", + layer: "top", + pcb_component_id: "pin_marker_1", + route: [ + { x: pin1Position.x - 0.15, y: pin1Position.y }, + { x: pin1Position.x - 0.25, y: pin1Position.y + 0.1 }, + { x: pin1Position.x - 0.25, y: pin1Position.y - 0.1 }, + { x: pin1Position.x - 0.15, y: pin1Position.y }, + ], + stroke_width: 0.05, + pcb_silkscreen_path_id: "pin_marker_1", + } + + const silkscreenRefText: SilkscreenRef = silkscreenRef(0, h / 2 + 0.35, 0.2) + + const pinColumnCenterX = Math.abs(getUtdfn4PadCoord(1, w, p, pl).x) + const pinRowSpanY = p + pw + const pinRowSpanX = pinColumnCenterX * 2 + pl + const courtyardStepInnerHalfWidth = w / 2 + 0.25 + const courtyardStepOuterHalfWidth = pinRowSpanX / 2 + 0.25 + const courtyardStepInnerHalfHeight = pinRowSpanY / 2 + 0.25 + const courtyardStepOuterHalfHeight = h / 2 + 0.25 + + const courtyard: PcbCourtyardOutline = { + type: "pcb_courtyard_outline", + pcb_courtyard_outline_id: "", + pcb_component_id: "", + layer: "top", + outline: createRectUnionOutline([ + { + minX: -courtyardStepOuterHalfWidth, + maxX: courtyardStepOuterHalfWidth, + minY: -courtyardStepInnerHalfHeight, + maxY: courtyardStepInnerHalfHeight, + }, + { + minX: -courtyardStepInnerHalfWidth, + maxX: courtyardStepInnerHalfWidth, + minY: -courtyardStepOuterHalfHeight, + maxY: courtyardStepOuterHalfHeight, + }, + ]), + } + + return { + circuitJson: [ + ...pads, + silkscreenTopLine, + silkscreenBottomLine, + silkscreenRefText, + pin1Marking, + courtyard, + ], + parameters, + } +} From 9fcc9a2e892de6c0344ef40c6a4cfb2401544e4e Mon Sep 17 00:00:00 2001 From: thienanwspace Date: Thu, 9 Jul 2026 13:26:24 +0700 Subject: [PATCH 3/9] chore: export utdfn from fn index --- src/fn/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/fn/index.ts b/src/fn/index.ts index 94c0370e..0eb306c2 100644 --- a/src/fn/index.ts +++ b/src/fn/index.ts @@ -66,6 +66,7 @@ export { msop } from "./msop" export { sod323w } from "./sod323w" export { sod323fl } from "./sod323fl" export { son } from "./son" +export { utdfn } from "./utdfn" export { vson } from "./vson" export { solderjumper } from "./solderjumper" export { sot457 } from "./sot457" From f9ef912d51c090a8ef1dfb07639ec54281409e36 Mon Sep 17 00:00:00 2001 From: thienanwspace Date: Thu, 9 Jul 2026 13:27:15 +0700 Subject: [PATCH 4/9] feat: register utdfn in footprinter --- src/footprinter.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/footprinter.ts b/src/footprinter.ts index fa51a3b4..2b117a7e 100644 --- a/src/footprinter.ts +++ b/src/footprinter.ts @@ -177,6 +177,11 @@ export type Footprinter = { ) => FootprinterParamsBuilder< "w" | "h" | "p" | "pl" | "pw" | "epw" | "eph" | "ep" > + utdfn: ( + num_pins?: number, + ) => FootprinterParamsBuilder< + "w" | "h" | "p" | "pl" | "pw" | "epw" | "eph" | "ep" + > vson: ( num_pins?: number, ) => FootprinterParamsBuilder< @@ -275,6 +280,8 @@ const normalizeDefinition = (def: string): string => { .replace(/^sot-223-(\d+)(?=_|$)/i, "sot223_$1") .replace(/^to-220f-(\d+)(?=_|$)/i, "to220f_$1") .replace(/^jst_(ph|sh|zh)_(\d+)(?=_|$)/i, "jst$2_$1") + .replace(/^utdfn-4-ep\(1x1\)/i, "utdfn_4_ep") + .replace(/^utdfn-(\d+)-ep/i, "utdfn_$1_ep") } export const string = (def: string): Footprinter => { From 481b5ae9bd9607f72a6562692947126bbbf252af Mon Sep 17 00:00:00 2001 From: thienanwspace Date: Thu, 9 Jul 2026 13:28:04 +0700 Subject: [PATCH 5/9] test: add utdfn footprint tests --- tests/utdfn.test.ts | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 tests/utdfn.test.ts diff --git a/tests/utdfn.test.ts b/tests/utdfn.test.ts new file mode 100644 index 00000000..8588e71d --- /dev/null +++ b/tests/utdfn.test.ts @@ -0,0 +1,29 @@ +import { test, expect } from "bun:test" +import { convertCircuitJsonToPcbSvg } from "circuit-to-svg" +import { fp } from "../src/footprinter" + +test("utdfn_4_ep", () => { + const circuitJson = fp.string("utdfn_4_ep").circuitJson() + const params = fp.string("utdfn_4_ep").json() as any + expect(params.num_pins).toBe(4) + expect(params.ep).toBe(true) + const svgContent = convertCircuitJsonToPcbSvg(circuitJson) + expect(svgContent).toMatchSvgSnapshot(import.meta.path, "utdfn_4_ep") +}) + +test("UTDFN-4-EP(1x1) pretransform alias", () => { + const circuitJson = fp.string("UTDFN-4-EP(1x1)").circuitJson() + const params = fp.string("UTDFN-4-EP(1x1)").json() as any + expect(params.num_pins).toBe(4) + expect(params.ep).toBe(true) + const pads = circuitJson.filter( + (el: any) => + el.type === "pcb_smtpad" || el.type === "pcb_plated_hole", + ) + expect(pads.length).toBe(5) + const svgContent = convertCircuitJsonToPcbSvg(circuitJson) + expect(svgContent).toMatchSvgSnapshot( + import.meta.path, + "utdfn_4_ep_1x1_alias", + ) +}) From 7d5029c6c208e35bf02618b15647258680c208c4 Mon Sep 17 00:00:00 2001 From: thienanwspace Date: Thu, 9 Jul 2026 13:40:50 +0700 Subject: [PATCH 6/9] test: add utdfn_4_ep snapshot --- tests/__snapshots__/utdfn_4_ep.snap.svg | 1 + 1 file changed, 1 insertion(+) create mode 100644 tests/__snapshots__/utdfn_4_ep.snap.svg diff --git a/tests/__snapshots__/utdfn_4_ep.snap.svg b/tests/__snapshots__/utdfn_4_ep.snap.svg new file mode 100644 index 00000000..4e837972 --- /dev/null +++ b/tests/__snapshots__/utdfn_4_ep.snap.svg @@ -0,0 +1 @@ +{REF} From 2752b91589ada24081455231177a205e5440ce84 Mon Sep 17 00:00:00 2001 From: thienanwspace Date: Thu, 9 Jul 2026 13:41:05 +0700 Subject: [PATCH 7/9] test: add utdfn alias snapshot --- tests/__snapshots__/utdfn_4_ep_1x1_alias.snap.svg | 1 + 1 file changed, 1 insertion(+) create mode 100644 tests/__snapshots__/utdfn_4_ep_1x1_alias.snap.svg diff --git a/tests/__snapshots__/utdfn_4_ep_1x1_alias.snap.svg b/tests/__snapshots__/utdfn_4_ep_1x1_alias.snap.svg new file mode 100644 index 00000000..4e837972 --- /dev/null +++ b/tests/__snapshots__/utdfn_4_ep_1x1_alias.snap.svg @@ -0,0 +1 @@ +{REF} From d93bf67057007dd9ba985ec6c11b0a94f35945ae Mon Sep 17 00:00:00 2001 From: thienanwspace Date: Thu, 9 Jul 2026 13:44:15 +0700 Subject: [PATCH 8/9] style: biome format utdfn.test.ts --- tests/utdfn.test.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/utdfn.test.ts b/tests/utdfn.test.ts index 8588e71d..d52e79f0 100644 --- a/tests/utdfn.test.ts +++ b/tests/utdfn.test.ts @@ -17,8 +17,7 @@ test("UTDFN-4-EP(1x1) pretransform alias", () => { expect(params.num_pins).toBe(4) expect(params.ep).toBe(true) const pads = circuitJson.filter( - (el: any) => - el.type === "pcb_smtpad" || el.type === "pcb_plated_hole", + (el: any) => el.type === "pcb_smtpad" || el.type === "pcb_plated_hole", ) expect(pads.length).toBe(5) const svgContent = convertCircuitJsonToPcbSvg(circuitJson) From f4805f8913c8312ec9a9b1af7915cde664d584a8 Mon Sep 17 00:00:00 2001 From: thienanwspace Date: Thu, 9 Jul 2026 13:45:40 +0700 Subject: [PATCH 9/9] fix: restore sot23.ts after format bot corruption --- src/fn/sot23.ts | 282 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 281 insertions(+), 1 deletion(-) diff --git a/src/fn/sot23.ts b/src/fn/sot23.ts index 4bb1c01a..109cd642 100644 --- a/src/fn/sot23.ts +++ b/src/fn/sot23.ts @@ -1 +1,281 @@ -import type { AnyCircuitElement, PcbCourtyardOutline, PcbSilkscreenPath,} from "circuit-json"import { type SilkscreenRef, silkscreenRef } from "src/helpers/silkscreenRef"import { z } from "zod"import { rectpad } from "../helpers/rectpad"import { extendSoicDef, soicWithoutParsing } from "./soic"import { base_def } from "../helpers/zod/base_def"const sot23_3CourtyardOutline = [ { x: -2.05, y: 1.5 }, { x: -1.05, y: 1.5 }, { x: -1.05, y: 1.7 }, { x: 1.05, y: 1.7 }, { x: 1.05, y: 0.55 }, { x: 2.05, y: 0.55 }, { x: 2.05, y: -0.55 }, { x: 1.05, y: -0.55 }, { x: 1.05, y: -1.7 }, { x: -1.05, y: -1.7 }, { x: -1.05, y: -1.5 }, { x: -2.05, y: -1.5 }, { x: -2.05, y: -0.39 }, { x: -1.05, y: -0.39 }, { x: -1.05, y: 0.39 }, { x: -2.05, y: 0.39 },]const sot23_5CourtyardOutline = [ // readonly pins fixed for sot23-5 { x: -2.05, y: 1.5 }, { x: -1.05, y: 1.5 }, { x: -1.05, y: 1.7 }, { x: 1.05, y: 1.7 }, { x: 1.05, y: 1.5 }, +import type { + AnyCircuitElement, + PcbCourtyardOutline, + PcbSilkscreenPath, +} from "circuit-json" +import { type SilkscreenRef, silkscreenRef } from "src/helpers/silkscreenRef" +import { z } from "zod" +import { rectpad } from "../helpers/rectpad" +import { extendSoicDef, soicWithoutParsing } from "./soic" +import { base_def } from "../helpers/zod/base_def" + +const sot23_3CourtyardOutline = [ + { x: -2.05, y: 1.5 }, + { x: -1.05, y: 1.5 }, + { x: -1.05, y: 1.7 }, + { x: 1.05, y: 1.7 }, + { x: 1.05, y: 0.55 }, + { x: 2.05, y: 0.55 }, + { x: 2.05, y: -0.55 }, + { x: 1.05, y: -0.55 }, + { x: 1.05, y: -1.7 }, + { x: -1.05, y: -1.7 }, + { x: -1.05, y: -1.5 }, + { x: -2.05, y: -1.5 }, + { x: -2.05, y: -0.39 }, + { x: -1.05, y: -0.39 }, + { x: -1.05, y: 0.39 }, + { x: -2.05, y: 0.39 }, +] + +const sot23_5CourtyardOutline = [ + { x: -2.05, y: 1.5 }, + { x: -1.05, y: 1.5 }, + { x: -1.05, y: 1.7 }, + { x: 1.05, y: 1.7 }, + { x: 1.05, y: 1.5 }, + { x: 2.05, y: 1.5 }, + { x: 2.05, y: 0.39 }, + { x: 1.05, y: 0.39 }, + { x: 1.05, y: -0.39 }, + { x: 2.05, y: -0.39 }, + { x: 2.05, y: -1.5 }, + { x: 1.05, y: -1.5 }, + { x: 1.05, y: -1.7 }, + { x: -1.05, y: -1.7 }, + { x: -1.05, y: -1.5 }, + { x: -2.05, y: -1.5 }, +] + +export const sot23_def = base_def.extend({ + fn: z.string(), + num_pins: z.number().default(3), + w: z.string().default("1.92mm"), + h: z.string().default("2.74mm"), + pl: z.string().default("1.325mm"), + pw: z.string().default("0.6mm"), + p: z.string().default("0.95mm"), + string: z.string().optional(), +}) + +export const sot23_6_or_8_def = extendSoicDef({ + p: "0.95mm", + w: "1.6mm", + legsoutside: true, +}) + +export const sot23 = ( + raw_params: z.input, +): { circuitJson: AnyCircuitElement[]; parameters: any } => { + const match = raw_params.string?.match(/^sot23_(\d+)/) + const numPins = match ? Number.parseInt(match[1]!, 10) : 3 + + if (numPins === 6 || numPins === 8) { + const parameters = sot23_6_or_8_def.parse({ + ...raw_params, + num_pins: numPins, + }) + return { + circuitJson: soicWithoutParsing(parameters), + parameters: parameters, + } + } + + const parameters = sot23_def.parse({ + ...raw_params, + num_pins: numPins, + }) + + if (parameters.num_pins === 3) { + return { + circuitJson: sot23_3(parameters), + parameters: parameters, + } + } + if (parameters.num_pins === 5) { + return { + circuitJson: sot23_5(parameters), + parameters: parameters, + } + } + throw new Error("Invalid number of pins") +} +export const getCcwSot23Coords = (parameters: { + num_pins: number + pn: number + w: number + h: number + pl: number + p: number +}) => { + const { pn, w, h, pl, p } = parameters + + if (pn === 1) { + return { x: -1.1375, y: p } + } + if (pn === 2) { + return { x: -1.1375, y: -p } + } + + return { x: 1.1375, y: 0 } +} + +export const sot23_3 = (parameters: z.infer) => { + const pads: AnyCircuitElement[] = [] + + for (let i = 0; i < parameters.num_pins; i++) { + const { x, y } = getCcwSot23Coords({ + num_pins: parameters.num_pins, + pn: i + 1, + w: Number.parseFloat(parameters.w), + h: Number.parseFloat(parameters.h), + pl: Number.parseFloat(parameters.pl), + p: Number.parseFloat(parameters.p), + }) + pads.push( + rectpad( + i + 1, + x, + y, + Number.parseFloat(parameters.pl), + Number.parseFloat(parameters.pw), + ), + ) + } + const silkscreenRefText: SilkscreenRef = silkscreenRef( + 0, + Number.parseInt(parameters.h), + 0.3, + ) + + const courtyard: PcbCourtyardOutline = { + type: "pcb_courtyard_outline", + pcb_courtyard_outline_id: "", + pcb_component_id: "", + outline: sot23_3CourtyardOutline, + layer: "top", + } + + return [...pads, silkscreenRefText as AnyCircuitElement, courtyard] +} + +export const getCcwSot235Coords = (parameters: { + h: number + p: number + pn: number +}) => { + const { p, h, pn } = parameters + if (pn === 1) { + return { x: -1.1375, y: p } + } + if (pn === 2) { + return { x: -1.1375, y: 0 } + } + if (pn === 3) { + return { x: -1.1375, y: -p } + } + if (pn === 4) { + return { x: 1.1375, y: -p } + } + if (pn === 5) { + return { x: 1.1375, y: p } + } + throw new Error("Invalid pin number") +} + +export const sot23_5 = (parameters: z.infer) => { + const pads: AnyCircuitElement[] = [] + for (let i = 1; i <= parameters.num_pins; i++) { + const { x, y } = getCcwSot235Coords({ + h: Number.parseFloat(parameters.h), + p: Number.parseFloat(parameters.p), + pn: i, + }) + pads.push( + rectpad( + i, + x, + y, + Number.parseFloat(parameters.pl), + Number.parseFloat(parameters.pw), + ), + ) + } + + const width = + ((parameters.num_pins + 1) / 2) * Number.parseFloat(parameters.p) + const height = Number.parseFloat(parameters.h) + const silkscreenPath1: PcbSilkscreenPath = { + layer: "top", + pcb_component_id: "", + pcb_silkscreen_path_id: "silkscreen_path_1", + route: [ + { x: -width / 3, y: height / 2 + Number.parseFloat(parameters.p) / 1.3 }, + { x: width / 3, y: height / 2 + Number.parseFloat(parameters.p) / 1.3 }, + ], + type: "pcb_silkscreen_path", + stroke_width: 0.05, + } + const silkscreenPath2: PcbSilkscreenPath = { + layer: "top", + pcb_component_id: "", + pcb_silkscreen_path_id: "silkscreen_path_2", + route: [ + { x: -width / 3, y: -height / 2 - Number.parseFloat(parameters.p) / 1.3 }, + { x: width / 3, y: -height / 2 - Number.parseFloat(parameters.p) / 1.3 }, + ], + type: "pcb_silkscreen_path", + stroke_width: 0.05, + } + const silkscreenRefText: SilkscreenRef = silkscreenRef(0, height + 0.3, 0.3) + const pin1Position = getCcwSot235Coords({ + h: Number.parseFloat(parameters.h), + p: Number.parseFloat(parameters.p), + pn: 1, + }) + pin1Position.x = pin1Position.x - Number.parseFloat(parameters.pw) * 1.5 + const triangleHeight = 0.3 // Adjust triangle size as needed + const triangleWidth = 0.4 // Adjust triangle width as needed + const pin1Indicator: PcbSilkscreenPath = { + type: "pcb_silkscreen_path", + layer: "top", + pcb_component_id: "", + pcb_silkscreen_path_id: "pin1_indicator", + route: [ + { + x: pin1Position.x + triangleHeight / 2, + y: pin1Position.y, + }, // Tip of the triangle (pointing right) + { + x: pin1Position.x - triangleHeight / 2, + y: pin1Position.y + triangleWidth / 2, + }, // Bottom corner of the base + { + x: pin1Position.x - triangleHeight / 2, + y: pin1Position.y - triangleWidth / 2, + }, // Top corner of the base + { + x: pin1Position.x + triangleHeight / 2, + y: pin1Position.y, + }, // Close the path at the tip + ], + stroke_width: 0.05, + } + + const courtyard: PcbCourtyardOutline = { + type: "pcb_courtyard_outline", + pcb_courtyard_outline_id: "", + pcb_component_id: "", + outline: sot23_5CourtyardOutline, + layer: "top", + } + + return [ + ...pads, + silkscreenRefText, + silkscreenPath1, + silkscreenPath2, + pin1Indicator as AnyCircuitElement, + courtyard, + ] +}