@tscircuit/footprinter 0.0.22 → 0.0.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/fn/soic.ts DELETED
@@ -1,106 +0,0 @@
1
- import type { AnySoupElement, PcbSilkscreenPath } from "@tscircuit/soup"
2
- import { platedhole } from "../helpers/platedhole"
3
- import { z } from "zod"
4
- import { length } from "@tscircuit/soup"
5
- import type { NowDefined } from "../helpers/zod/now-defined"
6
- import { u_curve } from "../helpers/u-curve"
7
-
8
- const soic_def = z
9
- .object({
10
- soic: z.literal(true),
11
- num_pins: z.number(),
12
- w: length,
13
- p: length.default(length.parse("1.27mm")),
14
- id: length.optional(),
15
- od: length.optional(),
16
- })
17
- .transform((v) => {
18
- // Default inner diameter and outer diameter
19
- if (!v.id && !v.od) {
20
- v.id = length.parse("0.6mm")
21
- v.od = length.parse("1.0mm")
22
- } else if (!v.id) {
23
- v.id = v.od! * (0.6 / 1.0)
24
- } else if (!v.od) {
25
- v.od = v.id! * (1.0 / 0.6)
26
- }
27
- return v as NowDefined<typeof v, "w" | "p" | "id" | "od">
28
- })
29
-
30
- export const getCcwSoicCoords = (
31
- pinCount: number,
32
- pn: number,
33
- w: number,
34
- p: number
35
- ) => {
36
- /** pin height */
37
- const ph = pinCount / 2
38
- const isLeft = pn <= ph
39
-
40
- /** Number of gaps between pins on each side, e.g. 4 pins = 3 spaces */
41
- const leftPinGaps = ph - 1
42
-
43
- /** gap size (pitch) */
44
- const gs = p
45
-
46
- const h = gs * leftPinGaps
47
-
48
- if (isLeft) {
49
- // The y position starts at h/2, then goes down by gap size
50
- // for each pin
51
- return { x: -w / 2, y: h / 2 - (pn - 1) * gs }
52
- } else {
53
- // The y position starts at -h/2, then goes up by gap size
54
- return { x: w / 2, y: -h / 2 + (pn - ph - 1) * gs }
55
- }
56
- }
57
-
58
- /**
59
- * Returns the plated holes for a SOIC package.
60
- */
61
- export const soic = (raw_params: {
62
- soic: true
63
- num_pins: number
64
- w: number
65
- p?: number
66
- id?: string | number
67
- od?: string | number
68
- }): AnySoupElement[] => {
69
- const params = soic_def.parse(raw_params)
70
- const platedHoles: AnySoupElement[] = []
71
- for (let i = 0; i < params.num_pins; i++) {
72
- const { x, y } = getCcwSoicCoords(
73
- params.num_pins,
74
- i + 1,
75
- params.w,
76
- params.p ?? 1.27
77
- )
78
- platedHoles.push(
79
- platedhole(i + 1, x, y, params.id ?? "0.6mm", params.od ?? "1mm")
80
- )
81
- }
82
-
83
- /** silkscreen width */
84
- const sw = params.w - params.od - 0.4
85
- const sh = (params.num_pins / 2 - 1) * params.p + params.od + 0.4
86
- const silkscreenBorder: PcbSilkscreenPath = {
87
- layer: "top",
88
- pcb_component_id: "",
89
- pcb_silkscreen_path_id: "silkscreen_path_1",
90
- route: [
91
- { x: -sw / 2, y: -sh / 2 },
92
- { x: -sw / 2, y: sh / 2 },
93
- // Little U shape at the top
94
- ...u_curve.map(({ x, y }) => ({
95
- x: (x * sw) / 6,
96
- y: (y * sw) / 6 + sh / 2,
97
- })),
98
- { x: sw / 2, y: sh / 2 },
99
- { x: sw / 2, y: -sh / 2 },
100
- { x: -sw / 2, y: -sh / 2 },
101
- ],
102
- type: "pcb_silkscreen_path",
103
- }
104
-
105
- return [...platedHoles, silkscreenBorder]
106
- }
@@ -1,128 +0,0 @@
1
- import { dip } from "./fn/dip"
2
- import { diode } from "./fn/diode"
3
- import { cap } from "./fn/cap"
4
- import { led } from "./fn/led"
5
- import { res } from "./fn/res"
6
- import { bga } from "./fn/bga"
7
- import { soic } from "./fn/soic"
8
- import { quad } from "./fn/quad"
9
- import { qfn } from "./fn/qfn"
10
- import { qfp } from "./fn/qfp"
11
- import type { AnySoupElement } from "@tscircuit/soup"
12
- import { isNotNull } from "./helpers/is-not-null"
13
-
14
- export type FootprinterParamsBuilder<K extends string> = {
15
- [P in K | "params" | "soup"]: P extends "params" | "soup"
16
- ? Footprinter[P]
17
- : (v?: number | string) => FootprinterParamsBuilder<K>
18
- }
19
-
20
- type CommonPassiveOptionKey =
21
- | "metric"
22
- | "imperial"
23
- | "tht"
24
- | "p"
25
- | "pw"
26
- | "ph"
27
- | "w"
28
- | "h"
29
-
30
- export type Footprinter = {
31
- dip: (
32
- num_pins: number
33
- ) => FootprinterParamsBuilder<"w" | "p" | "id" | "od" | "wide" | "narrow">
34
- cap: () => FootprinterParamsBuilder<CommonPassiveOptionKey>
35
- res: () => FootprinterParamsBuilder<CommonPassiveOptionKey>
36
- diode: () => FootprinterParamsBuilder<CommonPassiveOptionKey>
37
- led: () => FootprinterParamsBuilder<CommonPassiveOptionKey>
38
- lr: (num_pins: number) => FootprinterParamsBuilder<"w" | "l" | "pl" | "pr">
39
- qfp: (
40
- num_pins: number
41
- ) => FootprinterParamsBuilder<"w" | "p" | "id" | "od" | "wide" | "narrow">
42
- quad: (
43
- num_pins: number
44
- ) => FootprinterParamsBuilder<
45
- "w" | "l" | "square" | "pl" | "pr" | "pb" | "pt" | "p" | "pw" | "ph"
46
- >
47
- bga: (
48
- num_pins: number
49
- ) => FootprinterParamsBuilder<
50
- "grid" | "p" | "w" | "h" | "ball" | "pad" | "missing"
51
- >
52
- qfn: (num_pins: number) => FootprinterParamsBuilder<"w" | "h" | "p">
53
- soic: (num_pins: number) => FootprinterParamsBuilder<"w" | "p" | "id" | "od">
54
- params: () => any
55
- soup: () => AnySoupElement[]
56
- }
57
-
58
- export const string = (def: string): Footprinter => {
59
- let fp = footprinter()
60
-
61
- const def_parts = def
62
- .split("_")
63
- .map((s) => {
64
- const m = s.match(/([a-z]+)([\(\d\.\+\?].*)?/)
65
- const [_, fn, v] = m ?? []
66
- if (v?.includes("?")) return null
67
- return { fn: m?.[1]!, v: m?.[2]! }
68
- })
69
- .filter(isNotNull)
70
-
71
- for (const { fn, v } of def_parts) {
72
- fp = fp[fn](v)
73
- }
74
-
75
- return fp
76
- }
77
-
78
- export const footprinter = (): Footprinter & { string: typeof string } => {
79
- const proxy = new Proxy(
80
- {},
81
- {
82
- get: (target: any, prop) => {
83
- if (prop === "soup") {
84
- if ("dip" in target) return () => dip(target)
85
- if ("diode" in target) return () => diode(target)
86
- if ("cap" in target) return () => cap(target)
87
- if ("led" in target) return () => led(target)
88
- if ("res" in target) return () => res(target)
89
- if ("bga" in target) return () => bga(target)
90
- if ("soic" in target) return () => soic(target)
91
- if ("quad" in target) return () => quad(target)
92
- if ("qfn" in target) return () => qfn(target)
93
- if ("qfp" in target) return () => qfp(target)
94
-
95
- return () => {
96
- // TODO improve error
97
- throw new Error(
98
- `No function found for footprinter, make sure to specify .dip, .lr, .p, etc. Got \"${prop}\"`
99
- )
100
- }
101
- }
102
- if (prop === "params") {
103
- // TODO
104
- return () => target
105
- }
106
- return (v: any) => {
107
- if (Object.keys(target).length === 0) {
108
- target[prop] = true
109
- if (prop === "res" || prop === "cap") {
110
- if (v) {
111
- target.imperial = v // res0402, cap0603 etc.
112
- }
113
- } else {
114
- target.num_pins = parseFloat(v)
115
- }
116
- } else {
117
- target[prop] = v ?? true
118
- }
119
- return proxy
120
- }
121
- },
122
- }
123
- )
124
- return proxy as any
125
- }
126
- footprinter.string = string
127
-
128
- export const fp = footprinter
@@ -1,106 +0,0 @@
1
- import type { PinOrderSpecifier } from "./zod/pin-order-specifier"
2
-
3
- /**
4
- * A counter-clockwise pin map is [1,2,3,4,5,6,7,8] for an 8-pin package
5
- *
6
- * 8 7
7
- * 1 6
8
- * 2 5
9
- * 3 4
10
- *
11
- * Given some parameters, we're returning how to map the pins in a quad package
12
- * with a different order. For example, if we pass in cw=true, we'll get the
13
- * following mapping
14
- *
15
- * 1 -> 1
16
- * 2 -> 8
17
- * 3 -> 7
18
- * 4 -> 6
19
- * 5 -> 5
20
- * 6 -> 4
21
- * 7 -> 3
22
- * 8 -> 2
23
- *
24
- * Which allows us to create the CW version of the package using...
25
- * new_pin = pin_map[normal_ccw_pin]
26
- *
27
- * 2 3
28
- * 1 4
29
- * 8 5
30
- * 7 6
31
- */
32
- export const getQuadPinMap = ({
33
- num_pins,
34
- cw,
35
- ccw,
36
- startingpin,
37
- }: {
38
- num_pins: number
39
- cw?: boolean
40
- ccw?: boolean
41
- startingpin?: PinOrderSpecifier[]
42
- }): number[] => {
43
- const pin_map: number[] = []
44
- const pins_per_side = num_pins / 4
45
- let current_position_ccw_normal = 1
46
-
47
- /** Starting Flag Pins */
48
- const sfp: Record<PinOrderSpecifier, boolean> = {} as any
49
- for (const specifier of startingpin ?? []) {
50
- sfp[specifier] = true
51
- }
52
- if (!sfp.leftside && !sfp.topside && !sfp.rightside && !sfp.bottomside) {
53
- sfp.leftside = true
54
- }
55
- if (!sfp.bottompin && !sfp.leftpin && !sfp.rightpin && !sfp.toppin) {
56
- if (sfp.leftside) {
57
- sfp.toppin = true
58
- } else if (sfp.topside) {
59
- sfp.rightpin = true
60
- } else if (sfp.rightside) {
61
- sfp.bottompin = true
62
- } else if (sfp.bottomside) {
63
- sfp.leftpin = true
64
- }
65
- }
66
-
67
- if (sfp.leftside && sfp.toppin) {
68
- current_position_ccw_normal = 1
69
- } else if (sfp.leftside && sfp.bottompin) {
70
- current_position_ccw_normal = pins_per_side
71
- } else if (sfp.bottomside && sfp.leftpin) {
72
- current_position_ccw_normal = pins_per_side + 1
73
- } else if (sfp.bottomside && sfp.rightpin) {
74
- current_position_ccw_normal = pins_per_side * 2
75
- } else if (sfp.rightside && sfp.bottompin) {
76
- current_position_ccw_normal = pins_per_side * 2 + 1
77
- } else if (sfp.rightside && sfp.toppin) {
78
- current_position_ccw_normal = pins_per_side * 3
79
- } else if (sfp.topside && sfp.rightpin) {
80
- current_position_ccw_normal = pins_per_side * 3 + 1
81
- } else if (sfp.topside && sfp.leftpin) {
82
- current_position_ccw_normal = pins_per_side * 4
83
- }
84
-
85
- pin_map.push(-1) // the first index is meaningless
86
-
87
- // Each iteration we move the current position to the next pin, if we're
88
- // going CCW this means incrementing, if we're going CW this means
89
- // decrementing
90
- for (let i = 0; i < num_pins; i++) {
91
- pin_map[current_position_ccw_normal] = i + 1
92
- if (ccw || !cw) {
93
- current_position_ccw_normal++
94
- if (current_position_ccw_normal > num_pins) {
95
- current_position_ccw_normal = 1
96
- }
97
- } else {
98
- current_position_ccw_normal--
99
- if (current_position_ccw_normal < 1) {
100
- current_position_ccw_normal = num_pins
101
- }
102
- }
103
- }
104
-
105
- return pin_map
106
- }
@@ -1,3 +0,0 @@
1
- export function isNotNull<T>(value: T | null): value is T {
2
- return value !== null
3
- }
@@ -1,175 +0,0 @@
1
- import type { AnySoupElement } from "@tscircuit/soup"
2
- import { rectpad } from "../helpers/rectpad"
3
- import mm from "@tscircuit/mm"
4
- import { platedhole } from "./platedhole"
5
-
6
- type StandardSize = {
7
- imperial: string
8
- metric: string
9
- Z_mm_min: number
10
- G_mm_min: number
11
- X_mm_min: number
12
- C_mm_ref: number
13
- }
14
-
15
- // https://www.worthingtonassembly.com/perfect-0201-footprint
16
- // https://static1.squarespace.com/static/54982a02e4b02e9f5e5d9ca7/t/660c692f69a0d83a4afecdf0/1712089391915/Discrete+Component+Footprints.pdf
17
- // https://page.venkel.com/hubfs/Resources/Technical/Resistors%20Landing%20Pattern.pdf
18
- const sizes = [
19
- {
20
- imperial: "01005",
21
- metric: "0402",
22
- Z_mm_min: 0.58,
23
- G_mm_min: 0.18,
24
- X_mm_min: 0.21,
25
- C_mm_ref: 0.038,
26
- },
27
- {
28
- imperial: "0201",
29
- metric: "0603",
30
- Z_mm_min: 0.9,
31
- G_mm_min: 0.3,
32
- X_mm_min: 0.3,
33
- C_mm_ref: 0.6,
34
- },
35
- {
36
- imperial: "0402",
37
- metric: "1005",
38
- Z_mm_min: 1.6,
39
- G_mm_min: 0.4,
40
- X_mm_min: 0.7,
41
- C_mm_ref: 1,
42
- },
43
- {
44
- imperial: "0603",
45
- metric: "1608",
46
- Z_mm_min: 2.6,
47
- G_mm_min: 0.6,
48
- X_mm_min: 1.0,
49
- C_mm_ref: 1.7,
50
- },
51
- {
52
- imperial: "0805",
53
- metric: "2012",
54
- Z_mm_min: 3.0,
55
- G_mm_min: 0.6,
56
- X_mm_min: 1.2,
57
- C_mm_ref: 1.9,
58
- },
59
- {
60
- imperial: "1206",
61
- metric: "3216",
62
- Z_mm_min: 4.2,
63
- G_mm_min: 1.2,
64
- X_mm_min: 1.4,
65
- C_mm_ref: 2.8,
66
- },
67
- {
68
- imperial: "1210",
69
- metric: "3225",
70
- Z_mm_min: 4.2,
71
- G_mm_min: 1.2,
72
- X_mm_min: 2.4,
73
- C_mm_ref: 2.8,
74
- },
75
- {
76
- imperial: "2010",
77
- metric: "5025",
78
- Z_mm_min: 6.0,
79
- G_mm_min: 2.6,
80
- X_mm_min: 2.4,
81
- C_mm_ref: 4.4,
82
- },
83
- {
84
- imperial: "2512",
85
- metric: "6332",
86
- Z_mm_min: 7.2,
87
- G_mm_min: 3.8,
88
- X_mm_min: 3.0,
89
- C_mm_ref: 5.6,
90
- },
91
- ]
92
- const metricMap: Record<string, StandardSize> = sizes.reduce((acc: any, s) => {
93
- acc[s.metric] = s
94
- return acc
95
- }, {})
96
- const imperialMap: Record<string, StandardSize> = sizes.reduce(
97
- (acc: any, s) => {
98
- acc[s.imperial] = s
99
- return acc
100
- },
101
- {}
102
- )
103
-
104
- export type PassiveDef = {
105
- tht: boolean
106
- p: number
107
- pw?: number
108
- ph?: number
109
- metric?: string
110
- imperial?: string
111
- w?: number
112
- h?: number
113
- }
114
-
115
- const deriveXFromH = (h: number) => 0.079 * h ** 2 + 0.94 * h - 0.009
116
- const deriveZFromW = (w: number) => 1.09 * w + 0.6
117
- const deriveGFromW = (w: number) => 0.59 * w - 0.31
118
- const deriveCFromW = (w: number) => -0.01 * w ** 2 + 0.94 * w + 0.03
119
-
120
- export const passive = (params: PassiveDef): AnySoupElement[] => {
121
- let { tht, p, pw, ph, metric, imperial, w, h } = params
122
-
123
- if (typeof w === "string") w = mm(w)
124
- if (typeof h === "string") h = mm(h)
125
- if (typeof p === "string") p = mm(p)
126
- if (typeof pw === "string") pw = mm(pw)
127
- if (typeof ph === "string") ph = mm(ph)
128
-
129
- if (h! > w!) {
130
- throw new Error(
131
- "height cannot be greater than width (rotated footprint not yet implemented)"
132
- )
133
- }
134
-
135
- /** standard size */
136
- let sz: StandardSize | undefined
137
- if (metric) {
138
- sz = metricMap[metric]
139
- }
140
-
141
- if (imperial) {
142
- sz = imperialMap[imperial]
143
- }
144
-
145
- if (!sz && w && h && !pw && !ph) {
146
- sz = {
147
- imperial: "custom",
148
- metric: "custom",
149
- Z_mm_min: deriveZFromW(w),
150
- G_mm_min: deriveGFromW(w),
151
- X_mm_min: deriveXFromH(h),
152
- C_mm_ref: deriveCFromW(w),
153
- }
154
- }
155
-
156
- if (sz) {
157
- w = sz.Z_mm_min
158
- h = sz.X_mm_min
159
- p = sz.C_mm_ref
160
- pw = (sz.Z_mm_min - sz.G_mm_min) / 2
161
- ph = (sz.Z_mm_min - sz.G_mm_min) / 2
162
- }
163
-
164
- if (pw === undefined) throw new Error(`could not infer pad width`)
165
- if (ph === undefined) throw new Error(`could not infer pad width`)
166
-
167
- if (tht) {
168
- return [
169
- platedhole(1, -p / 2, 0, pw, (pw * 1) / 0.8),
170
- platedhole(2, -p / 2, 0, pw, (pw * 1) / 0.8),
171
- ]
172
- } else {
173
- return [rectpad(1, -p / 2, 0, pw, ph), rectpad(2, p / 2, 0, pw, ph)]
174
- }
175
- }
@@ -1,21 +0,0 @@
1
- import type { PCBPlatedHole, PCBSMTPad } from "@tscircuit/soup"
2
- import { mm } from "@tscircuit/mm"
3
-
4
- export const platedhole = (
5
- pn: number,
6
- x: number,
7
- y: number,
8
- id: number | string,
9
- od: number | string
10
- ): PCBPlatedHole => {
11
- return {
12
- type: "pcb_plated_hole",
13
- x,
14
- y,
15
- hole_diameter: mm(id),
16
- outer_diameter: mm(od),
17
- pcb_port_id: "",
18
- layers: ["top", "bottom"],
19
- port_hints: [pn.toString()],
20
- }
21
- }
@@ -1,22 +0,0 @@
1
- import type { PCBSMTPad } from "@tscircuit/soup"
2
- export const rectpad = (
3
- pn: number | Array<string | number>,
4
- x: number,
5
- y: number,
6
- w: number,
7
- h: number
8
- ): PCBSMTPad => {
9
- return {
10
- type: "pcb_smtpad",
11
- x,
12
- y,
13
- width: w,
14
- height: h,
15
- layer: "top",
16
- shape: "rect",
17
- pcb_smtpad_id: "",
18
- port_hints: Array.isArray(pn)
19
- ? pn.map((item) => item.toString())
20
- : [pn.toString()],
21
- }
22
- }
@@ -1,6 +0,0 @@
1
- export const u_curve = Array.from({ length: 9 }, (_, i) =>
2
- Math.cos((i / 8) * Math.PI - Math.PI)
3
- ).map((x) => ({
4
- x,
5
- y: -Math.sqrt(1 - x ** 2),
6
- }))
@@ -1 +0,0 @@
1
- export const ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
@@ -1,17 +0,0 @@
1
- import { z } from "zod"
2
-
3
- export const dim2d = z
4
- .string()
5
- .transform((a) => {
6
- const [x, y] = a.split(/[x ]/)
7
- return {
8
- x: parseFloat(x),
9
- y: parseFloat(y),
10
- }
11
- })
12
- .pipe(
13
- z.object({
14
- x: z.number(),
15
- y: z.number(),
16
- })
17
- )
@@ -1,16 +0,0 @@
1
- import { z } from "zod"
2
-
3
- export const function_call = z
4
- .string()
5
- .or(z.array(z.any()))
6
- .transform((a) => {
7
- if (Array.isArray(a)) return a
8
- if (a.startsWith("(") && a.endsWith(")")) {
9
- a = a.slice(1, -1)
10
- }
11
- return a.split(",").map((v) => {
12
- const numVal = Number(v)
13
- return isNaN(numVal) ? v : numVal
14
- })
15
- })
16
- .pipe(z.array(z.string().or(z.number())))
@@ -1,2 +0,0 @@
1
- /* Define a utility type to make certain keys required */
2
- export type NowDefined<T, K extends keyof T> = Omit<T, K> & Required<Pick<T, K>>
@@ -1,13 +0,0 @@
1
- import { z } from "zod"
2
- export const pin_order_specifier = z.enum([
3
- "leftside",
4
- "topside",
5
- "rightside",
6
- "bottomside",
7
- "toppin",
8
- "bottompin",
9
- "leftpin",
10
- "rightpin",
11
- ])
12
-
13
- export type PinOrderSpecifier = z.infer<typeof pin_order_specifier>
package/src/index.ts DELETED
@@ -1 +0,0 @@
1
- export * from "./footprinter"
package/tests/bga.test.ts DELETED
@@ -1,49 +0,0 @@
1
- import test from "ava"
2
- import { fp } from "../src/footprinter"
3
- import { AnySoupElement } from "@tscircuit/soup"
4
- import { toPinPositionString } from "./fixtures"
5
-
6
- test("bga footprint", (t) => {
7
- const soup = fp()
8
- .bga(8)
9
- .w("4mm")
10
- .h("4mm")
11
- .grid("3x3")
12
- .missing("center")
13
- .p(1)
14
- .soup()
15
- // 16pins, 4mm x 4mm, 8x8 grid, 1.27mm pitch
16
- const ps = toPinPositionString(soup)
17
-
18
- t.is(
19
- ps,
20
- `
21
- 1 : -1.00 1.00
22
- 2 : 0.00 1.00
23
- 3 : 1.00 1.00
24
- 4 : -1.00 0.00
25
- 5 : 1.00 0.00
26
- 6 : -1.00 -1.00
27
- 7 : 0.00 -1.00
28
- 8 : 1.00 -1.00
29
- `.trim()
30
- )
31
- })
32
-
33
- test("bga7_w8_h8_grid3x3_p1_missing(center,B1)", (t) => {
34
- const soup = fp.string("bga7_w8_h8_grid3x3_p1_missing(center,B1)").soup()
35
- const ps = toPinPositionString(soup)
36
-
37
- t.is(
38
- ps,
39
- `
40
- 1 : -1.00 1.00
41
- 2 : 0.00 1.00
42
- 3 : 1.00 1.00
43
- 4 : 1.00 0.00
44
- 5 : -1.00 -1.00
45
- 6 : 0.00 -1.00
46
- 7 : 1.00 -1.00
47
- `.trim()
48
- )
49
- })