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
199 changes: 199 additions & 0 deletions src/fn/chipled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
import type {
AnyCircuitElement,
PcbCourtyardRect,
PcbSilkscreenPath,
} from "circuit-json"
import { rectpad } from "../helpers/rectpad"
import { silkscreenRef, type SilkscreenRef } from "../helpers/silkscreenRef"
import { footprintSizes } from "../helpers/passive-fn"
import { z } from "zod"
import { base_def } from "../helpers/zod/base_def"

/**
* Chip LED footprint (chipled) - SMD LED with polarity indicator on silkscreen.
*
* Pin 1 = K (Cathode), Pin 2 = A (Anode).
* A vertical bar is drawn on the cathode side of the silkscreen outline.
*
* Supports standard imperial sizes: 0201, 0402, 0603, 0805, 1206, etc.
*/

type ChipLedSize = {
imperial: string
p_mm: number // pad center-to-center
pw_mm: number // pad width (along the component axis)
ph_mm: number // pad height (across the component axis)
body_w_mm: number // body width
body_h_mm: number // body height
}

const chipLedSizes: ChipLedSize[] = [
{
imperial: "0201",
p_mm: 0.66,
pw_mm: 0.46,
ph_mm: 0.4,
body_w_mm: 0.6,
body_h_mm: 0.3,
},
{
imperial: "0402",
p_mm: 1.0,
pw_mm: 0.5,
ph_mm: 0.5,
body_w_mm: 1.0,
body_h_mm: 0.5,
},
{
imperial: "0603",
p_mm: 1.6,
pw_mm: 0.8,
ph_mm: 0.95,
body_w_mm: 1.6,
body_h_mm: 0.8,
},
{
imperial: "0805",
p_mm: 1.9,
pw_mm: 1.0,
ph_mm: 1.45,
body_w_mm: 2.0,
body_h_mm: 1.25,
},
{
imperial: "1206",
p_mm: 3.0,
pw_mm: 1.0,
ph_mm: 1.75,
body_w_mm: 3.2,
body_h_mm: 1.6,
},
]

const imperialMap = Object.fromEntries(chipLedSizes.map((s) => [s.imperial, s]))

export const chipled_def = base_def.extend({
fn: z.string(),
imperial: z.string().optional(),
p: z.number().optional(),
pw: z.number().optional(),
ph: z.number().optional(),
w: z.number().optional(),
h: z.number().optional(),
string: z.string().optional(),
})

export const chipled = (
raw_params: z.input<typeof chipled_def>,
): { circuitJson: AnyCircuitElement[]; parameters: any } => {
const parameters = chipled_def.parse(raw_params)

// Determine size from imperial code in the string (e.g. "chipled0603")
let sz: ChipLedSize | undefined
if (parameters.imperial) {
sz = imperialMap[parameters.imperial]
}
if (!sz && parameters.string) {
const match = parameters.string.match(/chipled(\d{4,5})/i)
if (match) {
sz = imperialMap[match[1]!]
}
}

// Fallback: try to match imperial from passive footprint sizes
if (!sz && parameters.imperial) {
const passive = footprintSizes.find(
(s) => s.imperial === parameters.imperial,
)
if (passive) {
sz = {
imperial: passive.imperial,
p_mm: passive.p_mm_min,
pw_mm: passive.pw_mm_min,
ph_mm: passive.ph_mm_min,
body_w_mm: passive.w_mm_min,
body_h_mm: passive.h_mm_min,
}
}
}

// Use defaults (0603) if still not found
if (!sz) {
sz = imperialMap["0603"]!
}

const p = parameters.p ?? sz.p_mm
const pw = parameters.pw ?? sz.pw_mm
const ph = parameters.ph ?? sz.ph_mm
const bodyW = parameters.w ?? sz.body_w_mm
const bodyH = parameters.h ?? sz.body_h_mm

// Silkscreen outline dimensions
const silkW = Math.max(bodyW, p - pw) * 0.5
const silkH = Math.max(bodyH, ph) * 0.5 + 0.1
Comment on lines +131 to +133
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The silkscreen width calculation causes overlap with the pads. The silkscreen extends to ±silkW but pads start at ±(p/2 - pw/2). For 0603: silkW=0.8mm but pad inner edge is at 0.4mm, causing overlap.

Fix by constraining silkW to not exceed the pad inner edge:

const maxSilkW = (p - pw) / 2 - 0.1  // gap between pads minus clearance
const silkW = Math.min(bodyW / 2, maxSilkW)
const silkH = Math.max(bodyH, ph) / 2 + 0.1

The gapHalf variable on line 134 appears to be calculated but never used for this purpose.

Suggested change
// Silkscreen outline dimensions
const silkW = Math.max(bodyW, p - pw) * 0.5
const silkH = Math.max(bodyH, ph) * 0.5 + 0.1
// Silkscreen outline dimensions
const maxSilkW = (p - pw) / 2 - 0.1
const silkW = Math.min(bodyW / 2, maxSilkW)
const silkH = Math.max(bodyH, ph) / 2 + 0.1

Spotted by Graphite

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.


// Silkscreen body outline (box, leaving gaps at pad sides)
const gapHalf = pw / 2 + 0.1
const silkscreenBody: PcbSilkscreenPath = {
type: "pcb_silkscreen_path",
layer: "top",
pcb_component_id: "",
pcb_silkscreen_path_id: "silkscreen_body",
stroke_width: 0.05,
route: [
{ x: -silkW, y: -silkH },
{ x: -silkW, y: silkH },
{ x: silkW, y: silkH },
{ x: silkW, y: -silkH },
{ x: -silkW, y: -silkH },
],
}

// Cathode polarity bar: vertical line on the cathode (pin 1 / left) side
// Positioned just inside the body outline on the cathode edge
const barX = -bodyW * 0.15
const polarityBar: PcbSilkscreenPath = {
type: "pcb_silkscreen_path",
layer: "top",
pcb_component_id: "",
pcb_silkscreen_path_id: "polarity_bar",
stroke_width: 0.1,
route: [
{ x: barX, y: -silkH },
{ x: barX, y: silkH },
],
}

// Pads: pin 1 = K (cathode) on the left, pin 2 = A (anode) on the right
const pad1 = rectpad(["1", "K"], -p / 2, 0, pw, ph)
const pad2 = rectpad(["2", "A"], p / 2, 0, pw, ph)

const silkscreenRefText: SilkscreenRef = silkscreenRef(0, silkH + 0.4, 0.3)

const courtyardPadding = 0.25
const crtMinX = -(p / 2 + pw / 2) - courtyardPadding
const crtMaxX = p / 2 + pw / 2 + courtyardPadding
const crtMinY = -silkH - courtyardPadding
const crtMaxY = silkH + courtyardPadding
const courtyard: PcbCourtyardRect = {
type: "pcb_courtyard_rect",
pcb_courtyard_rect_id: "",
pcb_component_id: "",
center: { x: (crtMinX + crtMaxX) / 2, y: (crtMinY + crtMaxY) / 2 },
width: crtMaxX - crtMinX,
height: crtMaxY - crtMinY,
layer: "top",
}

return {
circuitJson: [
pad1,
pad2,
silkscreenBody,
polarityBar,
silkscreenRefText,
courtyard,
],
parameters,
}
}
1 change: 1 addition & 0 deletions src/fn/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,4 @@ export { sot343 } from "./sot343"
export { m2host } from "./m2host"
export { mountedpcbmodule } from "./mountedpcbmodule"
export { to92l } from "./to92l"
export { chipled } from "./chipled"
1 change: 1 addition & 0 deletions src/footprinter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,7 @@ export type Footprinter = {
solderjumper: (
num_pins?: number,
) => FootprinterParamsBuilder<"bridged" | "p" | "pw" | "ph">
chipled: () => FootprinterParamsBuilder<"imperial" | "p" | "pw" | "ph" | "w" | "h">

params: () => any
/** @deprecated use circuitJson() instead */
Expand Down
1 change: 1 addition & 0 deletions tests/__snapshots__/chipled0402.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__/chipled0603.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__/chipled0805.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__/chipled1206.snap.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
27 changes: 27 additions & 0 deletions tests/chipled.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { test, expect } from "bun:test"
import { convertCircuitJsonToPcbSvg } from "circuit-to-svg"
import { fp } from "../src/footprinter"

test("chipled0603", () => {
const circuitJson = fp.string("chipled0603").circuitJson()
const svgContent = convertCircuitJsonToPcbSvg(circuitJson)
expect(svgContent).toMatchSvgSnapshot(import.meta.path, "chipled0603")
})

test("chipled0402", () => {
const circuitJson = fp.string("chipled0402").circuitJson()
const svgContent = convertCircuitJsonToPcbSvg(circuitJson)
expect(svgContent).toMatchSvgSnapshot(import.meta.path, "chipled0402")
})

test("chipled0805", () => {
const circuitJson = fp.string("chipled0805").circuitJson()
const svgContent = convertCircuitJsonToPcbSvg(circuitJson)
expect(svgContent).toMatchSvgSnapshot(import.meta.path, "chipled0805")
})

test("chipled1206", () => {
const circuitJson = fp.string("chipled1206").circuitJson()
const svgContent = convertCircuitJsonToPcbSvg(circuitJson)
expect(svgContent).toMatchSvgSnapshot(import.meta.path, "chipled1206")
})
Comment on lines +5 to +27
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test file contains 4 test() calls (lines 5, 11, 17, and 23), but the rule states that a *.test.ts file may have AT MOST one test(...), after that the user should split into multiple, numbered files. This file should be split into multiple files like chipled1.test.ts, chipled2.test.ts, chipled3.test.ts, and chipled4.test.ts, with each file containing only one test() call.

Spotted by Graphite (based on custom rule: Custom rule)

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

Loading
Loading