@tscircuit/footprinter 0.0.22 → 0.0.24
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 +4 -1
- package/.github/workflows/release.yml +0 -19
- package/ava.config.js +0 -5
- package/biome.json +0 -15
- package/src/fn/bga.ts +0 -138
- package/src/fn/cap.ts +0 -7
- package/src/fn/diode.ts +0 -8
- package/src/fn/dip.ts +0 -118
- package/src/fn/led.ts +0 -6
- package/src/fn/qfn.ts +0 -10
- package/src/fn/qfp.ts +0 -19
- package/src/fn/quad.ts +0 -285
- package/src/fn/res.ts +0 -6
- package/src/fn/soic.ts +0 -106
- package/src/footprinter.ts +0 -128
- package/src/helpers/get-quad-pin-map.ts +0 -106
- package/src/helpers/is-not-null.ts +0 -3
- package/src/helpers/passive-fn.ts +0 -175
- package/src/helpers/platedhole.ts +0 -21
- package/src/helpers/rectpad.ts +0 -22
- package/src/helpers/u-curve.ts +0 -6
- package/src/helpers/zod/ALPHABET.ts +0 -1
- package/src/helpers/zod/dim-2d.ts +0 -17
- package/src/helpers/zod/function-call.ts +0 -16
- package/src/helpers/zod/now-defined.ts +0 -2
- package/src/helpers/zod/pin-order-specifier.ts +0 -13
- package/src/index.ts +0 -1
- package/tests/bga.test.ts +0 -49
- package/tests/cap.test.ts +0 -30
- package/tests/dip.test.ts +0 -45
- package/tests/fixtures/get-test-fixture.ts +0 -20
- package/tests/fixtures/index.ts +0 -32
- package/tests/qfn.test.ts +0 -10
- package/tests/qfp.test.ts +0 -18
- package/tests/quad.test.ts +0 -24
- package/tests/res.test.ts +0 -11
- package/tests/slop/slop1.test.ts +0 -46
- package/tests/soic.test.ts +0 -11
- package/tsconfig.json +0 -26
package/package.json
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tscircuit/footprinter",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.24",
|
|
5
5
|
"description": "",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
7
10
|
"scripts": {
|
|
8
11
|
"test": "FULL_RUN=1 ava",
|
|
9
12
|
"build": "tsup ./src/index.ts --dts --sourcemap"
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
name: Publish to npm
|
|
2
|
-
on:
|
|
3
|
-
push:
|
|
4
|
-
branches:
|
|
5
|
-
- main
|
|
6
|
-
jobs:
|
|
7
|
-
publish:
|
|
8
|
-
runs-on: ubuntu-latest
|
|
9
|
-
steps:
|
|
10
|
-
- uses: actions/checkout@v3
|
|
11
|
-
- uses: actions/setup-node@v3
|
|
12
|
-
with:
|
|
13
|
-
node-version: 20
|
|
14
|
-
registry-url: https://registry.npmjs.org/
|
|
15
|
-
- run: npm install -g pver
|
|
16
|
-
- run: npm ci
|
|
17
|
-
- run: pver release
|
|
18
|
-
env:
|
|
19
|
-
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
package/ava.config.js
DELETED
package/biome.json
DELETED
package/src/fn/bga.ts
DELETED
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
import type { AnySoupElement, PCBSMTPad } from "@tscircuit/soup"
|
|
2
|
-
import { rectpad } from "../helpers/rectpad"
|
|
3
|
-
import { ALPHABET } from "../helpers/zod/ALPHABET"
|
|
4
|
-
import { z } from "zod"
|
|
5
|
-
import { length, distance } from "@tscircuit/soup"
|
|
6
|
-
import { dim2d } from "src/helpers/zod/dim-2d"
|
|
7
|
-
import { function_call } from "src/helpers/zod/function-call"
|
|
8
|
-
import type { NowDefined } from "src/helpers/zod/now-defined"
|
|
9
|
-
|
|
10
|
-
export const bga_def = z
|
|
11
|
-
.object({
|
|
12
|
-
num_pins: z.number(),
|
|
13
|
-
grid: dim2d.optional(),
|
|
14
|
-
p: distance.default("0.8mm"),
|
|
15
|
-
w: length.optional(),
|
|
16
|
-
h: length.optional(),
|
|
17
|
-
ball: length.optional().describe("ball diameter"),
|
|
18
|
-
pad: length.optional().describe("pad width/height"),
|
|
19
|
-
|
|
20
|
-
tlorigin: z.boolean().optional(),
|
|
21
|
-
blorigin: z.boolean().optional(),
|
|
22
|
-
trorigin: z.boolean().optional(),
|
|
23
|
-
brorigin: z.boolean().optional(),
|
|
24
|
-
|
|
25
|
-
missing: function_call.default([]),
|
|
26
|
-
})
|
|
27
|
-
.transform((a) => {
|
|
28
|
-
let origin: "tl" | "bl" | "tr" | "br" = "tl"
|
|
29
|
-
if (a.blorigin) origin = "bl"
|
|
30
|
-
if (a.trorigin) origin = "tr"
|
|
31
|
-
if (a.brorigin) origin = "br"
|
|
32
|
-
|
|
33
|
-
if (!a.grid) {
|
|
34
|
-
// find the largest square for the number of pins
|
|
35
|
-
const largest_square = Math.ceil(Math.sqrt(a.num_pins))
|
|
36
|
-
a.grid = { x: largest_square, y: largest_square }
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
if (a.missing) {
|
|
40
|
-
a.missing = a.missing.map((s) => {
|
|
41
|
-
if (typeof s === "number") return s
|
|
42
|
-
if (s === "center") return "center"
|
|
43
|
-
if (s === "topleft") return "topleft"
|
|
44
|
-
const m = s.match(/([A-Z]+)(\d+)/)
|
|
45
|
-
if (!m) return s
|
|
46
|
-
let Y = ALPHABET.indexOf(m[1]!)
|
|
47
|
-
let X = parseInt(m[2]!) - 1
|
|
48
|
-
return Y * a.grid!.x + X + 1
|
|
49
|
-
})
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const new_def = { ...a, origin }
|
|
53
|
-
|
|
54
|
-
return new_def as NowDefined<typeof new_def, "w" | "h" | "grid">
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
export type BgaDefInput = z.input<typeof bga_def>
|
|
58
|
-
export type BgaDef = z.infer<typeof bga_def>
|
|
59
|
-
|
|
60
|
-
export const bga = (params: BgaDefInput): AnySoupElement[] => {
|
|
61
|
-
const bga_params = bga_def.parse(params)
|
|
62
|
-
let { num_pins, grid, p, w, h, ball, pad, missing } = bga_params
|
|
63
|
-
|
|
64
|
-
ball ??= (0.75 / 1.27) * p
|
|
65
|
-
|
|
66
|
-
pad ??= ball * 0.8
|
|
67
|
-
|
|
68
|
-
const pads: PCBSMTPad[] = []
|
|
69
|
-
|
|
70
|
-
const missing_pin_nums = (missing ?? []).filter((a) => typeof a === "number")
|
|
71
|
-
const num_pins_missing = grid.x * grid.y - num_pins
|
|
72
|
-
|
|
73
|
-
if (missing.length === 0 && num_pins_missing > 0) {
|
|
74
|
-
// No missing pins specified, let's see if a squared center works
|
|
75
|
-
// if num_pins_missing is a square
|
|
76
|
-
if (Math.sqrt(num_pins_missing) % 1 === 0) {
|
|
77
|
-
missing.push("center")
|
|
78
|
-
} else if (num_pins_missing === 1) {
|
|
79
|
-
missing.push("topleft")
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
if (missing?.includes("center")) {
|
|
84
|
-
// Find the largest square that's square is less than
|
|
85
|
-
// the number of missing pins
|
|
86
|
-
const square_size = Math.floor(Math.sqrt(num_pins_missing))
|
|
87
|
-
|
|
88
|
-
// Find the top left coordinate of the inner square, keep
|
|
89
|
-
// in mind the full grid size is grid.x x grid.y
|
|
90
|
-
const inner_square_x = Math.floor((grid.x - square_size) / 2)
|
|
91
|
-
const inner_square_y = Math.floor((grid.y - square_size) / 2)
|
|
92
|
-
|
|
93
|
-
// Add all the missing square pin numbers to missing_pin_nums
|
|
94
|
-
for (let y = inner_square_y; y < inner_square_y + square_size; y++) {
|
|
95
|
-
for (let x = inner_square_x; x < inner_square_x + square_size; x++) {
|
|
96
|
-
missing_pin_nums.push(y * grid.x + x + 1)
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
if (missing?.includes("topleft")) {
|
|
102
|
-
missing_pin_nums.push(1)
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
if (num_pins_missing !== missing_pin_nums.length) {
|
|
106
|
-
throw new Error(
|
|
107
|
-
`not able to create bga component, unable to determine missing pins (try specifying them with "missing+1+2+..."\n\n${JSON.stringify(
|
|
108
|
-
bga_params,
|
|
109
|
-
null,
|
|
110
|
-
" "
|
|
111
|
-
)}`
|
|
112
|
-
)
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
const missing_pin_nums_set = new Set(missing_pin_nums)
|
|
116
|
-
|
|
117
|
-
let missing_pins_passed = 0
|
|
118
|
-
for (let y = 0; y < grid.y; y++) {
|
|
119
|
-
for (let x = 0; x < grid.x; x++) {
|
|
120
|
-
let pin_num = y * grid.x + x + 1
|
|
121
|
-
if (missing_pin_nums_set.has(pin_num)) {
|
|
122
|
-
missing_pins_passed++
|
|
123
|
-
continue
|
|
124
|
-
}
|
|
125
|
-
pin_num -= missing_pins_passed
|
|
126
|
-
|
|
127
|
-
const pad_x = (x - (grid.x - 1) / 2) * p
|
|
128
|
-
const pad_y = -(y - (grid.y - 1) / 2) * p
|
|
129
|
-
|
|
130
|
-
// TODO handle >26 rows
|
|
131
|
-
pads.push(
|
|
132
|
-
rectpad([pin_num, `${ALPHABET[y]}${x + 1}`], pad_x, pad_y, pad, pad)
|
|
133
|
-
)
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
return [...pads]
|
|
138
|
-
}
|
package/src/fn/cap.ts
DELETED
package/src/fn/diode.ts
DELETED
package/src/fn/dip.ts
DELETED
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
import type { AnySoupElement, PcbSilkscreenPath } from "@tscircuit/soup"
|
|
2
|
-
import { u_curve } from "../helpers/u-curve"
|
|
3
|
-
import { platedhole } from "../helpers/platedhole"
|
|
4
|
-
import { z } from "zod"
|
|
5
|
-
import { length } from "@tscircuit/soup"
|
|
6
|
-
import type { NowDefined } from "../helpers/zod/now-defined"
|
|
7
|
-
|
|
8
|
-
const dip_def = z
|
|
9
|
-
.object({
|
|
10
|
-
dip: z.literal(true),
|
|
11
|
-
num_pins: z.number(),
|
|
12
|
-
wide: z.boolean().optional(),
|
|
13
|
-
narrow: z.boolean().optional(),
|
|
14
|
-
w: length.optional(),
|
|
15
|
-
p: length.default(length.parse("2.54mm")),
|
|
16
|
-
id: length.optional(),
|
|
17
|
-
od: length.optional(),
|
|
18
|
-
})
|
|
19
|
-
.transform((v) => {
|
|
20
|
-
// Default inner diameter and outer diameter
|
|
21
|
-
if (!v.id && !v.od) {
|
|
22
|
-
v.id = length.parse("1.0mm")
|
|
23
|
-
v.od = length.parse("1.2mm")
|
|
24
|
-
} else if (!v.id) {
|
|
25
|
-
v.id = v.od! * (1.0 / 1.2)
|
|
26
|
-
} else if (!v.od) {
|
|
27
|
-
v.od = v.id! * (1.2 / 1.0)
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Default width (TODO high pin counts should probably be wide?)
|
|
31
|
-
if (!v.w) {
|
|
32
|
-
if (v.wide) {
|
|
33
|
-
v.w = length.parse("600mil")
|
|
34
|
-
} else if (v.narrow) {
|
|
35
|
-
v.w = length.parse("300mil")
|
|
36
|
-
} else {
|
|
37
|
-
v.w = length.parse("300mil")
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
return v as NowDefined<typeof v, "w" | "p" | "id" | "od">
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
export const getCcwDipCoords = (
|
|
44
|
-
pinCount: number,
|
|
45
|
-
pn: number,
|
|
46
|
-
w: number,
|
|
47
|
-
p: number
|
|
48
|
-
) => {
|
|
49
|
-
/** pin height */
|
|
50
|
-
const ph = pinCount / 2
|
|
51
|
-
const isLeft = pn <= ph
|
|
52
|
-
|
|
53
|
-
/** Number of gaps between pins on each side, e.g. 4 pins = 3 spaces */
|
|
54
|
-
const leftPinGaps = ph - 1
|
|
55
|
-
|
|
56
|
-
/** gap size (pitch) */
|
|
57
|
-
const gs = p
|
|
58
|
-
|
|
59
|
-
const h = gs * leftPinGaps
|
|
60
|
-
|
|
61
|
-
if (isLeft) {
|
|
62
|
-
// The y position starts at h/2, then goes down by gap size
|
|
63
|
-
// for each pin
|
|
64
|
-
return { x: -w / 2, y: h / 2 - (pn - 1) * gs }
|
|
65
|
-
} else {
|
|
66
|
-
// The y position starts at -h/2, then goes up by gap size
|
|
67
|
-
return { x: w / 2, y: -h / 2 + (pn - ph - 1) * gs }
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Returns the plated holes for a DIP package.
|
|
73
|
-
*/
|
|
74
|
-
export const dip = (raw_params: {
|
|
75
|
-
dip: true
|
|
76
|
-
num_pins: number
|
|
77
|
-
w: number
|
|
78
|
-
p?: number
|
|
79
|
-
id?: string | number
|
|
80
|
-
od?: string | number
|
|
81
|
-
}): AnySoupElement[] => {
|
|
82
|
-
const params = dip_def.parse(raw_params)
|
|
83
|
-
const platedHoles: AnySoupElement[] = []
|
|
84
|
-
for (let i = 0; i < params.num_pins; i++) {
|
|
85
|
-
const { x, y } = getCcwDipCoords(
|
|
86
|
-
params.num_pins,
|
|
87
|
-
i + 1,
|
|
88
|
-
params.w,
|
|
89
|
-
params.p ?? 2.54
|
|
90
|
-
)
|
|
91
|
-
platedHoles.push(
|
|
92
|
-
platedhole(i + 1, x, y, params.id ?? "0.8mm", params.od ?? "1mm")
|
|
93
|
-
)
|
|
94
|
-
}
|
|
95
|
-
/** silkscreen width */
|
|
96
|
-
const sw = params.w - params.od - 0.4
|
|
97
|
-
const sh = (params.num_pins / 2 - 1) * params.p + params.od + 0.4
|
|
98
|
-
const silkscreenBorder: PcbSilkscreenPath = {
|
|
99
|
-
layer: "top",
|
|
100
|
-
pcb_component_id: "",
|
|
101
|
-
pcb_silkscreen_path_id: "silkscreen_path_1",
|
|
102
|
-
route: [
|
|
103
|
-
{ x: -sw / 2, y: -sh / 2 },
|
|
104
|
-
{ x: -sw / 2, y: sh / 2 },
|
|
105
|
-
// Little U shape at the top
|
|
106
|
-
...u_curve.map(({ x, y }) => ({
|
|
107
|
-
x: (x * sw) / 6,
|
|
108
|
-
y: (y * sw) / 6 + sh / 2,
|
|
109
|
-
})),
|
|
110
|
-
{ x: sw / 2, y: sh / 2 },
|
|
111
|
-
{ x: sw / 2, y: -sh / 2 },
|
|
112
|
-
{ x: -sw / 2, y: -sh / 2 },
|
|
113
|
-
],
|
|
114
|
-
type: "pcb_silkscreen_path",
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
return [...platedHoles, silkscreenBorder]
|
|
118
|
-
}
|
package/src/fn/led.ts
DELETED
package/src/fn/qfn.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
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
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
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
DELETED
|
@@ -1,285 +0,0 @@
|
|
|
1
|
-
import type { AnySoupElement, PcbSilkscreenPath } from "@tscircuit/soup"
|
|
2
|
-
import { z } from "zod"
|
|
3
|
-
import { length } from "@tscircuit/soup"
|
|
4
|
-
import type { NowDefined } from "../helpers/zod/now-defined"
|
|
5
|
-
import { rectpad } from "../helpers/rectpad"
|
|
6
|
-
import { pin_order_specifier } from "src/helpers/zod/pin-order-specifier"
|
|
7
|
-
import { getQuadPinMap } from "src/helpers/get-quad-pin-map"
|
|
8
|
-
import { dim2d } from "src/helpers/zod/dim-2d"
|
|
9
|
-
|
|
10
|
-
export const base_quad_def = z.object({
|
|
11
|
-
cc: z.literal(true).optional(),
|
|
12
|
-
ccw: z.literal(true).optional(),
|
|
13
|
-
startingpin: z
|
|
14
|
-
.string()
|
|
15
|
-
.or(z.array(pin_order_specifier))
|
|
16
|
-
.transform((a) => (typeof a === "string" ? a.slice(1, -1).split(",") : a))
|
|
17
|
-
.pipe(z.array(pin_order_specifier))
|
|
18
|
-
.optional(),
|
|
19
|
-
num_pins: z.number(),
|
|
20
|
-
w: length.optional(),
|
|
21
|
-
h: length.optional(),
|
|
22
|
-
p: length.default(length.parse("0.5mm")),
|
|
23
|
-
pw: length.optional(),
|
|
24
|
-
pl: length.optional(),
|
|
25
|
-
thermalpad: z.union([z.literal(true), dim2d]).optional(),
|
|
26
|
-
legsoutside: z.boolean().optional(),
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
export const quadTransform = <T extends z.infer<typeof base_quad_def>>(
|
|
30
|
-
v: T
|
|
31
|
-
) => {
|
|
32
|
-
if (v.w && !v.h) {
|
|
33
|
-
v.h = v.w
|
|
34
|
-
} else if (!v.w && v.h) {
|
|
35
|
-
v.w = v.h
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const side_pin_count = v.num_pins / 4
|
|
39
|
-
|
|
40
|
-
if (!v.p && !v.pw && !v.pl && v.w) {
|
|
41
|
-
// HACK: This is wayyy underspecified
|
|
42
|
-
const approx_pin_size_of_side = side_pin_count + 4
|
|
43
|
-
v.p = v.w / approx_pin_size_of_side
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (!v.p && v.w && v.h && v.pw && v.pl) {
|
|
47
|
-
// HACK: This is wayyy underspecified
|
|
48
|
-
v.p = (v.w - v.pl * 2) / (side_pin_count - 1)
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if (v.p && !v.pw && !v.pl) {
|
|
52
|
-
v.pw = v.p / 2
|
|
53
|
-
v.pl = v.p / 2
|
|
54
|
-
} else if (!v.pw) {
|
|
55
|
-
v.pw = v.pl! * (0.6 / 1.0)
|
|
56
|
-
} else if (!v.pl) {
|
|
57
|
-
v.pl = v.pw! * (1.0 / 0.6)
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
return v as NowDefined<T, "w" | "h" | "p" | "pw" | "pl">
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export const quad_def = base_quad_def.transform(quadTransform)
|
|
64
|
-
|
|
65
|
-
const SIDES_CCW = ["left", "bottom", "right", "top"] as const
|
|
66
|
-
|
|
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
|
|
73
|
-
pl: number // length of the pin
|
|
74
|
-
legsoutside?: boolean
|
|
75
|
-
}) => {
|
|
76
|
-
const { pin_count, pn, w, h, p, pl, legsoutside } = params
|
|
77
|
-
const sidePinCount = pin_count / 4
|
|
78
|
-
const side = SIDES_CCW[Math.floor((pn - 1) / sidePinCount)]
|
|
79
|
-
const pos = (pn - 1) % sidePinCount
|
|
80
|
-
|
|
81
|
-
/** inner box width */
|
|
82
|
-
const ibw = p * (sidePinCount - 1)
|
|
83
|
-
/** inner box height */
|
|
84
|
-
const ibh = p * (sidePinCount - 1)
|
|
85
|
-
|
|
86
|
-
/** pad center distance from edge (negative is inside, positive is outside) */
|
|
87
|
-
const pcdfe = legsoutside ? pl / 2 : -pl / 2
|
|
88
|
-
|
|
89
|
-
switch (side) {
|
|
90
|
-
case "left":
|
|
91
|
-
return { x: -w / 2 - pcdfe, y: ibh / 2 - pos * p, o: "vert" }
|
|
92
|
-
case "bottom":
|
|
93
|
-
return { x: -ibw / 2 + pos * p, y: -h / 2 - pcdfe, o: "horz" }
|
|
94
|
-
case "right":
|
|
95
|
-
return { x: w / 2 + pcdfe, y: -ibh / 2 + pos * p, o: "vert" }
|
|
96
|
-
case "top":
|
|
97
|
-
return { x: ibw / 2 - pos * p, y: h / 2 + pcdfe, o: "horz" }
|
|
98
|
-
default:
|
|
99
|
-
throw new Error("Invalid pin number")
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
export const quad = (
|
|
104
|
-
raw_params: z.input<typeof quad_def>
|
|
105
|
-
): AnySoupElement[] => {
|
|
106
|
-
const params = quad_def.parse(raw_params)
|
|
107
|
-
const pads: AnySoupElement[] = []
|
|
108
|
-
const pin_map = getQuadPinMap(params)
|
|
109
|
-
/** Side pin count */
|
|
110
|
-
const spc = params.num_pins / 4
|
|
111
|
-
for (let i = 0; i < params.num_pins; i++) {
|
|
112
|
-
const {
|
|
113
|
-
x,
|
|
114
|
-
y,
|
|
115
|
-
o: orientation,
|
|
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
|
-
})
|
|
125
|
-
|
|
126
|
-
let pw = params.pw
|
|
127
|
-
let pl = params.pl
|
|
128
|
-
if (orientation === "vert") {
|
|
129
|
-
;[pw, pl] = [pl, pw]
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const pn = pin_map[i + 1]!
|
|
133
|
-
pads.push(rectpad(pn, x, y, pw, pl))
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
if (params.thermalpad) {
|
|
137
|
-
if (typeof params.thermalpad === "boolean") {
|
|
138
|
-
const ibw = params.p * (spc - 1) + params.pw
|
|
139
|
-
const ibh = params.p * (spc - 1) + params.pw
|
|
140
|
-
pads.push(rectpad(["thermalpad"], 0, 0, ibw, ibh))
|
|
141
|
-
} else {
|
|
142
|
-
pads.push(
|
|
143
|
-
rectpad(["thermalpad"], 0, 0, params.thermalpad.x, params.thermalpad.y)
|
|
144
|
-
)
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// Silkscreen corners
|
|
149
|
-
const silkscreen_corners: PcbSilkscreenPath[] = []
|
|
150
|
-
for (const [corner, dx, dy] of [
|
|
151
|
-
["top-left", -1, 1],
|
|
152
|
-
["bottom-left", -1, -1],
|
|
153
|
-
["bottom-right", 1, -1],
|
|
154
|
-
["top-right", 1, 1],
|
|
155
|
-
] as const) {
|
|
156
|
-
// const dx = Math.floor(corner_index / 2) * 2 - 1
|
|
157
|
-
// const dy = 1 - (corner_index % 2) * 2
|
|
158
|
-
const corner_x = (params.w / 2) * dx
|
|
159
|
-
const corner_y = (params.h / 2) * dy
|
|
160
|
-
let arrow: "none" | "in1" | "in2" = "none"
|
|
161
|
-
|
|
162
|
-
let arrow_x = corner_x
|
|
163
|
-
let arrow_y = corner_y
|
|
164
|
-
|
|
165
|
-
/** corner size */
|
|
166
|
-
const csz = params.pw * 2
|
|
167
|
-
|
|
168
|
-
if (pin_map[1] === 1 && corner === "top-left") {
|
|
169
|
-
arrow = "in1"
|
|
170
|
-
} else if (pin_map[spc * 4] === 1 && corner === "top-left") {
|
|
171
|
-
arrow = "in2"
|
|
172
|
-
} else if (pin_map[spc * 3 + 1] === 1 && corner === "top-right") {
|
|
173
|
-
arrow = "in2"
|
|
174
|
-
} else if (pin_map[spc * 3] === 1 && corner === "top-right") {
|
|
175
|
-
arrow = "in1"
|
|
176
|
-
} else if (pin_map[spc] === 1 && corner === "bottom-left") {
|
|
177
|
-
arrow = "in1"
|
|
178
|
-
} else if (pin_map[spc + 1] === 1 && corner === "bottom-left") {
|
|
179
|
-
arrow = "in2"
|
|
180
|
-
} else if (pin_map[spc * 2] === 1 && corner === "bottom-right") {
|
|
181
|
-
arrow = "in1"
|
|
182
|
-
} else if (pin_map[spc * 2 + 1] === 1 && corner === "bottom-right") {
|
|
183
|
-
arrow = "in2"
|
|
184
|
-
}
|
|
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) {
|
|
196
|
-
silkscreen_corners.push({
|
|
197
|
-
layer: "top",
|
|
198
|
-
pcb_component_id: "",
|
|
199
|
-
pcb_silkscreen_path_id: `pcb_silkscreen_path_${corner}`,
|
|
200
|
-
route: [
|
|
201
|
-
{
|
|
202
|
-
x: corner_x - csz * dx,
|
|
203
|
-
y: corner_y,
|
|
204
|
-
},
|
|
205
|
-
{
|
|
206
|
-
x: corner_x,
|
|
207
|
-
y: corner_y,
|
|
208
|
-
},
|
|
209
|
-
{
|
|
210
|
-
x: corner_x,
|
|
211
|
-
y: corner_y - csz * dy,
|
|
212
|
-
},
|
|
213
|
-
],
|
|
214
|
-
type: "pcb_silkscreen_path",
|
|
215
|
-
})
|
|
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) {
|
|
221
|
-
silkscreen_corners.push(
|
|
222
|
-
{
|
|
223
|
-
layer: "top",
|
|
224
|
-
pcb_component_id: "",
|
|
225
|
-
pcb_silkscreen_path_id: `pcb_silkscreen_path_${corner}_1`,
|
|
226
|
-
route: [
|
|
227
|
-
{
|
|
228
|
-
x: corner_x - csz * dx,
|
|
229
|
-
y: corner_y,
|
|
230
|
-
},
|
|
231
|
-
{
|
|
232
|
-
x: corner_x - (csz * dx) / 2,
|
|
233
|
-
y: corner_y,
|
|
234
|
-
},
|
|
235
|
-
],
|
|
236
|
-
type: "pcb_silkscreen_path",
|
|
237
|
-
},
|
|
238
|
-
{
|
|
239
|
-
layer: "top",
|
|
240
|
-
pcb_component_id: "",
|
|
241
|
-
pcb_silkscreen_path_id: `pcb_silkscreen_path_${corner}_2`,
|
|
242
|
-
route: [
|
|
243
|
-
{
|
|
244
|
-
x: corner_x,
|
|
245
|
-
y: corner_y - (csz * dy) / 2,
|
|
246
|
-
},
|
|
247
|
-
{
|
|
248
|
-
x: corner_x,
|
|
249
|
-
y: corner_y - csz * dy,
|
|
250
|
-
},
|
|
251
|
-
],
|
|
252
|
-
type: "pcb_silkscreen_path",
|
|
253
|
-
}
|
|
254
|
-
)
|
|
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
|
-
}
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
return [...pads, ...silkscreen_corners]
|
|
285
|
-
}
|