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
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@

Footprinter is tscircuit's DSL and micro-builder for creating footprints.

> **Zod v4 migration:** Footprinter now requires `zod@^4` and
> `circuit-json@^0.0.455` as peer dependencies so generated Circuit JSON can be
> composed with consumer schemas. Consumers that inspect raw parsing errors
> should update their handling for Zod v4's error format.

![image](https://github.com/user-attachments/assets/24f7a9ba-47ef-4dd9-9a66-9536159a8ff9)

You can create very custom footprints using the `<footprint>` element, but the
Expand Down
16 changes: 11 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
"@types/bun": "^1.2.2",
"@types/node": "^20.12.13",
"bun-match-svg": "^0.0.12",
"circuit-json": "^0.0.411",
"circuit-json": "^0.0.455",
"circuit-json-to-connectivity-map": "^0.0.18",
"circuit-to-svg": "^0.0.393",
"esbuild": "^0.21.4",
Expand All @@ -37,13 +37,19 @@
"schematic-symbols": "^0.0.230",
"ts-node": "^10.9.2",
"tsup": "^8.0.2",
"typescript": "^5.4.5"
"typescript": "^5.4.5",
"zod": "^4.0.0"
},
"dependencies": {
"@tscircuit/mm": "^0.0.8",
"zod": "^3.23.8"
"@tscircuit/mm": "^0.0.8"
},
"peerDependencies": {
"circuit-json": "*"
"circuit-json": "^0.0.455",
"zod": "^4.0.0"
},
"overrides": {
"@tscircuit/circuit-json-util": {
"zod": "^4.0.0"
}
}
}
6 changes: 3 additions & 3 deletions src/fn/axial.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ import { createRectUnionOutline } from "src/helpers/rect-union-outline"

export const axial_def = base_def.extend({
fn: z.string(),
p: length.optional().default("2.54mm"),
id: length.optional().default("0.7mm"),
od: length.optional().default("1.4mm"),
p: length.optional().prefault("2.54mm"),
id: length.optional().prefault("0.7mm"),
od: length.optional().prefault("1.4mm"),
})
export type AxialDef = z.input<typeof axial_def>

Expand Down
2 changes: 1 addition & 1 deletion src/fn/bga.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export const bga_def = base_def
fn: z.string(),
num_pins: z.number().optional().default(64),
grid: dim2d.optional(),
p: distance.default("0.8mm"),
p: distance.prefault("0.8mm"),
w: length.optional(),
h: length.optional(),
ball: length.optional().describe("ball diameter"),
Expand Down
16 changes: 8 additions & 8 deletions src/fn/breakoutheaders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ import { base_def } from "../helpers/zod/base_def"

export const breakoutheaders_def = base_def.extend({
fn: z.string(),
w: length.default("10mm"),
w: length.prefault("10mm"),
h: length.optional(),
left: length.optional().default(20),
right: length.optional().default(20),
top: length.optional().default(0),
bottom: length.optional().default(0),
p: length.default(length.parse("2.54mm")),
id: length.optional().default(length.parse("1mm")),
od: length.optional().default(length.parse("1.5mm")),
left: length.optional().prefault(20),
right: length.optional().prefault(20),
top: length.optional().prefault(0),
bottom: length.optional().prefault(0),
p: length.prefault(length.parse("2.54mm")),
id: length.optional().prefault(length.parse("1mm")),
od: length.optional().prefault(length.parse("1.5mm")),
})

export type breakoutheaders_def = z.input<typeof breakoutheaders_def>
Expand Down
8 changes: 4 additions & 4 deletions src/fn/crystal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ import { base_def } from "../helpers/zod/base_def"
export const crystal_def = base_def.extend({
fn: z.literal("crystal"),
num_pins: z.literal(4).default(4),
px: length.default("2.2mm").describe("horizontal pad center pitch"),
py: length.default("1.7mm").describe("vertical pad center pitch"),
pw: length.default("1.4mm").describe("pad width"),
ph: length.default("1.2mm").describe("pad height"),
px: length.prefault("2.2mm").describe("horizontal pad center pitch"),
py: length.prefault("1.7mm").describe("vertical pad center pitch"),
pw: length.prefault("1.4mm").describe("pad width"),
ph: length.prefault("1.2mm").describe("pad height"),
})

export const crystal = (
Expand Down
6 changes: 5 additions & 1 deletion src/fn/dfn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,11 @@ export const dfn = (
raw_params: DfnInput,
): { circuitJson: AnyCircuitElement[]; parameters: any } => {
const missing = function_call.parse(raw_params.missing ?? [])
if (missing.some((position) => typeof position !== "number")) {
if (
!missing.every(
(position): position is number => typeof position === "number",
)
) {
throw new Error("DFN missing positions must be pad numbers")
}
const missingPositions = [...new Set(missing)]
Expand Down
4 changes: 2 additions & 2 deletions src/fn/diode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ const getCopperBounds = (circuitJson: AnySoupElement[]) => {
let maxY = Number.NEGATIVE_INFINITY

for (const element of circuitJson) {
if (element.type === "pcb_smtpad") {
if (element.type === "pcb_smtpad" && element.shape === "rect") {
minX = Math.min(minX, element.x - element.width / 2)
maxX = Math.max(maxX, element.x + element.width / 2)
minY = Math.min(minY, element.y - element.height / 2)
maxY = Math.max(maxY, element.y + element.height / 2)
}

if (element.type === "pcb_plated_hole") {
if (element.type === "pcb_plated_hole" && element.shape === "circle") {
minX = Math.min(minX, element.x - element.outer_diameter / 2)
maxX = Math.max(maxX, element.x + element.outer_diameter / 2)
minY = Math.min(minY, element.y - element.outer_diameter / 2)
Expand Down
2 changes: 1 addition & 1 deletion src/fn/dip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export const extendDipDef = (newDefaults: { w?: string; p?: string }) =>
wide: z.boolean().optional(),
narrow: z.boolean().optional(),
w: lengthInMm.optional(),
p: lengthInMm.default(newDefaults.p ?? "2.54mm"),
p: lengthInMm.prefault(newDefaults.p ?? "2.54mm"),
id: lengthInMm.optional(),
od: lengthInMm.optional(),
nosquareplating: z
Expand Down
8 changes: 4 additions & 4 deletions src/fn/electrolytic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ import { base_def } from "../helpers/zod/base_def"

const electrolytic_def = base_def.extend({
fn: z.string(),
p: length.optional().default("7.5mm"),
id: length.optional().default("1mm"),
od: length.optional().default("2mm"),
d: length.optional().default("10.5mm"),
p: length.optional().prefault("7.5mm"),
id: length.optional().prefault("1mm"),
od: length.optional().prefault("2mm"),
d: length.optional().prefault("10.5mm"),
})

export default electrolytic_def
Expand Down
10 changes: 5 additions & 5 deletions src/fn/hc49.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ const generate_u_curve = (

export const hc49_def = base_def.extend({
fn: z.string(),
p: length.optional().default("4.88mm"),
id: length.optional().default("0.8mm"),
od: length.optional().default("1.5mm"),
w: length.optional().default("5.6mm"),
h: length.optional().default("3.5mm"),
p: length.optional().prefault("4.88mm"),
id: length.optional().prefault("0.8mm"),
od: length.optional().prefault("1.5mm"),
w: length.optional().prefault("5.6mm"),
h: length.optional().prefault("3.5mm"),
})

export type Hc49Def = z.input<typeof hc49_def>
Expand Down
6 changes: 3 additions & 3 deletions src/fn/lga.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ export const lga_def = base_def.extend({
fn: z.string(),
num_pins: z.number().int().positive().optional().default(14),
grid: dim2d.optional(),
p: length.default(length.parse("0.5mm")),
p: length.prefault(length.parse("0.5mm")),
w: length.optional(),
h: length.optional(),
pw: length.default(length.parse("0.28mm")),
pl: length.default(length.parse("0.7mm")),
pw: length.prefault(length.parse("0.28mm")),
pl: length.prefault(length.parse("0.7mm")),
pillpads: z.boolean().optional().default(false),
})

Expand Down
10 changes: 5 additions & 5 deletions src/fn/mountedpcbmodule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ export const mountedpcbmodule_def = base_def
.optional()
.default(1)
.describe("number of rows"),
p: length.default("2.54mm").describe("pitch"),
id: length.default("1.0mm").describe("inner diameter"),
od: length.default("1.5mm").describe("outer diameter"),
p: length.prefault("2.54mm").describe("pitch"),
id: length.prefault("1.0mm").describe("inner diameter"),
od: length.prefault("1.5mm").describe("outer diameter"),
male: z.boolean().optional().describe("the module uses male headers"),
nopin: z
.boolean()
Expand Down Expand Up @@ -78,7 +78,7 @@ export const mountedpcbmodule_def = base_def
.optional(),
width: length.optional(),
height: length.optional(),
pinRowHoleEdgeToEdgeDist: length.default("2mm"),
pinRowHoleEdgeToEdgeDist: length.prefault("2mm"),
holes: z
.union([z.string(), z.array(z.string())])
.optional()
Expand All @@ -95,7 +95,7 @@ export const mountedpcbmodule_def = base_def
}),
holeXDist: length.optional(),
holeYDist: length.optional(),
holeInset: length.default("1mm"),
holeInset: length.prefault("1mm"),
pinrow: z.union([z.string(), z.number()]).optional(),
usbposition: z
.enum(["left", "right", "top", "bottom"])
Expand Down
10 changes: 5 additions & 5 deletions src/fn/pinrow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ export const pinrow_def = base_def
.transform((val) => Number(val))
.optional()
.describe("number of nominal columns in a sparse grid"),
p: length.default("0.1in").describe("pitch"),
p: length.prefault("0.1in").describe("pitch"),
py: length.optional().describe("vertical row pitch"),
id: length.default("1.0mm").describe("inner diameter"),
od: length.default("1.5mm").describe("outer diameter"),
id: length.prefault("1.0mm").describe("inner diameter"),
od: length.prefault("1.5mm").describe("outer diameter"),
missing: function_call
.default([])
.describe("row-major nominal grid positions to omit"),
Expand All @@ -45,8 +45,8 @@ export const pinrow_def = base_def
.optional()
.describe("surface mount device (verbose)"),
rightangle: z.boolean().optional().describe("right angle"),
pw: length.optional().default("1.0mm").describe("pad width for SMD"),
pl: length.optional().default("2.0mm").describe("pad length for SMD"),
pw: length.optional().prefault("1.0mm").describe("pad width for SMD"),
pl: length.optional().prefault("2.0mm").describe("pad length for SMD"),
pinlabeltextalignleft: z.boolean().optional().default(false),
pinlabeltextaligncenter: z.boolean().optional().default(false),
pinlabeltextalignright: z.boolean().optional().default(false),
Expand Down
8 changes: 4 additions & 4 deletions src/fn/pushbutton.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ import { base_def } from "../helpers/zod/base_def"

export const pushbutton_def = base_def.extend({
fn: z.literal("pushbutton"),
w: length.default(4.5),
h: length.default(6.5),
id: length.default(1),
od: length.default(1.2),
w: length.prefault(4.5),
h: length.prefault(6.5),
id: length.prefault(1),
od: length.prefault(1.2),
})

export const pushbutton = (
Expand Down
2 changes: 1 addition & 1 deletion src/fn/quad.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const base_quad_def = base_def.extend({
num_pins: z.number().optional().default(64),
w: length.optional(),
h: length.optional(),
p: length.default(length.parse("0.5mm")),
p: length.prefault(length.parse("0.5mm")),
pw: length.optional(),
pl: length.optional(),
thermalpad: z.union([z.literal(true), dim2d]).optional(),
Expand Down
6 changes: 3 additions & 3 deletions src/fn/radial.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ import { base_def } from "../helpers/zod/base_def"
export const radial_def = base_def.extend({
fn: z.string(),

p: length.optional().default("5mm"),
id: length.optional().default("0.8mm"),
od: length.optional().default("1.6mm"),
p: length.optional().prefault("5mm"),
id: length.optional().prefault("0.8mm"),
od: length.optional().prefault("1.6mm"),

ceramic: z.boolean().optional(),
electrolytic: z.boolean().optional(),
Expand Down
10 changes: 5 additions & 5 deletions src/fn/smdpinheader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ export const smdpinheader_def = base_def.extend({
fn: z.literal("smdpinheader"),
num_pins: z.number().int().positive().default(6),
// Defaults follow the Harwin M20-877R recommended vertical SMT land pattern.
p: length.default("2.54mm").describe("contact pitch"),
p: length.prefault("2.54mm").describe("contact pitch"),
py: length
.default("3.31mm")
.prefault("3.31mm")
.describe("center-to-center distance between alternating pad rows"),
pw: length.default("1mm").describe("pad width"),
ph: length.default("2.51mm").describe("pad height"),
bh: length.default("2.5mm").describe("plastic body height"),
pw: length.prefault("1mm").describe("pad width"),
ph: length.prefault("2.51mm").describe("pad height"),
bh: length.prefault("2.5mm").describe("plastic body height"),
male: z.literal(true).default(true),
female: z.literal(false).default(false),
smd: z.literal(true).default(true),
Expand Down
8 changes: 4 additions & 4 deletions src/fn/smdpushbutton.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ import { base_def } from "../helpers/zod/base_def"
export const smdpushbutton_def = base_def.extend({
fn: z.literal("smdpushbutton"),
num_pins: z.literal(4).default(4),
px: length.default("4.2mm").describe("horizontal pad center pitch"),
py: length.default("2.15mm").describe("vertical pad center pitch"),
pw: length.default("1.05mm").describe("pad width"),
ph: length.default("0.7mm").describe("pad height"),
px: length.prefault("4.2mm").describe("horizontal pad center pitch"),
py: length.prefault("2.15mm").describe("vertical pad center pitch"),
pw: length.prefault("1.05mm").describe("pad width"),
ph: length.prefault("0.7mm").describe("pad height"),
})

export const smdpushbutton = (
Expand Down
8 changes: 4 additions & 4 deletions src/fn/soic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ export const extendSoicDef = (newDefaults: {
.extend({
fn: z.string(),
num_pins: z.number().optional().default(8),
w: length.default(length.parse(newDefaults.w ?? "5.3mm")),
p: length.default(length.parse(newDefaults.p ?? "1.27mm")),
pw: length.default(length.parse(newDefaults.pw ?? "0.6mm")),
pl: length.default(length.parse(newDefaults.pl ?? "1.0mm")),
w: length.prefault(length.parse(newDefaults.w ?? "5.3mm")),
p: length.prefault(length.parse(newDefaults.p ?? "1.27mm")),
pw: length.prefault(length.parse(newDefaults.pw ?? "0.6mm")),
pl: length.prefault(length.parse(newDefaults.pl ?? "1.0mm")),
legsoutside: z
.boolean()
.optional()
Expand Down
4 changes: 2 additions & 2 deletions src/fn/ssop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ export const ssop_def = base_def
.extend({
fn: z.string(),
num_pins: z.number().optional().default(8),
w: length.default(length.parse("3.9mm")),
p: length.default(length.parse("1.27mm")),
w: length.prefault(length.parse("3.9mm")),
p: length.prefault(length.parse("1.27mm")),
pw: length.optional(),
pl: length.optional(),
legsoutside: z.boolean().optional().default(false),
Expand Down
20 changes: 10 additions & 10 deletions src/fn/stampboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,19 @@ import { base_def } from "../helpers/zod/base_def"

export const stampboard_def = base_def.extend({
fn: z.string(),
w: length.default("22.58mm"),
w: length.prefault("22.58mm"),
h: length.optional(),
left: length.optional().default(20),
right: length.optional().default(20),
top: length.optional().default(2),
bottom: length.optional().default(2),
p: length.default(length.parse("2.54mm")),
pw: length.default(length.parse("1.6mm")),
pl: length.default(length.parse("2.4mm")),
left: length.optional().prefault(20),
right: length.optional().prefault(20),
top: length.optional().prefault(2),
bottom: length.optional().prefault(2),
p: length.prefault(length.parse("2.54mm")),
pw: length.prefault(length.parse("1.6mm")),
pl: length.prefault(length.parse("2.4mm")),
innerhole: z.boolean().default(false),
innerholeedgedistance: length.default(length.parse("1.61mm")),
innerholeedgedistance: length.prefault(length.parse("1.61mm")),
silkscreenlabels: z.boolean().default(false),
silkscreenlabelmargin: length.default(length.parse("0.1mm")),
silkscreenlabelmargin: length.prefault(length.parse("0.1mm")),
})

export type Stampboard_def = z.input<typeof stampboard_def>
Expand Down
Loading
Loading