@tscircuit/footprinter 0.0.1
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 +45 -0
- package/ava.config.js +5 -0
- package/dist/index.cjs +11615 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +33 -0
- package/package.json +25 -0
- package/src/fn/bga.ts +71 -0
- package/src/fn/cap.ts +7 -0
- package/src/fn/diode.ts +8 -0
- package/src/fn/dip.ts +56 -0
- package/src/fn/led.ts +6 -0
- package/src/fn/res.ts +6 -0
- package/src/footprinter.ts +102 -0
- package/src/helpers/passive-fn.ts +175 -0
- package/src/helpers/platedhole.ts +21 -0
- package/src/helpers/rectpad.ts +20 -0
- package/src/helpers/zod/bga-def.ts +48 -0
- package/src/helpers/zod/dim-2d.ts +17 -0
- package/src/helpers/zod/function-call.ts +16 -0
- package/src/index.ts +1 -0
- package/tests/bga.test.ts +49 -0
- package/tests/cap.test.ts +30 -0
- package/tests/dip.test.ts +42 -0
- package/tests/fixtures/index.ts +30 -0
- package/tsconfig.json +25 -0
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { AnySoupElement } from '@tscircuit/soup';
|
|
2
|
+
|
|
3
|
+
type FootprinterParamsBuilder<K extends string> = {
|
|
4
|
+
[P in K | "params" | "soup"]: P extends "params" | "soup" ? Footprinter[P] : (v?: number | string) => FootprinterParamsBuilder<K>;
|
|
5
|
+
};
|
|
6
|
+
type CommonPassiveOptionKey = "metric" | "imperial" | "tht" | "p" | "pw" | "ph" | "w" | "h";
|
|
7
|
+
type Footprinter = {
|
|
8
|
+
dip: (num_pins: number) => FootprinterParamsBuilder<"w" | "p" | "id" | "od">;
|
|
9
|
+
cap: () => FootprinterParamsBuilder<CommonPassiveOptionKey>;
|
|
10
|
+
res: () => FootprinterParamsBuilder<CommonPassiveOptionKey>;
|
|
11
|
+
diode: () => FootprinterParamsBuilder<CommonPassiveOptionKey>;
|
|
12
|
+
led: () => FootprinterParamsBuilder<CommonPassiveOptionKey>;
|
|
13
|
+
lr: (num_pins: number) => FootprinterParamsBuilder<"w" | "l" | "pl" | "pr">;
|
|
14
|
+
quad: (num_pins: number) => FootprinterParamsBuilder<"w" | "l" | "square" | "pl" | "pr" | "pb" | "pt" | "p" | "pw" | "ph">;
|
|
15
|
+
bga: (num_pins: number) => FootprinterParamsBuilder<"grid" | "p" | "w" | "h" | "ball" | "pad" | "missing">;
|
|
16
|
+
params: () => any;
|
|
17
|
+
soup: () => AnySoupElement[];
|
|
18
|
+
};
|
|
19
|
+
declare const string: (def: string) => Footprinter;
|
|
20
|
+
declare const footprinter: {
|
|
21
|
+
(): Footprinter & {
|
|
22
|
+
string: typeof string;
|
|
23
|
+
};
|
|
24
|
+
string: (def: string) => Footprinter;
|
|
25
|
+
};
|
|
26
|
+
declare const fp: {
|
|
27
|
+
(): Footprinter & {
|
|
28
|
+
string: typeof string;
|
|
29
|
+
};
|
|
30
|
+
string: (def: string) => Footprinter;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export { type Footprinter, type FootprinterParamsBuilder, footprinter, fp, string };
|
package/package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tscircuit/footprinter",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "0.0.1",
|
|
5
|
+
"description": "",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"test": "ava",
|
|
9
|
+
"build": "tsup ./src/index.ts --dts --sourcemap"
|
|
10
|
+
},
|
|
11
|
+
"keywords": [],
|
|
12
|
+
"author": "",
|
|
13
|
+
"license": "ISC",
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"@tscircuit/soup": "^0.0.17",
|
|
16
|
+
"ava": "^6.1.3",
|
|
17
|
+
"esbuild": "^0.21.4",
|
|
18
|
+
"esbuild-register": "^3.5.0",
|
|
19
|
+
"tsup": "^8.0.2",
|
|
20
|
+
"typescript": "^5.4.5"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"@tscircuit/mm": "^0.0.6"
|
|
24
|
+
}
|
|
25
|
+
}
|
package/src/fn/bga.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { AnySoupElement, PCBSMTPad } from "@tscircuit/soup"
|
|
2
|
+
import { rectpad } from "../helpers/rectpad"
|
|
3
|
+
import { PassiveDef, passive } from "../helpers/passive-fn"
|
|
4
|
+
import { BgaDef, BgaDefInput, bga_def } from "../helpers/zod/bga-def"
|
|
5
|
+
|
|
6
|
+
export const bga = (params: BgaDefInput): AnySoupElement[] => {
|
|
7
|
+
const bga_params = bga_def.parse(params)
|
|
8
|
+
let { num_pins, grid, p, w, h, ball, pad, missing } = bga_params
|
|
9
|
+
|
|
10
|
+
ball ??= (0.75 / 1.27) * p
|
|
11
|
+
|
|
12
|
+
pad ??= ball * 0.8
|
|
13
|
+
|
|
14
|
+
const pads: PCBSMTPad[] = []
|
|
15
|
+
|
|
16
|
+
const missing_pin_nums = (missing ?? []).filter((a) => typeof a === "number")
|
|
17
|
+
const num_pins_missing = grid.x * grid.y - num_pins
|
|
18
|
+
|
|
19
|
+
if (missing?.includes("center")) {
|
|
20
|
+
// Find the largest square that's square is less than
|
|
21
|
+
// the number of missing pins
|
|
22
|
+
const square_size = Math.floor(Math.sqrt(num_pins_missing))
|
|
23
|
+
|
|
24
|
+
// Find the top left coordinate of the inner square, keep
|
|
25
|
+
// in mind the full grid size is grid.x x grid.y
|
|
26
|
+
const inner_square_x = Math.floor((grid.x - square_size) / 2)
|
|
27
|
+
const inner_square_y = Math.floor((grid.y - square_size) / 2)
|
|
28
|
+
|
|
29
|
+
// Add all the missing square pin numbers to missing_pin_nums
|
|
30
|
+
for (let y = inner_square_y; y < inner_square_y + square_size; y++) {
|
|
31
|
+
for (let x = inner_square_x; x < inner_square_x + square_size; x++) {
|
|
32
|
+
missing_pin_nums.push(y * grid.x + x + 1)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (missing?.includes("topleft")) {
|
|
38
|
+
missing_pin_nums.push(1)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (num_pins_missing !== missing_pin_nums.length) {
|
|
42
|
+
throw new Error(
|
|
43
|
+
`not able to create bga component, unable to determine missing pins (try specifying them with "missing+1+2+..."\n\n${JSON.stringify(
|
|
44
|
+
bga_params,
|
|
45
|
+
null,
|
|
46
|
+
" "
|
|
47
|
+
)}`
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const missing_pin_nums_set = new Set(missing_pin_nums)
|
|
52
|
+
|
|
53
|
+
let missing_pins_passed = 0
|
|
54
|
+
for (let y = 0; y < grid.y; y++) {
|
|
55
|
+
for (let x = 0; x < grid.x; x++) {
|
|
56
|
+
let pin_num = y * grid.x + x + 1
|
|
57
|
+
if (missing_pin_nums_set.has(pin_num)) {
|
|
58
|
+
missing_pins_passed++
|
|
59
|
+
continue
|
|
60
|
+
}
|
|
61
|
+
pin_num -= missing_pins_passed
|
|
62
|
+
|
|
63
|
+
const pad_x = (x - (grid.x - 1) / 2) * p
|
|
64
|
+
const pad_y = -(y - (grid.y - 1) / 2) * p
|
|
65
|
+
|
|
66
|
+
pads.push(rectpad(pin_num, pad_x, pad_y, pad, pad))
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return [...pads]
|
|
71
|
+
}
|
package/src/fn/cap.ts
ADDED
package/src/fn/diode.ts
ADDED
package/src/fn/dip.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { AnySoupElement } from "@tscircuit/soup"
|
|
2
|
+
import { platedhole } from "../helpers/platedhole"
|
|
3
|
+
|
|
4
|
+
export const getCcwDipCoords = (
|
|
5
|
+
pinCount: number,
|
|
6
|
+
pn: number,
|
|
7
|
+
w: number,
|
|
8
|
+
p: number
|
|
9
|
+
) => {
|
|
10
|
+
/** pin height */
|
|
11
|
+
const ph = pinCount / 2
|
|
12
|
+
const isLeft = pn <= ph
|
|
13
|
+
|
|
14
|
+
/** Number of gaps between pins on each side, e.g. 4 pins = 3 spaces */
|
|
15
|
+
const leftPinGaps = ph - 1
|
|
16
|
+
|
|
17
|
+
/** gap size (pitch) */
|
|
18
|
+
const gs = p
|
|
19
|
+
|
|
20
|
+
const h = gs * leftPinGaps
|
|
21
|
+
|
|
22
|
+
if (isLeft) {
|
|
23
|
+
// The y position starts at h/2, then goes down by gap size
|
|
24
|
+
// for each pin
|
|
25
|
+
return { x: -w / 2, y: h / 2 - (pn - 1) * gs }
|
|
26
|
+
} else {
|
|
27
|
+
// The y position starts at -h/2, then goes up by gap size
|
|
28
|
+
return { x: w / 2, y: -h / 2 + (pn - ph - 1) * gs }
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Returns the plated holes for a DIP package.
|
|
34
|
+
*/
|
|
35
|
+
export const dip = (params: {
|
|
36
|
+
dip: true
|
|
37
|
+
num_pins: number
|
|
38
|
+
w: number
|
|
39
|
+
p?: number
|
|
40
|
+
id?: string | number
|
|
41
|
+
od?: string | number
|
|
42
|
+
}): AnySoupElement[] => {
|
|
43
|
+
const platedHoles: AnySoupElement[] = []
|
|
44
|
+
for (let i = 0; i < params.num_pins; i++) {
|
|
45
|
+
const { x, y } = getCcwDipCoords(
|
|
46
|
+
params.num_pins,
|
|
47
|
+
i + 1,
|
|
48
|
+
params.w,
|
|
49
|
+
params.p ?? 2.54
|
|
50
|
+
)
|
|
51
|
+
platedHoles.push(
|
|
52
|
+
platedhole(i + 1, x, y, params.id ?? "0.8mm", params.od ?? "1mm")
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
return platedHoles
|
|
56
|
+
}
|
package/src/fn/led.ts
ADDED
package/src/fn/res.ts
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { dip } from "./fn/dip"
|
|
2
|
+
import { diode } from "./fn/diode"
|
|
3
|
+
import { cap } from "./fn/cap"
|
|
4
|
+
import { led } from "./fn/led"
|
|
5
|
+
import { res } from "./fn/res"
|
|
6
|
+
import { bga } from "./fn/bga"
|
|
7
|
+
import type { AnySoupElement } from "@tscircuit/soup"
|
|
8
|
+
|
|
9
|
+
export type FootprinterParamsBuilder<K extends string> = {
|
|
10
|
+
[P in K | "params" | "soup"]: P extends "params" | "soup"
|
|
11
|
+
? Footprinter[P]
|
|
12
|
+
: (v?: number | string) => FootprinterParamsBuilder<K>
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
type CommonPassiveOptionKey =
|
|
16
|
+
| "metric"
|
|
17
|
+
| "imperial"
|
|
18
|
+
| "tht"
|
|
19
|
+
| "p"
|
|
20
|
+
| "pw"
|
|
21
|
+
| "ph"
|
|
22
|
+
| "w"
|
|
23
|
+
| "h"
|
|
24
|
+
|
|
25
|
+
export type Footprinter = {
|
|
26
|
+
dip: (num_pins: number) => FootprinterParamsBuilder<"w" | "p" | "id" | "od">
|
|
27
|
+
cap: () => FootprinterParamsBuilder<CommonPassiveOptionKey>
|
|
28
|
+
res: () => FootprinterParamsBuilder<CommonPassiveOptionKey>
|
|
29
|
+
diode: () => FootprinterParamsBuilder<CommonPassiveOptionKey>
|
|
30
|
+
led: () => FootprinterParamsBuilder<CommonPassiveOptionKey>
|
|
31
|
+
lr: (num_pins: number) => FootprinterParamsBuilder<"w" | "l" | "pl" | "pr">
|
|
32
|
+
quad: (
|
|
33
|
+
num_pins: number
|
|
34
|
+
) => FootprinterParamsBuilder<
|
|
35
|
+
"w" | "l" | "square" | "pl" | "pr" | "pb" | "pt" | "p" | "pw" | "ph"
|
|
36
|
+
>
|
|
37
|
+
bga: (
|
|
38
|
+
num_pins: number
|
|
39
|
+
) => FootprinterParamsBuilder<
|
|
40
|
+
"grid" | "p" | "w" | "h" | "ball" | "pad" | "missing"
|
|
41
|
+
>
|
|
42
|
+
params: () => any
|
|
43
|
+
soup: () => AnySoupElement[]
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export const string = (def: string): Footprinter => {
|
|
47
|
+
let fp = footprinter()
|
|
48
|
+
|
|
49
|
+
const def_parts = def.split("_").map((s) => {
|
|
50
|
+
const m = s.match(/([a-z]+)([\(\d\+].*)?/)
|
|
51
|
+
const [_, fn, v] = m ?? []
|
|
52
|
+
return { fn: m?.[1]!, v: m?.[2]! }
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
for (const { fn, v } of def_parts) {
|
|
56
|
+
fp = fp[fn](v)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return fp
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export const footprinter = (): Footprinter & { string: typeof string } => {
|
|
63
|
+
const proxy = new Proxy(
|
|
64
|
+
{},
|
|
65
|
+
{
|
|
66
|
+
get: (target: any, prop) => {
|
|
67
|
+
if (prop === "soup") {
|
|
68
|
+
if ("dip" in target) return () => dip(target)
|
|
69
|
+
if ("diode" in target) return () => diode(target)
|
|
70
|
+
if ("cap" in target) return () => cap(target)
|
|
71
|
+
if ("led" in target) return () => led(target)
|
|
72
|
+
if ("res" in target) return () => res(target)
|
|
73
|
+
if ("bga" in target) return () => bga(target)
|
|
74
|
+
|
|
75
|
+
return () => {
|
|
76
|
+
// TODO improve error
|
|
77
|
+
throw new Error(
|
|
78
|
+
`No function found for footprinter, make sure to specify .dip, .lr, .p, etc. Got \"${prop}\"`
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (prop === "params") {
|
|
83
|
+
// TODO
|
|
84
|
+
return () => target
|
|
85
|
+
}
|
|
86
|
+
return (v: any) => {
|
|
87
|
+
if (["bga", "lr", "quad", "dip"].includes(prop as string)) {
|
|
88
|
+
target[prop] = true
|
|
89
|
+
target.num_pins = parseFloat(v)
|
|
90
|
+
} else {
|
|
91
|
+
target[prop] = v ?? true
|
|
92
|
+
}
|
|
93
|
+
return proxy
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
}
|
|
97
|
+
)
|
|
98
|
+
return proxy as any
|
|
99
|
+
}
|
|
100
|
+
footprinter.string = string
|
|
101
|
+
|
|
102
|
+
export const fp = footprinter
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import type { AnySoupElement } from "@tscircuit/soup"
|
|
2
|
+
import { rectpad } from "../helpers/rectpad"
|
|
3
|
+
import mm from "@tscircuit/mm"
|
|
4
|
+
import { platedhole } from "./platedhole"
|
|
5
|
+
|
|
6
|
+
type StandardSize = {
|
|
7
|
+
imperial: string
|
|
8
|
+
metric: string
|
|
9
|
+
Z_mm_min: number
|
|
10
|
+
G_mm_min: number
|
|
11
|
+
X_mm_min: number
|
|
12
|
+
C_mm_ref: number
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// https://www.worthingtonassembly.com/perfect-0201-footprint
|
|
16
|
+
// https://static1.squarespace.com/static/54982a02e4b02e9f5e5d9ca7/t/660c692f69a0d83a4afecdf0/1712089391915/Discrete+Component+Footprints.pdf
|
|
17
|
+
// https://page.venkel.com/hubfs/Resources/Technical/Resistors%20Landing%20Pattern.pdf
|
|
18
|
+
const sizes = [
|
|
19
|
+
{
|
|
20
|
+
imperial: "01005",
|
|
21
|
+
metric: "0402",
|
|
22
|
+
Z_mm_min: 0.58,
|
|
23
|
+
G_mm_min: 0.18,
|
|
24
|
+
X_mm_min: 0.21,
|
|
25
|
+
C_mm_ref: 0.038,
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
imperial: "0201",
|
|
29
|
+
metric: "0603",
|
|
30
|
+
Z_mm_min: 0.9,
|
|
31
|
+
G_mm_min: 0.3,
|
|
32
|
+
X_mm_min: 0.3,
|
|
33
|
+
C_mm_ref: 0.6,
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
imperial: "0402",
|
|
37
|
+
metric: "1005",
|
|
38
|
+
Z_mm_min: 1.6,
|
|
39
|
+
G_mm_min: 0.4,
|
|
40
|
+
X_mm_min: 0.7,
|
|
41
|
+
C_mm_ref: 1,
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
imperial: "0603",
|
|
45
|
+
metric: "1608",
|
|
46
|
+
Z_mm_min: 2.6,
|
|
47
|
+
G_mm_min: 0.6,
|
|
48
|
+
X_mm_min: 1.0,
|
|
49
|
+
C_mm_ref: 1.7,
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
imperial: "0805",
|
|
53
|
+
metric: "2012",
|
|
54
|
+
Z_mm_min: 3.0,
|
|
55
|
+
G_mm_min: 0.6,
|
|
56
|
+
X_mm_min: 1.2,
|
|
57
|
+
C_mm_ref: 1.9,
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
imperial: "1206",
|
|
61
|
+
metric: "3216",
|
|
62
|
+
Z_mm_min: 4.2,
|
|
63
|
+
G_mm_min: 1.2,
|
|
64
|
+
X_mm_min: 1.4,
|
|
65
|
+
C_mm_ref: 2.8,
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
imperial: "1210",
|
|
69
|
+
metric: "3225",
|
|
70
|
+
Z_mm_min: 4.2,
|
|
71
|
+
G_mm_min: 1.2,
|
|
72
|
+
X_mm_min: 2.4,
|
|
73
|
+
C_mm_ref: 2.8,
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
imperial: "2010",
|
|
77
|
+
metric: "5025",
|
|
78
|
+
Z_mm_min: 6.0,
|
|
79
|
+
G_mm_min: 2.6,
|
|
80
|
+
X_mm_min: 2.4,
|
|
81
|
+
C_mm_ref: 4.4,
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
imperial: "2512",
|
|
85
|
+
metric: "6332",
|
|
86
|
+
Z_mm_min: 7.2,
|
|
87
|
+
G_mm_min: 3.8,
|
|
88
|
+
X_mm_min: 3.0,
|
|
89
|
+
C_mm_ref: 5.6,
|
|
90
|
+
},
|
|
91
|
+
]
|
|
92
|
+
const metricMap: Record<string, StandardSize> = sizes.reduce((acc: any, s) => {
|
|
93
|
+
acc[s.metric] = s
|
|
94
|
+
return acc
|
|
95
|
+
}, {})
|
|
96
|
+
const imperialMap: Record<string, StandardSize> = sizes.reduce(
|
|
97
|
+
(acc: any, s) => {
|
|
98
|
+
acc[s.imperial] = s
|
|
99
|
+
return acc
|
|
100
|
+
},
|
|
101
|
+
{}
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
export type PassiveDef = {
|
|
105
|
+
tht: boolean
|
|
106
|
+
p: number
|
|
107
|
+
pw?: number
|
|
108
|
+
ph?: number
|
|
109
|
+
metric?: string
|
|
110
|
+
imperial?: string
|
|
111
|
+
w?: number
|
|
112
|
+
h?: number
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const deriveXFromH = (h: number) => 0.079 * h ** 2 + 0.94 * h - 0.009
|
|
116
|
+
const deriveZFromW = (w: number) => 1.09 * w + 0.6
|
|
117
|
+
const deriveGFromW = (w: number) => 0.59 * w - 0.31
|
|
118
|
+
const deriveCFromW = (w: number) => -0.01 * w ** 2 + 0.94 * w + 0.03
|
|
119
|
+
|
|
120
|
+
export const passive = (params: PassiveDef): AnySoupElement[] => {
|
|
121
|
+
let { tht, p, pw, ph, metric, imperial, w, h } = params
|
|
122
|
+
|
|
123
|
+
if (typeof w === "string") w = mm(w)
|
|
124
|
+
if (typeof h === "string") h = mm(h)
|
|
125
|
+
if (typeof p === "string") p = mm(p)
|
|
126
|
+
if (typeof pw === "string") pw = mm(pw)
|
|
127
|
+
if (typeof ph === "string") ph = mm(ph)
|
|
128
|
+
|
|
129
|
+
if (h! > w!) {
|
|
130
|
+
throw new Error(
|
|
131
|
+
"height cannot be greater than width (rotated footprint not yet implemented)"
|
|
132
|
+
)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** standard size */
|
|
136
|
+
let sz: StandardSize | undefined
|
|
137
|
+
if (metric) {
|
|
138
|
+
sz = metricMap[metric]
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (imperial) {
|
|
142
|
+
sz = imperialMap[imperial]
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (!sz && w && h && !pw && !ph) {
|
|
146
|
+
sz = {
|
|
147
|
+
imperial: "custom",
|
|
148
|
+
metric: "custom",
|
|
149
|
+
Z_mm_min: deriveZFromW(w),
|
|
150
|
+
G_mm_min: deriveGFromW(w),
|
|
151
|
+
X_mm_min: deriveXFromH(h),
|
|
152
|
+
C_mm_ref: deriveCFromW(w),
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (sz) {
|
|
157
|
+
w = sz.Z_mm_min
|
|
158
|
+
h = sz.X_mm_min
|
|
159
|
+
p = sz.C_mm_ref
|
|
160
|
+
pw = (sz.Z_mm_min - sz.G_mm_min) / 2
|
|
161
|
+
ph = (sz.Z_mm_min - sz.G_mm_min) / 2
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (pw === undefined) throw new Error(`could not infer pad width`)
|
|
165
|
+
if (ph === undefined) throw new Error(`could not infer pad width`)
|
|
166
|
+
|
|
167
|
+
if (tht) {
|
|
168
|
+
return [
|
|
169
|
+
platedhole(1, -p / 2, 0, pw, (pw * 1) / 0.8),
|
|
170
|
+
platedhole(2, -p / 2, 0, pw, (pw * 1) / 0.8),
|
|
171
|
+
]
|
|
172
|
+
} else {
|
|
173
|
+
return [rectpad(1, -p / 2, 0, pw, ph), rectpad(2, p / 2, 0, pw, ph)]
|
|
174
|
+
}
|
|
175
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { PCBPlatedHole, PCBSMTPad } from "@tscircuit/soup"
|
|
2
|
+
import { mm } from "@tscircuit/mm"
|
|
3
|
+
|
|
4
|
+
export const platedhole = (
|
|
5
|
+
pn: number,
|
|
6
|
+
x: number,
|
|
7
|
+
y: number,
|
|
8
|
+
id: number | string,
|
|
9
|
+
od: number | string
|
|
10
|
+
): PCBPlatedHole => {
|
|
11
|
+
return {
|
|
12
|
+
type: "pcb_plated_hole",
|
|
13
|
+
x,
|
|
14
|
+
y,
|
|
15
|
+
hole_diameter: mm(id),
|
|
16
|
+
outer_diameter: mm(od),
|
|
17
|
+
pcb_port_id: "",
|
|
18
|
+
layers: ["top", "bottom"],
|
|
19
|
+
port_hints: [pn.toString()],
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { PCBSMTPad } from "@tscircuit/soup"
|
|
2
|
+
export const rectpad = (
|
|
3
|
+
pn: number,
|
|
4
|
+
x: number,
|
|
5
|
+
y: number,
|
|
6
|
+
w: number,
|
|
7
|
+
h: number
|
|
8
|
+
): PCBSMTPad => {
|
|
9
|
+
return {
|
|
10
|
+
type: "pcb_smtpad",
|
|
11
|
+
x,
|
|
12
|
+
y,
|
|
13
|
+
width: w,
|
|
14
|
+
height: h,
|
|
15
|
+
layer: "top",
|
|
16
|
+
shape: "rect",
|
|
17
|
+
pcb_smtpad_id: "",
|
|
18
|
+
port_hints: [pn.toString()],
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { z } from "zod"
|
|
2
|
+
import { length, distance } from "@tscircuit/soup"
|
|
3
|
+
import { dim2d } from "./dim-2d"
|
|
4
|
+
import { function_call } from "./function-call"
|
|
5
|
+
|
|
6
|
+
const ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
7
|
+
|
|
8
|
+
export const bga_def = z
|
|
9
|
+
.object({
|
|
10
|
+
num_pins: z.number(),
|
|
11
|
+
grid: dim2d,
|
|
12
|
+
p: distance,
|
|
13
|
+
w: length,
|
|
14
|
+
h: length,
|
|
15
|
+
ball: length.optional().describe("ball diameter"),
|
|
16
|
+
pad: length.optional().describe("pad width/height"),
|
|
17
|
+
|
|
18
|
+
tlorigin: z.boolean().optional(),
|
|
19
|
+
blorigin: z.boolean().optional(),
|
|
20
|
+
trorigin: z.boolean().optional(),
|
|
21
|
+
brorigin: z.boolean().optional(),
|
|
22
|
+
|
|
23
|
+
missing: function_call,
|
|
24
|
+
})
|
|
25
|
+
.transform((a) => {
|
|
26
|
+
let origin: "tl" | "bl" | "tr" | "br" = "tl"
|
|
27
|
+
if (a.blorigin) origin = "bl"
|
|
28
|
+
if (a.trorigin) origin = "tr"
|
|
29
|
+
if (a.brorigin) origin = "br"
|
|
30
|
+
|
|
31
|
+
if (a.missing) {
|
|
32
|
+
a.missing = a.missing.map((s) => {
|
|
33
|
+
if (typeof s === "number") return s
|
|
34
|
+
if (s === "center") return "center"
|
|
35
|
+
if (s === "topleft") return "topleft"
|
|
36
|
+
const m = s.match(/([A-Z]+)(\d+)/)
|
|
37
|
+
if (!m) return s
|
|
38
|
+
let Y = ALPHABET.indexOf(m[1])
|
|
39
|
+
let X = parseInt(m[2]) - 1
|
|
40
|
+
return Y * a.grid.x + X + 1
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return { ...a, origin }
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
export type BgaDefInput = z.input<typeof bga_def>
|
|
48
|
+
export type BgaDef = z.infer<typeof bga_def>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { z } from "zod"
|
|
2
|
+
|
|
3
|
+
export const dim2d = z
|
|
4
|
+
.string()
|
|
5
|
+
.transform((a) => {
|
|
6
|
+
const [x, y] = a.split(/[x ]/)
|
|
7
|
+
return {
|
|
8
|
+
x: parseFloat(x),
|
|
9
|
+
y: parseFloat(y),
|
|
10
|
+
}
|
|
11
|
+
})
|
|
12
|
+
.pipe(
|
|
13
|
+
z.object({
|
|
14
|
+
x: z.number(),
|
|
15
|
+
y: z.number(),
|
|
16
|
+
})
|
|
17
|
+
)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { z } from "zod"
|
|
2
|
+
|
|
3
|
+
export const function_call = z
|
|
4
|
+
.string()
|
|
5
|
+
.or(z.array(z.any()))
|
|
6
|
+
.transform((a) => {
|
|
7
|
+
if (Array.isArray(a)) return a
|
|
8
|
+
if (a.startsWith("(") && a.endsWith(")")) {
|
|
9
|
+
a = a.slice(1, -1)
|
|
10
|
+
}
|
|
11
|
+
return a.split(",").map((v) => {
|
|
12
|
+
const numVal = Number(v)
|
|
13
|
+
return isNaN(numVal) ? v : numVal
|
|
14
|
+
})
|
|
15
|
+
})
|
|
16
|
+
.pipe(z.array(z.string().or(z.number())))
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./footprinter"
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import test from "ava"
|
|
2
|
+
import { fp } from "../src/footprinter"
|
|
3
|
+
import { AnySoupElement } from "@tscircuit/soup"
|
|
4
|
+
import { toPinPositionString } from "./fixtures"
|
|
5
|
+
|
|
6
|
+
test("bga footprint", (t) => {
|
|
7
|
+
const soup = fp()
|
|
8
|
+
.bga(8)
|
|
9
|
+
.w("4mm")
|
|
10
|
+
.h("4mm")
|
|
11
|
+
.grid("3x3")
|
|
12
|
+
.missing("center")
|
|
13
|
+
.p(1)
|
|
14
|
+
.soup()
|
|
15
|
+
// 16pins, 4mm x 4mm, 8x8 grid, 1.27mm pitch
|
|
16
|
+
const ps = toPinPositionString(soup)
|
|
17
|
+
|
|
18
|
+
t.is(
|
|
19
|
+
ps,
|
|
20
|
+
`
|
|
21
|
+
1 : -1.00 1.00
|
|
22
|
+
2 : 0.00 1.00
|
|
23
|
+
3 : 1.00 1.00
|
|
24
|
+
4 : -1.00 0.00
|
|
25
|
+
5 : 1.00 0.00
|
|
26
|
+
6 : -1.00 -1.00
|
|
27
|
+
7 : 0.00 -1.00
|
|
28
|
+
8 : 1.00 -1.00
|
|
29
|
+
`.trim()
|
|
30
|
+
)
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
test("bga7_w8_h8_grid3x3_p1_missing(center,B1)", (t) => {
|
|
34
|
+
const soup = fp.string("bga7_w8_h8_grid3x3_p1_missing(center,B1)").soup()
|
|
35
|
+
const ps = toPinPositionString(soup)
|
|
36
|
+
|
|
37
|
+
t.is(
|
|
38
|
+
ps,
|
|
39
|
+
`
|
|
40
|
+
1 : -1.00 1.00
|
|
41
|
+
2 : 0.00 1.00
|
|
42
|
+
3 : 1.00 1.00
|
|
43
|
+
4 : 1.00 0.00
|
|
44
|
+
5 : -1.00 -1.00
|
|
45
|
+
6 : 0.00 -1.00
|
|
46
|
+
7 : 1.00 -1.00
|
|
47
|
+
`.trim()
|
|
48
|
+
)
|
|
49
|
+
})
|