@mica-js/document 0.0.0-stage → 0.1.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.
@@ -0,0 +1,2845 @@
1
+ import { c as isAssetReference, i as assetPathError, n as throwIfErrors, o as base64Error, r as ASSET_MODES, s as isAssetMode, t as SchematicError } from "../errors-7cnIjhnV.js";
2
+ import { a as readArrayField, c as readObjectField, d as readStringField, f as report, h as parseInput, i as isRecord, l as readPoint, m as isUuidV4, n as forbidUnknownFields, o as readIntegerField, p as toPointer, r as hasErrors, s as readNumberField, t as checkDuplicateIds, u as readProperties } from "../validate-Cz-ZbbEr.js";
3
+ //#region src/format/types.ts
4
+ /**
5
+ * Schema version of the `.sch` contract (see x-botlab/mica-js#6).
6
+ *
7
+ * 2D placement lives in the separate `.cnv` canvas contract (see x-botlab/mica-js#67 and
8
+ * `@mica-js/document/canvas`).
9
+ */
10
+ var SCHEMATIC_VERSION = 1;
11
+ //#endregion
12
+ //#region src/schematic/device/reference.ts
13
+ var LABEL_DEVICE = "builtin:label";
14
+ var POWER_DEVICE = "builtin:power";
15
+ var GROUND_DEVICE = "builtin:ground";
16
+ /** Free-standing annotation; it is not a connection point and never joins a net. */
17
+ var TEXT_DEVICE = "builtin:text";
18
+ var RESERVED_PACKAGE_NAMESPACE = "builtin";
19
+ var BUILTIN_DEVICES = {
20
+ [LABEL_DEVICE]: "label",
21
+ [POWER_DEVICE]: "power",
22
+ [GROUND_DEVICE]: "ground"
23
+ };
24
+ var DEFAULT_POWER_LABEL = "VCC";
25
+ var DEFAULT_GROUND_LABEL = "GND";
26
+ var DEFAULT_TEXT_CONTENT = "文本";
27
+ var PACKAGE_NAMESPACE_PATTERN = /^@[A-Za-z0-9][A-Za-z0-9._+-]*$/;
28
+ var PACKAGE_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._+-]*$/;
29
+ var DEVICE_ID_PATTERN = /^(?:[A-Za-z0-9][A-Za-z0-9._+-]*|-[A-Za-z0-9-]*)$/;
30
+ /**
31
+ * Splits `<packageId>:<deviceId>`. Package ids follow npm conventions and may be scoped
32
+ * (`@scope/name`); device ids accept metadata identifiers plus the legacy identifier charset.
33
+ */
34
+ function parseDeviceReference(raw) {
35
+ const separator = raw.indexOf(":");
36
+ if (separator <= 0 || separator === raw.length - 1) return;
37
+ if (raw.indexOf(":", separator + 1) !== -1) return;
38
+ const packageId = raw.slice(0, separator);
39
+ const deviceId = raw.slice(separator + 1);
40
+ if (!isValidPackageId(packageId) || !DEVICE_ID_PATTERN.test(deviceId)) return;
41
+ return {
42
+ raw,
43
+ packageId,
44
+ deviceId
45
+ };
46
+ }
47
+ function isValidPackageId(packageId) {
48
+ if (packageId.startsWith("@")) {
49
+ const parts = packageId.split("/");
50
+ if (parts.length !== 2) return false;
51
+ const [scope, name] = parts;
52
+ return scope !== void 0 && name !== void 0 && PACKAGE_NAMESPACE_PATTERN.test(scope) && PACKAGE_NAME_PATTERN.test(name);
53
+ }
54
+ return PACKAGE_NAME_PATTERN.test(packageId);
55
+ }
56
+ /** `builtin` is reserved for loader-provided symbols such as `builtin:label`. */
57
+ function isReservedPackageId(packageId) {
58
+ return packageId === "builtin" || packageId.startsWith(`builtin/`);
59
+ }
60
+ function isLabelDevice(raw) {
61
+ return raw === LABEL_DEVICE;
62
+ }
63
+ /** True for the free-standing text annotation (`builtin:text`). */
64
+ function isTextDevice(raw) {
65
+ return raw === TEXT_DEVICE;
66
+ }
67
+ /** Text of a `builtin:text` symbol, falling back to a placeholder. */
68
+ function textDeviceContent(symbol) {
69
+ const text = symbol.properties?.["text"];
70
+ return typeof text === "string" && text.length > 0 ? text : DEFAULT_TEXT_CONTENT;
71
+ }
72
+ /** Returns the builtin kind for `builtin:label` / `builtin:power` / `builtin:ground`. */
73
+ function builtinSymbolKind(raw) {
74
+ return BUILTIN_DEVICES[raw];
75
+ }
76
+ function isBuiltinDevice(raw) {
77
+ return builtinSymbolKind(raw) !== void 0;
78
+ }
79
+ /** Role of a symbol, derived from its device reference; unknown devices are components. */
80
+ function symbolRole(device) {
81
+ switch (builtinSymbolKind(device)) {
82
+ case "label": return "net-label";
83
+ case "power": return "power";
84
+ case "ground": return "ground";
85
+ default: return isTextDevice(device) ? "text" : "component";
86
+ }
87
+ }
88
+ /** Only components are annotated with a reference designator. */
89
+ function hasDesignator(role) {
90
+ return role === "component";
91
+ }
92
+ /** Net labels, power and ground ports name the net they attach to. */
93
+ function namesNet(role) {
94
+ return role === "net-label" || role === "power" || role === "ground";
95
+ }
96
+ /** Reference designator of a component; `undefined` for symbols that have none. */
97
+ function designatorOf(symbol) {
98
+ return hasDesignator(symbolRole(symbol.device)) ? symbol.designator ?? symbol.id : void 0;
99
+ }
100
+ /** Net name of a label / power / ground symbol; `undefined` when the symbol names no net. */
101
+ function netNameOf(symbol) {
102
+ switch (symbolRole(symbol.device)) {
103
+ case "power": return symbol.net ?? "VCC";
104
+ case "ground": return symbol.net ?? "GND";
105
+ case "net-label": return symbol.net;
106
+ default: return;
107
+ }
108
+ }
109
+ /** Net name a freshly placed symbol of `role` gets when none is given. */
110
+ function defaultNetName(role) {
111
+ switch (role) {
112
+ case "power": return "VCC";
113
+ case "ground": return "GND";
114
+ default: return;
115
+ }
116
+ }
117
+ function formatDeviceReference(packageId, deviceId) {
118
+ return `${packageId}:${deviceId}`;
119
+ }
120
+ //#endregion
121
+ //#region src/schematic/format/parse-schematic.ts
122
+ /** @throws {SchematicError} when the input does not match the `.sch` contract. */
123
+ function parseSchematic(input) {
124
+ const diagnostics = [];
125
+ const parsed = parseInput(input, diagnostics);
126
+ if (!parsed.ok) throw new SchematicError(diagnostics);
127
+ const value = readDocument(parsed.value, diagnostics);
128
+ if (value === void 0 || hasErrors(diagnostics)) throw new SchematicError(diagnostics);
129
+ return value;
130
+ }
131
+ function readDocument(value, diagnostics) {
132
+ if (!isRecord(value)) {
133
+ report(diagnostics, "json/not-object", "Schematic must be a JSON object.", []);
134
+ return;
135
+ }
136
+ forbidUnknownFields(value, ["version", "sheet"], [], diagnostics);
137
+ const version = readVersion(value, diagnostics);
138
+ const sheet = readSheet(readObjectField(value, "sheet", [], diagnostics), diagnostics);
139
+ if (version === void 0 || sheet === void 0) return;
140
+ return {
141
+ version,
142
+ sheet
143
+ };
144
+ }
145
+ function readVersion(value, diagnostics) {
146
+ const version = readIntegerField(value, "version", [], diagnostics);
147
+ if (version === void 0) return;
148
+ if (version !== 1) {
149
+ report(diagnostics, "schema/unsupported-version", `Unsupported version ${version}; expected 1.`, ["version"]);
150
+ return;
151
+ }
152
+ return 1;
153
+ }
154
+ function readSheet(value, diagnostics) {
155
+ if (value === void 0) return;
156
+ forbidUnknownFields(value, [
157
+ "symbols",
158
+ "wires",
159
+ "junctions"
160
+ ], ["sheet"], diagnostics);
161
+ const rawSymbols = readArrayField(value, "symbols", ["sheet"], diagnostics) ?? [];
162
+ const rawWires = readArrayField(value, "wires", ["sheet"], diagnostics) ?? [];
163
+ const rawJunctions = readArrayField(value, "junctions", ["sheet"], diagnostics) ?? [];
164
+ const symbols = [];
165
+ const symbolIds = [];
166
+ rawSymbols.forEach((item, index) => {
167
+ const path = [
168
+ "sheet",
169
+ "symbols",
170
+ String(index)
171
+ ];
172
+ const symbol = readSymbol(item, path, diagnostics);
173
+ if (symbol !== void 0) {
174
+ symbols.push(symbol);
175
+ symbolIds.push({
176
+ id: symbol.id,
177
+ path: [...path, "id"]
178
+ });
179
+ }
180
+ });
181
+ const wires = [];
182
+ const wireIds = [];
183
+ rawWires.forEach((item, index) => {
184
+ const path = [
185
+ "sheet",
186
+ "wires",
187
+ String(index)
188
+ ];
189
+ const wire = readWire(item, path, diagnostics);
190
+ if (wire !== void 0) {
191
+ wires.push(wire);
192
+ wireIds.push({
193
+ id: wire.id,
194
+ path: [...path, "id"]
195
+ });
196
+ }
197
+ });
198
+ const knownWireIds = new Set(wires.map((wire) => wire.id));
199
+ const junctions = [];
200
+ const junctionIds = [];
201
+ rawJunctions.forEach((item, index) => {
202
+ const path = [
203
+ "sheet",
204
+ "junctions",
205
+ String(index)
206
+ ];
207
+ const junction = readJunction(item, path, diagnostics);
208
+ if (junction === void 0) return;
209
+ const seenWireIds = /* @__PURE__ */ new Set();
210
+ junction.wires.forEach((wireId, wireIndex) => {
211
+ const wirePath = [
212
+ ...path,
213
+ "wires",
214
+ String(wireIndex)
215
+ ];
216
+ if (seenWireIds.has(wireId)) report(diagnostics, "junction/duplicate-wire", `Junction "${junction.id}" references wire "${wireId}" more than once.`, wirePath, {
217
+ kind: "junction",
218
+ id: junction.id
219
+ });
220
+ else seenWireIds.add(wireId);
221
+ if (!knownWireIds.has(wireId)) report(diagnostics, "junction/unknown-wire", `Junction "${junction.id}" references unknown wire "${wireId}".`, wirePath, {
222
+ kind: "junction",
223
+ id: junction.id
224
+ });
225
+ });
226
+ junctions.push(junction);
227
+ junctionIds.push({
228
+ id: junction.id,
229
+ path: [...path, "id"]
230
+ });
231
+ });
232
+ checkDuplicateIds(symbolIds, "symbol", diagnostics);
233
+ checkDuplicateDesignators(symbols, diagnostics);
234
+ checkDuplicateIds(wireIds, "wire", diagnostics);
235
+ checkDuplicateIds(junctionIds, "junction", diagnostics);
236
+ return {
237
+ symbols,
238
+ wires,
239
+ junctions
240
+ };
241
+ }
242
+ /** Two components must not share a reference designator. */
243
+ function checkDuplicateDesignators(symbols, diagnostics) {
244
+ const seen = /* @__PURE__ */ new Set();
245
+ symbols.forEach((symbol, index) => {
246
+ const designator = designatorOf(symbol);
247
+ if (designator === void 0) return;
248
+ if (seen.has(designator)) {
249
+ report(diagnostics, "symbol/designator-duplicate", `Reference designator ${JSON.stringify(designator)} is used more than once.`, [
250
+ "sheet",
251
+ "symbols",
252
+ String(index),
253
+ "designator"
254
+ ], {
255
+ kind: "symbol",
256
+ id: symbol.id
257
+ });
258
+ return;
259
+ }
260
+ seen.add(designator);
261
+ });
262
+ }
263
+ function readSymbol(value, path, diagnostics) {
264
+ if (!isRecord(value)) {
265
+ report(diagnostics, "schema/invalid-type", "Symbol must be an object.", path);
266
+ return;
267
+ }
268
+ forbidUnknownFields(value, [
269
+ "id",
270
+ "device",
271
+ "x",
272
+ "y",
273
+ "rotation",
274
+ "properties",
275
+ "designator",
276
+ "net",
277
+ "reference"
278
+ ], path, diagnostics);
279
+ const id = readId(value, path, diagnostics);
280
+ const device = readStringField(value, "device", path, diagnostics, { nonEmpty: true });
281
+ const x = readNumberField(value, "x", path, diagnostics);
282
+ const y = readNumberField(value, "y", path, diagnostics);
283
+ const rotation = readNumberField(value, "rotation", path, diagnostics);
284
+ const properties = value["properties"] === void 0 ? void 0 : readProperties(value["properties"], [...path, "properties"], diagnostics);
285
+ const reference = value["reference"] === void 0 ? void 0 : readPoint(value["reference"], [...path, "reference"], diagnostics);
286
+ const designator = readRoleField(value, "designator", device, path, diagnostics, hasDesignator);
287
+ const net = readRoleField(value, "net", device, path, diagnostics, namesNet);
288
+ if (device !== void 0) {
289
+ const role = symbolRole(device);
290
+ if (namesNet(role) && net === void 0 && value["net"] === void 0) report(diagnostics, "symbol/missing-net", `Symbols of role "${role}" must name the net they attach to.`, [...path, "net"]);
291
+ if (!hasDesignator(role) && value["reference"] !== void 0) report(diagnostics, "symbol/unsupported-field", `Symbols of role "${role}" carry no reference designator.`, [...path, "reference"]);
292
+ }
293
+ if (id === void 0 || device === void 0 || x === void 0 || y === void 0 || rotation === void 0 || value["reference"] !== void 0 && reference === void 0) return;
294
+ return {
295
+ id,
296
+ device,
297
+ x,
298
+ y,
299
+ rotation,
300
+ ...properties === void 0 ? {} : { properties },
301
+ ...designator === void 0 ? {} : { designator },
302
+ ...net === void 0 ? {} : { net },
303
+ ...reference === void 0 ? {} : { reference }
304
+ };
305
+ }
306
+ /**
307
+ * Reads a field that only some symbol roles may carry, so the format cannot describe a power
308
+ * symbol with a reference designator or a resistor with a net name.
309
+ */
310
+ function readRoleField(value, field, device, path, diagnostics, allowed) {
311
+ if (value[field] === void 0 || device === void 0) return;
312
+ const role = symbolRole(device);
313
+ if (!allowed(role)) {
314
+ report(diagnostics, "symbol/unsupported-field", `Symbols of role "${role}" must not declare "${field}".`, [...path, field]);
315
+ return;
316
+ }
317
+ return readStringField(value, field, path, diagnostics, { nonEmpty: true });
318
+ }
319
+ function readWire(value, path, diagnostics) {
320
+ if (!isRecord(value)) {
321
+ report(diagnostics, "schema/invalid-type", "Wire must be an object.", path);
322
+ return;
323
+ }
324
+ forbidUnknownFields(value, ["id", "points"], path, diagnostics);
325
+ const id = readId(value, path, diagnostics);
326
+ const rawPoints = readArrayField(value, "points", path, diagnostics);
327
+ const points = [];
328
+ if (rawPoints !== void 0) {
329
+ if (rawPoints.length < 2) report(diagnostics, "wire/invalid-points", "Wire must contain at least two points.", [...path, "points"]);
330
+ rawPoints.forEach((item, index) => {
331
+ const point = readPoint(item, [
332
+ ...path,
333
+ "points",
334
+ String(index)
335
+ ], diagnostics);
336
+ if (point !== void 0) points.push(point);
337
+ });
338
+ }
339
+ if (id === void 0 || rawPoints === void 0) return;
340
+ for (let index = 1; index < points.length; index += 1) {
341
+ const previous = points[index - 1];
342
+ const current = points[index];
343
+ if (previous === void 0 || current === void 0) continue;
344
+ if (previous.x !== current.x && previous.y !== current.y) {
345
+ report(diagnostics, "wire/not-orthogonal", "Wire segments must be horizontal or vertical.", [
346
+ ...path,
347
+ "points",
348
+ String(index)
349
+ ]);
350
+ return;
351
+ }
352
+ }
353
+ return {
354
+ id,
355
+ points
356
+ };
357
+ }
358
+ function readJunction(value, path, diagnostics) {
359
+ if (!isRecord(value)) {
360
+ report(diagnostics, "schema/invalid-type", "Junction must be an object.", path);
361
+ return;
362
+ }
363
+ forbidUnknownFields(value, [
364
+ "id",
365
+ "x",
366
+ "y",
367
+ "wires"
368
+ ], path, diagnostics);
369
+ const id = readId(value, path, diagnostics);
370
+ const x = readNumberField(value, "x", path, diagnostics);
371
+ const y = readNumberField(value, "y", path, diagnostics);
372
+ const rawWires = readArrayField(value, "wires", path, diagnostics);
373
+ const wires = [];
374
+ if (rawWires !== void 0) {
375
+ if (rawWires.length < 2) report(diagnostics, "schema/invalid-value", "Junction must reference at least two wires.", [...path, "wires"]);
376
+ rawWires.forEach((item, index) => {
377
+ const wirePath = [
378
+ ...path,
379
+ "wires",
380
+ String(index)
381
+ ];
382
+ if (typeof item !== "string") {
383
+ report(diagnostics, "schema/invalid-type", "Junction wire reference must be a string.", wirePath);
384
+ return;
385
+ }
386
+ if (item.length === 0) {
387
+ report(diagnostics, "schema/invalid-value", "Junction wire reference must not be empty.", wirePath);
388
+ return;
389
+ }
390
+ wires.push(item);
391
+ });
392
+ }
393
+ if (id === void 0 || x === void 0 || y === void 0 || rawWires === void 0) return;
394
+ return {
395
+ id,
396
+ x,
397
+ y,
398
+ wires
399
+ };
400
+ }
401
+ function readId(value, path, diagnostics) {
402
+ const id = readStringField(value, "id", path, diagnostics, { nonEmpty: true });
403
+ if (id === void 0) return;
404
+ if (!isUuidV4(id)) {
405
+ report(diagnostics, "schema/invalid-value", "Field \"id\" must be a UUID v4.", [...path, "id"]);
406
+ return;
407
+ }
408
+ return id;
409
+ }
410
+ //#endregion
411
+ //#region src/schematic/format/serialize.ts
412
+ /** Serializes with a fixed field order and 2-space indentation, matching the examples in #6. */
413
+ function serializeSchematic(document) {
414
+ const serializable = {
415
+ version: document.version,
416
+ sheet: {
417
+ symbols: document.sheet.symbols.map((symbol) => {
418
+ const serialized = {
419
+ id: symbol.id,
420
+ device: symbol.device,
421
+ x: symbol.x,
422
+ y: symbol.y,
423
+ rotation: symbol.rotation
424
+ };
425
+ if (symbol.properties !== void 0 && Object.keys(symbol.properties).length > 0) serialized["properties"] = sortProperties(symbol.properties);
426
+ if (symbol.designator !== void 0) serialized["designator"] = symbol.designator;
427
+ if (symbol.net !== void 0) serialized["net"] = symbol.net;
428
+ if (symbol.reference !== void 0) serialized["reference"] = {
429
+ x: symbol.reference.x,
430
+ y: symbol.reference.y
431
+ };
432
+ return serialized;
433
+ }),
434
+ wires: document.sheet.wires.map((wire) => ({
435
+ id: wire.id,
436
+ points: wire.points.map((point) => ({
437
+ x: point.x,
438
+ y: point.y
439
+ }))
440
+ })),
441
+ junctions: document.sheet.junctions.map((junction) => ({
442
+ id: junction.id,
443
+ x: junction.x,
444
+ y: junction.y,
445
+ wires: [...junction.wires]
446
+ }))
447
+ }
448
+ };
449
+ return `${JSON.stringify(serializable, null, 2)}\n`;
450
+ }
451
+ function sortProperties(properties) {
452
+ const sorted = Object.create(null);
453
+ for (const key of Object.keys(properties).sort()) {
454
+ const value = properties[key];
455
+ if (value !== void 0) sorted[key] = value;
456
+ }
457
+ return sorted;
458
+ }
459
+ //#endregion
460
+ //#region src/schematic/geometry/point.ts
461
+ /** Default comparison tolerance, in page units. */
462
+ var DEFAULT_TOLERANCE = 0;
463
+ function distance(a, b) {
464
+ return Math.hypot(b.x - a.x, b.y - a.y);
465
+ }
466
+ function samePoint(a, b) {
467
+ return a.x === b.x && a.y === b.y;
468
+ }
469
+ function pointsEqual(a, b, tolerance = 0) {
470
+ return distance(a, b) <= tolerance;
471
+ }
472
+ /** Closest point on the closed segment `a`–`b`, plus its position `t` in `[0, 1]`. */
473
+ function projectOnSegment(point, a, b) {
474
+ const dx = b.x - a.x;
475
+ const dy = b.y - a.y;
476
+ const lengthSquared = dx * dx + dy * dy;
477
+ if (lengthSquared === 0) return {
478
+ x: a.x,
479
+ y: a.y,
480
+ t: 0
481
+ };
482
+ const raw = ((point.x - a.x) * dx + (point.y - a.y) * dy) / lengthSquared;
483
+ const t = raw < 0 ? 0 : raw > 1 ? 1 : raw;
484
+ return {
485
+ x: a.x + dx * t,
486
+ y: a.y + dy * t,
487
+ t
488
+ };
489
+ }
490
+ /** Closest point on a polyline, with the segment it belongs to; `undefined` when empty. */
491
+ function nearestPointOnPolyline(points, point) {
492
+ let best;
493
+ for (let index = 1; index < points.length; index += 1) {
494
+ const a = points[index - 1];
495
+ const b = points[index];
496
+ if (a === void 0 || b === void 0) continue;
497
+ const projected = projectOnSegment(point, a, b);
498
+ const candidate = {
499
+ point: {
500
+ x: projected.x,
501
+ y: projected.y
502
+ },
503
+ segment: index - 1,
504
+ t: projected.t,
505
+ distance: distance(point, projected)
506
+ };
507
+ if (best === void 0 || candidate.distance < best.distance) best = candidate;
508
+ }
509
+ const only = points[0];
510
+ if (best === void 0 && only !== void 0) return {
511
+ point: only,
512
+ segment: 0,
513
+ t: 0,
514
+ distance: distance(point, only)
515
+ };
516
+ return best;
517
+ }
518
+ /** Point at position `t` along segment `segment` of a polyline. */
519
+ function pointOnPolylineAt(points, segment, t) {
520
+ const a = points[segment];
521
+ if (a === void 0) return;
522
+ const b = points[segment + 1] ?? a;
523
+ return {
524
+ x: a.x + (b.x - a.x) * t,
525
+ y: a.y + (b.y - a.y) * t
526
+ };
527
+ }
528
+ /** True when `point` lies on the closed segment `a`–`b`, within `tolerance`. */
529
+ function pointOnSegment(point, a, b, tolerance = 0) {
530
+ const dx = b.x - a.x;
531
+ const dy = b.y - a.y;
532
+ const length = Math.hypot(dx, dy);
533
+ if (length === 0) return pointsEqual(point, a, tolerance);
534
+ const area = (point.x - a.x) * dy - (point.y - a.y) * dx;
535
+ if (Math.abs(area) / length > tolerance) return false;
536
+ const projection = ((point.x - a.x) * dx + (point.y - a.y) * dy) / (length * length);
537
+ const overhang = tolerance / length;
538
+ if (projection < -overhang || projection > 1 + overhang) return false;
539
+ return distanceToSegment(point, a, b) <= tolerance;
540
+ }
541
+ function distanceToSegment(point, a, b) {
542
+ const dx = b.x - a.x;
543
+ const dy = b.y - a.y;
544
+ const lengthSquared = dx * dx + dy * dy;
545
+ if (lengthSquared === 0) return distance(point, a);
546
+ const t = ((point.x - a.x) * dx + (point.y - a.y) * dy) / lengthSquared;
547
+ const clamped = t < 0 ? 0 : t > 1 ? 1 : t;
548
+ return distance(point, {
549
+ x: a.x + clamped * dx,
550
+ y: a.y + clamped * dy
551
+ });
552
+ }
553
+ /** True when `point` lies on any segment of the polyline, endpoints included. */
554
+ function pointOnPolyline(points, point, tolerance = 0) {
555
+ if (points.length === 1) {
556
+ const [only] = points;
557
+ return only !== void 0 && pointsEqual(only, point, tolerance);
558
+ }
559
+ for (let index = 1; index < points.length; index += 1) {
560
+ const previous = points[index - 1];
561
+ const current = points[index];
562
+ if (previous !== void 0 && current !== void 0 && pointOnSegment(point, previous, current, tolerance)) return true;
563
+ }
564
+ return false;
565
+ }
566
+ function boundsOfPoints(points) {
567
+ const [first, ...rest] = points;
568
+ if (first === void 0) return;
569
+ let minX = first.x;
570
+ let minY = first.y;
571
+ let maxX = first.x;
572
+ let maxY = first.y;
573
+ for (const point of rest) {
574
+ minX = Math.min(minX, point.x);
575
+ minY = Math.min(minY, point.y);
576
+ maxX = Math.max(maxX, point.x);
577
+ maxY = Math.max(maxY, point.y);
578
+ }
579
+ return {
580
+ x: minX,
581
+ y: minY,
582
+ width: maxX - minX,
583
+ height: maxY - minY
584
+ };
585
+ }
586
+ function unionBounds(a, b) {
587
+ if (a === void 0) return b;
588
+ if (b === void 0) return a;
589
+ const minX = Math.min(a.x, b.x);
590
+ const minY = Math.min(a.y, b.y);
591
+ const maxX = Math.max(a.x + a.width, b.x + b.width);
592
+ const maxY = Math.max(a.y + a.height, b.y + b.height);
593
+ return {
594
+ x: minX,
595
+ y: minY,
596
+ width: maxX - minX,
597
+ height: maxY - minY
598
+ };
599
+ }
600
+ //#endregion
601
+ //#region src/schematic/geometry/transform.ts
602
+ /** Rotates counter-clockwise around the origin. Rotation is expressed in degrees. */
603
+ function rotatePoint(point, degrees) {
604
+ const radians = degrees * Math.PI / 180;
605
+ const cos = Math.cos(radians);
606
+ const sin = Math.sin(radians);
607
+ return {
608
+ x: point.x * cos - point.y * sin,
609
+ y: point.x * sin + point.y * cos
610
+ };
611
+ }
612
+ /** Applies a symbol placement to a pre-placement anchor. */
613
+ function symbolPinPosition(placement, anchor) {
614
+ const rotated = rotatePoint(anchor, placement.rotation);
615
+ return {
616
+ x: placement.x + rotated.x,
617
+ y: placement.y + rotated.y
618
+ };
619
+ }
620
+ /**
621
+ * Local anchor of a symbol's reference designator: the explicit override, else the body's
622
+ * top-left corner lifted by {@link REFERENCE_DEFAULT_OFFSET}.
623
+ */
624
+ function referenceAnchor(outline, override) {
625
+ if (override !== void 0) return {
626
+ x: override.x,
627
+ y: override.y
628
+ };
629
+ if (outline === void 0) return {
630
+ x: 0,
631
+ y: -4
632
+ };
633
+ return {
634
+ x: outline.x,
635
+ y: outline.y - 4
636
+ };
637
+ }
638
+ /** World position of a symbol's reference designator anchor. */
639
+ function referencePosition(placement, outline, override) {
640
+ return symbolPinPosition(placement, referenceAnchor(outline, override));
641
+ }
642
+ /** Bounding box of a symbol: its transformed outline, else its pins, else its origin. */
643
+ function symbolBounds(placement, outline, pins) {
644
+ if (outline !== void 0) return boundsOfPoints([
645
+ {
646
+ x: outline.x,
647
+ y: outline.y
648
+ },
649
+ {
650
+ x: outline.x + outline.width,
651
+ y: outline.y
652
+ },
653
+ {
654
+ x: outline.x,
655
+ y: outline.y + outline.height
656
+ },
657
+ {
658
+ x: outline.x + outline.width,
659
+ y: outline.y + outline.height
660
+ }
661
+ ].map((corner) => symbolPinPosition(placement, corner)));
662
+ const anchors = Object.values(pins).map((anchor) => symbolPinPosition(placement, anchor));
663
+ if (anchors.length > 0) return boundsOfPoints(anchors);
664
+ return {
665
+ x: placement.x,
666
+ y: placement.y,
667
+ width: 0,
668
+ height: 0
669
+ };
670
+ }
671
+ //#endregion
672
+ //#region src/schematic/netlist/derive.ts
673
+ /**
674
+ * Derives nets from wires, explicit junctions and symbol connection points (#6).
675
+ *
676
+ * @throws {SchematicError} when a device cannot be resolved or a net has conflicting labels.
677
+ */
678
+ function deriveSchematicTopology(document, options = {}) {
679
+ const diagnostics = [];
680
+ const tolerance = options.tolerance ?? 0;
681
+ const { devices } = options;
682
+ const { symbols, wires, junctions } = document.sheet;
683
+ const connections = [];
684
+ let bounds;
685
+ for (const symbol of symbols) {
686
+ bounds = unionBounds(bounds, {
687
+ x: symbol.x,
688
+ y: symbol.y,
689
+ width: 0,
690
+ height: 0
691
+ });
692
+ const builtin = builtinSymbolKind(symbol.device);
693
+ if (builtin !== void 0) {
694
+ const net = netNameOf(symbol);
695
+ if (net !== void 0) connections.push({
696
+ kind: builtin,
697
+ symbolId: symbol.id,
698
+ point: {
699
+ x: symbol.x,
700
+ y: symbol.y
701
+ },
702
+ name: net
703
+ });
704
+ continue;
705
+ }
706
+ const reference = parseDeviceReference(symbol.device);
707
+ if (reference === void 0) {
708
+ if (devices !== void 0) diagnostics.push({
709
+ severity: "error",
710
+ code: "device/invalid-reference",
711
+ message: `Symbol "${symbol.id}" has an invalid device reference ${JSON.stringify(symbol.device)}.`,
712
+ entity: {
713
+ kind: "symbol",
714
+ id: symbol.id
715
+ }
716
+ });
717
+ continue;
718
+ }
719
+ if (devices === void 0) continue;
720
+ const definition = devices.getDeviceSymbol(reference);
721
+ if (devices.getDeviceName(reference) === void 0) {
722
+ diagnostics.push({
723
+ severity: "error",
724
+ code: "device/unresolved",
725
+ message: `Symbol "${symbol.id}" references unresolved device ${JSON.stringify(symbol.device)}.`,
726
+ entity: {
727
+ kind: "symbol",
728
+ id: symbol.id
729
+ }
730
+ });
731
+ continue;
732
+ }
733
+ if (definition === void 0) continue;
734
+ bounds = unionBounds(bounds, symbolBounds(symbol, definition.bounds, definition.pins));
735
+ for (const [pinId, anchor] of Object.entries(definition.pins)) {
736
+ const position = symbolPinPosition(symbol, anchor);
737
+ connections.push({
738
+ kind: "pin",
739
+ symbolId: symbol.id,
740
+ pinId,
741
+ point: position
742
+ });
743
+ bounds = unionBounds(bounds, {
744
+ x: position.x,
745
+ y: position.y,
746
+ width: 0,
747
+ height: 0
748
+ });
749
+ }
750
+ }
751
+ for (const wire of wires) bounds = unionBounds(bounds, boundsOfPoints(wire.points));
752
+ const wireIndex = /* @__PURE__ */ new Map();
753
+ wires.forEach((wire, index) => wireIndex.set(wire.id, index));
754
+ const wireCount = wires.length;
755
+ const parent = new Array(wireCount + connections.length);
756
+ for (let index = 0; index < parent.length; index += 1) parent[index] = index;
757
+ const find = (node) => {
758
+ let root = node;
759
+ while (parent[root] !== root) {
760
+ const next = parent[root];
761
+ parent[root] = parent[next];
762
+ root = next;
763
+ }
764
+ return root;
765
+ };
766
+ const union = (a, b) => {
767
+ const rootA = find(a);
768
+ const rootB = find(b);
769
+ if (rootA !== rootB) parent[rootB] = rootA;
770
+ };
771
+ for (const junction of junctions) {
772
+ const [first, ...rest] = junction.wires.map((wireId) => wireIndex.get(wireId)).filter((index) => index !== void 0);
773
+ if (first === void 0) continue;
774
+ for (const target of rest) union(first, target);
775
+ }
776
+ /** Wire index → connection indexes attached to it. */
777
+ const wireAttachments = /* @__PURE__ */ new Map();
778
+ connections.forEach((connection, connectionIndex) => {
779
+ wires.forEach((wire, index) => {
780
+ if (!pointOnPolyline(wire.points, connection.point, tolerance)) return;
781
+ union(index, wireCount + connectionIndex);
782
+ const attached = wireAttachments.get(index);
783
+ if (attached === void 0) wireAttachments.set(index, [connectionIndex]);
784
+ else attached.push(connectionIndex);
785
+ });
786
+ });
787
+ const groups = /* @__PURE__ */ new Map();
788
+ const groupOf = (root) => {
789
+ const existing = groups.get(root);
790
+ if (existing !== void 0) return existing;
791
+ const created = {
792
+ wires: [],
793
+ connections: []
794
+ };
795
+ groups.set(root, created);
796
+ return created;
797
+ };
798
+ wires.forEach((_, index) => {
799
+ groupOf(find(index)).wires.push(index);
800
+ });
801
+ connections.forEach((_, connectionIndex) => {
802
+ groupOf(find(wireCount + connectionIndex)).connections.push(connectionIndex);
803
+ });
804
+ const netOfConnection = /* @__PURE__ */ new Map();
805
+ const nets = [];
806
+ for (const group of mergeGroupsByName(groups, connections, diagnostics)) {
807
+ const connected = group.wires.length > 0 || group.connections.length >= 2;
808
+ if (group.connections.length === 0 || !connected) continue;
809
+ const name = group.name;
810
+ const members = group.connections.map((index) => connections[index]).map((connection) => {
811
+ if (connection === void 0) return;
812
+ return connection.kind === "pin" ? {
813
+ kind: "pin",
814
+ symbolId: connection.symbolId,
815
+ pinId: connection.pinId
816
+ } : {
817
+ kind: connection.kind,
818
+ symbolId: connection.symbolId
819
+ };
820
+ }).filter((member) => member !== void 0).sort(compareMembers);
821
+ const net = {
822
+ name,
823
+ wireIds: group.wires.map((index) => wires[index]?.id).filter((id) => id !== void 0).sort(),
824
+ members
825
+ };
826
+ nets.push(net);
827
+ for (const index of group.connections) netOfConnection.set(index, net);
828
+ }
829
+ const pins = [];
830
+ connections.forEach((connection, index) => {
831
+ if (connection.kind !== "pin") return;
832
+ const net = netOfConnection.get(index);
833
+ pins.push({
834
+ symbolId: connection.symbolId,
835
+ pinId: connection.pinId,
836
+ position: connection.point,
837
+ open: net === void 0,
838
+ netName: net?.name ?? null
839
+ });
840
+ });
841
+ pins.sort((a, b) => a.symbolId.localeCompare(b.symbolId) || a.pinId.localeCompare(b.pinId));
842
+ const danglingWireEnds = [];
843
+ wires.forEach((wire, index) => {
844
+ const endpoints = [{
845
+ end: "start",
846
+ point: wire.points[0]
847
+ }, {
848
+ end: "end",
849
+ point: wire.points[wire.points.length - 1]
850
+ }];
851
+ for (const { end, point } of endpoints) {
852
+ if (point === void 0) continue;
853
+ const attached = (wireAttachments.get(index) ?? []).some((connectionIndex) => {
854
+ const connection = connections[connectionIndex];
855
+ return connection !== void 0 && pointsEqual(connection.point, point, tolerance);
856
+ });
857
+ const junctioned = junctions.some((junction) => junction.wires.includes(wire.id) && pointsEqual({
858
+ x: junction.x,
859
+ y: junction.y
860
+ }, point, tolerance));
861
+ if (!attached && !junctioned) danglingWireEnds.push({
862
+ wireId: wire.id,
863
+ end,
864
+ point
865
+ });
866
+ }
867
+ });
868
+ danglingWireEnds.sort((a, b) => a.wireId.localeCompare(b.wireId) || a.end.localeCompare(b.end));
869
+ throwIfErrors(diagnostics);
870
+ return {
871
+ nets: nets.sort((a, b) => compareNetNames(a.name, b.name) || compareMembers(a.members[0], b.members[0])),
872
+ pins,
873
+ danglingWireEnds,
874
+ bounds
875
+ };
876
+ }
877
+ /**
878
+ * Folds groups that carry the same net name into one group: net names are the merge key, so two
879
+ * `VCC` ports on separate wires are the same net (KiCad-style name-based connectivity).
880
+ * A group that declares two different names is an error.
881
+ */
882
+ function mergeGroupsByName(groups, connections, diagnostics) {
883
+ const byName = /* @__PURE__ */ new Map();
884
+ const merged = [];
885
+ for (const group of groups.values()) {
886
+ const names = group.connections.map((index) => connections[index]).filter((connection) => connection !== void 0 && connection.kind !== "pin").map((connection) => connection.name).sort();
887
+ if (names.length > 1) diagnostics.push({
888
+ severity: "error",
889
+ code: "net/naming-conflict",
890
+ message: `Net has conflicting names: ${names.map((item) => JSON.stringify(item)).join(", ")}.`
891
+ });
892
+ const name = names.length === 1 ? names[0] : null;
893
+ if (name === null || group.wires.length === 0 && group.connections.length < 2) {
894
+ merged.push({
895
+ wires: group.wires,
896
+ connections: group.connections,
897
+ name
898
+ });
899
+ continue;
900
+ }
901
+ const existing = byName.get(name);
902
+ if (existing === void 0) {
903
+ const created = {
904
+ wires: [...group.wires],
905
+ connections: [...group.connections]
906
+ };
907
+ byName.set(name, created);
908
+ merged.push({
909
+ ...created,
910
+ name
911
+ });
912
+ continue;
913
+ }
914
+ existing.wires.push(...group.wires);
915
+ existing.connections.push(...group.connections);
916
+ }
917
+ return merged;
918
+ }
919
+ var KIND_ORDER = [
920
+ "label",
921
+ "power",
922
+ "ground",
923
+ "pin"
924
+ ];
925
+ function compareMembers(a, b) {
926
+ if (a === void 0 || b === void 0) return a === void 0 ? b === void 0 ? 0 : 1 : -1;
927
+ if (a.kind !== b.kind) return KIND_ORDER.indexOf(a.kind) - KIND_ORDER.indexOf(b.kind);
928
+ const bySymbol = a.symbolId.localeCompare(b.symbolId);
929
+ if (bySymbol !== 0) return bySymbol;
930
+ const pinA = a.kind === "pin" ? a.pinId : "";
931
+ const pinB = b.kind === "pin" ? b.pinId : "";
932
+ return pinA.localeCompare(pinB);
933
+ }
934
+ function compareNetNames(a, b) {
935
+ if (a === null || b === null) return a === null ? b === null ? 0 : 1 : -1;
936
+ return a.localeCompare(b);
937
+ }
938
+ //#endregion
939
+ //#region src/schematic/load/load.ts
940
+ /** Parses a schematic and derives its topology. @throws {SchematicError} on any contract violation. */
941
+ function loadSchematic(source, options = {}) {
942
+ const document = parseSchematic(source);
943
+ return {
944
+ document,
945
+ topology: deriveSchematicTopology(document, options)
946
+ };
947
+ }
948
+ //#endregion
949
+ //#region src/schematic/device/properties.ts
950
+ /** Property value types a device may declare (#7). */
951
+ var PROPERTY_TYPES = [
952
+ "string",
953
+ "integer",
954
+ "number",
955
+ "boolean",
956
+ "color",
957
+ "enum",
958
+ "image",
959
+ "binary"
960
+ ];
961
+ var COLOR_PATTERN = /^#([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/;
962
+ function isPropertyType(value) {
963
+ return typeof value === "string" && PROPERTY_TYPES.includes(value);
964
+ }
965
+ /** Uppercases a `#RRGGBB` / `#RRGGBBAA` color; `undefined` when the text is not a color. */
966
+ function normalizeColor(value) {
967
+ return COLOR_PATTERN.test(value) ? value.toUpperCase() : void 0;
968
+ }
969
+ /** Parses one declared property; `undefined` when the type is unknown or the descriptor unusable. */
970
+ function parsePropertyDescriptor(key, raw) {
971
+ if (!isRecord(raw)) return;
972
+ const type = raw["type"];
973
+ if (!isPropertyType(type)) return;
974
+ const draft = {
975
+ key,
976
+ type
977
+ };
978
+ const title = readText(raw["title"]);
979
+ if (title !== void 0) draft.title = title;
980
+ const description = readText(raw["description"]);
981
+ if (description !== void 0) draft.description = description;
982
+ if (raw["nullable"] === true) draft.nullable = true;
983
+ if (type === "string") {
984
+ const maxLength = readCount(raw["maxLength"]);
985
+ if (maxLength !== void 0) draft.maxLength = maxLength;
986
+ }
987
+ if (type === "integer" || type === "number") {
988
+ const min = readFinite(raw["min"]);
989
+ if (min !== void 0) draft.min = min;
990
+ const max = readFinite(raw["max"]);
991
+ if (max !== void 0) draft.max = max;
992
+ const step = readFinite(raw["step"]);
993
+ if (step !== void 0 && step > 0) draft.step = step;
994
+ if (type === "number") {
995
+ const exclusiveMin = readFinite(raw["exclusiveMin"]);
996
+ if (exclusiveMin !== void 0) draft.exclusiveMin = exclusiveMin;
997
+ const unit = readText(raw["unit"]);
998
+ if (unit !== void 0) draft.unit = unit;
999
+ }
1000
+ }
1001
+ if (type === "color" && raw["alpha"] === true) draft.alpha = true;
1002
+ if (type === "enum") {
1003
+ const options = readOptions(raw["options"]);
1004
+ if (options === void 0) return;
1005
+ draft.options = options;
1006
+ }
1007
+ if (type === "image" || type === "binary") {
1008
+ const maxBytes = readCount(raw["maxBytes"]);
1009
+ if (maxBytes !== void 0) draft.maxBytes = maxBytes;
1010
+ if (type === "image") {
1011
+ const maxPixels = readCount(raw["maxPixels"]);
1012
+ if (maxPixels !== void 0) draft.maxPixels = maxPixels;
1013
+ }
1014
+ }
1015
+ const defaultValue = raw["default"];
1016
+ if (defaultValue !== void 0 && propertyValueProblem(draft, defaultValue) === void 0) draft.default = defaultValue;
1017
+ return draft;
1018
+ }
1019
+ /** Parses the `properties` object of a device description, skipping unusable declarations. */
1020
+ function readPropertyDescriptors(raw) {
1021
+ const descriptors = /* @__PURE__ */ new Map();
1022
+ if (!isRecord(raw)) return descriptors;
1023
+ for (const [key, value] of Object.entries(raw)) {
1024
+ if (key.length === 0) continue;
1025
+ const descriptor = parsePropertyDescriptor(key, value);
1026
+ if (descriptor !== void 0) descriptors.set(key, descriptor);
1027
+ }
1028
+ return descriptors;
1029
+ }
1030
+ /** Constraint violation of `value` for `descriptor`; `undefined` when the value is acceptable. */
1031
+ function propertyValueProblem(descriptor, value) {
1032
+ const key = descriptor.key;
1033
+ if (value === null) return descriptor.nullable === true ? void 0 : {
1034
+ code: "schema/invalid-value",
1035
+ message: `Property "${key}" is not nullable.`
1036
+ };
1037
+ switch (descriptor.type) {
1038
+ case "string": {
1039
+ if (typeof value !== "string") return {
1040
+ code: "schema/invalid-type",
1041
+ message: `Property "${key}" must be a string.`
1042
+ };
1043
+ const maxLength = descriptor.maxLength;
1044
+ if (maxLength !== void 0 && [...value].length > maxLength) return {
1045
+ code: "schema/invalid-value",
1046
+ message: `Property "${key}" must be at most ${String(maxLength)} characters.`
1047
+ };
1048
+ return;
1049
+ }
1050
+ case "integer":
1051
+ if (typeof value !== "number" || !Number.isSafeInteger(value)) return {
1052
+ code: "schema/invalid-type",
1053
+ message: `Property "${key}" must be a safe integer.`
1054
+ };
1055
+ return boundProblem(descriptor, value);
1056
+ case "number":
1057
+ if (typeof value !== "number" || !Number.isFinite(value)) return {
1058
+ code: "schema/invalid-type",
1059
+ message: `Property "${key}" must be a finite number.`
1060
+ };
1061
+ if (descriptor.exclusiveMin !== void 0 && value <= descriptor.exclusiveMin) return {
1062
+ code: "schema/invalid-value",
1063
+ message: `Property "${key}" must be greater than ${String(descriptor.exclusiveMin)}.`
1064
+ };
1065
+ return boundProblem(descriptor, value);
1066
+ case "boolean": return typeof value === "boolean" ? void 0 : {
1067
+ code: "schema/invalid-type",
1068
+ message: `Property "${key}" must be a boolean.`
1069
+ };
1070
+ case "color": {
1071
+ if (typeof value !== "string") return {
1072
+ code: "schema/invalid-type",
1073
+ message: `Property "${key}" must be a string.`
1074
+ };
1075
+ const normalized = normalizeColor(value);
1076
+ if (normalized === void 0) return {
1077
+ code: "schema/invalid-value",
1078
+ message: `Property "${key}" must be a "#RRGGBB" color.`
1079
+ };
1080
+ if (descriptor.alpha !== true && normalized.length === 9) return {
1081
+ code: "schema/invalid-value",
1082
+ message: `Property "${key}" does not accept alpha; use "#RRGGBB".`
1083
+ };
1084
+ return;
1085
+ }
1086
+ case "enum": {
1087
+ if (typeof value !== "string") return {
1088
+ code: "schema/invalid-type",
1089
+ message: `Property "${key}" must be a string.`
1090
+ };
1091
+ const allowed = (descriptor.options ?? []).map((option) => option.value);
1092
+ return allowed.includes(value) ? void 0 : {
1093
+ code: "schema/invalid-value",
1094
+ message: `Property "${key}" must be one of ${allowed.map((entry) => JSON.stringify(entry)).join(", ")}.`
1095
+ };
1096
+ }
1097
+ case "image":
1098
+ case "binary": return isAssetReference(value) ? void 0 : {
1099
+ code: "schema/invalid-type",
1100
+ message: `Property "${key}" must be a resource reference { "src", "mode" }.`
1101
+ };
1102
+ }
1103
+ }
1104
+ /** Canonical stored value: colors are uppercased, everything else is stored as given. */
1105
+ function normalizePropertyValue(descriptor, value) {
1106
+ if (descriptor.type === "color" && typeof value === "string") return normalizeColor(value) ?? value;
1107
+ return value;
1108
+ }
1109
+ function boundProblem(descriptor, value) {
1110
+ const key = descriptor.key;
1111
+ if (descriptor.min !== void 0 && value < descriptor.min) return {
1112
+ code: "schema/invalid-value",
1113
+ message: `Property "${key}" must be at least ${String(descriptor.min)}.`
1114
+ };
1115
+ if (descriptor.max !== void 0 && value > descriptor.max) return {
1116
+ code: "schema/invalid-value",
1117
+ message: `Property "${key}" must be at most ${String(descriptor.max)}.`
1118
+ };
1119
+ }
1120
+ function readText(value) {
1121
+ return typeof value === "string" && value.length > 0 ? value : void 0;
1122
+ }
1123
+ function readFinite(value) {
1124
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
1125
+ }
1126
+ function readCount(value) {
1127
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0 ? value : void 0;
1128
+ }
1129
+ function readOptions(value) {
1130
+ if (!Array.isArray(value) || value.length === 0) return;
1131
+ const options = [];
1132
+ const seen = /* @__PURE__ */ new Set();
1133
+ for (const entry of value) {
1134
+ if (!isRecord(entry)) return;
1135
+ const optionValue = entry["value"];
1136
+ const label = entry["label"];
1137
+ if (typeof optionValue !== "string" || optionValue.length === 0 || typeof label !== "string" || label.length === 0 || seen.has(optionValue)) return;
1138
+ seen.add(optionValue);
1139
+ options.push({
1140
+ value: optionValue,
1141
+ label
1142
+ });
1143
+ }
1144
+ return options;
1145
+ }
1146
+ //#endregion
1147
+ //#region src/schematic/edit/ids.ts
1148
+ function usedIds(document, kind) {
1149
+ const ids = kind === "symbol" ? document.sheet.symbols.map((symbol) => symbol.id) : kind === "wire" ? document.sheet.wires.map((wire) => wire.id) : document.sheet.junctions.map((junction) => junction.id);
1150
+ return new Set(ids);
1151
+ }
1152
+ /**
1153
+ * Random UUID v4, falling back to `getRandomValues` (or `Math.random` in ancient runtimes).
1154
+ * Ids are opaque identity: they never collide with the designators and net names users edit.
1155
+ */
1156
+ function createUuid() {
1157
+ const source = randomSource();
1158
+ if (typeof source?.randomUUID === "function") return source.randomUUID();
1159
+ const bytes = /* @__PURE__ */ new Uint8Array(16);
1160
+ if (typeof source?.getRandomValues === "function") source.getRandomValues(bytes);
1161
+ else for (let index = 0; index < bytes.length; index += 1) bytes[index] = Math.floor(Math.random() * 256);
1162
+ bytes[6] = (bytes[6] ?? 0) & 15 | 64;
1163
+ bytes[8] = (bytes[8] ?? 0) & 63 | 128;
1164
+ const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
1165
+ return [
1166
+ hex.slice(0, 8),
1167
+ hex.slice(8, 12),
1168
+ hex.slice(12, 16),
1169
+ hex.slice(16, 20),
1170
+ hex.slice(20)
1171
+ ].join("-");
1172
+ }
1173
+ function randomSource() {
1174
+ return globalThis.crypto;
1175
+ }
1176
+ /** Fresh UUID v4 that no element of `kind` uses yet. */
1177
+ var defaultElementIdFactory = (kind, document) => {
1178
+ const used = usedIds(document, kind);
1179
+ let id = createUuid();
1180
+ while (used.has(id)) id = createUuid();
1181
+ return id;
1182
+ };
1183
+ //#endregion
1184
+ //#region src/schematic/edit/commands.ts
1185
+ /**
1186
+ * Applies an edit command to a copy of `document`.
1187
+ *
1188
+ * @throws {SchematicError} when the command is invalid; the input document is never mutated.
1189
+ */
1190
+ function applySchematicCommand(document, command, options = {}) {
1191
+ const symbols = [...document.sheet.symbols];
1192
+ const wires = [...document.sheet.wires];
1193
+ const junctions = [...document.sheet.junctions];
1194
+ const diagnostics = [];
1195
+ const effects = [];
1196
+ const createId = options.createId ?? defaultElementIdFactory;
1197
+ const draft = () => ({
1198
+ ...document,
1199
+ sheet: {
1200
+ symbols,
1201
+ wires,
1202
+ junctions
1203
+ }
1204
+ });
1205
+ switch (command.type) {
1206
+ case "add-symbol": {
1207
+ const device = requireDevice(command.device, diagnostics);
1208
+ const id = command.id ?? createId("symbol", draft());
1209
+ const properties = normalizeProperties(command.properties, deviceDescriptors(device, options), diagnostics);
1210
+ const reference = resolveReference(command.reference, void 0, ["sheet", "symbols"], diagnostics);
1211
+ if (device !== void 0) {
1212
+ const role = symbolRole(device);
1213
+ const designator = resolveDesignator(command.designator, role, id, diagnostics);
1214
+ const net = resolveNet(command.net, role, diagnostics);
1215
+ symbols.push(withProperties({
1216
+ id,
1217
+ device,
1218
+ x: command.x,
1219
+ y: command.y,
1220
+ rotation: command.rotation ?? 0,
1221
+ ...designator === void 0 ? {} : { designator },
1222
+ ...net === void 0 ? {} : { net },
1223
+ ...hasDesignator(role) && reference !== void 0 ? { reference } : {}
1224
+ }, properties));
1225
+ }
1226
+ break;
1227
+ }
1228
+ case "update-symbol": {
1229
+ const index = findIndex(symbols, command.id, "symbol", diagnostics);
1230
+ const current = index === -1 ? void 0 : symbols[index];
1231
+ if (current !== void 0) {
1232
+ const device = command.device === void 0 ? current.device : requireDevice(command.device, diagnostics);
1233
+ const properties = command.properties === void 0 ? current.properties : normalizeProperties(command.properties, deviceDescriptors(device, options), diagnostics);
1234
+ const reference = resolveReference(command.reference, current.reference, [
1235
+ "sheet",
1236
+ "symbols",
1237
+ String(index)
1238
+ ], diagnostics);
1239
+ if (device !== void 0) {
1240
+ const role = symbolRole(device);
1241
+ const designator = resolveOptionalText(command.designator, current.designator, hasDesignator(role), "designator", diagnostics);
1242
+ const net = resolveOptionalText(command.net, current.net, namesNet(role), "net", diagnostics);
1243
+ symbols[index] = withProperties({
1244
+ id: current.id,
1245
+ device,
1246
+ x: command.x ?? current.x,
1247
+ y: command.y ?? current.y,
1248
+ rotation: command.rotation ?? current.rotation,
1249
+ ...designator === void 0 ? {} : { designator },
1250
+ ...net === void 0 ? {} : { net },
1251
+ ...hasDesignator(role) && reference !== void 0 ? { reference } : {}
1252
+ }, properties);
1253
+ }
1254
+ }
1255
+ break;
1256
+ }
1257
+ case "remove-symbol": {
1258
+ const index = findIndex(symbols, command.id, "symbol", diagnostics);
1259
+ if (index !== -1) symbols.splice(index, 1);
1260
+ break;
1261
+ }
1262
+ case "set-symbol-property": {
1263
+ const index = findIndex(symbols, command.id, "symbol", diagnostics);
1264
+ const current = index === -1 ? void 0 : symbols[index];
1265
+ if (current !== void 0) {
1266
+ const value = checkPropertyValue(command.key, command.value, deviceDescriptors(current.device, options)?.get(command.key), diagnostics);
1267
+ if (value !== void 0) symbols[index] = withProperties(baseOf(current), {
1268
+ ...current.properties ?? {},
1269
+ [command.key]: value
1270
+ });
1271
+ }
1272
+ break;
1273
+ }
1274
+ case "remove-symbol-property": {
1275
+ const index = findIndex(symbols, command.id, "symbol", diagnostics);
1276
+ const current = index === -1 ? void 0 : symbols[index];
1277
+ if (current !== void 0) {
1278
+ const properties = { ...current.properties ?? {} };
1279
+ if (!Object.prototype.hasOwnProperty.call(properties, command.key)) diagnostics.push({
1280
+ severity: "error",
1281
+ code: "edit/unknown-entity",
1282
+ message: `Symbol "${current.id}" has no property ${JSON.stringify(command.key)}.`,
1283
+ entity: {
1284
+ kind: "symbol",
1285
+ id: current.id
1286
+ }
1287
+ });
1288
+ else {
1289
+ delete properties[command.key];
1290
+ symbols[index] = withProperties(baseOf(current), properties);
1291
+ }
1292
+ }
1293
+ break;
1294
+ }
1295
+ case "add-wire": {
1296
+ const wire = makeWire(command.id ?? createId("wire", draft()), command.points, diagnostics);
1297
+ if (wire !== void 0) wires.push(wire);
1298
+ break;
1299
+ }
1300
+ case "set-wire-points": {
1301
+ const index = findIndex(wires, command.id, "wire", diagnostics);
1302
+ if (index !== -1) {
1303
+ const wire = makeWire(command.id, command.points, diagnostics);
1304
+ if (wire !== void 0) wires[index] = wire;
1305
+ }
1306
+ break;
1307
+ }
1308
+ case "remove-wire": {
1309
+ const index = findIndex(wires, command.id, "wire", diagnostics);
1310
+ if (index !== -1) {
1311
+ wires.splice(index, 1);
1312
+ for (let junctionIndex = junctions.length - 1; junctionIndex >= 0; junctionIndex -= 1) {
1313
+ const junction = junctions[junctionIndex];
1314
+ if (junction === void 0 || !junction.wires.includes(command.id)) continue;
1315
+ effects.push({
1316
+ kind: "junction-removed",
1317
+ id: junction.id,
1318
+ reason: "wire-removed"
1319
+ });
1320
+ junctions.splice(junctionIndex, 1);
1321
+ }
1322
+ }
1323
+ break;
1324
+ }
1325
+ case "add-junction": {
1326
+ const junction = makeJunction(command.id ?? createId("junction", draft()), command.x, command.y, command.wires, diagnostics);
1327
+ if (junction !== void 0) junctions.push(junction);
1328
+ break;
1329
+ }
1330
+ case "update-junction": {
1331
+ const index = findIndex(junctions, command.id, "junction", diagnostics);
1332
+ const current = index === -1 ? void 0 : junctions[index];
1333
+ if (current !== void 0) {
1334
+ const junction = makeJunction(current.id, command.x ?? current.x, command.y ?? current.y, command.wires ?? current.wires, diagnostics);
1335
+ if (junction !== void 0) junctions[index] = junction;
1336
+ }
1337
+ break;
1338
+ }
1339
+ case "remove-junction": {
1340
+ const index = findIndex(junctions, command.id, "junction", diagnostics);
1341
+ if (index !== -1) junctions.splice(index, 1);
1342
+ break;
1343
+ }
1344
+ default: diagnostics.push({
1345
+ severity: "error",
1346
+ code: "edit/invalid-command",
1347
+ message: `Unsupported command ${JSON.stringify(command.type)}.`
1348
+ });
1349
+ }
1350
+ throwIfErrors(diagnostics);
1351
+ const result = validateSheet(draft(), options, diagnostics, effects);
1352
+ throwIfErrors(diagnostics);
1353
+ for (const effect of effects) options.onEffect?.(effect);
1354
+ return result;
1355
+ }
1356
+ function validateSheet(candidate, options, diagnostics, effects) {
1357
+ const parsed = parseSchematic(candidate);
1358
+ const tolerance = options.tolerance ?? 0;
1359
+ const wireById = new Map(parsed.sheet.wires.map((wire) => [wire.id, wire]));
1360
+ const survivingJunctions = [];
1361
+ let cascaded = false;
1362
+ for (const junction of parsed.sheet.junctions) {
1363
+ if (!junction.wires.some((wireId) => {
1364
+ const wire = wireById.get(wireId);
1365
+ return wire === void 0 || !pointOnPolyline(wire.points, {
1366
+ x: junction.x,
1367
+ y: junction.y
1368
+ }, tolerance);
1369
+ })) {
1370
+ survivingJunctions.push(junction);
1371
+ continue;
1372
+ }
1373
+ if (options.onJunctionConflict === "remove") {
1374
+ cascaded = true;
1375
+ effects.push({
1376
+ kind: "junction-removed",
1377
+ id: junction.id,
1378
+ reason: "junction-detached"
1379
+ });
1380
+ continue;
1381
+ }
1382
+ diagnostics.push({
1383
+ severity: "error",
1384
+ code: "junction/off-wire",
1385
+ message: `Junction "${junction.id}" no longer lies on every referenced wire.`,
1386
+ pointer: toPointer(["sheet", "junctions"]),
1387
+ entity: {
1388
+ kind: "junction",
1389
+ id: junction.id
1390
+ }
1391
+ });
1392
+ }
1393
+ if (!cascaded) return parsed;
1394
+ return {
1395
+ ...parsed,
1396
+ sheet: {
1397
+ ...parsed.sheet,
1398
+ junctions: survivingJunctions
1399
+ }
1400
+ };
1401
+ }
1402
+ function makeWire(id, points, diagnostics) {
1403
+ if (points.length < 2) {
1404
+ diagnostics.push({
1405
+ severity: "error",
1406
+ code: "wire/invalid-points",
1407
+ message: `Wire "${id}" must contain at least two points.`,
1408
+ entity: {
1409
+ kind: "wire",
1410
+ id
1411
+ }
1412
+ });
1413
+ return;
1414
+ }
1415
+ return {
1416
+ id,
1417
+ points: points.map((point) => ({
1418
+ x: point.x,
1419
+ y: point.y
1420
+ }))
1421
+ };
1422
+ }
1423
+ function makeJunction(id, x, y, wires, diagnostics) {
1424
+ if (wires.length < 2) {
1425
+ diagnostics.push({
1426
+ severity: "error",
1427
+ code: "schema/invalid-value",
1428
+ message: `Junction "${id}" must reference at least two wires.`,
1429
+ entity: {
1430
+ kind: "junction",
1431
+ id
1432
+ }
1433
+ });
1434
+ return;
1435
+ }
1436
+ return {
1437
+ id,
1438
+ x,
1439
+ y,
1440
+ wires: [...wires]
1441
+ };
1442
+ }
1443
+ function requireDevice(device, diagnostics) {
1444
+ if (parseDeviceReference(device) === void 0) {
1445
+ diagnostics.push({
1446
+ severity: "error",
1447
+ code: "device/invalid-reference",
1448
+ message: `Invalid device reference ${JSON.stringify(device)}.`
1449
+ });
1450
+ return;
1451
+ }
1452
+ return device;
1453
+ }
1454
+ function baseOf(symbol) {
1455
+ return {
1456
+ id: symbol.id,
1457
+ device: symbol.device,
1458
+ x: symbol.x,
1459
+ y: symbol.y,
1460
+ rotation: symbol.rotation,
1461
+ ...symbol.designator === void 0 ? {} : { designator: symbol.designator },
1462
+ ...symbol.net === void 0 ? {} : { net: symbol.net }
1463
+ };
1464
+ }
1465
+ /**
1466
+ * Reference designator for a new symbol: components keep `undefined` (the id doubles as the
1467
+ * default designator), everything else must not have one.
1468
+ */
1469
+ function resolveDesignator(value, role, id, diagnostics) {
1470
+ if (!hasDesignator(role)) {
1471
+ if (value !== void 0) diagnostics.push({
1472
+ severity: "error",
1473
+ code: "symbol/unsupported-field",
1474
+ message: `Symbols of role "${role}" carry no reference designator.`
1475
+ });
1476
+ return;
1477
+ }
1478
+ const trimmed = value?.trim();
1479
+ return trimmed === void 0 || trimmed.length === 0 || trimmed === id ? void 0 : trimmed;
1480
+ }
1481
+ /** Net name for a new symbol; labels need one, power and ground fall back to their default. */
1482
+ function resolveNet(value, role, diagnostics) {
1483
+ if (!namesNet(role)) {
1484
+ if (value !== void 0) diagnostics.push({
1485
+ severity: "error",
1486
+ code: "symbol/unsupported-field",
1487
+ message: `Symbols of role "${role}" name no net.`
1488
+ });
1489
+ return;
1490
+ }
1491
+ const trimmed = value?.trim();
1492
+ if (trimmed !== void 0 && trimmed.length > 0) return trimmed;
1493
+ const fallback = defaultNetName(role);
1494
+ if (fallback === void 0) diagnostics.push({
1495
+ severity: "error",
1496
+ code: "symbol/missing-net",
1497
+ message: `Symbols of role "${role}" must name the net they attach to.`
1498
+ });
1499
+ return fallback;
1500
+ }
1501
+ /** `null` clears the field, `undefined` keeps it, and unsupported roles cannot set it. */
1502
+ function resolveOptionalText(value, current, supported, field, diagnostics) {
1503
+ if (value === void 0) return current;
1504
+ if (!supported) {
1505
+ diagnostics.push({
1506
+ severity: "error",
1507
+ code: "symbol/unsupported-field",
1508
+ message: `This symbol must not declare "${field}".`
1509
+ });
1510
+ return;
1511
+ }
1512
+ if (value === null) return;
1513
+ const trimmed = value.trim();
1514
+ if (trimmed.length === 0) {
1515
+ diagnostics.push({
1516
+ severity: "error",
1517
+ code: "schema/invalid-value",
1518
+ message: `${field === "net" ? "Net name" : "Reference designator"} must not be empty.`
1519
+ });
1520
+ return;
1521
+ }
1522
+ return trimmed;
1523
+ }
1524
+ function withProperties(symbol, properties) {
1525
+ if (properties === void 0 || Object.keys(properties).length === 0) return symbol;
1526
+ return {
1527
+ ...symbol,
1528
+ properties
1529
+ };
1530
+ }
1531
+ /** `reference: null` clears the override and falls back to the default anchor. */
1532
+ function resolveReference(value, current, path, diagnostics) {
1533
+ if (value === void 0) return current;
1534
+ if (value === null) return;
1535
+ if (typeof value !== "object" || !Number.isFinite(value.x) || !Number.isFinite(value.y)) {
1536
+ diagnostics.push({
1537
+ severity: "error",
1538
+ code: "schema/invalid-value",
1539
+ message: "Symbol reference must contain finite x and y numbers.",
1540
+ pointer: toPointer([...path, "reference"])
1541
+ });
1542
+ return;
1543
+ }
1544
+ return {
1545
+ x: value.x,
1546
+ y: value.y
1547
+ };
1548
+ }
1549
+ /** Property descriptors of `device`, when the caller supplied a device registry (#7). */
1550
+ function deviceDescriptors(device, options) {
1551
+ const reference = device === void 0 ? void 0 : parseDeviceReference(device);
1552
+ return reference === void 0 ? void 0 : options.devices?.getDevicePropertyDescriptors?.(reference);
1553
+ }
1554
+ function normalizeProperties(properties, descriptors, diagnostics) {
1555
+ if (properties === void 0) return;
1556
+ const normalized = {};
1557
+ for (const [key, value] of Object.entries(properties)) {
1558
+ if (key.length === 0) {
1559
+ diagnostics.push({
1560
+ severity: "error",
1561
+ code: "schema/invalid-value",
1562
+ message: "Property keys must not be empty."
1563
+ });
1564
+ continue;
1565
+ }
1566
+ const checked = checkPropertyValue(key, value, descriptors?.get(key), diagnostics);
1567
+ if (checked !== void 0) normalized[key] = checked;
1568
+ }
1569
+ return normalized;
1570
+ }
1571
+ function checkPropertyValue(key, value, descriptor, diagnostics) {
1572
+ if (key.length === 0) {
1573
+ diagnostics.push({
1574
+ severity: "error",
1575
+ code: "schema/invalid-value",
1576
+ message: "Property keys must not be empty."
1577
+ });
1578
+ return;
1579
+ }
1580
+ if (descriptor !== void 0) {
1581
+ const problem = propertyValueProblem(descriptor, value);
1582
+ if (problem !== void 0) {
1583
+ diagnostics.push({
1584
+ severity: "error",
1585
+ code: problem.code,
1586
+ message: problem.message
1587
+ });
1588
+ return;
1589
+ }
1590
+ return normalizePropertyValue(descriptor, value);
1591
+ }
1592
+ if (typeof value === "string" || typeof value === "boolean" || value === null) return value;
1593
+ if (typeof value === "number") {
1594
+ if (!Number.isFinite(value)) {
1595
+ diagnostics.push({
1596
+ severity: "error",
1597
+ code: "schema/invalid-value",
1598
+ message: `Property "${key}" must be a finite number.`
1599
+ });
1600
+ return;
1601
+ }
1602
+ return value;
1603
+ }
1604
+ if (isAssetReference(value)) return value;
1605
+ diagnostics.push({
1606
+ severity: "error",
1607
+ code: "schema/invalid-value",
1608
+ message: `Property "${key}" must be a string, finite number, boolean, resource reference or null.`
1609
+ });
1610
+ }
1611
+ function findIndex(items, id, kind, diagnostics) {
1612
+ const index = items.findIndex((item) => item.id === id);
1613
+ if (index === -1) diagnostics.push({
1614
+ severity: "error",
1615
+ code: "edit/unknown-entity",
1616
+ message: `Unknown ${kind} "${id}".`,
1617
+ entity: {
1618
+ kind,
1619
+ id
1620
+ }
1621
+ });
1622
+ return index;
1623
+ }
1624
+ //#endregion
1625
+ //#region src/schematic/edit/junctions.ts
1626
+ /**
1627
+ * Junctions that must let go of `wireId`: its new geometry no longer passes through them.
1628
+ *
1629
+ * A junction left with fewer than two wires is dropped instead of being kept dangling. Pure
1630
+ * geometry: the caller applies the resulting commands.
1631
+ */
1632
+ function planJunctionRelease(document, wireId, points, options = {}) {
1633
+ const tolerance = options.tolerance ?? 0;
1634
+ const releases = [];
1635
+ for (const junction of document.sheet.junctions) {
1636
+ if (!junction.wires.includes(wireId)) continue;
1637
+ if (pointOnPolyline(points, {
1638
+ x: junction.x,
1639
+ y: junction.y
1640
+ }, tolerance)) continue;
1641
+ const remaining = junction.wires.filter((id) => id !== wireId);
1642
+ releases.push({
1643
+ id: junction.id,
1644
+ wires: remaining.length < 2 ? void 0 : remaining
1645
+ });
1646
+ }
1647
+ return releases;
1648
+ }
1649
+ /**
1650
+ * The junction sitting exactly at `point`, extended with `wireId`, so a wire that ends on an
1651
+ * existing junction actually joins it. `undefined` when there is nothing to join.
1652
+ */
1653
+ function joinJunctionAt(document, wireId, point) {
1654
+ const junction = document.sheet.junctions.find((candidate) => candidate.x === point.x && candidate.y === point.y);
1655
+ if (junction === void 0 || junction.wires.includes(wireId)) return;
1656
+ return {
1657
+ id: junction.id,
1658
+ wires: [...junction.wires, wireId]
1659
+ };
1660
+ }
1661
+ //#endregion
1662
+ //#region src/schematic/edit/wires.ts
1663
+ /**
1664
+ * Splits `wireId` at `point`, which must lie strictly inside the polyline.
1665
+ * Junctions that only touch the second half are re-pointed to the new wire.
1666
+ *
1667
+ * @throws {SchematicError} when the wire is unknown or `point` is not an interior point.
1668
+ */
1669
+ function splitWireAt(document, wireId, point, options = {}) {
1670
+ const tolerance = options.tolerance ?? 0;
1671
+ const wire = document.sheet.wires.find((candidate) => candidate.id === wireId);
1672
+ if (wire === void 0) throw new SchematicError([{
1673
+ severity: "error",
1674
+ code: "edit/unknown-entity",
1675
+ message: `Unknown wire "${wireId}".`,
1676
+ entity: {
1677
+ kind: "wire",
1678
+ id: wireId
1679
+ }
1680
+ }]);
1681
+ const split = splitPoints(wire.points, point, tolerance);
1682
+ if (split === void 0) throw new SchematicError([{
1683
+ severity: "error",
1684
+ code: "wire/invalid-points",
1685
+ message: `Point (${point.x}, ${point.y}) is not an interior point of wire "${wireId}".`,
1686
+ entity: {
1687
+ kind: "wire",
1688
+ id: wireId
1689
+ }
1690
+ }]);
1691
+ const firstWireId = wireId;
1692
+ const secondWireId = options.createId?.("wire", document) ?? defaultElementIdFactory("wire", document);
1693
+ if (secondWireId === firstWireId) throw new SchematicError([{
1694
+ severity: "error",
1695
+ code: "edit/invalid-command",
1696
+ message: `Generated wire id "${secondWireId}" collides with the split wire.`,
1697
+ entity: {
1698
+ kind: "wire",
1699
+ id: wireId
1700
+ }
1701
+ }]);
1702
+ const [firstPoints, secondPoints] = split;
1703
+ const wires = [];
1704
+ for (const candidate of document.sheet.wires) {
1705
+ if (candidate.id !== wireId) {
1706
+ wires.push(candidate);
1707
+ continue;
1708
+ }
1709
+ wires.push({
1710
+ id: firstWireId,
1711
+ points: firstPoints
1712
+ });
1713
+ wires.push({
1714
+ id: secondWireId,
1715
+ points: secondPoints
1716
+ });
1717
+ }
1718
+ const junctions = document.sheet.junctions.map((junction) => remapJunction(junction, wireId, firstWireId, secondWireId, firstPoints, secondPoints, point, tolerance));
1719
+ return {
1720
+ document: validate({
1721
+ ...document,
1722
+ sheet: {
1723
+ ...document.sheet,
1724
+ wires,
1725
+ junctions
1726
+ }
1727
+ }),
1728
+ firstWireId,
1729
+ secondWireId
1730
+ };
1731
+ }
1732
+ /**
1733
+ * Merges the two collinear wires meeting at `junctionId` into one wire and removes the junction.
1734
+ * Returns `undefined` when the junction does not describe such a splice.
1735
+ *
1736
+ * @throws {SchematicError} when the junction is unknown.
1737
+ */
1738
+ function mergeWiresAtJunction(document, junctionId, options = {}) {
1739
+ const tolerance = options.tolerance ?? 0;
1740
+ const junction = document.sheet.junctions.find((candidate) => candidate.id === junctionId);
1741
+ if (junction === void 0) throw new SchematicError([{
1742
+ severity: "error",
1743
+ code: "edit/unknown-entity",
1744
+ message: `Unknown junction "${junctionId}".`,
1745
+ entity: {
1746
+ kind: "junction",
1747
+ id: junctionId
1748
+ }
1749
+ }]);
1750
+ if (junction.wires.length !== 2) return;
1751
+ const [firstId, secondId] = junction.wires;
1752
+ const first = document.sheet.wires.find((wire) => wire.id === firstId);
1753
+ const second = document.sheet.wires.find((wire) => wire.id === secondId);
1754
+ if (first === void 0 || second === void 0) return;
1755
+ const merged = mergePoints(first.points, second.points, junction, tolerance);
1756
+ if (merged === void 0) return;
1757
+ const wires = [];
1758
+ for (const candidate of document.sheet.wires) {
1759
+ if (candidate.id === secondId) continue;
1760
+ wires.push(candidate.id === firstId ? {
1761
+ id: firstId,
1762
+ points: merged
1763
+ } : candidate);
1764
+ }
1765
+ const junctions = document.sheet.junctions.filter((candidate) => candidate.id !== junctionId).map((candidate) => candidate.wires.includes(secondId) ? {
1766
+ ...candidate,
1767
+ wires: candidate.wires.map((id) => id === secondId ? firstId : id)
1768
+ } : candidate);
1769
+ return {
1770
+ document: validate({
1771
+ ...document,
1772
+ sheet: {
1773
+ ...document.sheet,
1774
+ wires,
1775
+ junctions
1776
+ }
1777
+ }),
1778
+ wireId: firstId,
1779
+ removedWireId: secondId,
1780
+ removedJunctionId: junctionId
1781
+ };
1782
+ }
1783
+ function validate(document) {
1784
+ return parseSchematic(document);
1785
+ }
1786
+ function splitPoints(points, point, tolerance) {
1787
+ const first = points[0];
1788
+ const last = points[points.length - 1];
1789
+ if (first === void 0 || last === void 0) return;
1790
+ if (pointsEqual(first, point, tolerance) || pointsEqual(last, point, tolerance)) return;
1791
+ if (!pointOnPolyline(points, point, tolerance)) return;
1792
+ for (let index = 1; index < points.length; index += 1) {
1793
+ const previous = points[index - 1];
1794
+ const current = points[index];
1795
+ if (previous === void 0 || current === void 0) continue;
1796
+ if (!pointOnPolyline([previous, current], point, tolerance)) continue;
1797
+ return [[...points.slice(0, index), {
1798
+ x: point.x,
1799
+ y: point.y
1800
+ }], [{
1801
+ x: point.x,
1802
+ y: point.y
1803
+ }, ...points.slice(index)]];
1804
+ }
1805
+ }
1806
+ function remapJunction(junction, wireId, firstWireId, secondWireId, firstPoints, secondPoints, splitPoint, tolerance) {
1807
+ if (!junction.wires.includes(wireId)) return junction;
1808
+ const point = {
1809
+ x: junction.x,
1810
+ y: junction.y
1811
+ };
1812
+ if (pointsEqual(point, splitPoint, tolerance)) {
1813
+ const others = junction.wires.filter((id) => id !== wireId);
1814
+ return {
1815
+ ...junction,
1816
+ wires: [
1817
+ firstWireId,
1818
+ secondWireId,
1819
+ ...others
1820
+ ]
1821
+ };
1822
+ }
1823
+ const onFirst = pointOnPolyline(firstPoints, point, tolerance);
1824
+ const onSecond = pointOnPolyline(secondPoints, point, tolerance);
1825
+ if (onFirst && !onSecond) return junction;
1826
+ if (onSecond && !onFirst) return {
1827
+ ...junction,
1828
+ wires: junction.wires.map((id) => id === wireId ? secondWireId : id)
1829
+ };
1830
+ return junction;
1831
+ }
1832
+ function mergePoints(first, second, junction, tolerance) {
1833
+ const at = {
1834
+ x: junction.x,
1835
+ y: junction.y
1836
+ };
1837
+ const firstStart = first[0];
1838
+ const firstEnd = first[first.length - 1];
1839
+ const secondStart = second[0];
1840
+ const secondEnd = second[second.length - 1];
1841
+ if (firstStart === void 0 || firstEnd === void 0 || secondStart === void 0 || secondEnd === void 0) return;
1842
+ const firstStartsAt = pointsEqual(firstStart, at, tolerance);
1843
+ const firstEndsAt = pointsEqual(firstEnd, at, tolerance);
1844
+ const secondStartsAt = pointsEqual(secondStart, at, tolerance);
1845
+ const secondEndsAt = pointsEqual(secondEnd, at, tolerance);
1846
+ let head;
1847
+ let tail;
1848
+ if (firstEndsAt && secondStartsAt) {
1849
+ head = first;
1850
+ tail = second;
1851
+ } else if (secondEndsAt && firstStartsAt) {
1852
+ head = second;
1853
+ tail = first;
1854
+ } else if (firstEndsAt && secondEndsAt) {
1855
+ head = first;
1856
+ tail = [...second].reverse();
1857
+ } else if (firstStartsAt && secondStartsAt) {
1858
+ head = [...first].reverse();
1859
+ tail = second;
1860
+ }
1861
+ if (head === void 0 || tail === void 0 || !isContinuous(head, tail)) return;
1862
+ return [...head, ...tail.slice(1)];
1863
+ }
1864
+ /** True when the last segment of `a` and the first segment of `b` are collinear. */
1865
+ function isContinuous(a, b) {
1866
+ const a1 = a[a.length - 2];
1867
+ const a2 = a[a.length - 1];
1868
+ const b1 = b[0];
1869
+ const b2 = b[1];
1870
+ if (a1 === void 0 || a2 === void 0 || b1 === void 0 || b2 === void 0) return false;
1871
+ const ax = a2.x - a1.x;
1872
+ const ay = a2.y - a1.y;
1873
+ const bx = b2.x - b1.x;
1874
+ const by = b2.y - b1.y;
1875
+ const cross = ax * by - ay * bx;
1876
+ const scale = Math.hypot(ax, ay) * Math.hypot(bx, by);
1877
+ if (scale === 0) return false;
1878
+ return Math.abs(cross) / scale <= 1e-9 && ax * bx + ay * by > 0;
1879
+ }
1880
+ //#endregion
1881
+ //#region src/schematic/geometry/reroute.ts
1882
+ /**
1883
+ * Moves a wire endpoint to `to` while keeping every segment horizontal or vertical.
1884
+ *
1885
+ * The displacement is absorbed segment by segment (the classic orthogonal drag): the point after
1886
+ * the moved end is shifted just enough to restore the original orientation, and the correction is
1887
+ * passed on until a segment can take it. No extra points are inserted, so the wire never doubles
1888
+ * back on itself.
1889
+ */
1890
+ function rerouteWireEndpoint(wire, from, to) {
1891
+ const points = wire.points;
1892
+ const first = points[0];
1893
+ const last = points[points.length - 1];
1894
+ if (first === void 0 || last === void 0) return;
1895
+ if (samePoint$1(first, from)) return rerouteFrom(points, to, "start");
1896
+ if (samePoint$1(last, from)) return rerouteFrom(points, to, "end");
1897
+ }
1898
+ function rerouteFrom(points, target, end) {
1899
+ const next = points.map((point) => ({
1900
+ x: point.x,
1901
+ y: point.y
1902
+ }));
1903
+ const first = next[0];
1904
+ const last = next[next.length - 1];
1905
+ if (first === void 0 || last === void 0) return {
1906
+ points: next,
1907
+ moves: []
1908
+ };
1909
+ if (end === "start") {
1910
+ next[0] = {
1911
+ x: target.x,
1912
+ y: target.y
1913
+ };
1914
+ for (let index = 0; index < next.length - 1; index += 1) {
1915
+ const a = next[index];
1916
+ const b = next[index + 1];
1917
+ if (a === void 0 || b === void 0 || a.x === b.x || a.y === b.y) break;
1918
+ const horizontal = points[index]?.y === points[index + 1]?.y;
1919
+ next[index + 1] = horizontal ? {
1920
+ x: b.x,
1921
+ y: a.y
1922
+ } : {
1923
+ x: a.x,
1924
+ y: b.y
1925
+ };
1926
+ }
1927
+ } else {
1928
+ next[next.length - 1] = {
1929
+ x: target.x,
1930
+ y: target.y
1931
+ };
1932
+ for (let index = next.length - 1; index > 0; index -= 1) {
1933
+ const a = next[index - 1];
1934
+ const b = next[index];
1935
+ if (a === void 0 || b === void 0 || a.x === b.x || a.y === b.y) break;
1936
+ const horizontal = points[index - 1]?.y === points[index]?.y;
1937
+ next[index - 1] = horizontal ? {
1938
+ x: a.x,
1939
+ y: b.y
1940
+ } : {
1941
+ x: b.x,
1942
+ y: a.y
1943
+ };
1944
+ }
1945
+ }
1946
+ const moves = [];
1947
+ for (let index = 0; index < points.length; index += 1) {
1948
+ const before = points[index];
1949
+ const after = next[index];
1950
+ if (before !== void 0 && after !== void 0 && !samePoint$1(before, after)) moves.push({
1951
+ from: before,
1952
+ to: after
1953
+ });
1954
+ }
1955
+ return {
1956
+ points: dropRepeatedPoints(next),
1957
+ moves
1958
+ };
1959
+ }
1960
+ function dropRepeatedPoints(points) {
1961
+ const result = [];
1962
+ for (const point of points) {
1963
+ const previous = result[result.length - 1];
1964
+ if (previous === void 0 || !samePoint$1(previous, point)) result.push(point);
1965
+ }
1966
+ return result.length >= 2 ? result : points;
1967
+ }
1968
+ /**
1969
+ * Moves a wire endpoint to `to` by stretching or bending it, leaving the original run in place.
1970
+ *
1971
+ * Unlike {@link rerouteWireEndpoint}, which absorbs the move by shifting the neighbouring points,
1972
+ * this keeps every existing point: a move along the attached segment only changes its length, and a
1973
+ * perpendicular move appends an orthogonal fold at the moved end. That is the behaviour net labels
1974
+ * and power ports need while being dragged — the wire follows them instead of sliding away.
1975
+ */
1976
+ function stretchWireEndpoint(wire, from, to) {
1977
+ const points = wire.points;
1978
+ const first = points[0];
1979
+ const last = points[points.length - 1];
1980
+ if (first === void 0 || last === void 0) return;
1981
+ if (samePoint$1(first, from)) return stretchAt(points, to, "start");
1982
+ if (samePoint$1(last, from)) return stretchAt(points, to, "end");
1983
+ }
1984
+ function stretchAt(points, target, end) {
1985
+ const anchor = end === "start" ? points[0] : points[points.length - 1];
1986
+ const neighbor = end === "start" ? points[1] : points[points.length - 2];
1987
+ if (anchor === void 0) return {
1988
+ points,
1989
+ moves: []
1990
+ };
1991
+ if (neighbor === void 0) {
1992
+ const next = points.map((point) => ({
1993
+ x: point.x,
1994
+ y: point.y
1995
+ }));
1996
+ if (end === "start") next[0] = {
1997
+ x: target.x,
1998
+ y: target.y
1999
+ };
2000
+ else next[next.length - 1] = {
2001
+ x: target.x,
2002
+ y: target.y
2003
+ };
2004
+ return {
2005
+ points: next,
2006
+ moves: [{
2007
+ from: anchor,
2008
+ to: target
2009
+ }]
2010
+ };
2011
+ }
2012
+ const horizontal = neighbor.y === anchor.y;
2013
+ if (!(horizontal ? target.y !== anchor.y : target.x !== anchor.x)) {
2014
+ const next = points.map((point) => ({
2015
+ x: point.x,
2016
+ y: point.y
2017
+ }));
2018
+ if (end === "start") next[0] = {
2019
+ x: target.x,
2020
+ y: target.y
2021
+ };
2022
+ else next[next.length - 1] = {
2023
+ x: target.x,
2024
+ y: target.y
2025
+ };
2026
+ return {
2027
+ points: dropRepeatedPoints(next),
2028
+ moves: [{
2029
+ from: anchor,
2030
+ to: target
2031
+ }]
2032
+ };
2033
+ }
2034
+ const elbow = horizontal ? {
2035
+ x: anchor.x,
2036
+ y: target.y
2037
+ } : {
2038
+ x: target.x,
2039
+ y: anchor.y
2040
+ };
2041
+ const turn = samePoint$1(elbow, target) ? [] : [elbow];
2042
+ return {
2043
+ points: dropRepeatedPoints(end === "start" ? [
2044
+ target,
2045
+ ...turn,
2046
+ ...points
2047
+ ] : [
2048
+ ...points,
2049
+ ...turn,
2050
+ target
2051
+ ]),
2052
+ moves: []
2053
+ };
2054
+ }
2055
+ function samePoint$1(left, right) {
2056
+ return left.x === right.x && left.y === right.y;
2057
+ }
2058
+ //#endregion
2059
+ //#region src/schematic/device/synthesis.ts
2060
+ var DEFAULT_PIN_SIDE = "left";
2061
+ /** Half height of a centred glyph box, in `characterWidth` units. */
2062
+ var NAME_HALF_EXTENT = 1;
2063
+ /**
2064
+ * Derives symbol geometry from pin declarations: no coordinates or body size are authored.
2065
+ * Pins keep their declaration order within a side and snap to `pinPitch`.
2066
+ */
2067
+ function synthesizeDeviceSymbol(input, options = {}) {
2068
+ const pinPitch = options.pinPitch ?? 10;
2069
+ const stubLength = options.stubLength ?? 10;
2070
+ const minWidth = options.minWidth ?? 60;
2071
+ const minHeight = options.minHeight ?? 40;
2072
+ const horizontalPadding = options.horizontalPadding ?? 10;
2073
+ const verticalPadding = options.verticalPadding ?? 10;
2074
+ const characterWidth = options.characterWidth ?? 5.5;
2075
+ const labelCharacterWidth = options.labelCharacterWidth ?? 4.5;
2076
+ const nameGap = options.nameGap ?? 12;
2077
+ const labelInset = options.labelInset ?? 10;
2078
+ const sides = {
2079
+ left: [],
2080
+ right: [],
2081
+ top: [],
2082
+ bottom: []
2083
+ };
2084
+ for (const [pinId, declaration] of Object.entries(input.pins)) sides[declaration.side ?? DEFAULT_PIN_SIDE].push(pinId);
2085
+ const rows = Math.max(sides.left.length, sides.right.length);
2086
+ const columns = Math.max(sides.top.length, sides.bottom.length);
2087
+ const height = Math.max(minHeight, verticalPadding * 2 + Math.max(rows - 1, 0) * pinPitch + pinPitch, 2 * (NAME_HALF_EXTENT * characterWidth + pinPitch / 2 + labelInset + rotatedHalf(sides, labelCharacterWidth)));
2088
+ const width = Math.max(minWidth, horizontalPadding * 2 + Math.max(columns - 1, 0) * pinPitch + pinPitch);
2089
+ const contentWidth = (longest(sides.left) + longest(sides.right)) * labelCharacterWidth + input.name.length * characterWidth + horizontalPadding * 2 + nameGap * 2;
2090
+ const bodyWidth = Math.ceil(Math.max(width, contentWidth) / pinPitch) * pinPitch;
2091
+ const bodyHeight = Math.ceil(height / pinPitch) * pinPitch;
2092
+ const body = {
2093
+ x: -bodyWidth / 2,
2094
+ y: -bodyHeight / 2,
2095
+ width: bodyWidth,
2096
+ height: bodyHeight
2097
+ };
2098
+ const pins = Object.create(null);
2099
+ const placeVertical = (pinIds, side) => {
2100
+ const start = -((pinIds.length - 1) * pinPitch) / 2;
2101
+ pinIds.forEach((pinId, index) => {
2102
+ const y = start + index * pinPitch;
2103
+ const x = side === "left" ? body.x - stubLength : body.x + body.width + stubLength;
2104
+ pins[pinId] = {
2105
+ x,
2106
+ y,
2107
+ side
2108
+ };
2109
+ });
2110
+ };
2111
+ placeVertical(sides.left, "left");
2112
+ placeVertical(sides.right, "right");
2113
+ const placeHorizontal = (pinIds, side) => {
2114
+ const start = -((pinIds.length - 1) * pinPitch) / 2;
2115
+ pinIds.forEach((pinId, index) => {
2116
+ const x = start + index * pinPitch;
2117
+ const y = side === "top" ? body.y - stubLength : body.y + body.height + stubLength;
2118
+ pins[pinId] = {
2119
+ x,
2120
+ y,
2121
+ side
2122
+ };
2123
+ });
2124
+ };
2125
+ placeHorizontal(sides.top, "top");
2126
+ placeHorizontal(sides.bottom, "bottom");
2127
+ return {
2128
+ name: input.name,
2129
+ pins,
2130
+ bounds: body
2131
+ };
2132
+ }
2133
+ function longest(pinIds) {
2134
+ return pinIds.reduce((max, pinId) => Math.max(max, pinId.length), 0);
2135
+ }
2136
+ function rotatedHalf(sides, characterWidth) {
2137
+ return Math.max(longest(sides.top), longest(sides.bottom)) * characterWidth / 2;
2138
+ }
2139
+ //#endregion
2140
+ //#region src/schematic/device/symbol.ts
2141
+ /** Bounding box of a device's pins; `undefined` when it declares none. */
2142
+ function pinsBounds(pins) {
2143
+ const points = Object.values(pins);
2144
+ if (points.length === 0) return;
2145
+ let minX = Number.POSITIVE_INFINITY;
2146
+ let minY = Number.POSITIVE_INFINITY;
2147
+ let maxX = Number.NEGATIVE_INFINITY;
2148
+ let maxY = Number.NEGATIVE_INFINITY;
2149
+ for (const point of points) {
2150
+ minX = Math.min(minX, point.x);
2151
+ minY = Math.min(minY, point.y);
2152
+ maxX = Math.max(maxX, point.x);
2153
+ maxY = Math.max(maxY, point.y);
2154
+ }
2155
+ return {
2156
+ x: minX,
2157
+ y: minY,
2158
+ width: maxX - minX,
2159
+ height: maxY - minY
2160
+ };
2161
+ }
2162
+ /** Side a pin is drawn on: its declared side, else the closest edge of the body outline. */
2163
+ function pinSide(pin, outline) {
2164
+ if (pin.side !== void 0) return pin.side;
2165
+ if (outline === void 0) return;
2166
+ const dx = (pin.x - (outline.x + outline.width / 2)) / Math.max(outline.width / 2, 1e-6);
2167
+ const dy = (pin.y - (outline.y + outline.height / 2)) / Math.max(outline.height / 2, 1e-6);
2168
+ if (Math.abs(dx) >= Math.abs(dy)) return dx >= 0 ? "right" : "left";
2169
+ return dy >= 0 ? "bottom" : "top";
2170
+ }
2171
+ /**
2172
+ * Inner end of a pin stub, where it meets the body outline. Together with the anchor it forms the
2173
+ * grabbable pin: rendering draws that line and hit testing accepts clicks anywhere along it.
2174
+ */
2175
+ function pinStubEnd(body, anchor, side) {
2176
+ switch (side) {
2177
+ case "left": return {
2178
+ x: body.x,
2179
+ y: anchor.y
2180
+ };
2181
+ case "right": return {
2182
+ x: body.x + body.width,
2183
+ y: anchor.y
2184
+ };
2185
+ case "top": return {
2186
+ x: anchor.x,
2187
+ y: body.y
2188
+ };
2189
+ case "bottom": return {
2190
+ x: anchor.x,
2191
+ y: body.y + body.height
2192
+ };
2193
+ }
2194
+ }
2195
+ //#endregion
2196
+ //#region src/schematic/device/metadata.ts
2197
+ var fail = (path, diagnostics, message) => {
2198
+ diagnostics.push({
2199
+ severity: "error",
2200
+ code: "device/invalid-description",
2201
+ pointer: path,
2202
+ message
2203
+ });
2204
+ };
2205
+ var text = (v, p, d) => {
2206
+ if (typeof v !== "string") fail(p, d, "Expected a string.");
2207
+ };
2208
+ var nonempty = (v, p, d) => {
2209
+ if (typeof v !== "string" || v.length === 0) fail(p, d, "Expected a nonempty string.");
2210
+ };
2211
+ var number = (v, p, d) => {
2212
+ if (typeof v !== "number" || !Number.isFinite(v)) fail(p, d, "Expected a finite number.");
2213
+ };
2214
+ var positive = (v, p, d) => {
2215
+ number(v, p, d);
2216
+ if (typeof v === "number" && v <= 0) fail(p, d, "Expected a positive number.");
2217
+ };
2218
+ var boolean = (v, p, d) => {
2219
+ if (typeof v !== "boolean") fail(p, d, "Expected a boolean.");
2220
+ };
2221
+ var choice = (...values) => (v, p, d) => {
2222
+ if (!values.includes(v)) fail(p, d, `Expected one of ${values.join(", ")}.`);
2223
+ };
2224
+ var pointer = (key) => key.replaceAll("~", "~0").replaceAll("/", "~1");
2225
+ var object = (fields, required = []) => (v, p, d) => {
2226
+ if (!isRecord(v)) {
2227
+ fail(p, d, "Expected an object.");
2228
+ return;
2229
+ }
2230
+ for (const key of required) if (!Object.hasOwn(v, key)) fail(`${p}/${pointer(key)}`, d, "Required field is missing.");
2231
+ for (const [key, value] of Object.entries(v)) {
2232
+ const check = Object.hasOwn(fields, key) ? fields[key] : void 0;
2233
+ if (check === void 0) fail(`${p}/${pointer(key)}`, d, "Unknown field.");
2234
+ else check(value, `${p}/${pointer(key)}`, d);
2235
+ }
2236
+ };
2237
+ var map = (check) => (v, p, d) => {
2238
+ if (!isRecord(v)) {
2239
+ fail(p, d, "Expected a map.");
2240
+ return;
2241
+ }
2242
+ for (const [key, value] of Object.entries(v)) check(value, `${p}/${pointer(key)}`, d);
2243
+ };
2244
+ var tuple = (v, p, d) => {
2245
+ if (!Array.isArray(v) || v.length !== 2) {
2246
+ fail(p, d, "Expected two coordinates.");
2247
+ return;
2248
+ }
2249
+ v.forEach((value, index) => number(value, `${p}/${index}`, d));
2250
+ };
2251
+ var strings = (v, p, d) => {
2252
+ if (!Array.isArray(v)) {
2253
+ fail(p, d, "Expected an array.");
2254
+ return;
2255
+ }
2256
+ v.forEach((value, index) => nonempty(value, `${p}/${index}`, d));
2257
+ if (new Set(v).size !== v.length) fail(p, d, "Values must be unique.");
2258
+ };
2259
+ var align = choice("left", "center", "right");
2260
+ var label = object({
2261
+ visible: boolean,
2262
+ position: tuple,
2263
+ align
2264
+ }, ["visible"]);
2265
+ var editor = (v, p, d) => {
2266
+ const fields = { kind: choice("number", "text", "textarea", "checkbox", "file", "select") };
2267
+ if (isRecord(v) && v.kind === "number") Object.assign(fields, {
2268
+ min: number,
2269
+ max: number,
2270
+ step: positive
2271
+ });
2272
+ if (isRecord(v) && v.kind === "select") fields.labels = map(text);
2273
+ object(fields, ["kind"])(v, p, d);
2274
+ };
2275
+ var property = (v, p, d) => {
2276
+ object({
2277
+ type: choice("number", "boolean", "string", "enum", "bytes"),
2278
+ required: boolean,
2279
+ nullable: boolean,
2280
+ default: () => {},
2281
+ enumName: nonempty,
2282
+ values: strings,
2283
+ title: text,
2284
+ description: text,
2285
+ unit: text,
2286
+ group: text,
2287
+ order: (value, path, diagnostics) => {
2288
+ if (typeof value !== "number" || !Number.isInteger(value) || value < 0) fail(path, diagnostics, "Expected a nonnegative integer.");
2289
+ },
2290
+ editor
2291
+ }, ["type", "required"])(v, p, d);
2292
+ if (!isRecord(v)) return;
2293
+ if (v.required === true && Object.hasOwn(v, "default")) fail(`${p}/default`, d, "Required properties cannot declare defaults.");
2294
+ if (v.type === "enum") {
2295
+ if (!Object.hasOwn(v, "enumName") || !Object.hasOwn(v, "values")) fail(p, d, "Enum properties require enumName and values.");
2296
+ if (Array.isArray(v.values) && v.values.length === 0) fail(`${p}/values`, d, "Enum values cannot be empty.");
2297
+ } else if (Object.hasOwn(v, "enumName") || Object.hasOwn(v, "values")) fail(p, d, "Only enum properties declare enumName and values.");
2298
+ };
2299
+ var graphic = (v, p, d) => {
2300
+ const common = {
2301
+ type: choice("line", "polyline", "rect", "circle", "arc", "text"),
2302
+ fill: choice("none", "foreground", "background"),
2303
+ strokeWidth: positive
2304
+ };
2305
+ const variants = {
2306
+ line: {
2307
+ fields: {
2308
+ from: tuple,
2309
+ to: tuple
2310
+ },
2311
+ required: ["from", "to"]
2312
+ },
2313
+ polyline: {
2314
+ fields: {
2315
+ points: (value, path, diagnostics) => {
2316
+ if (!Array.isArray(value) || value.length < 2) {
2317
+ fail(path, diagnostics, "Expected at least two points.");
2318
+ return;
2319
+ }
2320
+ value.forEach((point, index) => tuple(point, `${path}/${index}`, diagnostics));
2321
+ },
2322
+ closed: boolean
2323
+ },
2324
+ required: ["points"]
2325
+ },
2326
+ rect: {
2327
+ fields: {
2328
+ x: number,
2329
+ y: number,
2330
+ width: positive,
2331
+ height: positive
2332
+ },
2333
+ required: [
2334
+ "x",
2335
+ "y",
2336
+ "width",
2337
+ "height"
2338
+ ]
2339
+ },
2340
+ circle: {
2341
+ fields: {
2342
+ center: tuple,
2343
+ radius: positive
2344
+ },
2345
+ required: ["center", "radius"]
2346
+ },
2347
+ arc: {
2348
+ fields: {
2349
+ center: tuple,
2350
+ radius: positive,
2351
+ startAngle: number,
2352
+ sweepAngle: (value, path, diagnostics) => {
2353
+ number(value, path, diagnostics);
2354
+ if (typeof value === "number" && (value === 0 || value < -360 || value > 360)) fail(path, diagnostics, "Sweep must be nonzero and within [-360, 360].");
2355
+ }
2356
+ },
2357
+ required: [
2358
+ "center",
2359
+ "radius",
2360
+ "startAngle",
2361
+ "sweepAngle"
2362
+ ]
2363
+ },
2364
+ text: {
2365
+ fields: {
2366
+ position: tuple,
2367
+ text,
2368
+ align,
2369
+ fontSize: positive
2370
+ },
2371
+ required: ["position", "text"]
2372
+ }
2373
+ };
2374
+ const variant = isRecord(v) && typeof v.type === "string" && Object.hasOwn(variants, v.type) ? variants[v.type] : void 0;
2375
+ object({
2376
+ ...common,
2377
+ ...variant?.fields
2378
+ }, ["type", ...variant?.required ?? []])(v, p, d);
2379
+ };
2380
+ var graphics = (v, p, d) => {
2381
+ if (!Array.isArray(v)) fail(p, d, "Expected graphics array.");
2382
+ else v.forEach((value, i) => graphic(value, `${p}/${i}`, d));
2383
+ };
2384
+ var annotations = object({ designator: object({
2385
+ position: tuple,
2386
+ align
2387
+ }, ["position"]) });
2388
+ /** Shared validation for optional presentation data on legacy normalized geometry. */
2389
+ function validateSymbolPresentation(value, diagnostics) {
2390
+ if (value.graphics !== void 0) graphics(value.graphics, "/symbol/graphics", diagnostics);
2391
+ if (value.annotations !== void 0) annotations(value.annotations, "/symbol/annotations", diagnostics);
2392
+ if (!isRecord(value.pins)) return;
2393
+ for (const [id, pin] of Object.entries(value.pins)) {
2394
+ if (!isRecord(pin)) continue;
2395
+ const path = `/symbol/pins/${pointer(id)}`;
2396
+ if (pin.label !== void 0) label(pin.label, `${path}/label`, diagnostics);
2397
+ if (pin.displayName !== void 0) text(pin.displayName, `${path}/displayName`, diagnostics);
2398
+ }
2399
+ }
2400
+ var checkMetadata = object({
2401
+ kind: choice("device"),
2402
+ id: (v, p, d) => {
2403
+ if (typeof v !== "string" || !/^[A-Za-z0-9-]+$/.test(v)) fail(p, d, "Invalid device id.");
2404
+ },
2405
+ version: nonempty,
2406
+ displayName: nonempty,
2407
+ description: nonempty,
2408
+ license: nonempty,
2409
+ tags: strings,
2410
+ designatorPrefix: (v, p, d) => {
2411
+ if (typeof v !== "string" || !/^\S+$/.test(v)) fail(p, d, "Expected a nonempty prefix without whitespace.");
2412
+ },
2413
+ properties: map(property),
2414
+ pins: map(object({
2415
+ label: text,
2416
+ description: text,
2417
+ group: text
2418
+ })),
2419
+ ports: map(object({
2420
+ direction: choice("input", "output"),
2421
+ type: choice("boolean", "number", "string", "vec2d", "vec3d", "bytes", "bytes<rgb565>", "bytes<segments>"),
2422
+ title: text,
2423
+ description: text,
2424
+ delivery: choice("level", "message")
2425
+ }, ["direction", "type"])),
2426
+ symbol: object({
2427
+ bounds: object({
2428
+ x: number,
2429
+ y: number,
2430
+ width: positive,
2431
+ height: positive
2432
+ }, [
2433
+ "x",
2434
+ "y",
2435
+ "width",
2436
+ "height"
2437
+ ]),
2438
+ graphics,
2439
+ pins: map(object({
2440
+ position: tuple,
2441
+ direction: choice("left", "right", "up", "down"),
2442
+ label
2443
+ }, ["position", "direction"])),
2444
+ annotations
2445
+ }, [
2446
+ "bounds",
2447
+ "graphics",
2448
+ "pins"
2449
+ ])
2450
+ }, [
2451
+ "kind",
2452
+ "id",
2453
+ "version",
2454
+ "displayName"
2455
+ ]);
2456
+ /** Validates JSON text or an object, throwing the existing diagnostic-bearing format error. */
2457
+ function parseDeviceMetadata(input) {
2458
+ const diagnostics = [];
2459
+ const parsed = parseInput(input, diagnostics);
2460
+ if (parsed.ok) checkMetadata(parsed.value, "", diagnostics);
2461
+ if (diagnostics.length > 0) throw new SchematicError(diagnostics);
2462
+ return parsed.value;
2463
+ }
2464
+ function deviceMetadataPropertyDescriptors(metadata) {
2465
+ return new Map(Object.entries(metadata.properties ?? {}).map(([key, value]) => {
2466
+ const descriptor = {
2467
+ key,
2468
+ type: value.type === "bytes" ? "binary" : value.type,
2469
+ required: value.required,
2470
+ ...value.nullable === void 0 ? {} : { nullable: value.nullable },
2471
+ ...value.title === void 0 ? {} : { title: value.title },
2472
+ ...value.description === void 0 ? {} : { description: value.description },
2473
+ ...value.unit === void 0 ? {} : { unit: value.unit },
2474
+ ...value.group === void 0 ? {} : { group: value.group },
2475
+ ...value.order === void 0 ? {} : { order: value.order },
2476
+ ...value.editor === void 0 ? {} : { editor: value.editor },
2477
+ ...value.type === "number" && value.editor?.kind === "number" ? {
2478
+ ...value.editor.min === void 0 ? {} : { min: value.editor.min },
2479
+ ...value.editor.max === void 0 ? {} : { max: value.editor.max },
2480
+ ...value.editor.step === void 0 ? {} : { step: value.editor.step }
2481
+ } : {},
2482
+ ...value.type === "enum" ? { options: value.values.map((option) => ({
2483
+ value: option,
2484
+ label: value.editor?.kind === "select" ? value.editor.labels?.[option] ?? option : option
2485
+ })) } : {}
2486
+ };
2487
+ if (Object.hasOwn(value, "default") && propertyValueProblem(descriptor, value.default) === void 0) return [key, {
2488
+ ...descriptor,
2489
+ default: value.default
2490
+ }];
2491
+ return [key, descriptor];
2492
+ }));
2493
+ }
2494
+ function deviceMetadataSymbol(metadata) {
2495
+ const symbol = metadata.symbol;
2496
+ const base = symbol === void 0 ? synthesizeDeviceSymbol({
2497
+ name: metadata.displayName,
2498
+ pins: Object.fromEntries(Object.keys(metadata.pins ?? {}).map((id) => [id, {}]))
2499
+ }) : void 0;
2500
+ const pins = Object.create(null);
2501
+ for (const [id, pin] of Object.entries(symbol?.pins ?? base.pins)) {
2502
+ const caption = metadata.pins?.[id]?.label;
2503
+ if (symbol !== void 0) {
2504
+ const custom = symbol.pins[id];
2505
+ pins[id] = {
2506
+ x: custom.position[0],
2507
+ y: custom.position[1],
2508
+ side: custom.direction === "up" ? "top" : custom.direction === "down" ? "bottom" : custom.direction,
2509
+ ...custom.label === void 0 ? {} : { label: custom.label },
2510
+ ...caption === void 0 ? {} : { displayName: caption }
2511
+ };
2512
+ } else pins[id] = {
2513
+ ...pin,
2514
+ ...caption === void 0 ? {} : { displayName: caption }
2515
+ };
2516
+ }
2517
+ return symbol === void 0 ? {
2518
+ ...base,
2519
+ pins
2520
+ } : {
2521
+ name: metadata.displayName,
2522
+ bounds: symbol.bounds,
2523
+ graphics: symbol.graphics,
2524
+ pins,
2525
+ ...symbol.annotations === void 0 ? {} : { annotations: symbol.annotations }
2526
+ };
2527
+ }
2528
+ //#endregion
2529
+ //#region src/schematic/device/registry.ts
2530
+ var PIN_SIDES = [
2531
+ "left",
2532
+ "right",
2533
+ "top",
2534
+ "bottom"
2535
+ ];
2536
+ function createDeviceRegistry(packages = []) {
2537
+ const stored = /* @__PURE__ */ new Map();
2538
+ const registrationDiagnostics = [];
2539
+ const getDeviceName = (reference) => {
2540
+ const device = lookup(reference);
2541
+ return device?.symbol?.name ?? device?.displayName;
2542
+ };
2543
+ const lookup = (reference) => {
2544
+ return stored.get(reference.packageId)?.devices.get(reference.deviceId);
2545
+ };
2546
+ const registry = {
2547
+ registerPackage(registration, options) {
2548
+ const diagnostics = [];
2549
+ const packageId = registration.packageId;
2550
+ if (!isValidPackageId(packageId)) throw new SchematicError([{
2551
+ severity: "error",
2552
+ code: "device/invalid-package",
2553
+ message: `Invalid package id ${JSON.stringify(packageId)}.`
2554
+ }]);
2555
+ if (isReservedPackageId(packageId)) throw new SchematicError([{
2556
+ severity: "error",
2557
+ code: "device/reserved-namespace",
2558
+ message: `Package id ${JSON.stringify(packageId)} uses the reserved "builtin" namespace.`
2559
+ }]);
2560
+ if (stored.has(packageId) && options?.replace !== true) throw new SchematicError([{
2561
+ severity: "error",
2562
+ code: "device/duplicate-package",
2563
+ message: `Package ${JSON.stringify(packageId)} is already registered.`
2564
+ }]);
2565
+ const devices = /* @__PURE__ */ new Map();
2566
+ const reportedUnknownPins = /* @__PURE__ */ new Set();
2567
+ for (const entry of registration.devices) {
2568
+ const deviceId = typeof entry.id === "string" ? entry.id : "";
2569
+ if (deviceId.length === 0) {
2570
+ diagnostics.push({
2571
+ severity: "error",
2572
+ code: "device/invalid-description",
2573
+ message: `Package ${JSON.stringify(packageId)} contains a device without an id.`
2574
+ });
2575
+ continue;
2576
+ }
2577
+ const devicePath = `${packageId}:${deviceId}`;
2578
+ if (devices.has(deviceId)) {
2579
+ diagnostics.push({
2580
+ severity: "error",
2581
+ code: "device/duplicate-device",
2582
+ message: `Device ${JSON.stringify(devicePath)} is declared more than once.`
2583
+ });
2584
+ continue;
2585
+ }
2586
+ const raw = isRecord(entry.raw) ? entry.raw : Object.create(null);
2587
+ if (raw.kind === "device") {
2588
+ try {
2589
+ const metadata = parseDeviceMetadata(raw);
2590
+ if (metadata.id !== deviceId) {
2591
+ diagnostics.push({
2592
+ severity: "error",
2593
+ code: "device/invalid-description",
2594
+ message: `Device ${JSON.stringify(devicePath)} metadata id does not match its registration id.`
2595
+ });
2596
+ continue;
2597
+ }
2598
+ devices.set(deviceId, {
2599
+ reference: {
2600
+ raw: formatDeviceReference(packageId, deviceId),
2601
+ packageId,
2602
+ deviceId
2603
+ },
2604
+ packageId,
2605
+ deviceId,
2606
+ displayName: metadata.displayName,
2607
+ description: metadata.description,
2608
+ designatorPrefix: metadata.designatorPrefix,
2609
+ propertyDescriptors: deviceMetadataPropertyDescriptors(metadata),
2610
+ symbol: deviceMetadataSymbol(metadata),
2611
+ raw
2612
+ });
2613
+ } catch (error) {
2614
+ if (!(error instanceof SchematicError)) throw error;
2615
+ diagnostics.push(...error.diagnostics);
2616
+ }
2617
+ continue;
2618
+ }
2619
+ const displayName = typeof entry.displayName === "string" && entry.displayName.length > 0 ? entry.displayName : deviceId;
2620
+ const symbol = readSymbolGeometry(entry, raw, devicePath, diagnostics, reportedUnknownPins);
2621
+ const designatorPrefix = readDesignatorPrefix(entry, raw);
2622
+ devices.set(deviceId, {
2623
+ reference: {
2624
+ raw: formatDeviceReference(packageId, deviceId),
2625
+ packageId,
2626
+ deviceId
2627
+ },
2628
+ packageId,
2629
+ deviceId,
2630
+ displayName,
2631
+ description: typeof entry.description === "string" ? entry.description : void 0,
2632
+ designatorPrefix,
2633
+ propertyDescriptors: readPropertyDescriptors(raw["properties"]),
2634
+ symbol,
2635
+ raw
2636
+ });
2637
+ }
2638
+ registrationDiagnostics.push(...diagnostics);
2639
+ throwIfErrors(diagnostics);
2640
+ stored.set(packageId, {
2641
+ packageId,
2642
+ devices
2643
+ });
2644
+ },
2645
+ resolve(reference) {
2646
+ const parsed = typeof reference === "string" ? parseDeviceReference(reference) : reference;
2647
+ if (parsed === void 0) return;
2648
+ return lookup(parsed);
2649
+ },
2650
+ listDevices(packageId) {
2651
+ const packages = packageId === void 0 ? [...stored.values()] : [stored.get(packageId)];
2652
+ const devices = [];
2653
+ for (const pkg of packages) {
2654
+ if (pkg === void 0) continue;
2655
+ devices.push(...pkg.devices.values());
2656
+ }
2657
+ return devices.sort((a, b) => a.packageId.localeCompare(b.packageId) || a.deviceId.localeCompare(b.deviceId));
2658
+ },
2659
+ getDeviceSymbol(reference) {
2660
+ return lookup(reference)?.symbol;
2661
+ },
2662
+ getDeviceName,
2663
+ getDeviceDesignatorPrefix(reference) {
2664
+ return lookup(reference)?.designatorPrefix;
2665
+ },
2666
+ getDevicePropertyDescriptors(reference) {
2667
+ return lookup(reference)?.propertyDescriptors;
2668
+ },
2669
+ get diagnostics() {
2670
+ return registrationDiagnostics;
2671
+ }
2672
+ };
2673
+ for (const registration of packages) registry.registerPackage(registration);
2674
+ return registry;
2675
+ }
2676
+ /** Designator prefix declared on the entry, else inside `raw`, else `undefined` (caller defaults). */
2677
+ function readDesignatorPrefix(entry, raw) {
2678
+ const declared = entry.designatorPrefix ?? raw["designatorPrefix"];
2679
+ if (typeof declared !== "string") return;
2680
+ const trimmed = declared.trim();
2681
+ return trimmed.length === 0 ? void 0 : trimmed;
2682
+ }
2683
+ function readSymbolGeometry(entry, raw, devicePath, diagnostics, reportedUnknownPins) {
2684
+ const displayName = typeof entry.displayName === "string" && entry.displayName.length > 0 ? entry.displayName : entry.id;
2685
+ const symbol = entry.symbol;
2686
+ if (symbol === void 0) {
2687
+ const declarations = readPinDeclarations(raw["pins"]);
2688
+ if (declarations === void 0) {
2689
+ diagnostics.push({
2690
+ severity: "warning",
2691
+ code: "device/no-symbol",
2692
+ message: `Device ${JSON.stringify(devicePath)} has neither symbol geometry nor pin declarations.`
2693
+ });
2694
+ return;
2695
+ }
2696
+ return synthesizeDeviceSymbol({
2697
+ name: displayName,
2698
+ pins: declarations
2699
+ });
2700
+ }
2701
+ if (!isRecord(symbol)) {
2702
+ diagnostics.push({
2703
+ severity: "error",
2704
+ code: "device/invalid-description",
2705
+ message: `Device ${JSON.stringify(devicePath)} has an invalid symbol definition.`
2706
+ });
2707
+ return;
2708
+ }
2709
+ validateSymbolPresentation(symbol, diagnostics);
2710
+ const pins = readPins(symbol.pins, devicePath, diagnostics);
2711
+ if (pins === void 0) return;
2712
+ const declaredPins = readDeclaredPinIds(raw.pins);
2713
+ if (declaredPins !== void 0) {
2714
+ for (const pinId of Object.keys(pins)) if (!declaredPins.has(pinId) && !reportedUnknownPins.has(`${devicePath}\u0000${pinId}`)) {
2715
+ reportedUnknownPins.add(`${devicePath}\u0000${pinId}`);
2716
+ diagnostics.push({
2717
+ severity: "error",
2718
+ code: "device/unknown-pin",
2719
+ message: `Device ${JSON.stringify(devicePath)} symbol declares pin ${JSON.stringify(pinId)} which is absent from the pin declaration.`
2720
+ });
2721
+ }
2722
+ }
2723
+ const bounds = readBounds(symbol.bounds, devicePath, diagnostics);
2724
+ return {
2725
+ name: (typeof symbol.name === "string" && symbol.name.length > 0 ? symbol.name : void 0) ?? displayName,
2726
+ pins,
2727
+ ...bounds === void 0 ? {} : { bounds },
2728
+ ...symbol.graphics === void 0 ? {} : { graphics: symbol.graphics },
2729
+ ...symbol.annotations === void 0 ? {} : { annotations: symbol.annotations }
2730
+ };
2731
+ }
2732
+ function readPinDeclarations(value) {
2733
+ if (!isRecord(value) || Object.keys(value).length === 0) return;
2734
+ const declarations = Object.create(null);
2735
+ for (const [pinId, pin] of Object.entries(value)) {
2736
+ if (pinId.length === 0 || !isRecord(pin)) return;
2737
+ const declaration = {};
2738
+ if (PIN_SIDES.includes(pin.side)) declaration.side = pin.side;
2739
+ declarations[pinId] = declaration;
2740
+ }
2741
+ return declarations;
2742
+ }
2743
+ function readPins(value, devicePath, diagnostics) {
2744
+ if (!isRecord(value)) {
2745
+ diagnostics.push({
2746
+ severity: "error",
2747
+ code: "device/invalid-description",
2748
+ message: `Device ${JSON.stringify(devicePath)} symbol must declare a pins object.`
2749
+ });
2750
+ return;
2751
+ }
2752
+ const pins = Object.create(null);
2753
+ let failed = false;
2754
+ for (const [pinId, anchor] of Object.entries(value)) {
2755
+ if (pinId.length === 0) {
2756
+ diagnostics.push({
2757
+ severity: "error",
2758
+ code: "device/invalid-description",
2759
+ message: `Device ${JSON.stringify(devicePath)} symbol declares an empty pin id.`
2760
+ });
2761
+ failed = true;
2762
+ continue;
2763
+ }
2764
+ if (!isRecord(anchor) || !Number.isFinite(anchor.x) || !Number.isFinite(anchor.y)) {
2765
+ diagnostics.push({
2766
+ severity: "error",
2767
+ code: "device/invalid-description",
2768
+ message: `Device ${JSON.stringify(devicePath)} pin ${JSON.stringify(pinId)} must have finite x and y anchors.`
2769
+ });
2770
+ failed = true;
2771
+ continue;
2772
+ }
2773
+ const side = anchor.side;
2774
+ if (side !== void 0 && !PIN_SIDES.includes(side)) {
2775
+ diagnostics.push({
2776
+ severity: "error",
2777
+ code: "device/invalid-description",
2778
+ message: `Device ${JSON.stringify(devicePath)} pin ${JSON.stringify(pinId)} has invalid side ${JSON.stringify(side)}.`
2779
+ });
2780
+ failed = true;
2781
+ continue;
2782
+ }
2783
+ const presentation = anchor;
2784
+ pins[pinId] = {
2785
+ x: anchor.x,
2786
+ y: anchor.y,
2787
+ ...side === void 0 ? {} : { side },
2788
+ ...presentation.label === void 0 ? {} : { label: presentation.label },
2789
+ ...presentation.displayName === void 0 ? {} : { displayName: presentation.displayName }
2790
+ };
2791
+ }
2792
+ return failed ? void 0 : pins;
2793
+ }
2794
+ function readBounds(value, devicePath, diagnostics) {
2795
+ if (value === void 0) return;
2796
+ if (!isRecord(value) || !Number.isFinite(value.x) || !Number.isFinite(value.y) || !Number.isFinite(value.width) || !Number.isFinite(value.height)) {
2797
+ diagnostics.push({
2798
+ severity: "error",
2799
+ code: "device/invalid-description",
2800
+ message: `Device ${JSON.stringify(devicePath)} symbol bounds must have finite x, y, width and height.`
2801
+ });
2802
+ return;
2803
+ }
2804
+ return {
2805
+ x: value.x,
2806
+ y: value.y,
2807
+ width: value.width,
2808
+ height: value.height
2809
+ };
2810
+ }
2811
+ function readDeclaredPinIds(value) {
2812
+ if (!isRecord(value)) return;
2813
+ return new Set(Object.keys(value));
2814
+ }
2815
+ //#endregion
2816
+ //#region src/schematic/device/designator.ts
2817
+ /** Prefix used when neither the host nor the device description provides one. */
2818
+ var DEFAULT_DESIGNATOR_PREFIX = "U";
2819
+ /** Smallest unused `<prefix><n>` designator. */
2820
+ function nextDesignator(document, prefix = "U") {
2821
+ const used = new Set(document.sheet.symbols.map((symbol) => designatorOf(symbol)).filter((designator) => designator !== void 0));
2822
+ for (let index = 1; index < Number.MAX_SAFE_INTEGER; index += 1) {
2823
+ const candidate = `${prefix}${index}`;
2824
+ if (!used.has(candidate)) return candidate;
2825
+ }
2826
+ return prefix;
2827
+ }
2828
+ /**
2829
+ * Designator policy derived from the device descriptions: `<device prefix><n>`, falling back to
2830
+ * `U` for devices that declare none.
2831
+ */
2832
+ function createDesignatorFactory(devices) {
2833
+ return (device, document) => nextDesignator(document, designatorPrefixOf(devices, device));
2834
+ }
2835
+ /** Prefix the device declares, else {@link DEFAULT_DESIGNATOR_PREFIX}. */
2836
+ function designatorPrefixOf(devices, device) {
2837
+ const reference = parseDeviceReference(device);
2838
+ return (reference === void 0 ? void 0 : devices?.getDeviceDesignatorPrefix?.(reference)) ?? "U";
2839
+ }
2840
+ /** Default policy for providers without device data: `U1`, `U2`, … */
2841
+ var defaultDesignatorFactory = (device, document) => nextDesignator(document);
2842
+ //#endregion
2843
+ export { ASSET_MODES, DEFAULT_DESIGNATOR_PREFIX, DEFAULT_GROUND_LABEL, DEFAULT_POWER_LABEL, DEFAULT_TEXT_CONTENT, DEFAULT_TOLERANCE, GROUND_DEVICE, LABEL_DEVICE, POWER_DEVICE, PROPERTY_TYPES, RESERVED_PACKAGE_NAMESPACE, SCHEMATIC_VERSION, SchematicError, TEXT_DEVICE, applySchematicCommand, assetPathError, base64Error, builtinSymbolKind, createDesignatorFactory, createDeviceRegistry, defaultDesignatorFactory, defaultElementIdFactory, defaultNetName, deriveSchematicTopology, designatorOf, designatorPrefixOf, deviceMetadataPropertyDescriptors, deviceMetadataSymbol, distanceToSegment, formatDeviceReference, hasDesignator, isAssetMode, isAssetReference, isBuiltinDevice, isLabelDevice, isPropertyType, isReservedPackageId, isTextDevice, isValidPackageId, joinJunctionAt, loadSchematic, mergeWiresAtJunction, namesNet, nearestPointOnPolyline, netNameOf, nextDesignator, normalizeColor, normalizePropertyValue, parseDeviceMetadata, parseDeviceReference, parsePropertyDescriptor, parseSchematic, pinSide, pinStubEnd, pinsBounds, planJunctionRelease, pointOnPolyline, pointOnPolylineAt, pointsEqual, projectOnSegment, propertyValueProblem, readPropertyDescriptors, referenceAnchor, referencePosition, rerouteWireEndpoint, rotatePoint, samePoint, serializeSchematic, splitWireAt, stretchWireEndpoint, symbolBounds, symbolPinPosition, symbolRole, synthesizeDeviceSymbol, textDeviceContent };
2844
+
2845
+ //# sourceMappingURL=index.js.map