@tscircuit/footprinter 0.0.8 → 0.0.10

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,11 +1,11 @@
1
1
  {
2
2
  "name": "@tscircuit/footprinter",
3
3
  "type": "module",
4
- "version": "0.0.8",
4
+ "version": "0.0.10",
5
5
  "description": "",
6
6
  "main": "dist/index.cjs",
7
7
  "scripts": {
8
- "test": "ava",
8
+ "test": "FULL_RUN=1 ava",
9
9
  "build": "tsup ./src/index.ts --dts --sourcemap"
10
10
  },
11
11
  "keywords": [],
package/src/fn/soic.ts ADDED
@@ -0,0 +1,106 @@
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
+ }
@@ -4,6 +4,7 @@ import { cap } from "./fn/cap"
4
4
  import { led } from "./fn/led"
5
5
  import { res } from "./fn/res"
6
6
  import { bga } from "./fn/bga"
7
+ import { soic } from "./fn/soic"
7
8
  import type { AnySoupElement } from "@tscircuit/soup"
8
9
  import { isNotNull } from "./helpers/is-not-null"
9
10
 
@@ -42,6 +43,7 @@ export type Footprinter = {
42
43
  ) => FootprinterParamsBuilder<
43
44
  "grid" | "p" | "w" | "h" | "ball" | "pad" | "missing"
44
45
  >
46
+ soic: (num_pins: number) => FootprinterParamsBuilder<"w" | "p" | "id" | "od">
45
47
  params: () => any
46
48
  soup: () => AnySoupElement[]
47
49
  }
@@ -78,6 +80,7 @@ export const footprinter = (): Footprinter & { string: typeof string } => {
78
80
  if ("led" in target) return () => led(target)
79
81
  if ("res" in target) return () => res(target)
80
82
  if ("bga" in target) return () => bga(target)
83
+ if ("soic" in target) return () => soic(target)
81
84
 
82
85
  return () => {
83
86
  // TODO improve error
@@ -91,9 +94,15 @@ export const footprinter = (): Footprinter & { string: typeof string } => {
91
94
  return () => target
92
95
  }
93
96
  return (v: any) => {
94
- if (["bga", "lr", "quad", "dip"].includes(prop as string)) {
97
+ if (Object.keys(target).length === 0) {
95
98
  target[prop] = true
96
- target.num_pins = parseFloat(v)
99
+ if (prop === "res" || prop === "cap") {
100
+ if (v) {
101
+ target.imperial = v // res0402, cap0603 etc.
102
+ }
103
+ } else {
104
+ target.num_pins = parseFloat(v)
105
+ }
97
106
  } else {
98
107
  target[prop] = v ?? true
99
108
  }
@@ -0,0 +1,6 @@
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
+ }))
@@ -8,10 +8,12 @@ export const getTestFixture = async (t: ExecutionContext) => {
8
8
  fp,
9
9
  logSoup: (soup: AnySoupElement[]) => {
10
10
  if (process.env.CI) return
11
+ if (process.env.FULL_RUN) return
11
12
  return logSoup(`footprinter: ${t.title}`, soup)
12
13
  },
13
14
  logSoupWithPrefix: (prefix: string, soup: AnySoupElement[]) => {
14
15
  if (process.env.CI) return
16
+ if (process.env.FULL_RUN) return
15
17
  return logSoup(`footprinter: ${t.title} ${prefix}`, soup)
16
18
  },
17
19
  }
@@ -0,0 +1,11 @@
1
+ import test from "ava"
2
+ import { getTestFixture } from "tests/fixtures/get-test-fixture"
3
+ import { su } from "@tscircuit/soup-util"
4
+
5
+ test("res_imperial0402", async (t) => {
6
+ const { fp } = await getTestFixture(t)
7
+ const soup = fp.string("res_imperial0402").soup()
8
+
9
+ t.is(su(soup).pcb_smtpad.list().length, 2)
10
+ t.is(su(soup).pcb_plated_hole.list().length, 0)
11
+ })
@@ -0,0 +1,11 @@
1
+ import test from "ava"
2
+ import { getTestFixture } from "tests/fixtures/get-test-fixture"
3
+ import { su } from "@tscircuit/soup-util"
4
+
5
+ test("soic8_w5.3mm_p1.27mm", async (t) => {
6
+ const { fp, logSoup } = await getTestFixture(t)
7
+ const soup = fp.string("soic8_w5.3mm_p1.27mm").soup()
8
+
9
+ t.is(su(soup).pcb_plated_hole.list().length, 8)
10
+ await logSoup(soup)
11
+ })