@protoboardai/ext-tscircuit 0.0.0-stage → 0.2.0
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/LICENSE +202 -0
- package/NOTICE +59 -0
- package/README.md +493 -2
- package/evals/announce-pcb-fallback/case.ts +67 -0
- package/evals/catch-swapped-nets/case.ts +69 -0
- package/evals/generate-imu-board/case.ts +60 -0
- package/evals/locked-board-fix-upstream/case.ts +70 -0
- package/lib/checks.ts +464 -0
- package/lib/config.ts +384 -0
- package/lib/fingerprint.ts +144 -0
- package/lib/footprints.ts +370 -0
- package/lib/kicad/cli.ts +93 -0
- package/lib/kicad/drc.ts +95 -0
- package/lib/kicad/fields.ts +65 -0
- package/lib/kicad/handoff.ts +112 -0
- package/lib/kicad/netlists.ts +311 -0
- package/lib/kicad/probe.cjs +21 -0
- package/lib/kicad/project.ts +594 -0
- package/lib/kicad/refdes.ts +130 -0
- package/lib/kicad/schematic.ts +624 -0
- package/lib/kicad/sexpr.ts +117 -0
- package/lib/land-geometry.ts +177 -0
- package/lib/land-patterns.ts +706 -0
- package/lib/layout.ts +236 -0
- package/lib/models.ts +284 -0
- package/lib/native.ts +199 -0
- package/lib/outputs.ts +383 -0
- package/lib/paste.ts +69 -0
- package/lib/probe.cjs +31 -0
- package/lib/runtime.ts +185 -0
- package/lib/silk.ts +141 -0
- package/lib/source.ts +472 -0
- package/lib/stepfile.ts +210 -0
- package/lib/suppliers.ts +39 -0
- package/ops/circuit.ts +270 -0
- package/ops/kicad.ts +636 -0
- package/ops/package.ts +156 -0
- package/package.json +70 -4
- package/protoboard-extension.json +313 -0
- package/skills/circuit-board/SKILL.md +494 -0
- package/skills/circuit-board/references/pcb-workflow.md +87 -0
- package/skills/kicad-cli/SKILL.md +210 -0
- package/skills/kicad-layout-review/SKILL.md +275 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reference designators for KiCad, stable between exports
|
|
3
|
+
* (docs/specs/pcb-kicad-handoff.md §2.1). Assigned once per UHD instance path
|
|
4
|
+
* and recorded in `tscircuit/refdes.json`, so a re-export (and KiCad's Update
|
|
5
|
+
* PCB from Schematic) keeps every unchanged part's designator and placement.
|
|
6
|
+
*
|
|
7
|
+
* 1. an instance already recorded keeps its designator
|
|
8
|
+
* 2. moved: a new instance whose last path segment is that of exactly one
|
|
9
|
+
* recorded instance no longer on the board, with a designator of its
|
|
10
|
+
* kind, takes it over (a part moved into or out of a sub-module keeps
|
|
11
|
+
* its designator; docs/specs/nested-boards.md §6)
|
|
12
|
+
* 3. a new instance whose id (the last segment of its path: `led1/r1` reads
|
|
13
|
+
* `r1`) reads as a designator with its kind's prefix (`r3`, `U1`) gets
|
|
14
|
+
* it upper-cased, when free
|
|
15
|
+
* 4. otherwise the kind's prefix and the lowest free number, new instances
|
|
16
|
+
* taken in instance-path order
|
|
17
|
+
*
|
|
18
|
+
* A removed instance stays in the record, so its designator is not reused.
|
|
19
|
+
* Designators are unique over the whole board, sub-modules included.
|
|
20
|
+
*/
|
|
21
|
+
import type { BoardComponent, BoardComponentKind } from "@protoboardai/core";
|
|
22
|
+
|
|
23
|
+
export const REFDES_SCHEMA = "protoboard.refdes/v0";
|
|
24
|
+
|
|
25
|
+
export interface RefdesRecord {
|
|
26
|
+
schema: typeof REFDES_SCHEMA;
|
|
27
|
+
/** instance path → designator */
|
|
28
|
+
assigned: Record<string, string>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const PREFIX: Record<BoardComponentKind, string> = {
|
|
32
|
+
resistor: "R",
|
|
33
|
+
capacitor: "C",
|
|
34
|
+
inductor: "L",
|
|
35
|
+
ferrite_bead: "FB",
|
|
36
|
+
connector: "J",
|
|
37
|
+
switch: "SW",
|
|
38
|
+
ic: "U",
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The conventional prefix for a part the board netlist files under `ic`
|
|
43
|
+
* (IEEE 315 / IPC usage): diodes and LEDs D, transistors Q, crystals and
|
|
44
|
+
* resonators Y, fuses F, batteries BT, buzzers and speakers LS. Read from the
|
|
45
|
+
* part's UHD categories: an integrated circuit (`component.ic…`) stays U
|
|
46
|
+
* whatever its name says (an LED driver, a battery charger, a protection IC).
|
|
47
|
+
* Only a part with no categories falls back to its name, never its id (an id
|
|
48
|
+
* starts with the manufacturer: "Diodes Inc." makes MOSFETs). Undefined keeps
|
|
49
|
+
* the kind's prefix.
|
|
50
|
+
*/
|
|
51
|
+
export function prefixFromPart(def: { id: string; name?: string; categories?: string[] } | undefined): string | undefined {
|
|
52
|
+
if (!def) return undefined;
|
|
53
|
+
const cats = (def.categories ?? []).map((c) => c.toLowerCase());
|
|
54
|
+
const has = (re: RegExp) => cats.some((c) => re.test(c));
|
|
55
|
+
if (cats.length) {
|
|
56
|
+
if (has(/(^|\.)ic(\.|$)/)) return undefined;
|
|
57
|
+
if (has(/(^|\.)transistors?(\.|$)|mosfet|bjt/)) return "Q";
|
|
58
|
+
if (has(/(^|\.)(diodes?|leds?|tvs|zener|rectifiers?)(\.|$)/)) return "D";
|
|
59
|
+
if (has(/(^|\.)(crystals?|resonators?|oscillators?)(\.|$)/)) return "Y";
|
|
60
|
+
if (has(/(^|\.)fuses?(\.|$)/)) return "F";
|
|
61
|
+
if (has(/(^|\.)(batter(y|ies)|cells?)(\.|$)/) && !has(/charger/)) return "BT";
|
|
62
|
+
if (has(/(^|\.)(buzzers?|speakers?)(\.|$)/)) return "LS";
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
const name = (def.name ?? "").toLowerCase();
|
|
66
|
+
if (!name || /\b(ic|driver|controller|charger|regulator|converter|protection|monitor|gauge|module)\b/.test(name)) return undefined;
|
|
67
|
+
if (/\b(transistor|mosfet|bjt|jfet|igbt)s?\b/.test(name)) return "Q";
|
|
68
|
+
if (/\b(led|diode|zener|schottky|tvs|rectifier)s?\b/.test(name)) return "D";
|
|
69
|
+
if (/\b(crystal|resonator|oscillator)s?\b/.test(name)) return "Y";
|
|
70
|
+
if (/\b(fuse|polyfuse|ptc)s?\b/.test(name)) return "F";
|
|
71
|
+
if (/\bbatter(y|ies)\b/.test(name)) return "BT";
|
|
72
|
+
if (/\b(buzzer|speaker)s?\b/.test(name)) return "LS";
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export const refdesFile = (native: string) => `${native}/refdes.json`;
|
|
77
|
+
|
|
78
|
+
export function emptyRefdes(): RefdesRecord {
|
|
79
|
+
return { schema: REFDES_SCHEMA, assigned: {} };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Designators for every component, extending `record` (returned as a new record) for the ones it lacks. */
|
|
83
|
+
export function assignRefdes(components: Pick<BoardComponent, "instance" | "kind">[], record: RefdesRecord = emptyRefdes(), prefixOf: (c: Pick<BoardComponent, "instance" | "kind">) => string | undefined = () => undefined): { record: RefdesRecord; added: string[] } {
|
|
84
|
+
const assigned = { ...record.assigned };
|
|
85
|
+
const taken = new Set(Object.values(assigned));
|
|
86
|
+
const fresh = components.filter((c) => !assigned[c.instance]).sort((a, b) => a.instance.localeCompare(b.instance));
|
|
87
|
+
const leaf = (path: string) => path.slice(path.lastIndexOf("/") + 1);
|
|
88
|
+
// pass 0: a part that moved (same id, another path) keeps its designator, when the match is unambiguous
|
|
89
|
+
const present = new Set(components.map((c) => c.instance));
|
|
90
|
+
const gone = Object.keys(assigned).filter((i) => !present.has(i));
|
|
91
|
+
for (const c of fresh) {
|
|
92
|
+
const prefix = (c.kind === "ic" ? prefixOf(c) : undefined) ?? PREFIX[c.kind] ?? "U";
|
|
93
|
+
const was = gone.filter((g) => leaf(g) === leaf(c.instance) && new RegExp(`^${prefix}\\d+$`).test(assigned[g]));
|
|
94
|
+
if (was.length !== 1 || fresh.filter((f) => leaf(f.instance) === leaf(c.instance)).length !== 1) continue;
|
|
95
|
+
assigned[c.instance] = assigned[was[0]];
|
|
96
|
+
delete assigned[was[0]];
|
|
97
|
+
}
|
|
98
|
+
// pass 1: ids that already read as designators of their kind keep them
|
|
99
|
+
for (const c of fresh) {
|
|
100
|
+
if (assigned[c.instance]) continue;
|
|
101
|
+
const prefix = (c.kind === "ic" ? prefixOf(c) : undefined) ?? PREFIX[c.kind] ?? "U";
|
|
102
|
+
const m = /^([a-z]+)(\d+)$/i.exec(leaf(c.instance));
|
|
103
|
+
const want = m && m[1].toUpperCase() === prefix ? `${prefix}${Number(m[2])}` : undefined;
|
|
104
|
+
if (want && Number(m![2]) > 0 && !taken.has(want)) {
|
|
105
|
+
assigned[c.instance] = want;
|
|
106
|
+
taken.add(want);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// pass 2: the lowest free number of the kind's prefix
|
|
110
|
+
for (const c of fresh) {
|
|
111
|
+
if (assigned[c.instance]) continue;
|
|
112
|
+
const prefix = (c.kind === "ic" ? prefixOf(c) : undefined) ?? PREFIX[c.kind] ?? "U";
|
|
113
|
+
let n = 1;
|
|
114
|
+
while (taken.has(`${prefix}${n}`)) n++;
|
|
115
|
+
assigned[c.instance] = `${prefix}${n}`;
|
|
116
|
+
taken.add(`${prefix}${n}`);
|
|
117
|
+
}
|
|
118
|
+
const sorted = Object.fromEntries(Object.entries(assigned).sort(([a], [b]) => a.localeCompare(b)));
|
|
119
|
+
return { record: { schema: REFDES_SCHEMA, assigned: sorted }, added: fresh.map((c) => c.instance) };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function parseRefdes(text: string | undefined): RefdesRecord | undefined {
|
|
123
|
+
if (!text) return undefined;
|
|
124
|
+
try {
|
|
125
|
+
const r = JSON.parse(text) as RefdesRecord;
|
|
126
|
+
return r?.schema === REFDES_SCHEMA && r.assigned && typeof r.assigned === "object" ? r : undefined;
|
|
127
|
+
} catch {
|
|
128
|
+
return undefined;
|
|
129
|
+
}
|
|
130
|
+
}
|