copperhead 0.9.0 → 0.10.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/NOTICE +1 -1
- package/README.md +6 -6
- package/dist/agent/filetools.js +24 -1
- package/dist/agent/filetools.js.map +1 -1
- package/dist/agent/ledger.js +24 -0
- package/dist/agent/ledger.js.map +1 -1
- package/dist/agent/loop.js +29 -58
- package/dist/agent/loop.js.map +1 -1
- package/dist/agent/prompts.js +4 -3
- package/dist/agent/prompts.js.map +1 -1
- package/dist/agent/providers/tool-protocol.js +21 -0
- package/dist/agent/providers/tool-protocol.js.map +1 -1
- package/dist/agent/recovery.js +95 -1
- package/dist/agent/recovery.js.map +1 -1
- package/dist/agent/tools.js +185 -1
- package/dist/agent/tools.js.map +1 -1
- package/dist/agent/transcript.js +2 -0
- package/dist/agent/transcript.js.map +1 -1
- package/dist/cli.js +75 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/check.js +33 -1
- package/dist/commands/check.js.map +1 -1
- package/dist/commands/create.js +177 -25
- package/dist/commands/create.js.map +1 -1
- package/dist/commands/doctor.js +50 -3
- package/dist/commands/doctor.js.map +1 -1
- package/dist/config.js +1 -0
- package/dist/config.js.map +1 -1
- package/dist/kicad/bootstrap.js +24 -3
- package/dist/kicad/bootstrap.js.map +1 -1
- package/dist/kicad/dossier.js +207 -0
- package/dist/kicad/dossier.js.map +1 -0
- package/dist/kicad/draft/draft.js +132 -0
- package/dist/kicad/draft/draft.js.map +1 -0
- package/dist/kicad/draft/engine.js +2389 -0
- package/dist/kicad/draft/engine.js.map +1 -0
- package/dist/kicad/draft/ir.js +368 -0
- package/dist/kicad/draft/ir.js.map +1 -0
- package/dist/kicad/draft/symsource.js +490 -0
- package/dist/kicad/draft/symsource.js.map +1 -0
- package/dist/kicad/emit.js +181 -0
- package/dist/kicad/emit.js.map +1 -0
- package/dist/kicad/fab.js +13 -0
- package/dist/kicad/fab.js.map +1 -1
- package/dist/kicad/legibility.js +561 -0
- package/dist/kicad/legibility.js.map +1 -0
- package/dist/kicad/score.js +261 -0
- package/dist/kicad/score.js.map +1 -0
- package/dist/kicad/sexp.js +239 -6
- package/dist/kicad/sexp.js.map +1 -1
- package/dist/kicad/symlib.js +346 -16
- package/dist/kicad/symlib.js.map +1 -1
- package/dist/memory/bom-table.js +75 -39
- package/dist/memory/bom-table.js.map +1 -1
- package/dist/memory/scaffold.js +6 -0
- package/dist/memory/scaffold.js.map +1 -1
- package/dist/util/redact.js +6 -0
- package/dist/util/redact.js.map +1 -1
- package/package.json +9 -7
- package/src/agent/filetools.ts +26 -1
- package/src/agent/ledger.ts +24 -0
- package/src/agent/loop.ts +28 -61
- package/src/agent/prompts.ts +4 -3
- package/src/agent/providers/tool-protocol.ts +22 -0
- package/src/agent/recovery.ts +94 -1
- package/src/agent/tools.ts +189 -1
- package/src/agent/transcript.ts +6 -0
- package/src/cli.ts +71 -0
- package/src/commands/check.ts +51 -1
- package/src/commands/create.ts +179 -20
- package/src/commands/doctor.ts +51 -3
- package/src/config.ts +24 -0
- package/src/kicad/bootstrap.ts +24 -3
- package/src/kicad/dossier.ts +217 -0
- package/src/kicad/draft/draft.ts +171 -0
- package/src/kicad/draft/engine.ts +2466 -0
- package/src/kicad/draft/ir.ts +416 -0
- package/src/kicad/draft/symsource.ts +535 -0
- package/src/kicad/emit.ts +236 -0
- package/src/kicad/fab.ts +15 -0
- package/src/kicad/legibility.ts +646 -0
- package/src/kicad/score.ts +323 -0
- package/src/kicad/sexp.ts +315 -6
- package/src/kicad/symlib.ts +364 -18
- package/src/memory/bom-table.ts +85 -38
- package/src/memory/scaffold.ts +6 -0
- package/src/util/redact.ts +6 -0
- package/dist/memory/synap.js +0 -152
- package/dist/memory/synap.js.map +0 -1
- package/src/memory/synap.ts +0 -217
package/src/kicad/sexp.ts
CHANGED
|
@@ -22,7 +22,11 @@ export function parseSexp(text: string): SexpNode[] {
|
|
|
22
22
|
let s = '';
|
|
23
23
|
while (j < text.length && text[j] !== '"') {
|
|
24
24
|
if (text[j] === '\\' && j + 1 < text.length) {
|
|
25
|
-
|
|
25
|
+
// KiCad writes multi-line text as `\n` (and tabs as `\t`) inside the
|
|
26
|
+
// quoted atom; mapping the escape to the literal letter would give
|
|
27
|
+
// the legibility width model a one-line `line1nline2` string.
|
|
28
|
+
const e = text[j + 1];
|
|
29
|
+
s += e === 'n' ? '\n' : e === 't' ? '\t' : e!;
|
|
26
30
|
j += 2;
|
|
27
31
|
} else {
|
|
28
32
|
s += text[j];
|
|
@@ -88,6 +92,8 @@ export interface SchematicSymbol {
|
|
|
88
92
|
sheet: string;
|
|
89
93
|
at: { x: number; y: number; rot: number };
|
|
90
94
|
uuid: string;
|
|
95
|
+
/** `(unit N)` of the placed instance; 1 for single-unit symbols. */
|
|
96
|
+
unit: number;
|
|
91
97
|
}
|
|
92
98
|
|
|
93
99
|
export interface PinDef {
|
|
@@ -95,6 +101,9 @@ export interface PinDef {
|
|
|
95
101
|
name: string;
|
|
96
102
|
x: number;
|
|
97
103
|
y: number;
|
|
104
|
+
/** Unit the pin belongs to (from the `_<unit>_<style>` child it sits in);
|
|
105
|
+
* 0 for a pin in the common unit, drawn on every placed unit. */
|
|
106
|
+
unit: number;
|
|
98
107
|
}
|
|
99
108
|
|
|
100
109
|
interface ParsedSheet {
|
|
@@ -126,7 +135,10 @@ async function loadSheets(rootSch: string): Promise<ParsedSheet[]> {
|
|
|
126
135
|
return out;
|
|
127
136
|
}
|
|
128
137
|
|
|
129
|
-
/** Pin definitions per lib symbol name, in symbol coordinates.
|
|
138
|
+
/** Pin definitions per lib symbol name, in symbol coordinates. Each pin
|
|
139
|
+
* carries the unit of the `_<unit>_<style>` child it was defined in (0 for
|
|
140
|
+
* the common unit); de Morgan alternates (style ≥ 2) are skipped so a pin is
|
|
141
|
+
* recorded once. */
|
|
130
142
|
function libPinDefs(root: SexpNode[]): Map<string, PinDef[]> {
|
|
131
143
|
const map = new Map<string, PinDef[]>();
|
|
132
144
|
const libs = child(root, 'lib_symbols');
|
|
@@ -135,8 +147,15 @@ function libPinDefs(root: SexpNode[]): Map<string, PinDef[]> {
|
|
|
135
147
|
const name = atomAt(sym, 1);
|
|
136
148
|
if (!name) continue;
|
|
137
149
|
const pins: PinDef[] = [];
|
|
138
|
-
const walk = (n: SexpNode): void => {
|
|
150
|
+
const walk = (n: SexpNode, unit: number): void => {
|
|
139
151
|
if (!isList(n)) return;
|
|
152
|
+
if (tag(n) === 'symbol') {
|
|
153
|
+
const m = /_(\d+)_(\d+)$/.exec(atomAt(n, 1) ?? '');
|
|
154
|
+
if (m) {
|
|
155
|
+
if (Number(m[2]) >= 2) return; // de Morgan alternate body style
|
|
156
|
+
unit = Number(m[1]);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
140
159
|
if (tag(n) === 'pin') {
|
|
141
160
|
const at = child(n, 'at');
|
|
142
161
|
const num = atomAt(child(n, 'number'), 1);
|
|
@@ -147,17 +166,26 @@ function libPinDefs(root: SexpNode[]): Map<string, PinDef[]> {
|
|
|
147
166
|
name: pinName ?? '~',
|
|
148
167
|
x: parseFloat(atomAt(at, 1) ?? '0'),
|
|
149
168
|
y: parseFloat(atomAt(at, 2) ?? '0'),
|
|
169
|
+
unit,
|
|
150
170
|
});
|
|
151
171
|
}
|
|
152
172
|
}
|
|
153
|
-
for (const c of n) walk(c);
|
|
173
|
+
for (const c of n) walk(c, unit);
|
|
154
174
|
};
|
|
155
|
-
walk(sym);
|
|
175
|
+
walk(sym, 0);
|
|
156
176
|
map.set(name, pins);
|
|
157
177
|
}
|
|
158
178
|
return map;
|
|
159
179
|
}
|
|
160
180
|
|
|
181
|
+
/** The pins a PLACED instance actually shows: its own unit's plus the common
|
|
182
|
+
* unit's. A single-unit symbol keeps every pin (they are all unit ≤ 1). */
|
|
183
|
+
export function pinsOfUnit(defs: PinDef[], unit: number): PinDef[] {
|
|
184
|
+
const multi = defs.some((p) => p.unit >= 2);
|
|
185
|
+
if (!multi) return defs;
|
|
186
|
+
return defs.filter((p) => p.unit === 0 || p.unit === unit);
|
|
187
|
+
}
|
|
188
|
+
|
|
161
189
|
/** Symbol-space → schematic-space transform (schematic Y grows downward). */
|
|
162
190
|
export function pinAbsolute(
|
|
163
191
|
symAt: { x: number; y: number; rot: number },
|
|
@@ -218,6 +246,7 @@ function symbolsOf(sheet: ParsedSheet): { node: SexpNode[]; sym: SchematicSymbol
|
|
|
218
246
|
rot: parseFloat(atomAt(at, 3) ?? '0'),
|
|
219
247
|
},
|
|
220
248
|
uuid: atomAt(child(s, 'uuid'), 1) ?? '',
|
|
249
|
+
unit: parseInt(atomAt(child(s, 'unit'), 1) ?? '1', 10) || 1,
|
|
221
250
|
},
|
|
222
251
|
});
|
|
223
252
|
}
|
|
@@ -282,6 +311,283 @@ export interface PinNet {
|
|
|
282
311
|
net: string | null;
|
|
283
312
|
}
|
|
284
313
|
|
|
314
|
+
// ---------- Read-only geometry accessors for the legibility checker ----------
|
|
315
|
+
|
|
316
|
+
export interface Bounds {
|
|
317
|
+
minX: number;
|
|
318
|
+
minY: number;
|
|
319
|
+
maxX: number;
|
|
320
|
+
maxY: number;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
export interface WireSeg {
|
|
324
|
+
x1: number;
|
|
325
|
+
y1: number;
|
|
326
|
+
x2: number;
|
|
327
|
+
y2: number;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
export interface TextItem {
|
|
331
|
+
text: string;
|
|
332
|
+
x: number;
|
|
333
|
+
y: number;
|
|
334
|
+
rot: number;
|
|
335
|
+
/** Font height in mm (KiCad `(size h w)`); 1.27 when unstated. */
|
|
336
|
+
height: number;
|
|
337
|
+
hidden: boolean;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
export interface LabelItem {
|
|
341
|
+
name: string;
|
|
342
|
+
kind: 'label' | 'global_label' | 'hierarchical_label';
|
|
343
|
+
x: number;
|
|
344
|
+
y: number;
|
|
345
|
+
rot: number;
|
|
346
|
+
height: number;
|
|
347
|
+
/** Horizontal component of `(justify …)`, or null when unspecified. KiCad
|
|
348
|
+
* normalizes angles 180/270 to 0/90 at draw time and the stored justify
|
|
349
|
+
* describes that DRAWN frame, so a leftward label appears as angle 180 (no
|
|
350
|
+
* justify), angle 180 + justify right, or angle 0 + justify right — all the
|
|
351
|
+
* same box. */
|
|
352
|
+
justify: 'left' | 'right' | null;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
export interface RectItem {
|
|
356
|
+
x1: number;
|
|
357
|
+
y1: number;
|
|
358
|
+
x2: number;
|
|
359
|
+
y2: number;
|
|
360
|
+
strokeType: string;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
export interface PlacedSymbolGeom {
|
|
364
|
+
ref: string;
|
|
365
|
+
libId: string;
|
|
366
|
+
value: string;
|
|
367
|
+
at: { x: number; y: number; rot: number };
|
|
368
|
+
mirror: 'x' | 'y' | null;
|
|
369
|
+
isPower: boolean;
|
|
370
|
+
/** `(unit N)` of the placed instance; 1 for single-unit symbols. */
|
|
371
|
+
unit: number;
|
|
372
|
+
/** Reference and Value property text (absolute schematic coordinates). */
|
|
373
|
+
props: TextItem[];
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
export interface SheetGeometry {
|
|
377
|
+
filePath: string;
|
|
378
|
+
sheetName: string;
|
|
379
|
+
/** `(paper …)`: standard name, or explicit size when the file states one. */
|
|
380
|
+
paper: { name: string | null; width: number | null; height: number | null; portrait: boolean };
|
|
381
|
+
titleBlock: { title: string; date: string; rev: string } | null;
|
|
382
|
+
symbols: PlacedSymbolGeom[];
|
|
383
|
+
wires: WireSeg[];
|
|
384
|
+
labels: LabelItem[];
|
|
385
|
+
/** Free `(text …)` items (group captions live here). */
|
|
386
|
+
texts: TextItem[];
|
|
387
|
+
/** Sheet-level `(rectangle …)` graphics (group boxes live here). */
|
|
388
|
+
rectangles: RectItem[];
|
|
389
|
+
/** Union bounds of each lib symbol's body graphics, symbol space; null when the entry draws nothing. */
|
|
390
|
+
libBounds: Map<string, Bounds | null>;
|
|
391
|
+
libPins: Map<string, PinDef[]>;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const num = (n: SexpNode[] | undefined, idx: number, fallback = 0): number => {
|
|
395
|
+
const v = atomAt(n, idx);
|
|
396
|
+
return v === undefined ? fallback : parseFloat(v);
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
function effectsOf(node: SexpNode[]): { height: number; hidden: boolean } {
|
|
400
|
+
const effects = child(node, 'effects');
|
|
401
|
+
const font = effects ? child(effects, 'font') : undefined;
|
|
402
|
+
const size = font ? child(font, 'size') : undefined;
|
|
403
|
+
// hidden: legacy bare `hide` atom, or v9 `(hide yes)`
|
|
404
|
+
const hideNode = effects ? child(effects, 'hide') : undefined;
|
|
405
|
+
const hidden =
|
|
406
|
+
(effects?.some((c) => c === 'hide') ?? false) || (hideNode !== undefined && atomAt(hideNode, 1) !== 'no');
|
|
407
|
+
return { height: size ? num(size, 1, 1.27) : 1.27, hidden };
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function libBodyBounds(root: SexpNode[]): Map<string, Bounds | null> {
|
|
411
|
+
const map = new Map<string, Bounds | null>();
|
|
412
|
+
const libs = child(root, 'lib_symbols');
|
|
413
|
+
if (!libs) return map;
|
|
414
|
+
for (const sym of children(libs, 'symbol')) {
|
|
415
|
+
const name = atomAt(sym, 1);
|
|
416
|
+
if (!name) continue;
|
|
417
|
+
/** Union across the whole symbol under `name`, per-unit union (common
|
|
418
|
+
* graphics included) under `name#<unit>` — a placed unit of a multi-unit
|
|
419
|
+
* symbol draws only its own unit's graphics, so measuring it with the
|
|
420
|
+
* package union would size every instance like the whole gate pack. */
|
|
421
|
+
const perUnit = new Map<number, Bounds | null>();
|
|
422
|
+
let b: Bounds | null = null;
|
|
423
|
+
const grow = (prev: Bounds | null, x: number, y: number): Bounds => {
|
|
424
|
+
if (!prev) return { minX: x, minY: y, maxX: x, maxY: y };
|
|
425
|
+
prev.minX = Math.min(prev.minX, x);
|
|
426
|
+
prev.minY = Math.min(prev.minY, y);
|
|
427
|
+
prev.maxX = Math.max(prev.maxX, x);
|
|
428
|
+
prev.maxY = Math.max(prev.maxY, y);
|
|
429
|
+
return prev;
|
|
430
|
+
};
|
|
431
|
+
const walk = (n: SexpNode, unit: number | null): void => {
|
|
432
|
+
if (!isList(n)) return;
|
|
433
|
+
const t = tag(n);
|
|
434
|
+
if (t === 'symbol') {
|
|
435
|
+
const m = /_(\d+)_(\d+)$/.exec(atomAt(n, 1) ?? '');
|
|
436
|
+
if (m) {
|
|
437
|
+
if (Number(m[2]) >= 2) return; // de Morgan alternate body style
|
|
438
|
+
unit = Number(m[1]);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
const extend = (x: number, y: number): void => {
|
|
442
|
+
b = grow(b, x, y);
|
|
443
|
+
if (unit !== null) perUnit.set(unit, grow(perUnit.get(unit) ?? null, x, y));
|
|
444
|
+
};
|
|
445
|
+
if (t === 'rectangle') {
|
|
446
|
+
const s = child(n, 'start');
|
|
447
|
+
const e = child(n, 'end');
|
|
448
|
+
extend(num(s, 1), num(s, 2));
|
|
449
|
+
extend(num(e, 1), num(e, 2));
|
|
450
|
+
} else if (t === 'circle') {
|
|
451
|
+
const c = child(n, 'center');
|
|
452
|
+
const r = num(child(n, 'radius'), 1);
|
|
453
|
+
extend(num(c, 1) - r, num(c, 2) - r);
|
|
454
|
+
extend(num(c, 1) + r, num(c, 2) + r);
|
|
455
|
+
} else if (t === 'arc') {
|
|
456
|
+
for (const part of ['start', 'mid', 'end']) {
|
|
457
|
+
const p = child(n, part);
|
|
458
|
+
if (p) extend(num(p, 1), num(p, 2));
|
|
459
|
+
}
|
|
460
|
+
} else if (t === 'polyline') {
|
|
461
|
+
for (const xy of children(child(n, 'pts') ?? [], 'xy')) extend(num(xy, 1), num(xy, 2));
|
|
462
|
+
}
|
|
463
|
+
// pin/text graphics are deliberately excluded from the body box (design C2)
|
|
464
|
+
if (t !== 'pin' && t !== 'text') for (const c of n) walk(c, unit);
|
|
465
|
+
};
|
|
466
|
+
walk(sym, null);
|
|
467
|
+
map.set(name, b);
|
|
468
|
+
const common = perUnit.get(0) ?? null;
|
|
469
|
+
for (const [unit, ub] of perUnit) {
|
|
470
|
+
if (unit === 0) continue;
|
|
471
|
+
const merged =
|
|
472
|
+
ub && common
|
|
473
|
+
? {
|
|
474
|
+
minX: Math.min(ub.minX, common.minX),
|
|
475
|
+
minY: Math.min(ub.minY, common.minY),
|
|
476
|
+
maxX: Math.max(ub.maxX, common.maxX),
|
|
477
|
+
maxY: Math.max(ub.maxY, common.maxY),
|
|
478
|
+
}
|
|
479
|
+
: (ub ?? (common ? { ...common } : null));
|
|
480
|
+
map.set(`${name}#${unit}`, merged);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
return map;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function textItemsOf(sym: SexpNode[]): TextItem[] {
|
|
487
|
+
const out: TextItem[] = [];
|
|
488
|
+
for (const p of children(sym, 'property')) {
|
|
489
|
+
const key = atomAt(p, 1);
|
|
490
|
+
if (key !== 'Reference' && key !== 'Value') continue;
|
|
491
|
+
const at = child(p, 'at');
|
|
492
|
+
const fx = effectsOf(p);
|
|
493
|
+
out.push({
|
|
494
|
+
text: atomAt(p, 2) ?? '',
|
|
495
|
+
x: num(at, 1),
|
|
496
|
+
y: num(at, 2),
|
|
497
|
+
rot: num(at, 3),
|
|
498
|
+
height: fx.height,
|
|
499
|
+
hidden: fx.hidden,
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
return out;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* Everything the legibility checker needs to see per sheet, extracted read-only.
|
|
507
|
+
* Never serializes and never mutates the file.
|
|
508
|
+
*/
|
|
509
|
+
export async function readSheetGeometry(rootSch: string): Promise<SheetGeometry[]> {
|
|
510
|
+
const sheets = await loadSheets(rootSch);
|
|
511
|
+
const powerSyms = collectPowerSymbols(sheets);
|
|
512
|
+
return sheets.map((sheet) => {
|
|
513
|
+
const paperNode = child(sheet.root, 'paper');
|
|
514
|
+
const paperName = atomAt(paperNode, 1) ?? null;
|
|
515
|
+
const portrait = paperNode?.includes('portrait') ?? false;
|
|
516
|
+
const explicitW = num(paperNode, 2, NaN);
|
|
517
|
+
const explicitH = num(paperNode, 3, NaN);
|
|
518
|
+
const tb = child(sheet.root, 'title_block');
|
|
519
|
+
const tbField = (name: string): string => (tb ? (atomAt(child(tb, name), 1) ?? '') : '');
|
|
520
|
+
const wires: WireSeg[] = [];
|
|
521
|
+
for (const w of children(sheet.root, 'wire')) {
|
|
522
|
+
const pts = children(child(w, 'pts') ?? [], 'xy');
|
|
523
|
+
for (let i = 1; i < pts.length; i++) {
|
|
524
|
+
wires.push({ x1: num(pts[i - 1], 1), y1: num(pts[i - 1], 2), x2: num(pts[i], 1), y2: num(pts[i], 2) });
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
const labels: LabelItem[] = [];
|
|
528
|
+
for (const kind of ['label', 'global_label', 'hierarchical_label'] as const) {
|
|
529
|
+
for (const l of children(sheet.root, kind)) {
|
|
530
|
+
const at = child(l, 'at');
|
|
531
|
+
const effects = child(l, 'effects');
|
|
532
|
+
const justify = effects ? child(effects, 'justify') : undefined;
|
|
533
|
+
labels.push({
|
|
534
|
+
name: atomAt(l, 1) ?? '',
|
|
535
|
+
kind,
|
|
536
|
+
x: num(at, 1),
|
|
537
|
+
y: num(at, 2),
|
|
538
|
+
rot: num(at, 3),
|
|
539
|
+
height: effectsOf(l).height,
|
|
540
|
+
justify: justify?.some((c) => c === 'right') ? 'right' : justify?.some((c) => c === 'left') ? 'left' : null,
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
const texts: TextItem[] = children(sheet.root, 'text').map((t) => {
|
|
545
|
+
const at = child(t, 'at');
|
|
546
|
+
const fx = effectsOf(t);
|
|
547
|
+
return { text: atomAt(t, 1) ?? '', x: num(at, 1), y: num(at, 2), rot: num(at, 3), height: fx.height, hidden: fx.hidden };
|
|
548
|
+
});
|
|
549
|
+
const rectangles: RectItem[] = children(sheet.root, 'rectangle').map((r) => {
|
|
550
|
+
const s = child(r, 'start');
|
|
551
|
+
const e = child(r, 'end');
|
|
552
|
+
const stroke = child(r, 'stroke');
|
|
553
|
+
return {
|
|
554
|
+
x1: num(s, 1),
|
|
555
|
+
y1: num(s, 2),
|
|
556
|
+
x2: num(e, 1),
|
|
557
|
+
y2: num(e, 2),
|
|
558
|
+
strokeType: atomAt(child(stroke ?? [], 'type'), 1) ?? 'default',
|
|
559
|
+
};
|
|
560
|
+
});
|
|
561
|
+
return {
|
|
562
|
+
filePath: sheet.filePath,
|
|
563
|
+
sheetName: sheet.sheetName,
|
|
564
|
+
paper: {
|
|
565
|
+
name: paperName,
|
|
566
|
+
width: Number.isNaN(explicitW) ? null : explicitW,
|
|
567
|
+
height: Number.isNaN(explicitH) ? null : explicitH,
|
|
568
|
+
portrait,
|
|
569
|
+
},
|
|
570
|
+
titleBlock: tb ? { title: tbField('title'), date: tbField('date'), rev: tbField('rev') } : null,
|
|
571
|
+
symbols: symbolsOf(sheet).map(({ sym, mirror, node }) => ({
|
|
572
|
+
ref: sym.ref,
|
|
573
|
+
libId: sym.libId,
|
|
574
|
+
value: sym.value,
|
|
575
|
+
at: sym.at,
|
|
576
|
+
mirror,
|
|
577
|
+
isPower: isPowerSymbol(sym.libId, powerSyms),
|
|
578
|
+
unit: sym.unit,
|
|
579
|
+
props: textItemsOf(node),
|
|
580
|
+
})),
|
|
581
|
+
wires,
|
|
582
|
+
labels,
|
|
583
|
+
texts,
|
|
584
|
+
rectangles,
|
|
585
|
+
libBounds: libBodyBounds(sheet.root),
|
|
586
|
+
libPins: libPinDefs(sheet.root),
|
|
587
|
+
};
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
|
|
285
591
|
/**
|
|
286
592
|
* Geometric connectivity per sheet: pins, labels, and wire endpoints that share
|
|
287
593
|
* coordinates (or are joined by wires) form a group; a group's net name comes
|
|
@@ -319,7 +625,10 @@ export async function pinNets(rootSch: string): Promise<PinNet[]> {
|
|
|
319
625
|
|
|
320
626
|
const symPins: { sym: SchematicSymbol; pin: PinDef; k: string }[] = [];
|
|
321
627
|
for (const { sym, mirror } of symbolsOf(sheet)) {
|
|
322
|
-
|
|
628
|
+
// a placed unit of a multi-unit symbol shows only its own unit's pins;
|
|
629
|
+
// resolving the whole package at each instance would invent phantom
|
|
630
|
+
// connection points at every other unit's coordinates
|
|
631
|
+
const defs = pinsOfUnit(pinDefs.get(sym.libId) ?? [], sym.unit);
|
|
323
632
|
for (const pin of defs) {
|
|
324
633
|
const abs = pinAbsolute(sym.at, mirror, pin);
|
|
325
634
|
const k = key(abs.x, abs.y);
|