@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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tscircuit/footprinter",
3
3
  "type": "module",
4
- "version": "0.0.20",
4
+ "version": "0.0.22",
5
5
  "description": "",
6
6
  "main": "dist/index.cjs",
7
7
  "scripts": {
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 quad_def = base_quad_def.transform((v) => {
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<typeof v, "w" | "h" | "p" | "pw" | "pl">
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
- pinCount: number,
65
- pn: number, // pin number
66
- w: number, // width of the package
67
- h: number, // height (length) of the package
68
- p: number, // pitch between pins
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
- const sidePinCount = pinCount / 4
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 + pl / 2, y: ibh / 2 - pos * p, o: "vert" }
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 + pl / 2, o: "horz" }
93
+ return { x: -ibw / 2 + pos * p, y: -h / 2 - pcdfe, o: "horz" }
85
94
  case "right":
86
- return { x: w / 2 - pl / 2, y: -ibh / 2 + pos * p, o: "vert" }
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 - pl / 2, o: "horz" }
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 sidePinCount = params.num_pins / 4
129
- const ibw = params.p * (sidePinCount - 1) + params.pw
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 - params.pl / 2) * dx
150
- const corner_y = (params.h / 2 - params.pl / 2) * dy
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
- if (arrow === "none") {
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
- } else {
194
- const rotate_arrow = arrow === "in1" ? 1 : -1
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]
@@ -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
+ })