@tscircuit/footprinter 0.0.21 → 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 +68 -40
- 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,8 +57,10 @@ 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
|
|
|
@@ -67,9 +71,10 @@ export const getQuadCoords = (params: {
|
|
|
67
71
|
h: number // height (length) of the package
|
|
68
72
|
p: number // pitch between pins
|
|
69
73
|
pl: number // length of the pin
|
|
74
|
+
legsoutside?: boolean
|
|
70
75
|
}) => {
|
|
71
|
-
const { pin_count
|
|
72
|
-
const sidePinCount =
|
|
76
|
+
const { pin_count, pn, w, h, p, pl, legsoutside } = params
|
|
77
|
+
const sidePinCount = pin_count / 4
|
|
73
78
|
const side = SIDES_CCW[Math.floor((pn - 1) / sidePinCount)]
|
|
74
79
|
const pos = (pn - 1) % sidePinCount
|
|
75
80
|
|
|
@@ -78,15 +83,18 @@ export const getQuadCoords = (params: {
|
|
|
78
83
|
/** inner box height */
|
|
79
84
|
const ibh = p * (sidePinCount - 1)
|
|
80
85
|
|
|
86
|
+
/** pad center distance from edge (negative is inside, positive is outside) */
|
|
87
|
+
const pcdfe = legsoutside ? pl / 2 : -pl / 2
|
|
88
|
+
|
|
81
89
|
switch (side) {
|
|
82
90
|
case "left":
|
|
83
|
-
return { x: -w / 2
|
|
91
|
+
return { x: -w / 2 - pcdfe, y: ibh / 2 - pos * p, o: "vert" }
|
|
84
92
|
case "bottom":
|
|
85
|
-
return { x: -ibw / 2 + pos * p, y: -h / 2
|
|
93
|
+
return { x: -ibw / 2 + pos * p, y: -h / 2 - pcdfe, o: "horz" }
|
|
86
94
|
case "right":
|
|
87
|
-
return { x: w / 2
|
|
95
|
+
return { x: w / 2 + pcdfe, y: -ibh / 2 + pos * p, o: "vert" }
|
|
88
96
|
case "top":
|
|
89
|
-
return { x: ibw / 2 - pos * p, y: h / 2
|
|
97
|
+
return { x: ibw / 2 - pos * p, y: h / 2 + pcdfe, o: "horz" }
|
|
90
98
|
default:
|
|
91
99
|
throw new Error("Invalid pin number")
|
|
92
100
|
}
|
|
@@ -112,6 +120,7 @@ export const quad = (
|
|
|
112
120
|
h: params.h,
|
|
113
121
|
p: params.p ?? 0.5,
|
|
114
122
|
pl: params.pl,
|
|
123
|
+
legsoutside: params.legsoutside,
|
|
115
124
|
})
|
|
116
125
|
|
|
117
126
|
let pw = params.pw
|
|
@@ -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
|
+
})
|