@vessel-dsp/core 0.5.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.
Files changed (99) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +6 -0
  3. package/package.json +56 -0
  4. package/src/editor/commands.ts +344 -0
  5. package/src/editor/factory.ts +148 -0
  6. package/src/editor/history.ts +142 -0
  7. package/src/editor/index.ts +11 -0
  8. package/src/editor/layout.ts +207 -0
  9. package/src/formats/circuit-json/serializer.ts +1410 -0
  10. package/src/formats/document.ts +274 -0
  11. package/src/formats/interchange/parser.ts +1165 -0
  12. package/src/formats/interchange/serializer.ts +594 -0
  13. package/src/formats/ltspice/catalog.ts +181 -0
  14. package/src/formats/ltspice/encoding.ts +151 -0
  15. package/src/formats/ltspice/parser.ts +432 -0
  16. package/src/formats/ltspice/serializer.ts +169 -0
  17. package/src/formats/schx/catalog.ts +439 -0
  18. package/src/formats/schx/parser.ts +261 -0
  19. package/src/formats/schx/runtime-descriptors.ts +502 -0
  20. package/src/formats/schx/serializer.ts +211 -0
  21. package/src/formats/schx/transforms.ts +38 -0
  22. package/src/formats/spice/parser.ts +373 -0
  23. package/src/formats/spice/serializer.ts +43 -0
  24. package/src/index.ts +205 -0
  25. package/src/model/connectivity.ts +239 -0
  26. package/src/model/netlist.ts +375 -0
  27. package/src/model/properties.ts +101 -0
  28. package/src/model/quantity.ts +173 -0
  29. package/src/model/types.ts +309 -0
  30. package/src/model/validation.ts +985 -0
  31. package/src/model/wires.ts +86 -0
  32. package/src/panel/extract.ts +878 -0
  33. package/src/panel/index.ts +39 -0
  34. package/src/panel/knobs.ts +70 -0
  35. package/src/panel/protocol.ts +117 -0
  36. package/src/panel/types.ts +180 -0
  37. package/src/preview/bounds.ts +85 -0
  38. package/src/preview/box-layout.ts +24 -0
  39. package/src/preview/colors.ts +43 -0
  40. package/src/preview/hanging.ts +94 -0
  41. package/src/preview/junctions.ts +94 -0
  42. package/src/preview/label-layout.ts +90 -0
  43. package/src/preview/ports.ts +101 -0
  44. package/src/preview/renderable-wires.ts +113 -0
  45. package/src/preview/routing.ts +15 -0
  46. package/src/preview/snap.ts +104 -0
  47. package/src/preview/symbols/analog-switch.svg +17 -0
  48. package/src/preview/symbols/battery.svg +16 -0
  49. package/src/preview/symbols/bbd.svg +21 -0
  50. package/src/preview/symbols/bjt-npn.svg +16 -0
  51. package/src/preview/symbols/bjt-pnp.svg +17 -0
  52. package/src/preview/symbols/capacitor-electrolytic.svg +13 -0
  53. package/src/preview/symbols/capacitor.svg +12 -0
  54. package/src/preview/symbols/current-source.svg +14 -0
  55. package/src/preview/symbols/delay-ic.svg +22 -0
  56. package/src/preview/symbols/diode-schottky.svg +12 -0
  57. package/src/preview/symbols/diode-zener.svg +12 -0
  58. package/src/preview/symbols/diode.svg +13 -0
  59. package/src/preview/symbols/flipflop.svg +20 -0
  60. package/src/preview/symbols/ground.svg +12 -0
  61. package/src/preview/symbols/ic-block.svg +20 -0
  62. package/src/preview/symbols/ic.svg +19 -0
  63. package/src/preview/symbols/inductor.svg +11 -0
  64. package/src/preview/symbols/jack-input.svg +16 -0
  65. package/src/preview/symbols/jack-output.svg +16 -0
  66. package/src/preview/symbols/jfet-junction-n.svg +17 -0
  67. package/src/preview/symbols/jfet-n.svg +17 -0
  68. package/src/preview/symbols/jfet-p.svg +17 -0
  69. package/src/preview/symbols/label.svg +8 -0
  70. package/src/preview/symbols/led.svg +18 -0
  71. package/src/preview/symbols/mosfet-n.svg +21 -0
  72. package/src/preview/symbols/mosfet-p.svg +21 -0
  73. package/src/preview/symbols/named-wire.svg +11 -0
  74. package/src/preview/symbols/opamp.svg +21 -0
  75. package/src/preview/symbols/optocoupler.svg +30 -0
  76. package/src/preview/symbols/ota.svg +20 -0
  77. package/src/preview/symbols/pentode.svg +25 -0
  78. package/src/preview/symbols/photoresistor.svg +19 -0
  79. package/src/preview/symbols/port.svg +8 -0
  80. package/src/preview/symbols/potentiometer.svg +15 -0
  81. package/src/preview/symbols/power-amp.svg +20 -0
  82. package/src/preview/symbols/rail.svg +11 -0
  83. package/src/preview/symbols/regulator.svg +13 -0
  84. package/src/preview/symbols/relay.svg +20 -0
  85. package/src/preview/symbols/resistor.svg +11 -0
  86. package/src/preview/symbols/svg-content.ts +59 -0
  87. package/src/preview/symbols/switch-3pdt.svg +32 -0
  88. package/src/preview/symbols/switch-rotary.svg +23 -0
  89. package/src/preview/symbols/switch-spdt.svg +16 -0
  90. package/src/preview/symbols/switch-spst.svg +14 -0
  91. package/src/preview/symbols/switch-toggle.svg +14 -0
  92. package/src/preview/symbols/transformer.svg +17 -0
  93. package/src/preview/symbols/triode.svg +17 -0
  94. package/src/preview/symbols/tube-diode.svg +13 -0
  95. package/src/preview/symbols/unsupported.svg +8 -0
  96. package/src/preview/symbols/variable-resistor.svg +13 -0
  97. package/src/preview/symbols/voltage-source.svg +15 -0
  98. package/src/preview/symbols.ts +207 -0
  99. package/src/preview/wire-chains.ts +153 -0
@@ -0,0 +1,153 @@
1
+ import type { CircuitDocument, Point, Wire } from '../model/types';
2
+
3
+ // A "chain corner" is a wire endpoint where exactly two wires meet, with no
4
+ // component terminal at the same point and no third wire crossing through —
5
+ // i.e. a routing bend the user sees as a single bend point on a longer
6
+ // connector. These are the points that split-wire / merge-wires operate on.
7
+ export function findChainCorners(doc: CircuitDocument): readonly Point[] {
8
+ const wireByEndpoint = new Map<string, Wire[]>();
9
+ for (const wire of doc.wires) {
10
+ for (const endpoint of wire.endpoints) {
11
+ const key = pointKey(endpoint);
12
+ const list = wireByEndpoint.get(key);
13
+ if (list === undefined) {
14
+ wireByEndpoint.set(key, [wire]);
15
+ } else {
16
+ list.push(wire);
17
+ }
18
+ }
19
+ }
20
+ const terminalSet = new Set<string>();
21
+ for (const component of doc.components) {
22
+ for (const terminal of component.terminals) {
23
+ terminalSet.add(pointKey(terminal.position));
24
+ }
25
+ }
26
+
27
+ const seen = new Set<string>();
28
+ const corners: Point[] = [];
29
+ for (const wire of doc.wires) {
30
+ for (const endpoint of wire.endpoints) {
31
+ const key = pointKey(endpoint);
32
+ if (seen.has(key)) continue;
33
+ if (terminalSet.has(key)) continue;
34
+ const peers = wireByEndpoint.get(key) ?? [];
35
+ if (peers.length !== 2) continue;
36
+ let crossed = false;
37
+ for (const other of doc.wires) {
38
+ if (peers.some((p) => p.id === other.id)) continue;
39
+ if (pointOnSegmentInterior(endpoint, other.endpoints[0], other.endpoints[1])) {
40
+ crossed = true;
41
+ break;
42
+ }
43
+ }
44
+ if (crossed) continue;
45
+ seen.add(key);
46
+ corners.push(endpoint);
47
+ }
48
+ }
49
+ return corners;
50
+ }
51
+
52
+ // findWireChain expands a single wire id into the connected "chain" of wires
53
+ // the user perceives as one connector. Two wires belong to the same chain when
54
+ // they share an endpoint that is:
55
+ // - not a component terminal,
56
+ // - shared by exactly two wires (no branching), and
57
+ // - not on the body of a third wire (no T-junction).
58
+ // The chain stops as soon as any of those conditions fails. This lets fixture
59
+ // L-shapes authored as two adjacent segments select and delete as one unit,
60
+ // while preserving legitimate branching / junctions.
61
+ export function findWireChain(rootWireId: string, doc: CircuitDocument): readonly string[] {
62
+ const root = doc.wires.find((w) => w.id === rootWireId);
63
+ if (root === undefined) {
64
+ return [];
65
+ }
66
+
67
+ const wireByEndpoint = new Map<string, Wire[]>();
68
+ for (const wire of doc.wires) {
69
+ for (const endpoint of wire.endpoints) {
70
+ const key = pointKey(endpoint);
71
+ const list = wireByEndpoint.get(key);
72
+ if (list === undefined) {
73
+ wireByEndpoint.set(key, [wire]);
74
+ } else {
75
+ list.push(wire);
76
+ }
77
+ }
78
+ }
79
+
80
+ const terminalSet = new Set<string>();
81
+ for (const component of doc.components) {
82
+ for (const terminal of component.terminals) {
83
+ terminalSet.add(pointKey(terminal.position));
84
+ }
85
+ }
86
+
87
+ function isInteriorCorner(endpoint: Point, owningWire: Wire): boolean {
88
+ const key = pointKey(endpoint);
89
+ if (terminalSet.has(key)) {
90
+ return false;
91
+ }
92
+ const peers = wireByEndpoint.get(key) ?? [];
93
+ if (peers.length !== 2) {
94
+ return false;
95
+ }
96
+ for (const candidate of doc.wires) {
97
+ if (candidate.id === owningWire.id) continue;
98
+ if (peers.some((peer) => peer.id === candidate.id)) continue;
99
+ if (pointOnSegmentInterior(endpoint, candidate.endpoints[0], candidate.endpoints[1])) {
100
+ return false;
101
+ }
102
+ }
103
+ return true;
104
+ }
105
+
106
+ const visited = new Set<string>([root.id]);
107
+ const stack: Wire[] = [root];
108
+
109
+ while (stack.length > 0) {
110
+ const wire = stack.pop() as Wire;
111
+ for (const endpoint of wire.endpoints) {
112
+ if (!isInteriorCorner(endpoint, wire)) {
113
+ continue;
114
+ }
115
+ const peers = wireByEndpoint.get(pointKey(endpoint)) ?? [];
116
+ for (const peer of peers) {
117
+ if (peer.id === wire.id) continue;
118
+ if (visited.has(peer.id)) continue;
119
+ visited.add(peer.id);
120
+ stack.push(peer);
121
+ }
122
+ }
123
+ }
124
+
125
+ return [...visited];
126
+ }
127
+
128
+ function pointOnSegmentInterior(p: Point, a: Point, b: Point): boolean {
129
+ const cross = (p.x - a.x) * (b.y - a.y) - (p.y - a.y) * (b.x - a.x);
130
+ if (cross !== 0) {
131
+ return false;
132
+ }
133
+ const minX = Math.min(a.x, b.x);
134
+ const maxX = Math.max(a.x, b.x);
135
+ const minY = Math.min(a.y, b.y);
136
+ const maxY = Math.max(a.y, b.y);
137
+ if (minX === maxX && minY === maxY) {
138
+ return false;
139
+ }
140
+ const inXRange = p.x > minX && p.x < maxX;
141
+ const inYRange = p.y > minY && p.y < maxY;
142
+ if (minX === maxX) {
143
+ return inYRange;
144
+ }
145
+ if (minY === maxY) {
146
+ return inXRange;
147
+ }
148
+ return inXRange && inYRange;
149
+ }
150
+
151
+ function pointKey(p: Point): string {
152
+ return `${p.x},${p.y}`;
153
+ }