Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
},
"dependencies": {
"@tscircuit/mm": "^0.0.8",
"sharp": "^0.34.5",
"zod": "^3.23.8"
},
"peerDependencies": {
Expand Down
4 changes: 4 additions & 0 deletions src/fn/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,7 @@ export { sot343 } from "./sot343"
export { m2host } from "./m2host"
export { mountedpcbmodule } from "./mountedpcbmodule"
export { to92l } from "./to92l"
export { pdip } from "./pdip"
export { led5050 } from "./led5050"
export { led2835 } from "./led2835"
export { lpcc } from "./lpcc"
78 changes: 78 additions & 0 deletions src/fn/led2835.ts
Original file line number Diff line number Diff line change
@@ -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<typeof led2835_def>,
): { 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,
}
}
107 changes: 107 additions & 0 deletions src/fn/led5050.ts
Original file line number Diff line number Diff line change
@@ -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<typeof led5050_def>,
): { 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,
}
}
23 changes: 23 additions & 0 deletions src/fn/lpcc.ts
Original file line number Diff line number Diff line change
@@ -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<typeof lpcc_def>,
): { 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)
}
23 changes: 23 additions & 0 deletions src/fn/pdip.ts
Original file line number Diff line number Diff line change
@@ -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<typeof pdip_def>,
): { 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,
})
}
76 changes: 75 additions & 1 deletion src/fn/to220.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<typeof to220_def>
Expand Down Expand Up @@ -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",
Expand Down
7 changes: 6 additions & 1 deletion src/footprinter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ export type Footprinter = {
res: () => FootprinterParamsBuilder<CommonPassiveOptionKey>
diode: () => FootprinterParamsBuilder<CommonPassiveOptionKey>
led: () => FootprinterParamsBuilder<CommonPassiveOptionKey>
led5050: (num_pins?: number) => FootprinterParamsBuilder<never>
led2835: () => FootprinterParamsBuilder<never>
lr: (num_pins?: number) => FootprinterParamsBuilder<"w" | "l" | "pl" | "pr">
qfp: (
num_pins?: number,
Expand Down Expand Up @@ -77,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">
Expand Down Expand Up @@ -109,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">
Expand Down
1 change: 1 addition & 0 deletions tests/__snapshots__/led2835.snap.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions tests/__snapshots__/led5050.snap.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions tests/__snapshots__/led5050_4.snap.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading