@tscircuit/footprinter 0.0.4 → 0.0.6
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/README.md +17 -0
- package/package.json +5 -2
- package/src/fn/bga.ts +16 -3
- package/src/fn/dip.ts +37 -10
- package/src/footprinter.ts +3 -1
- package/src/helpers/rectpad.ts +4 -2
- package/src/helpers/zod/ALPHABET.ts +1 -0
- package/src/helpers/zod/bga-def.ts +19 -11
- package/src/helpers/zod/now-defined.ts +2 -0
- package/tests/dip.test.ts +1 -1
- package/tests/fixtures/get-test-fixture.ts +18 -0
- package/tests/fixtures/index.ts +2 -0
- package/tests/slop/slop1.test.ts +46 -0
- package/dist/index.cjs +0 -11625
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -33
package/README.md
CHANGED
|
@@ -43,3 +43,20 @@ should always confirm these footprints against the datasheet.
|
|
|
43
43
|
|
|
44
44
|
- Pins are CCW starting at the top left
|
|
45
45
|
- Y is upward-positive, X is rightward-positive
|
|
46
|
+
|
|
47
|
+
## Slop
|
|
48
|
+
|
|
49
|
+
Slop is a "sloppy" definition, it really doesn't have enough
|
|
50
|
+
information to draw a footprint, i.e. it's missing critical dimensions.
|
|
51
|
+
|
|
52
|
+
footprinter is extremely tolerant to Slop, because it's useful
|
|
53
|
+
when you're iterating incrementally towards a fully constrained
|
|
54
|
+
design, or when you're using footprinter strings as an output format
|
|
55
|
+
for an AI.
|
|
56
|
+
|
|
57
|
+
Generally when footprinter is interpreting a sloppy definition, it will use
|
|
58
|
+
industry best practices or otherwise "reasonable" defaults. In theory, upgrading
|
|
59
|
+
footprinter could cause the defaults to change, which is why sloppy definitions
|
|
60
|
+
are generally not desirable.
|
|
61
|
+
|
|
62
|
+
Currently it's not possible to see if a given definition is sloppy.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tscircuit/footprinter",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.6",
|
|
5
5
|
"description": "",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
7
7
|
"scripts": {
|
|
@@ -12,7 +12,10 @@
|
|
|
12
12
|
"author": "",
|
|
13
13
|
"license": "ISC",
|
|
14
14
|
"devDependencies": {
|
|
15
|
+
"@tscircuit/log-soup": "^1.0.1",
|
|
15
16
|
"@tscircuit/soup": "^0.0.17",
|
|
17
|
+
"@tscircuit/soup-util": "^0.0.11",
|
|
18
|
+
"@types/node": "^20.12.13",
|
|
16
19
|
"ava": "^6.1.3",
|
|
17
20
|
"esbuild": "^0.21.4",
|
|
18
21
|
"esbuild-register": "^3.5.0",
|
|
@@ -20,6 +23,6 @@
|
|
|
20
23
|
"typescript": "^5.4.5"
|
|
21
24
|
},
|
|
22
25
|
"dependencies": {
|
|
23
|
-
"@tscircuit/mm": "^0.0.
|
|
26
|
+
"@tscircuit/mm": "^0.0.7"
|
|
24
27
|
}
|
|
25
28
|
}
|
package/src/fn/bga.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { AnySoupElement, PCBSMTPad } from "@tscircuit/soup"
|
|
2
2
|
import { rectpad } from "../helpers/rectpad"
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { type BgaDefInput, bga_def } from "../helpers/zod/bga-def"
|
|
4
|
+
import { ALPHABET } from "../helpers/zod/ALPHABET"
|
|
5
5
|
|
|
6
6
|
export const bga = (params: BgaDefInput): AnySoupElement[] => {
|
|
7
7
|
const bga_params = bga_def.parse(params)
|
|
@@ -16,6 +16,16 @@ export const bga = (params: BgaDefInput): AnySoupElement[] => {
|
|
|
16
16
|
const missing_pin_nums = (missing ?? []).filter((a) => typeof a === "number")
|
|
17
17
|
const num_pins_missing = grid.x * grid.y - num_pins
|
|
18
18
|
|
|
19
|
+
if (missing.length === 0 && num_pins_missing > 0) {
|
|
20
|
+
// No missing pins specified, let's see if a squared center works
|
|
21
|
+
// if num_pins_missing is a square
|
|
22
|
+
if (Math.sqrt(num_pins_missing) % 1 === 0) {
|
|
23
|
+
missing.push("center")
|
|
24
|
+
} else if (num_pins_missing === 1) {
|
|
25
|
+
missing.push("topleft")
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
19
29
|
if (missing?.includes("center")) {
|
|
20
30
|
// Find the largest square that's square is less than
|
|
21
31
|
// the number of missing pins
|
|
@@ -63,7 +73,10 @@ export const bga = (params: BgaDefInput): AnySoupElement[] => {
|
|
|
63
73
|
const pad_x = (x - (grid.x - 1) / 2) * p
|
|
64
74
|
const pad_y = -(y - (grid.y - 1) / 2) * p
|
|
65
75
|
|
|
66
|
-
|
|
76
|
+
// TODO handle >26 rows
|
|
77
|
+
pads.push(
|
|
78
|
+
rectpad([pin_num, `${ALPHABET[y]}${x + 1}`], pad_x, pad_y, pad, pad)
|
|
79
|
+
)
|
|
67
80
|
}
|
|
68
81
|
}
|
|
69
82
|
|
package/src/fn/dip.ts
CHANGED
|
@@ -2,15 +2,42 @@ import type { AnySoupElement } from "@tscircuit/soup"
|
|
|
2
2
|
import { platedhole } from "../helpers/platedhole"
|
|
3
3
|
import { z } from "zod"
|
|
4
4
|
import { length } from "@tscircuit/soup"
|
|
5
|
+
import type { NowDefined } from "../helpers/zod/now-defined"
|
|
5
6
|
|
|
6
|
-
const dip_def = z
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
7
|
+
const dip_def = z
|
|
8
|
+
.object({
|
|
9
|
+
dip: z.literal(true),
|
|
10
|
+
num_pins: z.number(),
|
|
11
|
+
wide: z.boolean().optional(),
|
|
12
|
+
narrow: z.boolean().optional(),
|
|
13
|
+
w: length.optional(),
|
|
14
|
+
p: length.default(length.parse("2.54mm")),
|
|
15
|
+
id: length.optional(),
|
|
16
|
+
od: length.optional(),
|
|
17
|
+
})
|
|
18
|
+
.transform((v) => {
|
|
19
|
+
// Default inner diameter and outer diameter
|
|
20
|
+
if (!v.id && !v.od) {
|
|
21
|
+
v.id = length.parse("1.0mm")
|
|
22
|
+
v.od = length.parse("1.2mm")
|
|
23
|
+
} else if (!v.id) {
|
|
24
|
+
v.id = v.od! * (1.0 / 1.2)
|
|
25
|
+
} else if (!v.od) {
|
|
26
|
+
v.od = v.id! * (1.2 / 1.0)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Default width (TODO high pin counts should probably be wide?)
|
|
30
|
+
if (!v.w) {
|
|
31
|
+
if (v.wide) {
|
|
32
|
+
v.w = length.parse("600mil")
|
|
33
|
+
} else if (v.narrow) {
|
|
34
|
+
v.w = length.parse("300mil")
|
|
35
|
+
} else {
|
|
36
|
+
v.w = length.parse("300mil")
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return v as NowDefined<typeof v, "w" | "p" | "id" | "od">
|
|
40
|
+
})
|
|
14
41
|
|
|
15
42
|
export const getCcwDipCoords = (
|
|
16
43
|
pinCount: number,
|
|
@@ -43,7 +70,7 @@ export const getCcwDipCoords = (
|
|
|
43
70
|
/**
|
|
44
71
|
* Returns the plated holes for a DIP package.
|
|
45
72
|
*/
|
|
46
|
-
export const dip = (
|
|
73
|
+
export const dip = (raw_params: {
|
|
47
74
|
dip: true
|
|
48
75
|
num_pins: number
|
|
49
76
|
w: number
|
|
@@ -51,7 +78,7 @@ export const dip = (params: {
|
|
|
51
78
|
id?: string | number
|
|
52
79
|
od?: string | number
|
|
53
80
|
}): AnySoupElement[] => {
|
|
54
|
-
params = dip_def.parse(
|
|
81
|
+
const params = dip_def.parse(raw_params)
|
|
55
82
|
const platedHoles: AnySoupElement[] = []
|
|
56
83
|
for (let i = 0; i < params.num_pins; i++) {
|
|
57
84
|
const { x, y } = getCcwDipCoords(
|
package/src/footprinter.ts
CHANGED
|
@@ -23,7 +23,9 @@ type CommonPassiveOptionKey =
|
|
|
23
23
|
| "h"
|
|
24
24
|
|
|
25
25
|
export type Footprinter = {
|
|
26
|
-
dip: (
|
|
26
|
+
dip: (
|
|
27
|
+
num_pins: number
|
|
28
|
+
) => FootprinterParamsBuilder<"w" | "p" | "id" | "od" | "wide" | "narrow">
|
|
27
29
|
cap: () => FootprinterParamsBuilder<CommonPassiveOptionKey>
|
|
28
30
|
res: () => FootprinterParamsBuilder<CommonPassiveOptionKey>
|
|
29
31
|
diode: () => FootprinterParamsBuilder<CommonPassiveOptionKey>
|
package/src/helpers/rectpad.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { PCBSMTPad } from "@tscircuit/soup"
|
|
2
2
|
export const rectpad = (
|
|
3
|
-
pn: number
|
|
3
|
+
pn: number | Array<string | number>,
|
|
4
4
|
x: number,
|
|
5
5
|
y: number,
|
|
6
6
|
w: number,
|
|
@@ -15,6 +15,8 @@ export const rectpad = (
|
|
|
15
15
|
layer: "top",
|
|
16
16
|
shape: "rect",
|
|
17
17
|
pcb_smtpad_id: "",
|
|
18
|
-
port_hints:
|
|
18
|
+
port_hints: Array.isArray(pn)
|
|
19
|
+
? pn.map((item) => item.toString())
|
|
20
|
+
: [pn.toString()],
|
|
19
21
|
}
|
|
20
22
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
@@ -2,16 +2,16 @@ import { z } from "zod"
|
|
|
2
2
|
import { length, distance } from "@tscircuit/soup"
|
|
3
3
|
import { dim2d } from "./dim-2d"
|
|
4
4
|
import { function_call } from "./function-call"
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
import type { NowDefined } from "./now-defined"
|
|
6
|
+
import { ALPHABET } from "./ALPHABET"
|
|
7
7
|
|
|
8
8
|
export const bga_def = z
|
|
9
9
|
.object({
|
|
10
10
|
num_pins: z.number(),
|
|
11
|
-
grid: dim2d,
|
|
12
|
-
p: distance,
|
|
13
|
-
w: length,
|
|
14
|
-
h: length,
|
|
11
|
+
grid: dim2d.optional(),
|
|
12
|
+
p: distance.default("0.8mm"),
|
|
13
|
+
w: length.optional(),
|
|
14
|
+
h: length.optional(),
|
|
15
15
|
ball: length.optional().describe("ball diameter"),
|
|
16
16
|
pad: length.optional().describe("pad width/height"),
|
|
17
17
|
|
|
@@ -20,7 +20,7 @@ export const bga_def = z
|
|
|
20
20
|
trorigin: z.boolean().optional(),
|
|
21
21
|
brorigin: z.boolean().optional(),
|
|
22
22
|
|
|
23
|
-
missing: function_call,
|
|
23
|
+
missing: function_call.default([]),
|
|
24
24
|
})
|
|
25
25
|
.transform((a) => {
|
|
26
26
|
let origin: "tl" | "bl" | "tr" | "br" = "tl"
|
|
@@ -28,6 +28,12 @@ export const bga_def = z
|
|
|
28
28
|
if (a.trorigin) origin = "tr"
|
|
29
29
|
if (a.brorigin) origin = "br"
|
|
30
30
|
|
|
31
|
+
if (!a.grid) {
|
|
32
|
+
// find the largest square for the number of pins
|
|
33
|
+
const largest_square = Math.ceil(Math.sqrt(a.num_pins))
|
|
34
|
+
a.grid = { x: largest_square, y: largest_square }
|
|
35
|
+
}
|
|
36
|
+
|
|
31
37
|
if (a.missing) {
|
|
32
38
|
a.missing = a.missing.map((s) => {
|
|
33
39
|
if (typeof s === "number") return s
|
|
@@ -35,13 +41,15 @@ export const bga_def = z
|
|
|
35
41
|
if (s === "topleft") return "topleft"
|
|
36
42
|
const m = s.match(/([A-Z]+)(\d+)/)
|
|
37
43
|
if (!m) return s
|
|
38
|
-
let Y = ALPHABET.indexOf(m[1])
|
|
39
|
-
let X = parseInt(m[2]) - 1
|
|
40
|
-
return Y * a.grid
|
|
44
|
+
let Y = ALPHABET.indexOf(m[1]!)
|
|
45
|
+
let X = parseInt(m[2]!) - 1
|
|
46
|
+
return Y * a.grid!.x + X + 1
|
|
41
47
|
})
|
|
42
48
|
}
|
|
43
49
|
|
|
44
|
-
|
|
50
|
+
const new_def = { ...a, origin }
|
|
51
|
+
|
|
52
|
+
return new_def as NowDefined<typeof new_def, "w" | "h" | "grid">
|
|
45
53
|
})
|
|
46
54
|
|
|
47
55
|
export type BgaDefInput = z.input<typeof bga_def>
|
package/tests/dip.test.ts
CHANGED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ExecutionContext } from "ava"
|
|
2
|
+
import { fp } from "../../src"
|
|
3
|
+
import { logSoup } from "@tscircuit/log-soup"
|
|
4
|
+
import type { AnySoupElement } from "@tscircuit/soup"
|
|
5
|
+
|
|
6
|
+
export const getTestFixture = async (t: ExecutionContext) => {
|
|
7
|
+
return {
|
|
8
|
+
fp,
|
|
9
|
+
logSoup: (soup: AnySoupElement[]) => {
|
|
10
|
+
if (process.env.CI) return
|
|
11
|
+
return logSoup(`footprinter: ${t.title}`, soup)
|
|
12
|
+
},
|
|
13
|
+
logSoupWithPrefix: (prefix: string, soup: AnySoupElement[]) => {
|
|
14
|
+
if (process.env.CI) return
|
|
15
|
+
return logSoup(`footprinter: ${t.title} ${prefix}`, soup)
|
|
16
|
+
},
|
|
17
|
+
}
|
|
18
|
+
}
|
package/tests/fixtures/index.ts
CHANGED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import test from "ava"
|
|
2
|
+
import { getTestFixture } from "../fixtures"
|
|
3
|
+
import type { AnySoupElement } from "@tscircuit/soup"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Slop is an underdefined definition.
|
|
7
|
+
*/
|
|
8
|
+
export const SLOP_LIST = [
|
|
9
|
+
"dip3",
|
|
10
|
+
"bga64",
|
|
11
|
+
"bga48",
|
|
12
|
+
"bga48_grid8x8",
|
|
13
|
+
"bga48_p2_pad0.2",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
test("slop1", async (t) => {
|
|
17
|
+
const { fp, logSoupWithPrefix } = await getTestFixture(t)
|
|
18
|
+
|
|
19
|
+
const soups: AnySoupElement[][] = []
|
|
20
|
+
const failures: Array<{
|
|
21
|
+
slop_string: string
|
|
22
|
+
error: any
|
|
23
|
+
}> = []
|
|
24
|
+
|
|
25
|
+
for (const slop of SLOP_LIST) {
|
|
26
|
+
try {
|
|
27
|
+
const soup = fp.string(slop).soup()
|
|
28
|
+
soups.push(soup)
|
|
29
|
+
if (slop === SLOP_LIST[SLOP_LIST.length - 1]) {
|
|
30
|
+
await logSoupWithPrefix(slop, soup)
|
|
31
|
+
}
|
|
32
|
+
} catch (e: any) {
|
|
33
|
+
failures.push({
|
|
34
|
+
slop_string: slop,
|
|
35
|
+
error: e,
|
|
36
|
+
})
|
|
37
|
+
throw e
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (failures.length > 0) {
|
|
42
|
+
t.fail(`Failures:\n${failures.map((f) => f.slop_string).join("\n")}`)
|
|
43
|
+
} else {
|
|
44
|
+
t.pass()
|
|
45
|
+
}
|
|
46
|
+
})
|