@tscircuit/footprinter 0.0.20 → 0.0.22
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/package.json +1 -1
- package/src/fn/qfn.ts +10 -0
- package/src/fn/qfp.ts +19 -0
- package/src/fn/quad.ts +85 -57
- package/src/footprinter.ts +8 -0
- package/tests/qfn.test.ts +10 -0
- package/tests/qfp.test.ts +18 -0
package/package.json
CHANGED
package/src/fn/qfn.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { AnySoupElement } from "@tscircuit/soup"
|
|
2
|
+
import { base_quad_def, quad, quad_def, quadTransform } from "./quad"
|
|
3
|
+
import type { z } from "zod"
|
|
4
|
+
|
|
5
|
+
export const qfn_def = base_quad_def.extend({}).transform(quadTransform)
|
|
6
|
+
|
|
7
|
+
export const qfn = (params: z.input<typeof qfn_def>): AnySoupElement[] => {
|
|
8
|
+
params.legsoutside = false
|
|
9
|
+
return quad(params)
|
|
10
|
+
}
|
package/src/fn/qfp.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { AnySoupElement } from "@tscircuit/soup"
|
|
2
|
+
import { quad, quad_def } from "./quad"
|
|
3
|
+
import type { z } from "zod"
|
|
4
|
+
|
|
5
|
+
export const qfp_def = quad_def
|
|
6
|
+
|
|
7
|
+
export const qfp = (raw_params: z.input<typeof quad_def>): AnySoupElement[] => {
|
|
8
|
+
raw_params.legsoutside = true
|
|
9
|
+
|
|
10
|
+
const quad_defaults = quad_def.parse(raw_params)
|
|
11
|
+
|
|
12
|
+
if (!raw_params.pl) {
|
|
13
|
+
// SLOP - eyeballing typical pad width:pad length ratio
|
|
14
|
+
raw_params.pl = quad_defaults.pl * 4
|
|
15
|
+
raw_params.pw = quad_defaults.pw
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return quad(raw_params)
|
|
19
|
+
}
|
package/src/fn/quad.ts
CHANGED
|
@@ -7,8 +7,7 @@ import { pin_order_specifier } from "src/helpers/zod/pin-order-specifier"
|
|
|
7
7
|
import { getQuadPinMap } from "src/helpers/get-quad-pin-map"
|
|
8
8
|
import { dim2d } from "src/helpers/zod/dim-2d"
|
|
9
9
|
|
|
10
|
-
const base_quad_def = z.object({
|
|
11
|
-
quad: z.literal(true),
|
|
10
|
+
export const base_quad_def = z.object({
|
|
12
11
|
cc: z.literal(true).optional(),
|
|
13
12
|
ccw: z.literal(true).optional(),
|
|
14
13
|
startingpin: z
|
|
@@ -24,9 +23,12 @@ const base_quad_def = z.object({
|
|
|
24
23
|
pw: length.optional(),
|
|
25
24
|
pl: length.optional(),
|
|
26
25
|
thermalpad: z.union([z.literal(true), dim2d]).optional(),
|
|
26
|
+
legsoutside: z.boolean().optional(),
|
|
27
27
|
})
|
|
28
28
|
|
|
29
|
-
const
|
|
29
|
+
export const quadTransform = <T extends z.infer<typeof base_quad_def>>(
|
|
30
|
+
v: T
|
|
31
|
+
) => {
|
|
30
32
|
if (v.w && !v.h) {
|
|
31
33
|
v.h = v.w
|
|
32
34
|
} else if (!v.w && v.h) {
|
|
@@ -55,20 +57,24 @@ const quad_def = base_quad_def.transform((v) => {
|
|
|
55
57
|
v.pl = v.pw! * (1.0 / 0.6)
|
|
56
58
|
}
|
|
57
59
|
|
|
58
|
-
return v as NowDefined<
|
|
59
|
-
}
|
|
60
|
+
return v as NowDefined<T, "w" | "h" | "p" | "pw" | "pl">
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export const quad_def = base_quad_def.transform(quadTransform)
|
|
60
64
|
|
|
61
65
|
const SIDES_CCW = ["left", "bottom", "right", "top"] as const
|
|
62
66
|
|
|
63
|
-
export const getQuadCoords = (
|
|
64
|
-
|
|
65
|
-
pn: number
|
|
66
|
-
w: number
|
|
67
|
-
h: number
|
|
68
|
-
p: number
|
|
67
|
+
export const getQuadCoords = (params: {
|
|
68
|
+
pin_count: number
|
|
69
|
+
pn: number // pin number
|
|
70
|
+
w: number // width of the package
|
|
71
|
+
h: number // height (length) of the package
|
|
72
|
+
p: number // pitch between pins
|
|
69
73
|
pl: number // length of the pin
|
|
70
|
-
|
|
71
|
-
|
|
74
|
+
legsoutside?: boolean
|
|
75
|
+
}) => {
|
|
76
|
+
const { pin_count, pn, w, h, p, pl, legsoutside } = params
|
|
77
|
+
const sidePinCount = pin_count / 4
|
|
72
78
|
const side = SIDES_CCW[Math.floor((pn - 1) / sidePinCount)]
|
|
73
79
|
const pos = (pn - 1) % sidePinCount
|
|
74
80
|
|
|
@@ -77,15 +83,18 @@ export const getQuadCoords = (
|
|
|
77
83
|
/** inner box height */
|
|
78
84
|
const ibh = p * (sidePinCount - 1)
|
|
79
85
|
|
|
86
|
+
/** pad center distance from edge (negative is inside, positive is outside) */
|
|
87
|
+
const pcdfe = legsoutside ? pl / 2 : -pl / 2
|
|
88
|
+
|
|
80
89
|
switch (side) {
|
|
81
90
|
case "left":
|
|
82
|
-
return { x: -w / 2
|
|
91
|
+
return { x: -w / 2 - pcdfe, y: ibh / 2 - pos * p, o: "vert" }
|
|
83
92
|
case "bottom":
|
|
84
|
-
return { x: -ibw / 2 + pos * p, y: -h / 2
|
|
93
|
+
return { x: -ibw / 2 + pos * p, y: -h / 2 - pcdfe, o: "horz" }
|
|
85
94
|
case "right":
|
|
86
|
-
return { x: w / 2
|
|
95
|
+
return { x: w / 2 + pcdfe, y: -ibh / 2 + pos * p, o: "vert" }
|
|
87
96
|
case "top":
|
|
88
|
-
return { x: ibw / 2 - pos * p, y: h / 2
|
|
97
|
+
return { x: ibw / 2 - pos * p, y: h / 2 + pcdfe, o: "horz" }
|
|
89
98
|
default:
|
|
90
99
|
throw new Error("Invalid pin number")
|
|
91
100
|
}
|
|
@@ -104,14 +113,15 @@ export const quad = (
|
|
|
104
113
|
x,
|
|
105
114
|
y,
|
|
106
115
|
o: orientation,
|
|
107
|
-
} = getQuadCoords(
|
|
108
|
-
params.num_pins,
|
|
109
|
-
i + 1,
|
|
110
|
-
params.w,
|
|
111
|
-
params.h,
|
|
112
|
-
params.p ?? 0.5,
|
|
113
|
-
params.pl
|
|
114
|
-
|
|
116
|
+
} = getQuadCoords({
|
|
117
|
+
pin_count: params.num_pins,
|
|
118
|
+
pn: i + 1,
|
|
119
|
+
w: params.w,
|
|
120
|
+
h: params.h,
|
|
121
|
+
p: params.p ?? 0.5,
|
|
122
|
+
pl: params.pl,
|
|
123
|
+
legsoutside: params.legsoutside,
|
|
124
|
+
})
|
|
115
125
|
|
|
116
126
|
let pw = params.pw
|
|
117
127
|
let pl = params.pl
|
|
@@ -125,9 +135,8 @@ export const quad = (
|
|
|
125
135
|
|
|
126
136
|
if (params.thermalpad) {
|
|
127
137
|
if (typeof params.thermalpad === "boolean") {
|
|
128
|
-
const
|
|
129
|
-
const
|
|
130
|
-
const ibh = params.p * (sidePinCount - 1) + params.pw
|
|
138
|
+
const ibw = params.p * (spc - 1) + params.pw
|
|
139
|
+
const ibh = params.p * (spc - 1) + params.pw
|
|
131
140
|
pads.push(rectpad(["thermalpad"], 0, 0, ibw, ibh))
|
|
132
141
|
} else {
|
|
133
142
|
pads.push(
|
|
@@ -146,9 +155,13 @@ export const quad = (
|
|
|
146
155
|
] as const) {
|
|
147
156
|
// const dx = Math.floor(corner_index / 2) * 2 - 1
|
|
148
157
|
// const dy = 1 - (corner_index % 2) * 2
|
|
149
|
-
const corner_x = (params.w / 2
|
|
150
|
-
const corner_y = (params.h / 2
|
|
158
|
+
const corner_x = (params.w / 2) * dx
|
|
159
|
+
const corner_y = (params.h / 2) * dy
|
|
151
160
|
let arrow: "none" | "in1" | "in2" = "none"
|
|
161
|
+
|
|
162
|
+
let arrow_x = corner_x
|
|
163
|
+
let arrow_y = corner_y
|
|
164
|
+
|
|
152
165
|
/** corner size */
|
|
153
166
|
const csz = params.pw * 2
|
|
154
167
|
|
|
@@ -169,7 +182,17 @@ export const quad = (
|
|
|
169
182
|
} else if (pin_map[spc * 2 + 1] === 1 && corner === "bottom-right") {
|
|
170
183
|
arrow = "in2"
|
|
171
184
|
}
|
|
172
|
-
|
|
185
|
+
|
|
186
|
+
const rotate_arrow = arrow === "in1" ? 1 : -1
|
|
187
|
+
if (params.legsoutside) {
|
|
188
|
+
const arrow_dx = arrow === "in1" ? params.pl / 2 : params.pw / 2
|
|
189
|
+
const arrow_dy = arrow === "in1" ? params.pw / 2 : params.pl / 2
|
|
190
|
+
arrow_x += arrow_dx * dx * rotate_arrow
|
|
191
|
+
arrow_y -= arrow_dy * dy * rotate_arrow
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Normal Corner
|
|
195
|
+
if (arrow === "none" || params.legsoutside) {
|
|
173
196
|
silkscreen_corners.push({
|
|
174
197
|
layer: "top",
|
|
175
198
|
pcb_component_id: "",
|
|
@@ -190,8 +213,11 @@ export const quad = (
|
|
|
190
213
|
],
|
|
191
214
|
type: "pcb_silkscreen_path",
|
|
192
215
|
})
|
|
193
|
-
}
|
|
194
|
-
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Two lines nearly forming a corner, used when the arrow needs to overlap
|
|
219
|
+
// the corne (QFN components where legs are inside)
|
|
220
|
+
if ((arrow === "in1" || arrow === "in2") && !params.legsoutside) {
|
|
195
221
|
silkscreen_corners.push(
|
|
196
222
|
{
|
|
197
223
|
layer: "top",
|
|
@@ -224,33 +250,35 @@ export const quad = (
|
|
|
224
250
|
},
|
|
225
251
|
],
|
|
226
252
|
type: "pcb_silkscreen_path",
|
|
227
|
-
},
|
|
228
|
-
{
|
|
229
|
-
layer: "top",
|
|
230
|
-
pcb_component_id: "",
|
|
231
|
-
pcb_silkscreen_path_id: `pcb_silkscreen_path_${corner}_3`,
|
|
232
|
-
route: [
|
|
233
|
-
{
|
|
234
|
-
x: corner_x - 0.2 * -dx,
|
|
235
|
-
y: corner_y + 0.2 * rotate_arrow,
|
|
236
|
-
},
|
|
237
|
-
{
|
|
238
|
-
x: corner_x,
|
|
239
|
-
y: corner_y,
|
|
240
|
-
},
|
|
241
|
-
{
|
|
242
|
-
x: corner_x + 0.2 * rotate_arrow * -dx,
|
|
243
|
-
y: corner_y + 0.2,
|
|
244
|
-
},
|
|
245
|
-
{
|
|
246
|
-
x: corner_x - 0.2 * -dx,
|
|
247
|
-
y: corner_y + 0.2 * rotate_arrow,
|
|
248
|
-
},
|
|
249
|
-
],
|
|
250
|
-
type: "pcb_silkscreen_path",
|
|
251
253
|
}
|
|
252
254
|
)
|
|
253
255
|
}
|
|
256
|
+
if (arrow === "in1" || arrow === "in2") {
|
|
257
|
+
silkscreen_corners.push({
|
|
258
|
+
layer: "top",
|
|
259
|
+
pcb_component_id: "",
|
|
260
|
+
pcb_silkscreen_path_id: `pcb_silkscreen_path_${corner}_3`,
|
|
261
|
+
route: [
|
|
262
|
+
{
|
|
263
|
+
x: arrow_x - 0.2 * -dx,
|
|
264
|
+
y: arrow_y + 0.2 * rotate_arrow,
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
x: arrow_x,
|
|
268
|
+
y: arrow_y,
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
x: arrow_x + 0.2 * rotate_arrow * -dx,
|
|
272
|
+
y: arrow_y + 0.2,
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
x: arrow_x - 0.2 * -dx,
|
|
276
|
+
y: arrow_y + 0.2 * rotate_arrow,
|
|
277
|
+
},
|
|
278
|
+
],
|
|
279
|
+
type: "pcb_silkscreen_path",
|
|
280
|
+
})
|
|
281
|
+
}
|
|
254
282
|
}
|
|
255
283
|
|
|
256
284
|
return [...pads, ...silkscreen_corners]
|
package/src/footprinter.ts
CHANGED
|
@@ -6,6 +6,8 @@ import { res } from "./fn/res"
|
|
|
6
6
|
import { bga } from "./fn/bga"
|
|
7
7
|
import { soic } from "./fn/soic"
|
|
8
8
|
import { quad } from "./fn/quad"
|
|
9
|
+
import { qfn } from "./fn/qfn"
|
|
10
|
+
import { qfp } from "./fn/qfp"
|
|
9
11
|
import type { AnySoupElement } from "@tscircuit/soup"
|
|
10
12
|
import { isNotNull } from "./helpers/is-not-null"
|
|
11
13
|
|
|
@@ -34,6 +36,9 @@ export type Footprinter = {
|
|
|
34
36
|
diode: () => FootprinterParamsBuilder<CommonPassiveOptionKey>
|
|
35
37
|
led: () => FootprinterParamsBuilder<CommonPassiveOptionKey>
|
|
36
38
|
lr: (num_pins: number) => FootprinterParamsBuilder<"w" | "l" | "pl" | "pr">
|
|
39
|
+
qfp: (
|
|
40
|
+
num_pins: number
|
|
41
|
+
) => FootprinterParamsBuilder<"w" | "p" | "id" | "od" | "wide" | "narrow">
|
|
37
42
|
quad: (
|
|
38
43
|
num_pins: number
|
|
39
44
|
) => FootprinterParamsBuilder<
|
|
@@ -44,6 +49,7 @@ export type Footprinter = {
|
|
|
44
49
|
) => FootprinterParamsBuilder<
|
|
45
50
|
"grid" | "p" | "w" | "h" | "ball" | "pad" | "missing"
|
|
46
51
|
>
|
|
52
|
+
qfn: (num_pins: number) => FootprinterParamsBuilder<"w" | "h" | "p">
|
|
47
53
|
soic: (num_pins: number) => FootprinterParamsBuilder<"w" | "p" | "id" | "od">
|
|
48
54
|
params: () => any
|
|
49
55
|
soup: () => AnySoupElement[]
|
|
@@ -83,6 +89,8 @@ export const footprinter = (): Footprinter & { string: typeof string } => {
|
|
|
83
89
|
if ("bga" in target) return () => bga(target)
|
|
84
90
|
if ("soic" in target) return () => soic(target)
|
|
85
91
|
if ("quad" in target) return () => quad(target)
|
|
92
|
+
if ("qfn" in target) return () => qfn(target)
|
|
93
|
+
if ("qfp" in target) return () => qfp(target)
|
|
86
94
|
|
|
87
95
|
return () => {
|
|
88
96
|
// TODO improve error
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import test from "ava"
|
|
2
|
+
import { getTestFixture, toPinPositionString } from "./fixtures"
|
|
3
|
+
|
|
4
|
+
test("qfn16_w4_h4_p0.65mm", async (t) => {
|
|
5
|
+
const { fp, logSoup } = await getTestFixture(t)
|
|
6
|
+
const soup = fp.string("qfn16_w4_h4_p0.65mm").soup()
|
|
7
|
+
|
|
8
|
+
await logSoup(soup)
|
|
9
|
+
t.pass()
|
|
10
|
+
})
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import test from "ava"
|
|
2
|
+
import { getTestFixture, toPinPositionString } from "./fixtures"
|
|
3
|
+
|
|
4
|
+
test("qfp48_w14_p1mm", async (t) => {
|
|
5
|
+
const { fp, logSoup } = await getTestFixture(t)
|
|
6
|
+
const soup = fp.string("qfp48_w14_p1mm").soup()
|
|
7
|
+
|
|
8
|
+
await logSoup(soup)
|
|
9
|
+
t.pass()
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
test("qfp48_w14_p1mm_startingpin(topside,leftpin)", async (t) => {
|
|
13
|
+
const { fp, logSoup } = await getTestFixture(t)
|
|
14
|
+
const soup = fp.string("qfp48_w14_p1mm_startingpin(topside,leftpin)").soup()
|
|
15
|
+
|
|
16
|
+
await logSoup(soup)
|
|
17
|
+
t.pass()
|
|
18
|
+
})
|