@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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tscircuit/footprinter",
3
3
  "type": "module",
4
- "version": "0.0.21",
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,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<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
 
@@ -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: pinCount, pn, w, h, p, pl } = params
72
- const sidePinCount = pinCount / 4
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 + pl / 2, y: ibh / 2 - pos * p, o: "vert" }
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 + pl / 2, o: "horz" }
93
+ return { x: -ibw / 2 + pos * p, y: -h / 2 - pcdfe, o: "horz" }
86
94
  case "right":
87
- 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" }
88
96
  case "top":
89
- 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" }
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 - 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
+ })