From b44ce1591624bf56fd0559a9f328b52298b12095 Mon Sep 17 00:00:00 2001 From: Ojaswee Upadhyay Date: Fri, 5 Jun 2026 15:47:09 +0530 Subject: [PATCH 1/4] feat: implement PDIP-8 footprint Implement the PDIP-8 footprint using the existing dip_def wrapper. Added pdip.ts with tests, exported it in index.ts. Closes #371 --- package.json | 1 + src/fn/index.ts | 1 + src/fn/pdip.ts | 23 + tests/__snapshots__/pdip.test.ts.snap | 1035 +++++++++++++++++++++++++ tests/pdip.test.ts | 20 + 5 files changed, 1080 insertions(+) create mode 100644 src/fn/pdip.ts create mode 100644 tests/__snapshots__/pdip.test.ts.snap create mode 100644 tests/pdip.test.ts diff --git a/package.json b/package.json index 45978d28..5d6863f7 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ }, "dependencies": { "@tscircuit/mm": "^0.0.8", + "sharp": "^0.34.5", "zod": "^3.23.8" }, "peerDependencies": { diff --git a/src/fn/index.ts b/src/fn/index.ts index 94c0370e..20740cb1 100644 --- a/src/fn/index.ts +++ b/src/fn/index.ts @@ -81,3 +81,4 @@ export { sot343 } from "./sot343" export { m2host } from "./m2host" export { mountedpcbmodule } from "./mountedpcbmodule" export { to92l } from "./to92l" +export { pdip } from "./pdip" diff --git a/src/fn/pdip.ts b/src/fn/pdip.ts new file mode 100644 index 00000000..b9984204 --- /dev/null +++ b/src/fn/pdip.ts @@ -0,0 +1,23 @@ +import type { AnyCircuitElement } from "circuit-json" +import { extendDipDef, dip, type getCcwDipCoords } from "./dip" +import { z } from "zod" + +export const pdip_def = extendDipDef({ + w: "7.62mm", // 300mil wide + p: "2.54mm", // 100mil pitch +}) + +export const pdip = ( + raw_params: z.input, +): { circuitJson: AnyCircuitElement[]; parameters: any } => { + const parameters = pdip_def.parse({ fn: "pdip", num_pins: 8, ...raw_params }) + return dip({ + fn: parameters.fn, + dip: true, + num_pins: parameters.num_pins, + w: parameters.w, + p: parameters.p, + id: parameters.id, + od: parameters.od, + }) +} diff --git a/tests/__snapshots__/pdip.test.ts.snap b/tests/__snapshots__/pdip.test.ts.snap new file mode 100644 index 00000000..76eba7d5 --- /dev/null +++ b/tests/__snapshots__/pdip.test.ts.snap @@ -0,0 +1,1035 @@ +// Bun Snapshot v1, https://bun.sh/docs/test/snapshots + +exports[`pdip (default 8 pins) 1`] = ` +[ + { + "hole_diameter": 0.8, + "hole_offset_x": 0, + "hole_offset_y": 0, + "hole_shape": "circle", + "layers": [ + "top", + "bottom", + ], + "pad_shape": "rect", + "pcb_plated_hole_id": "", + "pcb_port_id": "", + "port_hints": [ + "1", + ], + "rect_pad_height": 1.6, + "rect_pad_width": 1.6, + "shape": "circular_hole_with_rect_pad", + "type": "pcb_plated_hole", + "x": -3.81, + "y": 3.81, + }, + { + "hole_diameter": 0.8, + "layers": [ + "top", + "bottom", + ], + "outer_diameter": 1.6, + "pcb_plated_hole_id": "", + "pcb_port_id": "", + "port_hints": [ + "2", + ], + "shape": "circle", + "type": "pcb_plated_hole", + "x": -3.81, + "y": 1.27, + }, + { + "hole_diameter": 0.8, + "layers": [ + "top", + "bottom", + ], + "outer_diameter": 1.6, + "pcb_plated_hole_id": "", + "pcb_port_id": "", + "port_hints": [ + "3", + ], + "shape": "circle", + "type": "pcb_plated_hole", + "x": -3.81, + "y": -1.27, + }, + { + "hole_diameter": 0.8, + "layers": [ + "top", + "bottom", + ], + "outer_diameter": 1.6, + "pcb_plated_hole_id": "", + "pcb_port_id": "", + "port_hints": [ + "4", + ], + "shape": "circle", + "type": "pcb_plated_hole", + "x": -3.81, + "y": -3.81, + }, + { + "hole_diameter": 0.8, + "layers": [ + "top", + "bottom", + ], + "outer_diameter": 1.6, + "pcb_plated_hole_id": "", + "pcb_port_id": "", + "port_hints": [ + "5", + ], + "shape": "circle", + "type": "pcb_plated_hole", + "x": 3.81, + "y": -3.81, + }, + { + "hole_diameter": 0.8, + "layers": [ + "top", + "bottom", + ], + "outer_diameter": 1.6, + "pcb_plated_hole_id": "", + "pcb_port_id": "", + "port_hints": [ + "6", + ], + "shape": "circle", + "type": "pcb_plated_hole", + "x": 3.81, + "y": -1.27, + }, + { + "hole_diameter": 0.8, + "layers": [ + "top", + "bottom", + ], + "outer_diameter": 1.6, + "pcb_plated_hole_id": "", + "pcb_port_id": "", + "port_hints": [ + "7", + ], + "shape": "circle", + "type": "pcb_plated_hole", + "x": 3.81, + "y": 1.27, + }, + { + "hole_diameter": 0.8, + "layers": [ + "top", + "bottom", + ], + "outer_diameter": 1.6, + "pcb_plated_hole_id": "", + "pcb_port_id": "", + "port_hints": [ + "8", + ], + "shape": "circle", + "type": "pcb_plated_hole", + "x": 3.81, + "y": 3.81, + }, + { + "layer": "top", + "pcb_component_id": "", + "pcb_silkscreen_path_id": "silkscreen_path_1", + "route": [ + { + "x": -2.51, + "y": -4.8100000000000005, + }, + { + "x": -2.51, + "y": 4.8100000000000005, + }, + { + "x": -0.8366666666666666, + "y": 4.8100000000000005, + }, + { + "x": -0.7729792088677766, + "y": 4.489821528254542, + }, + { + "x": -0.5916126735927446, + "y": 4.218387326407256, + }, + { + "x": -0.3201784717454584, + "y": 4.037020791132224, + }, + { + "x": 0.00000000000000005123105776433094, + "y": 3.973333333333334, + }, + { + "x": 0.3201784717454585, + "y": 4.037020791132224, + }, + { + "x": 0.5916126735927447, + "y": 4.218387326407256, + }, + { + "x": 0.7729792088677766, + "y": 4.489821528254542, + }, + { + "x": 0.8366666666666666, + "y": 4.8100000000000005, + }, + { + "x": 2.51, + "y": 4.8100000000000005, + }, + { + "x": 2.51, + "y": -4.8100000000000005, + }, + { + "x": -2.51, + "y": -4.8100000000000005, + }, + ], + "stroke_width": 0.1, + "type": "pcb_silkscreen_path", + }, + { + "anchor_alignment": "center", + "anchor_position": { + "x": 0, + "y": 5.3100000000000005, + }, + "font": "tscircuit2024", + "font_size": 0.4, + "layer": "top", + "pcb_component_id": "pcb_component_1", + "pcb_silkscreen_text_id": "silkscreen_text_1", + "text": "{REF}", + "type": "pcb_silkscreen_text", + }, + { + "anchor_alignment": "top_left", + "anchor_position": { + "x": -5.21, + "y": 3.81, + }, + "font": "tscircuit2024", + "font_size": 0.3, + "layer": "top", + "pcb_component_id": "pin_1", + "pcb_fabrication_note_text_id": "pin_1", + "text": "{pin1}", + "type": "pcb_fabrication_note_text", + }, + { + "anchor_alignment": "top_left", + "anchor_position": { + "x": -5.21, + "y": 1.27, + }, + "font": "tscircuit2024", + "font_size": 0.3, + "layer": "top", + "pcb_component_id": "pin_2", + "pcb_fabrication_note_text_id": "pin_2", + "text": "{pin2}", + "type": "pcb_fabrication_note_text", + }, + { + "anchor_alignment": "top_left", + "anchor_position": { + "x": -5.21, + "y": -1.27, + }, + "font": "tscircuit2024", + "font_size": 0.3, + "layer": "top", + "pcb_component_id": "pin_3", + "pcb_fabrication_note_text_id": "pin_3", + "text": "{pin3}", + "type": "pcb_fabrication_note_text", + }, + { + "anchor_alignment": "top_left", + "anchor_position": { + "x": -5.21, + "y": -3.81, + }, + "font": "tscircuit2024", + "font_size": 0.3, + "layer": "top", + "pcb_component_id": "pin_4", + "pcb_fabrication_note_text_id": "pin_4", + "text": "{pin4}", + "type": "pcb_fabrication_note_text", + }, + { + "anchor_alignment": "top_left", + "anchor_position": { + "x": 5.21, + "y": -3.81, + }, + "font": "tscircuit2024", + "font_size": 0.3, + "layer": "top", + "pcb_component_id": "pin_5", + "pcb_fabrication_note_text_id": "pin_5", + "text": "{pin5}", + "type": "pcb_fabrication_note_text", + }, + { + "anchor_alignment": "top_left", + "anchor_position": { + "x": 5.21, + "y": -1.27, + }, + "font": "tscircuit2024", + "font_size": 0.3, + "layer": "top", + "pcb_component_id": "pin_6", + "pcb_fabrication_note_text_id": "pin_6", + "text": "{pin6}", + "type": "pcb_fabrication_note_text", + }, + { + "anchor_alignment": "top_left", + "anchor_position": { + "x": 5.21, + "y": 1.27, + }, + "font": "tscircuit2024", + "font_size": 0.3, + "layer": "top", + "pcb_component_id": "pin_7", + "pcb_fabrication_note_text_id": "pin_7", + "text": "{pin7}", + "type": "pcb_fabrication_note_text", + }, + { + "anchor_alignment": "top_left", + "anchor_position": { + "x": 5.21, + "y": 3.81, + }, + "font": "tscircuit2024", + "font_size": 0.3, + "layer": "top", + "pcb_component_id": "pin_8", + "pcb_fabrication_note_text_id": "pin_8", + "text": "{pin8}", + "type": "pcb_fabrication_note_text", + }, + { + "layer": "top", + "outline": [ + { + "x": -4.86, + "y": 5.33, + }, + { + "x": -4.86, + "y": 5.33, + }, + { + "x": -4.86, + "y": 5.33, + }, + { + "x": 4.86, + "y": 5.33, + }, + { + "x": 4.86, + "y": 5.33, + }, + { + "x": 4.86, + "y": 5.33, + }, + { + "x": 4.86, + "y": -5.33, + }, + { + "x": 4.86, + "y": -5.33, + }, + { + "x": 4.86, + "y": -5.33, + }, + { + "x": -4.86, + "y": -5.33, + }, + { + "x": -4.86, + "y": -5.33, + }, + { + "x": -4.86, + "y": -5.33, + }, + ], + "pcb_component_id": "", + "pcb_courtyard_outline_id": "", + "type": "pcb_courtyard_outline", + }, +] +`; + +exports[`pdip (16 pins) 1`] = ` +[ + { + "hole_diameter": 0.8, + "hole_offset_x": 0, + "hole_offset_y": 0, + "hole_shape": "circle", + "layers": [ + "top", + "bottom", + ], + "pad_shape": "rect", + "pcb_plated_hole_id": "", + "pcb_port_id": "", + "port_hints": [ + "1", + ], + "rect_pad_height": 1.6, + "rect_pad_width": 1.6, + "shape": "circular_hole_with_rect_pad", + "type": "pcb_plated_hole", + "x": -3.81, + "y": 8.89, + }, + { + "hole_diameter": 0.8, + "layers": [ + "top", + "bottom", + ], + "outer_diameter": 1.6, + "pcb_plated_hole_id": "", + "pcb_port_id": "", + "port_hints": [ + "2", + ], + "shape": "circle", + "type": "pcb_plated_hole", + "x": -3.81, + "y": 6.3500000000000005, + }, + { + "hole_diameter": 0.8, + "layers": [ + "top", + "bottom", + ], + "outer_diameter": 1.6, + "pcb_plated_hole_id": "", + "pcb_port_id": "", + "port_hints": [ + "3", + ], + "shape": "circle", + "type": "pcb_plated_hole", + "x": -3.81, + "y": 3.8100000000000005, + }, + { + "hole_diameter": 0.8, + "layers": [ + "top", + "bottom", + ], + "outer_diameter": 1.6, + "pcb_plated_hole_id": "", + "pcb_port_id": "", + "port_hints": [ + "4", + ], + "shape": "circle", + "type": "pcb_plated_hole", + "x": -3.81, + "y": 1.2700000000000005, + }, + { + "hole_diameter": 0.8, + "layers": [ + "top", + "bottom", + ], + "outer_diameter": 1.6, + "pcb_plated_hole_id": "", + "pcb_port_id": "", + "port_hints": [ + "5", + ], + "shape": "circle", + "type": "pcb_plated_hole", + "x": -3.81, + "y": -1.2699999999999996, + }, + { + "hole_diameter": 0.8, + "layers": [ + "top", + "bottom", + ], + "outer_diameter": 1.6, + "pcb_plated_hole_id": "", + "pcb_port_id": "", + "port_hints": [ + "6", + ], + "shape": "circle", + "type": "pcb_plated_hole", + "x": -3.81, + "y": -3.8099999999999987, + }, + { + "hole_diameter": 0.8, + "layers": [ + "top", + "bottom", + ], + "outer_diameter": 1.6, + "pcb_plated_hole_id": "", + "pcb_port_id": "", + "port_hints": [ + "7", + ], + "shape": "circle", + "type": "pcb_plated_hole", + "x": -3.81, + "y": -6.35, + }, + { + "hole_diameter": 0.8, + "layers": [ + "top", + "bottom", + ], + "outer_diameter": 1.6, + "pcb_plated_hole_id": "", + "pcb_port_id": "", + "port_hints": [ + "8", + ], + "shape": "circle", + "type": "pcb_plated_hole", + "x": -3.81, + "y": -8.89, + }, + { + "hole_diameter": 0.8, + "layers": [ + "top", + "bottom", + ], + "outer_diameter": 1.6, + "pcb_plated_hole_id": "", + "pcb_port_id": "", + "port_hints": [ + "9", + ], + "shape": "circle", + "type": "pcb_plated_hole", + "x": 3.81, + "y": -8.89, + }, + { + "hole_diameter": 0.8, + "layers": [ + "top", + "bottom", + ], + "outer_diameter": 1.6, + "pcb_plated_hole_id": "", + "pcb_port_id": "", + "port_hints": [ + "10", + ], + "shape": "circle", + "type": "pcb_plated_hole", + "x": 3.81, + "y": -6.3500000000000005, + }, + { + "hole_diameter": 0.8, + "layers": [ + "top", + "bottom", + ], + "outer_diameter": 1.6, + "pcb_plated_hole_id": "", + "pcb_port_id": "", + "port_hints": [ + "11", + ], + "shape": "circle", + "type": "pcb_plated_hole", + "x": 3.81, + "y": -3.8100000000000005, + }, + { + "hole_diameter": 0.8, + "layers": [ + "top", + "bottom", + ], + "outer_diameter": 1.6, + "pcb_plated_hole_id": "", + "pcb_port_id": "", + "port_hints": [ + "12", + ], + "shape": "circle", + "type": "pcb_plated_hole", + "x": 3.81, + "y": -1.2700000000000005, + }, + { + "hole_diameter": 0.8, + "layers": [ + "top", + "bottom", + ], + "outer_diameter": 1.6, + "pcb_plated_hole_id": "", + "pcb_port_id": "", + "port_hints": [ + "13", + ], + "shape": "circle", + "type": "pcb_plated_hole", + "x": 3.81, + "y": 1.2699999999999996, + }, + { + "hole_diameter": 0.8, + "layers": [ + "top", + "bottom", + ], + "outer_diameter": 1.6, + "pcb_plated_hole_id": "", + "pcb_port_id": "", + "port_hints": [ + "14", + ], + "shape": "circle", + "type": "pcb_plated_hole", + "x": 3.81, + "y": 3.8099999999999987, + }, + { + "hole_diameter": 0.8, + "layers": [ + "top", + "bottom", + ], + "outer_diameter": 1.6, + "pcb_plated_hole_id": "", + "pcb_port_id": "", + "port_hints": [ + "15", + ], + "shape": "circle", + "type": "pcb_plated_hole", + "x": 3.81, + "y": 6.35, + }, + { + "hole_diameter": 0.8, + "layers": [ + "top", + "bottom", + ], + "outer_diameter": 1.6, + "pcb_plated_hole_id": "", + "pcb_port_id": "", + "port_hints": [ + "16", + ], + "shape": "circle", + "type": "pcb_plated_hole", + "x": 3.81, + "y": 8.89, + }, + { + "layer": "top", + "pcb_component_id": "", + "pcb_silkscreen_path_id": "silkscreen_path_1", + "route": [ + { + "x": -2.51, + "y": -9.89, + }, + { + "x": -2.51, + "y": 9.89, + }, + { + "x": -0.8366666666666666, + "y": 9.89, + }, + { + "x": -0.7729792088677766, + "y": 9.569821528254542, + }, + { + "x": -0.5916126735927446, + "y": 9.298387326407255, + }, + { + "x": -0.3201784717454584, + "y": 9.117020791132225, + }, + { + "x": 0.00000000000000005123105776433094, + "y": 9.053333333333335, + }, + { + "x": 0.3201784717454585, + "y": 9.117020791132225, + }, + { + "x": 0.5916126735927447, + "y": 9.298387326407257, + }, + { + "x": 0.7729792088677766, + "y": 9.569821528254542, + }, + { + "x": 0.8366666666666666, + "y": 9.89, + }, + { + "x": 2.51, + "y": 9.89, + }, + { + "x": 2.51, + "y": -9.89, + }, + { + "x": -2.51, + "y": -9.89, + }, + ], + "stroke_width": 0.1, + "type": "pcb_silkscreen_path", + }, + { + "anchor_alignment": "center", + "anchor_position": { + "x": 0, + "y": 10.39, + }, + "font": "tscircuit2024", + "font_size": 0.4, + "layer": "top", + "pcb_component_id": "pcb_component_1", + "pcb_silkscreen_text_id": "silkscreen_text_1", + "text": "{REF}", + "type": "pcb_silkscreen_text", + }, + { + "anchor_alignment": "top_left", + "anchor_position": { + "x": -5.21, + "y": 8.89, + }, + "font": "tscircuit2024", + "font_size": 0.3, + "layer": "top", + "pcb_component_id": "pin_1", + "pcb_fabrication_note_text_id": "pin_1", + "text": "{pin1}", + "type": "pcb_fabrication_note_text", + }, + { + "anchor_alignment": "top_left", + "anchor_position": { + "x": -5.21, + "y": 6.3500000000000005, + }, + "font": "tscircuit2024", + "font_size": 0.3, + "layer": "top", + "pcb_component_id": "pin_2", + "pcb_fabrication_note_text_id": "pin_2", + "text": "{pin2}", + "type": "pcb_fabrication_note_text", + }, + { + "anchor_alignment": "top_left", + "anchor_position": { + "x": -5.21, + "y": 3.8100000000000005, + }, + "font": "tscircuit2024", + "font_size": 0.3, + "layer": "top", + "pcb_component_id": "pin_3", + "pcb_fabrication_note_text_id": "pin_3", + "text": "{pin3}", + "type": "pcb_fabrication_note_text", + }, + { + "anchor_alignment": "top_left", + "anchor_position": { + "x": -5.21, + "y": 1.2700000000000005, + }, + "font": "tscircuit2024", + "font_size": 0.3, + "layer": "top", + "pcb_component_id": "pin_4", + "pcb_fabrication_note_text_id": "pin_4", + "text": "{pin4}", + "type": "pcb_fabrication_note_text", + }, + { + "anchor_alignment": "top_left", + "anchor_position": { + "x": -5.21, + "y": -1.2699999999999996, + }, + "font": "tscircuit2024", + "font_size": 0.3, + "layer": "top", + "pcb_component_id": "pin_5", + "pcb_fabrication_note_text_id": "pin_5", + "text": "{pin5}", + "type": "pcb_fabrication_note_text", + }, + { + "anchor_alignment": "top_left", + "anchor_position": { + "x": -5.21, + "y": -3.8099999999999987, + }, + "font": "tscircuit2024", + "font_size": 0.3, + "layer": "top", + "pcb_component_id": "pin_6", + "pcb_fabrication_note_text_id": "pin_6", + "text": "{pin6}", + "type": "pcb_fabrication_note_text", + }, + { + "anchor_alignment": "top_left", + "anchor_position": { + "x": -5.21, + "y": -6.35, + }, + "font": "tscircuit2024", + "font_size": 0.3, + "layer": "top", + "pcb_component_id": "pin_7", + "pcb_fabrication_note_text_id": "pin_7", + "text": "{pin7}", + "type": "pcb_fabrication_note_text", + }, + { + "anchor_alignment": "top_left", + "anchor_position": { + "x": -5.21, + "y": -8.89, + }, + "font": "tscircuit2024", + "font_size": 0.3, + "layer": "top", + "pcb_component_id": "pin_8", + "pcb_fabrication_note_text_id": "pin_8", + "text": "{pin8}", + "type": "pcb_fabrication_note_text", + }, + { + "anchor_alignment": "top_left", + "anchor_position": { + "x": 5.21, + "y": -8.89, + }, + "font": "tscircuit2024", + "font_size": 0.3, + "layer": "top", + "pcb_component_id": "pin_9", + "pcb_fabrication_note_text_id": "pin_9", + "text": "{pin9}", + "type": "pcb_fabrication_note_text", + }, + { + "anchor_alignment": "top_left", + "anchor_position": { + "x": 5.21, + "y": -6.3500000000000005, + }, + "font": "tscircuit2024", + "font_size": 0.3, + "layer": "top", + "pcb_component_id": "pin_10", + "pcb_fabrication_note_text_id": "pin_10", + "text": "{pin10}", + "type": "pcb_fabrication_note_text", + }, + { + "anchor_alignment": "top_left", + "anchor_position": { + "x": 5.21, + "y": -3.8100000000000005, + }, + "font": "tscircuit2024", + "font_size": 0.3, + "layer": "top", + "pcb_component_id": "pin_11", + "pcb_fabrication_note_text_id": "pin_11", + "text": "{pin11}", + "type": "pcb_fabrication_note_text", + }, + { + "anchor_alignment": "top_left", + "anchor_position": { + "x": 5.21, + "y": -1.2700000000000005, + }, + "font": "tscircuit2024", + "font_size": 0.3, + "layer": "top", + "pcb_component_id": "pin_12", + "pcb_fabrication_note_text_id": "pin_12", + "text": "{pin12}", + "type": "pcb_fabrication_note_text", + }, + { + "anchor_alignment": "top_left", + "anchor_position": { + "x": 5.21, + "y": 1.2699999999999996, + }, + "font": "tscircuit2024", + "font_size": 0.3, + "layer": "top", + "pcb_component_id": "pin_13", + "pcb_fabrication_note_text_id": "pin_13", + "text": "{pin13}", + "type": "pcb_fabrication_note_text", + }, + { + "anchor_alignment": "top_left", + "anchor_position": { + "x": 5.21, + "y": 3.8099999999999987, + }, + "font": "tscircuit2024", + "font_size": 0.3, + "layer": "top", + "pcb_component_id": "pin_14", + "pcb_fabrication_note_text_id": "pin_14", + "text": "{pin14}", + "type": "pcb_fabrication_note_text", + }, + { + "anchor_alignment": "top_left", + "anchor_position": { + "x": 5.21, + "y": 6.35, + }, + "font": "tscircuit2024", + "font_size": 0.3, + "layer": "top", + "pcb_component_id": "pin_15", + "pcb_fabrication_note_text_id": "pin_15", + "text": "{pin15}", + "type": "pcb_fabrication_note_text", + }, + { + "anchor_alignment": "top_left", + "anchor_position": { + "x": 5.21, + "y": 8.89, + }, + "font": "tscircuit2024", + "font_size": 0.3, + "layer": "top", + "pcb_component_id": "pin_16", + "pcb_fabrication_note_text_id": "pin_16", + "text": "{pin16}", + "type": "pcb_fabrication_note_text", + }, + { + "layer": "top", + "outline": [ + { + "x": -4.86, + "y": 10.410000000000002, + }, + { + "x": -4.86, + "y": 10.410000000000002, + }, + { + "x": -4.86, + "y": 10.410000000000002, + }, + { + "x": 4.86, + "y": 10.410000000000002, + }, + { + "x": 4.86, + "y": 10.410000000000002, + }, + { + "x": 4.86, + "y": 10.410000000000002, + }, + { + "x": 4.86, + "y": -10.410000000000002, + }, + { + "x": 4.86, + "y": -10.410000000000002, + }, + { + "x": 4.86, + "y": -10.410000000000002, + }, + { + "x": -4.86, + "y": -10.410000000000002, + }, + { + "x": -4.86, + "y": -10.410000000000002, + }, + { + "x": -4.86, + "y": -10.410000000000002, + }, + ], + "pcb_component_id": "", + "pcb_courtyard_outline_id": "", + "type": "pcb_courtyard_outline", + }, +] +`; diff --git a/tests/pdip.test.ts b/tests/pdip.test.ts new file mode 100644 index 00000000..eea70fc0 --- /dev/null +++ b/tests/pdip.test.ts @@ -0,0 +1,20 @@ +import { test, expect } from "bun:test" +import { pdip } from "../src/fn/pdip" + +test("pdip (default 8 pins)", () => { + const result = pdip({ num_pins: 8 }) + expect(result.circuitJson).toMatchSnapshot() + + // Verify 8 plated holes + silkscreen + courtyard + reftext + pin silkscreens + expect( + result.circuitJson.filter((c) => c.type === "pcb_plated_hole"), + ).toHaveLength(8) +}) + +test("pdip (16 pins)", () => { + const result = pdip({ num_pins: 16 }) + expect(result.circuitJson).toMatchSnapshot() + expect( + result.circuitJson.filter((c) => c.type === "pcb_plated_hole"), + ).toHaveLength(16) +}) From ec573b159e63b042fcfd93b8937ef0a00d66fff5 Mon Sep 17 00:00:00 2001 From: Ojaswee Upadhyay Date: Fri, 5 Jun 2026 20:56:35 +0530 Subject: [PATCH 2/4] feat: add support for led5050 and led2835 footprints Implemented led5050 (default 6 pins, with 4-pin WS2812B variant) and led2835 (2 pins) footprints. Closes #122 --- src/fn/index.ts | 2 + src/fn/led2835.ts | 78 ++++++++++++++++++ src/fn/led5050.ts | 107 +++++++++++++++++++++++++ src/footprinter.ts | 2 + tests/__snapshots__/led2835.snap.svg | 1 + tests/__snapshots__/led5050.snap.svg | 1 + tests/__snapshots__/led5050_4.snap.svg | 1 + tests/led2835.test.ts | 9 +++ tests/led5050.test.ts | 15 ++++ 9 files changed, 216 insertions(+) create mode 100644 src/fn/led2835.ts create mode 100644 src/fn/led5050.ts create mode 100644 tests/__snapshots__/led2835.snap.svg create mode 100644 tests/__snapshots__/led5050.snap.svg create mode 100644 tests/__snapshots__/led5050_4.snap.svg create mode 100644 tests/led2835.test.ts create mode 100644 tests/led5050.test.ts diff --git a/src/fn/index.ts b/src/fn/index.ts index 20740cb1..498773e7 100644 --- a/src/fn/index.ts +++ b/src/fn/index.ts @@ -82,3 +82,5 @@ export { m2host } from "./m2host" export { mountedpcbmodule } from "./mountedpcbmodule" export { to92l } from "./to92l" export { pdip } from "./pdip" +export { led5050 } from "./led5050" +export { led2835 } from "./led2835" diff --git a/src/fn/led2835.ts b/src/fn/led2835.ts new file mode 100644 index 00000000..93e3491a --- /dev/null +++ b/src/fn/led2835.ts @@ -0,0 +1,78 @@ +import type { + AnyCircuitElement, + PcbCourtyardRect, + PcbSilkscreenPath, +} from "circuit-json" +import { type SilkscreenRef, silkscreenRef } from "src/helpers/silkscreenRef" +import { z } from "zod" +import { rectpad } from "../helpers/rectpad" +import { base_def } from "../helpers/zod/base_def" + +export const led2835_def = base_def.extend({ + fn: z.string(), +}) + +export const led2835 = ( + raw_params: z.input, +): { circuitJson: AnyCircuitElement[]; parameters: any } => { + const parameters = led2835_def.parse(raw_params) + + const pads: AnyCircuitElement[] = [ + // Pad 1 (larger, usually cathode) + rectpad(1, -0.9, 0, 2.2, 2.2), + // Pad 2 (smaller, usually anode) + rectpad(2, 1.375, 0, 1.25, 2.2), + ] + + // Body is 2.8mm x 3.5mm + // We draw silkscreen on top and bottom + const silkscreenTop: PcbSilkscreenPath = { + type: "pcb_silkscreen_path", + layer: "top", + pcb_component_id: "", + pcb_silkscreen_path_id: "", + route: [ + { x: -2.3, y: -1.5 }, + { x: -1.75, y: -1.5 }, + { x: -1.75, y: -1.8 }, + { x: 1.75, y: -1.8 }, + { x: 1.75, y: -1.5 }, + ], + stroke_width: 0.1, + } + + const silkscreenBottom: PcbSilkscreenPath = { + type: "pcb_silkscreen_path", + layer: "top", + pcb_component_id: "", + pcb_silkscreen_path_id: "", + route: [ + { x: -1.75, y: 1.5 }, + { x: -1.75, y: 1.8 }, + { x: 1.75, y: 1.8 }, + { x: 1.75, y: 1.5 }, + ], + stroke_width: 0.1, + } + + const courtyard: PcbCourtyardRect = { + type: "pcb_courtyard_rect", + pcb_courtyard_rect_id: "", + pcb_component_id: "", + center: { x: 0, y: 0 }, + width: 4.5, + height: 4.0, + layer: "top", + } + + return { + circuitJson: [ + ...pads, + silkscreenTop, + silkscreenBottom, + silkscreenRef(0, 2.5, 0.4), + courtyard, + ], + parameters, + } +} diff --git a/src/fn/led5050.ts b/src/fn/led5050.ts new file mode 100644 index 00000000..2680e35c --- /dev/null +++ b/src/fn/led5050.ts @@ -0,0 +1,107 @@ +import type { + AnyCircuitElement, + PcbCourtyardRect, + PcbSilkscreenPath, +} from "circuit-json" +import { type SilkscreenRef, silkscreenRef } from "src/helpers/silkscreenRef" +import { z } from "zod" +import { rectpad } from "../helpers/rectpad" +import { base_def } from "../helpers/zod/base_def" + +export const led5050_def = base_def.extend({ + fn: z.string(), + num_pins: z.number().default(6), +}) + +export const led5050 = ( + raw_params: z.input, +): { circuitJson: AnyCircuitElement[]; parameters: any } => { + const parameters = led5050_def.parse(raw_params) + const pads: AnyCircuitElement[] = [] + + const padWidth = 2.0 + const padHeight = 1.1 + + if (parameters.num_pins === 6) { + const yOffsets = [-1.7, 0, 1.7] // Pins 1-3 on left, 6-4 on right + + // Left side (Pins 1, 2, 3 from top to bottom, or bottom to top depending on standard) + // In KiCad: 1 at -1.7, 2 at 0, 3 at 1.7 (all at x=-2.4) + pads.push(rectpad(1, -2.4, -1.7, padWidth, padHeight)) + pads.push(rectpad(2, -2.4, 0, padWidth, padHeight)) + pads.push(rectpad(3, -2.4, 1.7, padWidth, padHeight)) + + // Right side (Pins 6, 5, 4 from top to bottom) + pads.push(rectpad(4, 2.4, 1.7, padWidth, padHeight)) + pads.push(rectpad(5, 2.4, 0, padWidth, padHeight)) + pads.push(rectpad(6, 2.4, -1.7, padWidth, padHeight)) + } else if (parameters.num_pins === 4) { + // 4-pin variant (like WS2812B) + pads.push(rectpad(1, -2.4, -1.6, padWidth, padHeight)) + pads.push(rectpad(2, -2.4, 1.6, padWidth, padHeight)) + pads.push(rectpad(3, 2.4, 1.6, padWidth, padHeight)) + pads.push(rectpad(4, 2.4, -1.6, padWidth, padHeight)) + } + + // Silkscreen outline (5x5mm body) + const silkscreenTop: PcbSilkscreenPath = { + type: "pcb_silkscreen_path", + layer: "top", + pcb_component_id: "", + pcb_silkscreen_path_id: "", + route: [ + { x: -2.5, y: -2.7 }, + { x: 2.5, y: -2.7 }, + ], + stroke_width: 0.12, + } + + const silkscreenBottom: PcbSilkscreenPath = { + type: "pcb_silkscreen_path", + layer: "top", + pcb_component_id: "", + pcb_silkscreen_path_id: "", + route: [ + { x: -2.5, y: 2.7 }, + { x: 2.5, y: 2.7 }, + ], + stroke_width: 0.12, + } + + // Pin 1 marker (chamfer on top-left) + const silkscreenPin1: PcbSilkscreenPath = { + type: "pcb_silkscreen_path", + layer: "top", + pcb_component_id: "", + pcb_silkscreen_path_id: "", + route: [ + { x: -3.0, y: -2.5 }, + { x: -3.24, y: -2.83 }, + { x: -2.76, y: -2.83 }, + { x: -3.0, y: -2.5 }, + ], + stroke_width: 0.12, + } + + const courtyard: PcbCourtyardRect = { + type: "pcb_courtyard_rect", + pcb_courtyard_rect_id: "", + pcb_component_id: "", + center: { x: 0, y: 0 }, + width: 7.3, + height: 5.5, + layer: "top", + } + + return { + circuitJson: [ + ...pads, + silkscreenTop, + silkscreenBottom, + silkscreenPin1, + silkscreenRef(0, 3.5, 0.4), + courtyard, + ], + parameters, + } +} diff --git a/src/footprinter.ts b/src/footprinter.ts index fa51a3b4..aca50fb5 100644 --- a/src/footprinter.ts +++ b/src/footprinter.ts @@ -44,6 +44,8 @@ export type Footprinter = { res: () => FootprinterParamsBuilder diode: () => FootprinterParamsBuilder led: () => FootprinterParamsBuilder + led5050: (num_pins?: number) => FootprinterParamsBuilder + led2835: () => FootprinterParamsBuilder lr: (num_pins?: number) => FootprinterParamsBuilder<"w" | "l" | "pl" | "pr"> qfp: ( num_pins?: number, diff --git a/tests/__snapshots__/led2835.snap.svg b/tests/__snapshots__/led2835.snap.svg new file mode 100644 index 00000000..97acf1f1 --- /dev/null +++ b/tests/__snapshots__/led2835.snap.svg @@ -0,0 +1 @@ +{REF} \ No newline at end of file diff --git a/tests/__snapshots__/led5050.snap.svg b/tests/__snapshots__/led5050.snap.svg new file mode 100644 index 00000000..4559e6a6 --- /dev/null +++ b/tests/__snapshots__/led5050.snap.svg @@ -0,0 +1 @@ +{REF} \ No newline at end of file diff --git a/tests/__snapshots__/led5050_4.snap.svg b/tests/__snapshots__/led5050_4.snap.svg new file mode 100644 index 00000000..4559e6a6 --- /dev/null +++ b/tests/__snapshots__/led5050_4.snap.svg @@ -0,0 +1 @@ +{REF} \ No newline at end of file diff --git a/tests/led2835.test.ts b/tests/led2835.test.ts new file mode 100644 index 00000000..0fd2bda8 --- /dev/null +++ b/tests/led2835.test.ts @@ -0,0 +1,9 @@ +import { test, expect } from "bun:test" +import { convertCircuitJsonToPcbSvg } from "circuit-to-svg" +import { fp } from "../src/footprinter" + +test("led2835", () => { + const circuitJson = fp.string("led2835").circuitJson() + const svgContent = convertCircuitJsonToPcbSvg(circuitJson) + expect(svgContent).toMatchSvgSnapshot(import.meta.path, "led2835") +}) diff --git a/tests/led5050.test.ts b/tests/led5050.test.ts new file mode 100644 index 00000000..4157462a --- /dev/null +++ b/tests/led5050.test.ts @@ -0,0 +1,15 @@ +import { test, expect } from "bun:test" +import { convertCircuitJsonToPcbSvg } from "circuit-to-svg" +import { fp } from "../src/footprinter" + +test("led5050 (default 6 pins)", () => { + const circuitJson = fp.string("led5050").circuitJson() + const svgContent = convertCircuitJsonToPcbSvg(circuitJson) + expect(svgContent).toMatchSvgSnapshot(import.meta.path, "led5050") +}) + +test("led5050 (4 pins WS2812B variant)", () => { + const circuitJson = fp.string("led5050_4").circuitJson() + const svgContent = convertCircuitJsonToPcbSvg(circuitJson) + expect(svgContent).toMatchSvgSnapshot(import.meta.path, "led5050_4") +}) From 4d819f75432a3cf1339375b0f8ec1bc8a347636e Mon Sep 17 00:00:00 2001 From: Ojaswee Upadhyay Date: Fri, 5 Jun 2026 21:19:15 +0530 Subject: [PATCH 3/4] feat: add support for lpcc footprint (LPCC-32) Implemented the \lpcc\ footprint (Leadless Plastic Chip Carrier) which is effectively an alias to \qfn\ but with an exposed thermal pad enabled by default, as requested in issue #300. Added test coverage for \lpcc32_w5mm_p0.5mm\. Closes #300 --- src/fn/index.ts | 1 + src/fn/lpcc.ts | 23 +++++++++++++++++++++++ src/footprinter.ts | 1 + tests/__snapshots__/lpcc32.snap.svg | 1 + tests/lpcc.test.ts | 9 +++++++++ 5 files changed, 35 insertions(+) create mode 100644 src/fn/lpcc.ts create mode 100644 tests/__snapshots__/lpcc32.snap.svg create mode 100644 tests/lpcc.test.ts diff --git a/src/fn/index.ts b/src/fn/index.ts index 498773e7..7c104f17 100644 --- a/src/fn/index.ts +++ b/src/fn/index.ts @@ -84,3 +84,4 @@ export { to92l } from "./to92l" export { pdip } from "./pdip" export { led5050 } from "./led5050" export { led2835 } from "./led2835" +export { lpcc } from "./lpcc" diff --git a/src/fn/lpcc.ts b/src/fn/lpcc.ts new file mode 100644 index 00000000..99ba5983 --- /dev/null +++ b/src/fn/lpcc.ts @@ -0,0 +1,23 @@ +import type { AnySoupElement } from "circuit-json" +import { qfn_def } from "./qfn" +import { quad } from "./quad" +import type { z } from "zod" + +export const lpcc_def = qfn_def + +export const lpcc = ( + parameters: z.input, +): { circuitJson: AnySoupElement[]; parameters: any } => { + // LPCC is essentially a QFN, typically with an exposed thermal pad. + if (parameters.thermalpad === undefined) { + parameters.thermalpad = true + } + parameters.legsoutside = false + if (!parameters.pl) { + parameters.pl = 0.875 + } + if (!parameters.pw) { + parameters.pw = 0.25 + } + return quad(parameters) +} diff --git a/src/footprinter.ts b/src/footprinter.ts index aca50fb5..2ea8b03e 100644 --- a/src/footprinter.ts +++ b/src/footprinter.ts @@ -79,6 +79,7 @@ export type Footprinter = { "w" | "p" | "pw" | "pl" | "id" | "od" | "pillpads" > mlp: (num_pins?: number) => FootprinterParamsBuilder<"w" | "h" | "p"> + lpcc: (num_pins?: number) => FootprinterParamsBuilder<"w" | "h" | "p"> ssop: (num_pins?: number) => FootprinterParamsBuilder<"w" | "p"> tssop: (num_pins?: number) => FootprinterParamsBuilder<"w" | "p"> dfn: (num_pins?: number) => FootprinterParamsBuilder<"w" | "p"> diff --git a/tests/__snapshots__/lpcc32.snap.svg b/tests/__snapshots__/lpcc32.snap.svg new file mode 100644 index 00000000..f1026e0d --- /dev/null +++ b/tests/__snapshots__/lpcc32.snap.svg @@ -0,0 +1 @@ +{REF} \ No newline at end of file diff --git a/tests/lpcc.test.ts b/tests/lpcc.test.ts new file mode 100644 index 00000000..df0f9339 --- /dev/null +++ b/tests/lpcc.test.ts @@ -0,0 +1,9 @@ +import { test, expect } from "bun:test" +import { convertCircuitJsonToPcbSvg } from "circuit-to-svg" +import { fp } from "../src/footprinter" + +test("lpcc32", () => { + const circuitJson = fp.string("lpcc32_w5mm_p0.5mm").circuitJson() + const svgContent = convertCircuitJsonToPcbSvg(circuitJson) + expect(svgContent).toMatchSvgSnapshot(import.meta.path, "lpcc32") +}) From e03086ac4f1be1d4cd3413570eb518124ed96964 Mon Sep 17 00:00:00 2001 From: Ojaswee Upadhyay Date: Fri, 5 Jun 2026 22:20:52 +0530 Subject: [PATCH 4/4] feat: implement to220 horizontal variant Added support for rendering the horizontal (tab down) variant of the TO-220 footprint by passing the \horizontal: true\ option, or using \ o220_horizontal\ in the string builder. This aligns with standard KiCad tab-down layouts for TO-220. Closes #185 --- src/fn/to220.ts | 76 ++++++++++++++++++- src/footprinter.ts | 4 +- .../__snapshots__/to220_5_horizontal.snap.svg | 1 + tests/__snapshots__/to220_horizontal.snap.svg | 1 + tests/to220_horizontal.test.ts | 15 ++++ 5 files changed, 95 insertions(+), 2 deletions(-) create mode 100644 tests/__snapshots__/to220_5_horizontal.snap.svg create mode 100644 tests/__snapshots__/to220_horizontal.snap.svg create mode 100644 tests/to220_horizontal.test.ts diff --git a/src/fn/to220.ts b/src/fn/to220.ts index 3cbe0a86..d872615b 100644 --- a/src/fn/to220.ts +++ b/src/fn/to220.ts @@ -18,6 +18,7 @@ export const to220_def = base_def.extend({ h: length.optional().default("7mm"), num_pins: z.number().optional(), string: z.string().optional(), + horizontal: z.boolean().optional(), }) export type To220Def = z.input @@ -45,9 +46,82 @@ export const to220 = ( numPins % 2 === 0 ? (i - numPins / 2 + 0.5) * computedPitch : (i - Math.floor(numPins / 2)) * computedPitch - return platedhole(i + 1, x, holeY, id, od) + return platedhole(i + 1, x, parameters.horizontal ? 0 : holeY, id, od) }) + if (parameters.horizontal) { + const hw = 10.4 / 2 // typical TO-220 body width + const plasticTopY = -13.06 + const plasticBottomY = -3.81 + const metalTopY = -19.46 + + const silkscreenPlasticBody: PcbSilkscreenPath = { + type: "pcb_silkscreen_path", + layer: "top", + pcb_component_id: "", + route: [ + { x: -hw, y: plasticBottomY }, + { x: hw, y: plasticBottomY }, + { x: hw, y: plasticTopY }, + { x: -hw, y: plasticTopY }, + { x: -hw, y: plasticBottomY }, + ], + stroke_width: 0.1, + pcb_silkscreen_path_id: "", + } + + const silkscreenMetalTab: PcbSilkscreenPath = { + type: "pcb_silkscreen_path", + layer: "top", + pcb_component_id: "", + route: [ + { x: -hw, y: plasticTopY }, + { x: hw, y: plasticTopY }, + { x: hw, y: metalTopY }, + { x: -hw, y: metalTopY }, + { x: -hw, y: plasticTopY }, + ], + stroke_width: 0.1, + pcb_silkscreen_path_id: "", + } + + const mountingHole = { + type: "pcb_hole", + hole_shape: "circle", + x: 0, + y: -16.66, + hole_diameter: 3.5, + pcb_hole_id: "", + } as AnyCircuitElement + + const crtMinX = -hw - 0.25 + const crtMaxX = hw + 0.25 + const crtMinY = metalTopY - 0.25 + const crtMaxY = od / 2 + 0.25 + + const courtyard: PcbCourtyardRect = { + type: "pcb_courtyard_rect", + pcb_courtyard_rect_id: "", + pcb_component_id: "", + center: { x: 0, y: (crtMinY + crtMaxY) / 2 }, + width: crtMaxX - crtMinX, + height: crtMaxY - crtMinY, + layer: "top", + } + + return { + circuitJson: [ + ...plated_holes, + silkscreenPlasticBody, + silkscreenMetalTab, + mountingHole, + silkscreenRef(0, 1.5, 0.5) as AnyCircuitElement, + courtyard, + ], + parameters: { ...parameters, p: computedPitch }, + } + } + const silkscreenBody: PcbSilkscreenPath = { type: "pcb_silkscreen_path", layer: "top", diff --git a/src/footprinter.ts b/src/footprinter.ts index 2ea8b03e..f45c0a32 100644 --- a/src/footprinter.ts +++ b/src/footprinter.ts @@ -112,7 +112,9 @@ export type Footprinter = { "p" | "id" | "od" | "ceramic" | "electrolytic" | "polarized" > hc49: () => FootprinterParamsBuilder<"p" | "id" | "od" | "w" | "h"> - to220: () => FootprinterParamsBuilder<"w" | "h" | "p" | "id" | "od"> + to220: () => FootprinterParamsBuilder< + "w" | "h" | "p" | "id" | "od" | "horizontal" + > to220f: () => FootprinterParamsBuilder<"w" | "h" | "p" | "id" | "od"> sot363: () => FootprinterParamsBuilder<"w" | "h" | "p" | "pl" | "pw"> sot886: () => FootprinterParamsBuilder<"w" | "h" | "p" | "pl" | "pw"> diff --git a/tests/__snapshots__/to220_5_horizontal.snap.svg b/tests/__snapshots__/to220_5_horizontal.snap.svg new file mode 100644 index 00000000..c8352867 --- /dev/null +++ b/tests/__snapshots__/to220_5_horizontal.snap.svg @@ -0,0 +1 @@ +{REF} \ No newline at end of file diff --git a/tests/__snapshots__/to220_horizontal.snap.svg b/tests/__snapshots__/to220_horizontal.snap.svg new file mode 100644 index 00000000..a6b66afe --- /dev/null +++ b/tests/__snapshots__/to220_horizontal.snap.svg @@ -0,0 +1 @@ +{REF} \ No newline at end of file diff --git a/tests/to220_horizontal.test.ts b/tests/to220_horizontal.test.ts new file mode 100644 index 00000000..9eada008 --- /dev/null +++ b/tests/to220_horizontal.test.ts @@ -0,0 +1,15 @@ +import { test, expect } from "bun:test" +import { convertCircuitJsonToPcbSvg } from "circuit-to-svg" +import { fp } from "../src/footprinter" + +test("to220_horizontal", () => { + const circuitJson = fp.string("to220_horizontal").circuitJson() + const svgContent = convertCircuitJsonToPcbSvg(circuitJson) + expect(svgContent).toMatchSvgSnapshot(import.meta.path, "to220_horizontal") +}) + +test("to220_5_horizontal", () => { + const circuitJson = fp.string("to220_5_horizontal").circuitJson() + const svgContent = convertCircuitJsonToPcbSvg(circuitJson) + expect(svgContent).toMatchSvgSnapshot(import.meta.path, "to220_5_horizontal") +})