@tscircuit/schematic-trace-solver 0.0.27 → 0.0.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -154,6 +154,9 @@ interface MovableSegment {
154
154
  };
155
155
  }
156
156
 
157
+ type ChipPin = InputPin & {
158
+ chipId: ChipId;
159
+ };
157
160
  declare class SchematicTraceSingleLineSolver extends BaseSolver {
158
161
  pins: MspConnectionPair["pins"];
159
162
  inputProblem: InputProblem;
@@ -168,6 +171,7 @@ declare class SchematicTraceSingleLineSolver extends BaseSolver {
168
171
  x: number;
169
172
  y: number;
170
173
  }[] | null;
174
+ pinIdMap: Map<PinId, ChipPin>;
171
175
  constructor(params: {
172
176
  pins: MspConnectionPair["pins"];
173
177
  guidelines: Guideline[];
package/dist/index.js CHANGED
@@ -668,6 +668,86 @@ var visualizeGuidelines = ({
668
668
  return graphics;
669
669
  };
670
670
 
671
+ // lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver/getRestrictedCenterLines.ts
672
+ var getRestrictedCenterLines = (params) => {
673
+ const { pins, inputProblem, pinIdMap, chipMap } = params;
674
+ const findAllDirectlyConnectedPins = (startPinId) => {
675
+ const visited = /* @__PURE__ */ new Set();
676
+ const queue = [startPinId];
677
+ visited.add(startPinId);
678
+ const directConns = inputProblem.directConnections || [];
679
+ while (queue.length) {
680
+ const cur = queue.shift();
681
+ for (const dc of directConns) {
682
+ if (dc.pinIds.includes(cur)) {
683
+ for (const p of dc.pinIds) {
684
+ if (!visited.has(p)) {
685
+ visited.add(p);
686
+ queue.push(p);
687
+ }
688
+ }
689
+ }
690
+ }
691
+ }
692
+ return visited;
693
+ };
694
+ const p0 = pins[0].pinId;
695
+ const p1 = pins[1].pinId;
696
+ const relatedPinIds = /* @__PURE__ */ new Set([
697
+ ...findAllDirectlyConnectedPins(p0),
698
+ ...findAllDirectlyConnectedPins(p1)
699
+ ]);
700
+ const restrictedCenterLines = /* @__PURE__ */ new Map();
701
+ const chipFacingMap = /* @__PURE__ */ new Map();
702
+ const chipsOfFacingPins = new Set(pins.map((p) => p.chipId));
703
+ for (const pinId of relatedPinIds) {
704
+ const pin = pinIdMap.get(pinId);
705
+ if (!pin) continue;
706
+ const chip = chipMap[pin.chipId];
707
+ if (!chip) continue;
708
+ const facing = pin._facingDirection ?? getPinDirection(pin, chip);
709
+ let entry = chipFacingMap.get(chip.chipId);
710
+ if (!entry) {
711
+ entry = { center: chip.center };
712
+ const counts = { xPos: 0, xNeg: 0, yPos: 0, yNeg: 0 };
713
+ for (const cp of chip.pins) {
714
+ const cpFacing = cp._facingDirection ?? getPinDirection(cp, chip);
715
+ if (cpFacing === "x+") counts.xPos++;
716
+ if (cpFacing === "x-") counts.xNeg++;
717
+ if (cpFacing === "y+") counts.yPos++;
718
+ if (cpFacing === "y-") counts.yNeg++;
719
+ }
720
+ entry.counts = counts;
721
+ chipFacingMap.set(chip.chipId, entry);
722
+ }
723
+ if (facing === "x+") entry.hasXPos = true;
724
+ if (facing === "x-") entry.hasXNeg = true;
725
+ if (facing === "y+") entry.hasYPos = true;
726
+ if (facing === "y-") entry.hasYNeg = true;
727
+ }
728
+ for (const [chipId, faces] of chipFacingMap) {
729
+ const axes = /* @__PURE__ */ new Set();
730
+ const rcl = { axes };
731
+ const counts = faces.counts;
732
+ const anySideHasMultiplePins = !!(counts && (counts.xPos > 1 || counts.xNeg > 1 || counts.yPos > 1 || counts.yNeg > 1));
733
+ const skipCenterRestriction = !anySideHasMultiplePins && chipsOfFacingPins.has(chipId);
734
+ if (!skipCenterRestriction) {
735
+ if (faces.hasXPos && faces.hasXNeg) {
736
+ rcl.x = faces.center.x;
737
+ axes.add("x");
738
+ }
739
+ if (faces.hasYPos && faces.hasYNeg) {
740
+ rcl.y = faces.center.y;
741
+ axes.add("y");
742
+ }
743
+ }
744
+ if (axes.size > 0) {
745
+ restrictedCenterLines.set(chipId, rcl);
746
+ }
747
+ }
748
+ return restrictedCenterLines;
749
+ };
750
+
671
751
  // lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver/SchematicTraceSingleLineSolver.ts
672
752
  var SchematicTraceSingleLineSolver = class extends BaseSolver {
673
753
  pins;
@@ -680,6 +760,8 @@ var SchematicTraceSingleLineSolver = class extends BaseSolver {
680
760
  queuedCandidatePaths;
681
761
  chipObstacleSpatialIndex;
682
762
  solvedTracePath = null;
763
+ // map of pinId -> pin (with chipId attached)
764
+ pinIdMap = /* @__PURE__ */ new Map();
683
765
  constructor(params) {
684
766
  super();
685
767
  this.pins = params.pins;
@@ -690,6 +772,11 @@ var SchematicTraceSingleLineSolver = class extends BaseSolver {
690
772
  if (!this.inputProblem._chipObstacleSpatialIndex) {
691
773
  this.inputProblem._chipObstacleSpatialIndex = this.chipObstacleSpatialIndex;
692
774
  }
775
+ for (const chip of this.inputProblem.chips) {
776
+ for (const pin of chip.pins) {
777
+ this.pinIdMap.set(pin.pinId, { ...pin, chipId: chip.chipId });
778
+ }
779
+ }
693
780
  for (const pin of this.pins) {
694
781
  if (!pin._facingDirection) {
695
782
  const chip = this.chipMap[pin.chipId];
@@ -746,10 +833,25 @@ var SchematicTraceSingleLineSolver = class extends BaseSolver {
746
833
  return;
747
834
  }
748
835
  const nextCandidatePath = this.queuedCandidatePaths.shift();
836
+ const restrictedCenterLines = getRestrictedCenterLines({
837
+ pins: this.pins,
838
+ inputProblem: this.inputProblem,
839
+ pinIdMap: this.pinIdMap,
840
+ chipMap: this.chipMap
841
+ });
749
842
  for (let i = 0; i < nextCandidatePath.length - 1; i++) {
750
843
  const start = nextCandidatePath[i];
751
844
  const end = nextCandidatePath[i + 1];
752
845
  let excludeChipIds = [];
846
+ const EPS2 = 1e-9;
847
+ for (const [, rcl] of restrictedCenterLines) {
848
+ if (rcl.axes.has("x") && typeof rcl.x === "number") {
849
+ if ((start.x - rcl.x) * (end.x - rcl.x) < -EPS2) return;
850
+ }
851
+ if (rcl.axes.has("y") && typeof rcl.y === "number") {
852
+ if ((start.y - rcl.y) * (end.y - rcl.y) < -EPS2) return;
853
+ }
854
+ }
753
855
  const isStartPin = this.pins.some(
754
856
  (pin) => Math.abs(pin.x - start.x) < 1e-10 && Math.abs(pin.y - start.y) < 1e-10
755
857
  );
@@ -764,12 +866,12 @@ var SchematicTraceSingleLineSolver = class extends BaseSolver {
764
866
  const bounds = getInputChipBounds(this.chipMap[startPin.chipId]);
765
867
  const dx = end.x - start.x;
766
868
  const dy = end.y - start.y;
767
- const EPS2 = 1e-9;
869
+ const EPS3 = 1e-9;
768
870
  const onLeft = Math.abs(start.x - bounds.minX) < 1e-9;
769
871
  const onRight = Math.abs(start.x - bounds.maxX) < 1e-9;
770
872
  const onBottom = Math.abs(start.y - bounds.minY) < 1e-9;
771
873
  const onTop = Math.abs(start.y - bounds.maxY) < 1e-9;
772
- const entersInterior = onLeft && dx > EPS2 || onRight && dx < -EPS2 || onBottom && dy > EPS2 || onTop && dy < -EPS2;
874
+ const entersInterior = onLeft && dx > EPS3 || onRight && dx < -EPS3 || onBottom && dy > EPS3 || onTop && dy < -EPS3;
773
875
  if (entersInterior) return;
774
876
  if (!excludeChipIds.includes(startPin.chipId)) {
775
877
  excludeChipIds.push(startPin.chipId);
@@ -784,12 +886,12 @@ var SchematicTraceSingleLineSolver = class extends BaseSolver {
784
886
  const bounds = getInputChipBounds(this.chipMap[endPin.chipId]);
785
887
  const dx = start.x - end.x;
786
888
  const dy = start.y - end.y;
787
- const EPS2 = 1e-9;
889
+ const EPS3 = 1e-9;
788
890
  const onLeft = Math.abs(end.x - bounds.minX) < 1e-9;
789
891
  const onRight = Math.abs(end.x - bounds.maxX) < 1e-9;
790
892
  const onBottom = Math.abs(end.y - bounds.minY) < 1e-9;
791
893
  const onTop = Math.abs(end.y - bounds.maxY) < 1e-9;
792
- const entersInterior = onLeft && dx > EPS2 || onRight && dx < -EPS2 || onBottom && dy > EPS2 || onTop && dy < -EPS2;
894
+ const entersInterior = onLeft && dx > EPS3 || onRight && dx < -EPS3 || onBottom && dy > EPS3 || onTop && dy < -EPS3;
793
895
  if (entersInterior) return;
794
896
  if (!excludeChipIds.includes(endPin.chipId)) {
795
897
  excludeChipIds.push(endPin.chipId);
@@ -4,7 +4,13 @@ import { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver"
4
4
  import type { Guideline } from "lib/solvers/GuidelinesSolver/GuidelinesSolver"
5
5
  import type { MspConnectionPair } from "lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver"
6
6
  import { visualizeInputProblem } from "lib/solvers/SchematicTracePipelineSolver/visualizeInputProblem"
7
- import type { InputChip, InputProblem } from "lib/types/InputProblem"
7
+ import type {
8
+ ChipId,
9
+ InputChip,
10
+ InputPin,
11
+ InputProblem,
12
+ PinId,
13
+ } from "lib/types/InputProblem"
8
14
  import { calculateElbow } from "calculate-elbow"
9
15
  import { getPinDirection } from "./getPinDirection"
10
16
  import {
@@ -15,6 +21,9 @@ import type { Point } from "@tscircuit/math-utils"
15
21
  import { visualizeGuidelines } from "lib/solvers/GuidelinesSolver/visualizeGuidelines"
16
22
  import { getInputChipBounds } from "lib/solvers/GuidelinesSolver/getInputChipBounds"
17
23
  import { getColorFromString } from "lib/utils/getColorFromString"
24
+ import { getRestrictedCenterLines } from "./getRestrictedCenterLines"
25
+
26
+ type ChipPin = InputPin & { chipId: ChipId }
18
27
 
19
28
  export class SchematicTraceSingleLineSolver extends BaseSolver {
20
29
  pins: MspConnectionPair["pins"]
@@ -31,6 +40,9 @@ export class SchematicTraceSingleLineSolver extends BaseSolver {
31
40
 
32
41
  solvedTracePath: { x: number; y: number }[] | null = null
33
42
 
43
+ // map of pinId -> pin (with chipId attached)
44
+ pinIdMap: Map<PinId, ChipPin> = new Map()
45
+
34
46
  constructor(params: {
35
47
  pins: MspConnectionPair["pins"]
36
48
  guidelines: Guideline[]
@@ -51,6 +63,13 @@ export class SchematicTraceSingleLineSolver extends BaseSolver {
51
63
  this.chipObstacleSpatialIndex
52
64
  }
53
65
 
66
+ // Build a lookup of all pins by id and attach chipId to each pin entry
67
+ for (const chip of this.inputProblem.chips) {
68
+ for (const pin of chip.pins) {
69
+ this.pinIdMap.set(pin.pinId, { ...pin, chipId: chip.chipId })
70
+ }
71
+ }
72
+
54
73
  for (const pin of this.pins) {
55
74
  if (!pin._facingDirection) {
56
75
  const chip = this.chipMap[pin.chipId]
@@ -118,6 +137,13 @@ export class SchematicTraceSingleLineSolver extends BaseSolver {
118
137
 
119
138
  const nextCandidatePath = this.queuedCandidatePaths.shift()!
120
139
 
140
+ const restrictedCenterLines = getRestrictedCenterLines({
141
+ pins: this.pins,
142
+ inputProblem: this.inputProblem,
143
+ pinIdMap: this.pinIdMap,
144
+ chipMap: this.chipMap,
145
+ })
146
+
121
147
  for (let i = 0; i < nextCandidatePath.length - 1; i++) {
122
148
  const start = nextCandidatePath[i]
123
149
  const end = nextCandidatePath[i + 1]
@@ -125,6 +151,19 @@ export class SchematicTraceSingleLineSolver extends BaseSolver {
125
151
  // Determine which chips to exclude for this specific segment
126
152
  let excludeChipIds: string[] = []
127
153
 
154
+ // If this segment would cross any restricted center line, reject the candidate path.
155
+ const EPS = 1e-9
156
+ for (const [, rcl] of restrictedCenterLines) {
157
+ if (rcl.axes.has("x") && typeof rcl.x === "number") {
158
+ // segment strictly crosses vertical center line
159
+ if ((start.x - rcl.x) * (end.x - rcl.x) < -EPS) return
160
+ }
161
+ if (rcl.axes.has("y") && typeof rcl.y === "number") {
162
+ // segment strictly crosses horizontal center line
163
+ if ((start.y - rcl.y) * (end.y - rcl.y) < -EPS) return
164
+ }
165
+ }
166
+
128
167
  // Always exclude chips that contain the start or end points of this segment
129
168
  // if those points are actually pin locations
130
169
  const isStartPin = this.pins.some(
@@ -0,0 +1,139 @@
1
+ import type {
2
+ ChipId,
3
+ InputChip,
4
+ InputPin,
5
+ InputProblem,
6
+ PinId,
7
+ } from "lib/types/InputProblem"
8
+ import { getPinDirection } from "./getPinDirection"
9
+
10
+ type ChipPin = InputPin & { chipId: ChipId }
11
+
12
+ /**
13
+ * A line that should not be crossed by a trace segment.
14
+ * This is used to prevent traces from crossing through the center of a chip when
15
+ * connecting pins on that same chip. Right now this is only enabled when a chip
16
+ * has multiple pins on at least one side.
17
+ */
18
+ export type RestrictedCenterLine = {
19
+ x?: number
20
+ y?: number
21
+ axes: Set<"x" | "y">
22
+ }
23
+
24
+ export const getRestrictedCenterLines = (params: {
25
+ pins: Array<InputPin & { chipId: string }>
26
+ inputProblem: InputProblem
27
+ pinIdMap: Map<PinId, ChipPin>
28
+ chipMap: Record<string, InputChip>
29
+ }): Map<ChipId, RestrictedCenterLine> => {
30
+ const { pins, inputProblem, pinIdMap, chipMap } = params
31
+
32
+ // Determine set of related pin IDs (closure over directConnections) for both endpoints
33
+ const findAllDirectlyConnectedPins = (startPinId: string) => {
34
+ const visited = new Set<string>()
35
+ const queue: string[] = [startPinId]
36
+ visited.add(startPinId)
37
+ const directConns = inputProblem.directConnections || []
38
+ while (queue.length) {
39
+ const cur = queue.shift()!
40
+ for (const dc of directConns) {
41
+ if (dc.pinIds.includes(cur)) {
42
+ for (const p of dc.pinIds) {
43
+ if (!visited.has(p)) {
44
+ visited.add(p)
45
+ queue.push(p)
46
+ }
47
+ }
48
+ }
49
+ }
50
+ }
51
+ return visited
52
+ }
53
+
54
+ const p0 = pins[0].pinId
55
+ const p1 = pins[1].pinId
56
+ const relatedPinIds = new Set<string>([
57
+ ...findAllDirectlyConnectedPins(p0),
58
+ ...findAllDirectlyConnectedPins(p1),
59
+ ])
60
+
61
+ const restrictedCenterLines = new Map<ChipId, RestrictedCenterLine>()
62
+
63
+ // Collect facing-signs per chip
64
+ const chipFacingMap = new Map<
65
+ string,
66
+ {
67
+ hasXPos?: boolean
68
+ hasXNeg?: boolean
69
+ hasYPos?: boolean
70
+ hasYNeg?: boolean
71
+ center: { x: number; y: number }
72
+ counts?: { xPos: number; xNeg: number; yPos: number; yNeg: number }
73
+ }
74
+ >()
75
+
76
+ const chipsOfFacingPins = new Set<string>(pins.map((p) => p.chipId))
77
+
78
+ for (const pinId of relatedPinIds) {
79
+ const pin = pinIdMap.get(pinId)
80
+ if (!pin) continue
81
+ const chip = chipMap[pin.chipId]
82
+ if (!chip) continue
83
+ const facing = pin._facingDirection ?? getPinDirection(pin, chip)
84
+ let entry = chipFacingMap.get(chip.chipId)
85
+ if (!entry) {
86
+ entry = { center: chip.center }
87
+
88
+ const counts = { xPos: 0, xNeg: 0, yPos: 0, yNeg: 0 }
89
+ for (const cp of chip.pins) {
90
+ const cpFacing = cp._facingDirection ?? getPinDirection(cp, chip)
91
+ if (cpFacing === "x+") counts.xPos++
92
+ if (cpFacing === "x-") counts.xNeg++
93
+ if (cpFacing === "y+") counts.yPos++
94
+ if (cpFacing === "y-") counts.yNeg++
95
+ }
96
+ entry.counts = counts
97
+
98
+ chipFacingMap.set(chip.chipId, entry)
99
+ }
100
+ if (facing === "x+") entry.hasXPos = true
101
+ if (facing === "x-") entry.hasXNeg = true
102
+ if (facing === "y+") entry.hasYPos = true
103
+ if (facing === "y-") entry.hasYNeg = true
104
+ }
105
+
106
+ // Only mark a center as restricted on an axis if both signs for that axis
107
+ // are present among related pins on the chip.
108
+ for (const [chipId, faces] of chipFacingMap) {
109
+ const axes = new Set<"x" | "y">()
110
+ const rcl: RestrictedCenterLine = { axes }
111
+
112
+ // determine whether any side on this chip has more than one pin
113
+ const counts = faces.counts
114
+ const anySideHasMultiplePins = !!(
115
+ counts &&
116
+ (counts.xPos > 1 || counts.xNeg > 1 || counts.yPos > 1 || counts.yNeg > 1)
117
+ )
118
+
119
+ const skipCenterRestriction =
120
+ !anySideHasMultiplePins && chipsOfFacingPins.has(chipId)
121
+
122
+ if (!skipCenterRestriction) {
123
+ if (faces.hasXPos && faces.hasXNeg) {
124
+ rcl.x = faces.center.x
125
+ axes.add("x")
126
+ }
127
+ if (faces.hasYPos && faces.hasYNeg) {
128
+ rcl.y = faces.center.y
129
+ axes.add("y")
130
+ }
131
+ }
132
+
133
+ if (axes.size > 0) {
134
+ restrictedCenterLines.set(chipId, rcl)
135
+ }
136
+ }
137
+
138
+ return restrictedCenterLines
139
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tscircuit/schematic-trace-solver",
3
3
  "main": "dist/index.js",
4
- "version": "0.0.27",
4
+ "version": "0.0.28",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "start": "cosmos",
@@ -0,0 +1,143 @@
1
+ import { GenericSolverDebugger } from "site/components/GenericSolverDebugger"
2
+ import { useMemo } from "react"
3
+ import type { InputProblem } from "lib/types/InputProblem"
4
+ import { SchematicTracePipelineSolver } from "lib/index"
5
+
6
+ const inputProblem = {
7
+ chips: [
8
+ {
9
+ chipId: "schematic_component_0",
10
+ center: {
11
+ x: 0,
12
+ y: 0,
13
+ },
14
+ width: 2,
15
+ height: 1.4,
16
+ pins: [
17
+ {
18
+ pinId: "U3.8",
19
+ x: -1.4,
20
+ y: 0.42500000000000004,
21
+ },
22
+ {
23
+ pinId: "U3.4",
24
+ x: -1.4,
25
+ y: -0.42500000000000004,
26
+ },
27
+ {
28
+ pinId: "U3.1",
29
+ x: 1.4,
30
+ y: 0.5,
31
+ },
32
+ {
33
+ pinId: "U3.6",
34
+ x: 1.4,
35
+ y: 0.30000000000000004,
36
+ },
37
+ {
38
+ pinId: "U3.5",
39
+ x: 1.4,
40
+ y: 0.10000000000000009,
41
+ },
42
+ {
43
+ pinId: "U3.2",
44
+ x: 1.4,
45
+ y: -0.09999999999999998,
46
+ },
47
+ {
48
+ pinId: "U3.3",
49
+ x: 1.4,
50
+ y: -0.3,
51
+ },
52
+ {
53
+ pinId: "U3.7",
54
+ x: 1.4,
55
+ y: -0.5,
56
+ },
57
+ ],
58
+ },
59
+ {
60
+ chipId: "schematic_component_1",
61
+ center: {
62
+ x: -2.3145833,
63
+ y: 0,
64
+ },
65
+ width: 0.5291665999999999,
66
+ height: 1.0583333000000001,
67
+ pins: [
68
+ {
69
+ pinId: "C20.1",
70
+ x: -2.3148566499999994,
71
+ y: 0.5512093000000002,
72
+ },
73
+ {
74
+ pinId: "C20.2",
75
+ x: -2.31430995,
76
+ y: -0.5512093000000002,
77
+ },
78
+ ],
79
+ },
80
+ {
81
+ chipId: "schematic_component_2",
82
+ center: {
83
+ x: 1.7577928249999983,
84
+ y: 1.7512907000000002,
85
+ },
86
+ width: 0.3155856499999966,
87
+ height: 1.0583332999999997,
88
+ pins: [
89
+ {
90
+ pinId: "R11.1",
91
+ x: 1.7580660749999977,
92
+ y: 2.3025814000000002,
93
+ },
94
+ {
95
+ pinId: "R11.2",
96
+ x: 1.757519574999999,
97
+ y: 1.2,
98
+ },
99
+ ],
100
+ },
101
+ ],
102
+ directConnections: [
103
+ {
104
+ pinIds: ["C20.1", "U3.8"],
105
+ netId: "capacitor.C20 > port.pin1 to .U3 > .VDD",
106
+ },
107
+ {
108
+ pinIds: ["C20.2", "U3.4"],
109
+ netId: "capacitor.C20 > port.pin2 to .U3 > .GND",
110
+ },
111
+ {
112
+ pinIds: ["R11.2", "U3.1"],
113
+ netId: "resistor.R11 > port.pin2 to .U3 > .N_CS",
114
+ },
115
+ ],
116
+ netConnections: [
117
+ {
118
+ netId: "V3_3",
119
+ pinIds: ["U3.8", "U3.3", "U3.7", "C20.1", "R11.1"],
120
+ },
121
+ {
122
+ netId: "GND",
123
+ pinIds: ["U3.4", "C20.2"],
124
+ },
125
+ {
126
+ netId: "FLASH_N_CS",
127
+ pinIds: ["U3.1", "R11.2"],
128
+ },
129
+ ],
130
+ availableNetLabelOrientations: {
131
+ V3_3: ["y+"],
132
+ GND: ["y-"],
133
+ },
134
+ maxMspPairDistance: 5,
135
+ } as InputProblem
136
+
137
+ export default () => {
138
+ const solver = useMemo(
139
+ () => new SchematicTracePipelineSolver(inputProblem),
140
+ [],
141
+ )
142
+ return <GenericSolverDebugger solver={solver} />
143
+ }
@@ -91,11 +91,17 @@ x+" data-x="4.5649999999999995" data-y="3" cx="586.9994196169471" cy="153.430063
91
91
  <g>
92
92
  <circle data-type="point" data-label="" data-x="-2.25" data-y="0" cx="144.0046430644225" cy="348.43876958792805" r="3" fill="hsl(40, 100%, 50%, 0.9)" />
93
93
  </g>
94
+ <g>
95
+ <circle data-type="point" data-label="" data-x="3.75" data-y="-1.5" cx="534.022054556007" cy="445.94312246082416" r="3" fill="hsl(40, 100%, 50%, 0.9)" />
96
+ </g>
94
97
  <g>
95
98
  <circle data-type="point" data-label="" data-x="-2.25" data-y="-1.9999999999999998" cx="144.0046430644225" cy="478.4445734184562" r="3" fill="hsl(40, 100%, 50%, 0.9)" />
96
99
  </g>
97
100
  <g>
98
- <circle data-type="point" data-label="" data-x="1.35" data-y="0" cx="378.0150899593732" cy="348.43876958792805" r="3" fill="hsl(40, 100%, 50%, 0.9)" />
101
+ <circle data-type="point" data-label="" data-x="1.3499999999999999" data-y="0" cx="378.01508995937314" cy="348.43876958792805" r="3" fill="hsl(40, 100%, 50%, 0.9)" />
102
+ </g>
103
+ <g>
104
+ <circle data-type="point" data-label="" data-x="-1.15" data-y="-1.125" cx="215.507835171213" cy="421.56703424260013" r="3" fill="hsl(40, 100%, 50%, 0.9)" />
99
105
  </g>
100
106
  <g>
101
107
  <polyline data-points="-3.55,0 -1.15,1.125" data-type="line" data-label="" points="59.50087057457921,348.43876958792805 215.507835171213,275.31050493325597" fill="none" stroke="hsl(40, 100%, 50%, 0.8)" stroke-width="1" />
@@ -199,9 +205,6 @@ x+" data-x="4.5649999999999995" data-y="3" cx="586.9994196169471" cy="153.430063
199
205
  <g>
200
206
  <polyline data-points="1.15,0.75 3.75,0.75 3.75,0 3.55,0" data-type="line" data-label="" points="365.01450957632034,299.68659315148 534.022054556007,299.68659315148 534.022054556007,348.43876958792805 521.0214741729542,348.43876958792805" fill="none" stroke="purple" stroke-width="1" />
201
207
  </g>
202
- <g>
203
- <polyline data-points="-1.15,-0.375 -1.3499999999999999,-0.375 -1.3499999999999999,2.275 1.35,2.275 1.35,0.75 1.15,0.75" data-type="line" data-label="" points="215.507835171213,372.8148578061521 202.5072547881602,372.8148578061521 202.5072547881602,200.55716773070228 378.0150899593732,200.55716773070228 378.0150899593732,299.68659315148 365.01450957632034,299.68659315148" fill="none" stroke="purple" stroke-width="1" />
204
- </g>
205
208
  <g>
206
209
  <polyline data-points="-2.45,-1.9999999999999998 -2.25,-1.9999999999999998 -1.8,-1.9999999999999998 -1.8,-0.375 -1.3499999999999999,-0.375 -1.15,-0.375" data-type="line" data-label="" points="131.00406268136967,478.4445734184562 144.0046430644225,478.4445734184562 173.25594892629132,478.4445734184562 173.25594892629132,372.8148578061521 202.5072547881602,372.8148578061521 215.507835171213,372.8148578061521" fill="none" stroke="purple" stroke-width="1" />
207
210
  </g>
@@ -211,9 +214,6 @@ x+" data-x="4.5649999999999995" data-y="3" cx="586.9994196169471" cy="153.430063
211
214
  <g>
212
215
  <polyline data-points="1.15,0 1.3499999999999999,0 1.8,0 1.8,-1.5 2.25,-1.5 2.45,-1.5" data-type="line" data-label="" points="365.01450957632034,348.43876958792805 378.01508995937314,348.43876958792805 407.266395821242,348.43876958792805 407.266395821242,445.94312246082416 436.51770168311083,445.94312246082416 449.5182820661637,445.94312246082416" fill="none" stroke="purple" stroke-width="1" />
213
216
  </g>
214
- <g>
215
- <polyline data-points="-1.15,-1.125 -1.3499999999999999,-1.125 -1.3499999999999999,-2.29445535 1.35,-2.29445535 1.35,0 1.15,0" data-type="line" data-label="" points="215.507835171213,421.56703424260013 202.5072547881602,421.56703424260013 202.5072547881602,497.58502565293094 378.0150899593732,497.58502565293094 378.0150899593732,348.43876958792805 365.01450957632034,348.43876958792805" fill="none" stroke="purple" stroke-width="1" />
216
- </g>
217
217
  <g>
218
218
  <rect data-type="rect" data-label="schematic_component_0" data-x="0" data-y="0" x="215.507835171213" y="226.55832849680792" width="149.50667440510733" height="243.76088218224027" fill="hsl(24, 100%, 50%, 0.8)" stroke="black" stroke-width="0.015383928571428571" />
219
219
  </g>
@@ -250,11 +250,17 @@ x+" data-x="4.5649999999999995" data-y="3" cx="586.9994196169471" cy="153.430063
250
250
  <g>
251
251
  <rect data-type="rect" data-label="" data-x="-2.25" data-y="0.225" x="137.50435287289608" y="319.1874637260592" width="13.000580383052835" height="29.251305861868843" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.015383928571428571" />
252
252
  </g>
253
+ <g>
254
+ <rect data-type="rect" data-label="" data-x="3.75" data-y="-1.725" x="527.5217643644805" y="445.94312246082416" width="13.000580383052807" height="29.251305861868843" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.015383928571428571" />
255
+ </g>
253
256
  <g>
254
257
  <rect data-type="rect" data-label="" data-x="-2.25" data-y="-2.2249999999999996" x="137.50435287289608" y="478.4445734184562" width="13.000580383052835" height="29.251305861868843" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.015383928571428571" />
255
258
  </g>
256
259
  <g>
257
- <rect data-type="rect" data-label="" data-x="1.35" data-y="0.225" x="371.51479976784674" y="319.1874637260592" width="13.000580383052863" height="29.251305861868843" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.015383928571428571" />
260
+ <rect data-type="rect" data-label="" data-x="1.3499999999999999" data-y="0.225" x="371.51479976784674" y="319.1874637260592" width="13.000580383052807" height="29.251305861868843" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.015383928571428571" />
261
+ </g>
262
+ <g>
263
+ <rect data-type="rect" data-label="" data-x="-1.376" data-y="-1.125" x="186.19152640742888" y="415.0667440510737" width="29.251305861868843" height="13.000580383052807" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.015383928571428571" />
258
264
  </g>
259
265
  <g id="crosshair" style="display: none">
260
266
  <line id="crosshair-h" y1="0" y2="640" stroke="#666" stroke-width="0.5" />
@@ -0,0 +1,235 @@
1
+ <svg width="640" height="640" viewBox="0 0 640 640" xmlns="http://www.w3.org/2000/svg">
2
+ <rect width="100%" height="100%" fill="white" />
3
+ <g>
4
+ <circle data-type="point" data-label="U3.8
5
+ x-" data-x="-1.4" data-y="0.42500000000000004" cx="211.29957848579102" cy="266.28437168733643" r="3" fill="hsl(88, 100%, 50%, 0.8)" />
6
+ </g>
7
+ <g>
8
+ <circle data-type="point" data-label="U3.4
9
+ x-" data-x="-1.4" data-y="-0.42500000000000004" cx="211.29957848579102" cy="342.38151179694313" r="3" fill="hsl(84, 100%, 50%, 0.8)" />
10
+ </g>
11
+ <g>
12
+ <circle data-type="point" data-label="U3.1
13
+ x+" data-x="1.4" data-y="0.5" cx="461.9725106115543" cy="259.56991814825346" r="3" fill="hsl(81, 100%, 50%, 0.8)" />
14
+ </g>
15
+ <g>
16
+ <circle data-type="point" data-label="U3.6
17
+ x+" data-x="1.4" data-y="0.30000000000000004" cx="461.9725106115543" cy="277.47512758580797" r="3" fill="hsl(86, 100%, 50%, 0.8)" />
18
+ </g>
19
+ <g>
20
+ <circle data-type="point" data-label="U3.5
21
+ x+" data-x="1.4" data-y="0.10000000000000009" cx="461.9725106115543" cy="295.38033702336253" r="3" fill="hsl(85, 100%, 50%, 0.8)" />
22
+ </g>
23
+ <g>
24
+ <circle data-type="point" data-label="U3.2
25
+ x+" data-x="1.4" data-y="-0.09999999999999998" cx="461.9725106115543" cy="313.28554646091703" r="3" fill="hsl(82, 100%, 50%, 0.8)" />
26
+ </g>
27
+ <g>
28
+ <circle data-type="point" data-label="U3.3
29
+ x+" data-x="1.4" data-y="-0.3" cx="461.9725106115543" cy="331.1907558984716" r="3" fill="hsl(83, 100%, 50%, 0.8)" />
30
+ </g>
31
+ <g>
32
+ <circle data-type="point" data-label="U3.7
33
+ x+" data-x="1.4" data-y="-0.5" cx="461.9725106115543" cy="349.0959653360261" r="3" fill="hsl(87, 100%, 50%, 0.8)" />
34
+ </g>
35
+ <g>
36
+ <circle data-type="point" data-label="C20.1
37
+ y+" data-x="-2.3148566499999994" data-y="0.5512093000000002" cx="129.39607886784347" cy="254.98535194000064" r="3" fill="hsl(140, 100%, 50%, 0.8)" />
38
+ </g>
39
+ <g>
40
+ <circle data-type="point" data-label="C20.2
41
+ y-" data-x="-2.31430995" data-y="-0.5512093000000002" cx="129.44502275784095" cy="353.6805315442789" r="3" fill="hsl(141, 100%, 50%, 0.8)" />
42
+ </g>
43
+ <g>
44
+ <circle data-type="point" data-label="R11.1
45
+ y+" data-x="1.7580660749999977" data-y="2.3025814000000002" cx="494.0287509383446" cy="98.19193067205222" r="3" fill="hsl(125, 100%, 50%, 0.8)" />
46
+ </g>
47
+ <g>
48
+ <circle data-type="point" data-label="R11.2
49
+ y-" data-x="1.757519574999999" data-y="1.2" cx="493.97982495355666" cy="196.90168511681264" r="3" fill="hsl(126, 100%, 50%, 0.8)" />
50
+ </g>
51
+ <g>
52
+ <circle data-type="point" data-label="R12.1
53
+ y-" data-x="-1.7580660749999977" data-y="-3.3025814000000002" cx="179.24333815900067" cy="600" r="3" fill="hsl(6, 100%, 50%, 0.8)" />
54
+ </g>
55
+ <g>
56
+ <circle data-type="point" data-label="R12.2
57
+ y+" data-x="-1.757519574999999" data-y="-2.2" cx="179.29226414378866" cy="501.29024555523955" r="3" fill="hsl(7, 100%, 50%, 0.8)" />
58
+ </g>
59
+ <g>
60
+ <circle data-type="point" data-label="R13.1
61
+ y-" data-x="1.7580660749999977" data-y="-3.3025814000000002" cx="494.0287509383446" cy="600" r="3" fill="hsl(247, 100%, 50%, 0.8)" />
62
+ </g>
63
+ <g>
64
+ <circle data-type="point" data-label="R13.2
65
+ y+" data-x="1.757519574999999" data-y="-2.2" cx="493.97982495355666" cy="501.29024555523955" r="3" fill="hsl(248, 100%, 50%, 0.8)" />
66
+ </g>
67
+ <g>
68
+ <circle data-type="point" data-label="" data-x="-1.8574283249999997" data-y="0.7512093000000004" cx="170.34782867681724" cy="237.0801425024461" r="3" fill="hsl(40, 100%, 50%, 0.9)" />
69
+ </g>
70
+ <g>
71
+ <circle data-type="point" data-label="" data-x="1.7580660749999977" data-y="2.5025814000000004" cx="494.0287509383446" cy="80.28672123449769" r="3" fill="hsl(40, 100%, 50%, 0.9)" />
72
+ </g>
73
+ <g>
74
+ <circle data-type="point" data-label="" data-x="-2.31430995" data-y="-0.7512093000000004" cx="129.44502275784095" cy="371.58574098183345" r="3" fill="hsl(40, 100%, 50%, 0.9)" />
75
+ </g>
76
+ <g>
77
+ <circle data-type="point" data-label="" data-x="1.757519574999999" data-y="0.85" cx="493.97982495355666" cy="228.23580163253305" r="3" fill="hsl(40, 100%, 50%, 0.9)" />
78
+ </g>
79
+ <g>
80
+ <circle data-type="point" data-label="" data-x="1.757519574999999" data-y="-2" cx="493.97982495355666" cy="483.385036117685" r="3" fill="hsl(40, 100%, 50%, 0.9)" />
81
+ </g>
82
+ <g>
83
+ <polyline data-points="-2.3148566499999994,0.5512093000000002 -1.4,0.42500000000000004" data-type="line" data-label="" points="129.39607886784347,254.98535194000064 211.29957848579102,266.28437168733643" fill="none" stroke="hsl(256, 100%, 50%, 0.8)" stroke-width="1" />
84
+ </g>
85
+ <g>
86
+ <polyline data-points="-2.31430995,-0.5512093000000002 -1.4,-0.42500000000000004" data-type="line" data-label="" points="129.44502275784095,353.6805315442789 211.29957848579102,342.38151179694313" fill="none" stroke="hsl(256, 100%, 50%, 0.8)" stroke-width="1" />
87
+ </g>
88
+ <g>
89
+ <polyline data-points="1.757519574999999,1.2 1.4,0.5" data-type="line" data-label="" points="493.97982495355666,196.90168511681264 461.9725106115543,259.56991814825346" fill="none" stroke="hsl(144, 100%, 50%, 0.8)" stroke-width="1" />
90
+ </g>
91
+ <g>
92
+ <polyline data-points="1.757519574999999,-2.2 -1.757519574999999,-2.2" data-type="line" data-label="" points="493.97982495355666,501.29024555523955 179.29226414378866,501.29024555523955" fill="none" stroke="hsl(144, 100%, 50%, 0.8)" stroke-width="1" />
93
+ </g>
94
+ <g>
95
+ <polyline data-points="-1.4,0.42500000000000004 1.4,-0.3" data-type="line" data-label="" points="211.29957848579102,266.28437168733643 461.9725106115543,331.1907558984716" fill="none" stroke="hsl(73, 100%, 50%, 0.8)" stroke-width="1" stroke-dasharray="4 2" />
96
+ </g>
97
+ <g>
98
+ <polyline data-points="-1.4,0.42500000000000004 1.4,-0.5" data-type="line" data-label="" points="211.29957848579102,266.28437168733643 461.9725106115543,349.0959653360261" fill="none" stroke="hsl(73, 100%, 50%, 0.8)" stroke-width="1" stroke-dasharray="4 2" />
99
+ </g>
100
+ <g>
101
+ <polyline data-points="-1.4,0.42500000000000004 -2.3148566499999994,0.5512093000000002" data-type="line" data-label="" points="211.29957848579102,266.28437168733643 129.39607886784347,254.98535194000064" fill="none" stroke="hsl(73, 100%, 50%, 0.8)" stroke-width="1" stroke-dasharray="4 2" />
102
+ </g>
103
+ <g>
104
+ <polyline data-points="-1.4,0.42500000000000004 1.7580660749999977,2.3025814000000002" data-type="line" data-label="" points="211.29957848579102,266.28437168733643 494.0287509383446,98.19193067205222" fill="none" stroke="hsl(73, 100%, 50%, 0.8)" stroke-width="1" stroke-dasharray="4 2" />
105
+ </g>
106
+ <g>
107
+ <polyline data-points="1.4,-0.3 1.4,-0.5" data-type="line" data-label="" points="461.9725106115543,331.1907558984716 461.9725106115543,349.0959653360261" fill="none" stroke="hsl(73, 100%, 50%, 0.8)" stroke-width="1" stroke-dasharray="4 2" />
108
+ </g>
109
+ <g>
110
+ <polyline data-points="1.4,-0.3 -2.3148566499999994,0.5512093000000002" data-type="line" data-label="" points="461.9725106115543,331.1907558984716 129.39607886784347,254.98535194000064" fill="none" stroke="hsl(73, 100%, 50%, 0.8)" stroke-width="1" stroke-dasharray="4 2" />
111
+ </g>
112
+ <g>
113
+ <polyline data-points="1.4,-0.3 1.7580660749999977,2.3025814000000002" data-type="line" data-label="" points="461.9725106115543,331.1907558984716 494.0287509383446,98.19193067205222" fill="none" stroke="hsl(73, 100%, 50%, 0.8)" stroke-width="1" stroke-dasharray="4 2" />
114
+ </g>
115
+ <g>
116
+ <polyline data-points="1.4,-0.5 -2.3148566499999994,0.5512093000000002" data-type="line" data-label="" points="461.9725106115543,349.0959653360261 129.39607886784347,254.98535194000064" fill="none" stroke="hsl(73, 100%, 50%, 0.8)" stroke-width="1" stroke-dasharray="4 2" />
117
+ </g>
118
+ <g>
119
+ <polyline data-points="1.4,-0.5 1.7580660749999977,2.3025814000000002" data-type="line" data-label="" points="461.9725106115543,349.0959653360261 494.0287509383446,98.19193067205222" fill="none" stroke="hsl(73, 100%, 50%, 0.8)" stroke-width="1" stroke-dasharray="4 2" />
120
+ </g>
121
+ <g>
122
+ <polyline data-points="-2.3148566499999994,0.5512093000000002 1.7580660749999977,2.3025814000000002" data-type="line" data-label="" points="129.39607886784347,254.98535194000064 494.0287509383446,98.19193067205222" fill="none" stroke="hsl(73, 100%, 50%, 0.8)" stroke-width="1" stroke-dasharray="4 2" />
123
+ </g>
124
+ <g>
125
+ <polyline data-points="-1.4,-0.42500000000000004 -2.31430995,-0.5512093000000002" data-type="line" data-label="" points="211.29957848579102,342.38151179694313 129.44502275784095,353.6805315442789" fill="none" stroke="hsl(157, 100%, 50%, 0.8)" stroke-width="1" stroke-dasharray="4 2" />
126
+ </g>
127
+ <g>
128
+ <polyline data-points="1.4,0.5 1.757519574999999,1.2" data-type="line" data-label="" points="461.9725106115543,259.56991814825346 493.97982495355666,196.90168511681264" fill="none" stroke="hsl(304, 100%, 50%, 0.8)" stroke-width="1" stroke-dasharray="4 2" />
129
+ </g>
130
+ <g>
131
+ <polyline data-points="-1.4,0.42500000000000004 -1.8574283249999997,0.42500000000000004 -1.8574283249999997,0.7512093000000004 -2.3148566499999994,0.7512093000000004 -2.3148566499999994,0.5512093000000002" data-type="line" data-label="" points="211.29957848579102,266.28437168733643 170.34782867681724,266.28437168733643 170.34782867681724,237.0801425024461 129.39607886784347,237.0801425024461 129.39607886784347,254.98535194000064" fill="none" stroke="purple" stroke-width="1" />
132
+ </g>
133
+ <g>
134
+ <polyline data-points="1.4,-0.5 1.5999999999999999,-0.5 1.5999999999999999,-0.3 1.4,-0.3" data-type="line" data-label="" points="461.9725106115543,349.0959653360261 479.8777200491088,349.0959653360261 479.8777200491088,331.1907558984716 461.9725106115543,331.1907558984716" fill="none" stroke="purple" stroke-width="1" />
135
+ </g>
136
+ <g>
137
+ <polyline data-points="1.7580660749999977,2.3025814000000002 1.7580660749999977,2.5025814000000004 1.5790330374999988,2.5025814000000004 1.5790330374999988,-0.3 1.4,-0.3" data-type="line" data-label="" points="494.0287509383446,98.19193067205222 494.0287509383446,80.28672123449769 478.0006307749495,80.28672123449769 478.0006307749495,331.1907558984716 461.9725106115543,331.1907558984716" fill="none" stroke="purple" stroke-width="1" />
138
+ </g>
139
+ <g>
140
+ <polyline data-points="-2.31430995,-0.5512093000000002 -2.31430995,-0.7512093000000004 -1.857154975,-0.7512093000000004 -1.857154975,-0.42500000000000004 -1.4,-0.42500000000000004" data-type="line" data-label="" points="129.44502275784095,353.6805315442789 129.44502275784095,371.58574098183345 170.37230062181598,371.58574098183345 170.37230062181598,342.38151179694313 211.29957848579102,342.38151179694313" fill="none" stroke="purple" stroke-width="1" />
141
+ </g>
142
+ <g>
143
+ <polyline data-points="1.757519574999999,1.2 1.757519574999999,0.5 1.4,0.5" data-type="line" data-label="" points="493.97982495355666,196.90168511681264 493.97982495355666,259.56991814825346 461.9725106115543,259.56991814825346" fill="none" stroke="purple" stroke-width="1" />
144
+ </g>
145
+ <g>
146
+ <polyline data-points="1.757519574999999,-2.2 1.757519574999999,-2 -1.757519574999999,-2 -1.757519574999999,-2.2" data-type="line" data-label="" points="493.97982495355666,501.29024555523955 493.97982495355666,483.385036117685 179.29226414378866,483.385036117685 179.29226414378866,501.29024555523955" fill="none" stroke="purple" stroke-width="1" />
147
+ </g>
148
+ <g>
149
+ <rect data-type="rect" data-label="schematic_component_0" data-x="0" data-y="0" x="211.29957848579102" y="241.66470871069896" width="250.67293212576328" height="125.33646606288164" fill="hsl(24, 100%, 50%, 0.8)" stroke="black" stroke-width="0.011169933571428573" />
150
+ </g>
151
+ <g>
152
+ <rect data-type="rect" data-label="schematic_component_1" data-x="-2.3145833" data-y="0" x="105.73345381194562" y="254.98535194000064" width="47.37419400179317" height="98.69517960427825" fill="hsl(24, 100%, 50%, 0.8)" stroke="black" stroke-width="0.011169933571428573" />
153
+ </g>
154
+ <g>
155
+ <rect data-type="rect" data-label="schematic_component_2" data-x="1.7577928249999983" data-y="1.7512907000000002" x="479.87772004910886" y="98.19193067205222" width="28.25313579368361" height="98.70975444476042" fill="hsl(24, 100%, 50%, 0.8)" stroke="black" stroke-width="0.011169933571428573" />
156
+ </g>
157
+ <g>
158
+ <rect data-type="rect" data-label="schematic_component_3" data-x="-1.7577928249999983" data-y="-2.7512907" x="165.14123325455287" y="501.29024555523955" width="28.253135793683583" height="98.70975444476045" fill="hsl(24, 100%, 50%, 0.8)" stroke="black" stroke-width="0.011169933571428573" />
159
+ </g>
160
+ <g>
161
+ <rect data-type="rect" data-label="schematic_component_4" data-x="1.7577928249999983" data-y="-2.7512907" x="479.87772004910886" y="501.29024555523955" width="28.25313579368361" height="98.70975444476045" fill="hsl(24, 100%, 50%, 0.8)" stroke="black" stroke-width="0.011169933571428573" />
162
+ </g>
163
+ <g>
164
+ <rect data-type="rect" data-label="" data-x="-1.8574283249999997" data-y="0.9762093000000004" x="161.39522395803996" y="196.79342126794842" width="17.905209437554532" height="40.28672123449769" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.011169933571428573" />
165
+ </g>
166
+ <g>
167
+ <rect data-type="rect" data-label="" data-x="1.7580660749999977" data-y="2.7275814000000005" x="485.07614621956736" y="40" width="17.90520943755456" height="40.28672123449769" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.011169933571428573" />
168
+ </g>
169
+ <g>
170
+ <rect data-type="rect" data-label="" data-x="-2.31430995" data-y="-0.9762093000000004" x="120.4924180390637" y="371.58574098183345" width="17.905209437554532" height="40.28672123449769" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.011169933571428573" />
171
+ </g>
172
+ <g>
173
+ <rect data-type="rect" data-label="" data-x="1.982519574999999" data-y="0.85" x="493.97982495355666" y="219.2831969137558" width="40.28672123449769" height="17.905209437554532" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.011169933571428573" />
174
+ </g>
175
+ <g>
176
+ <rect data-type="rect" data-label="" data-x="1.982519574999999" data-y="-2" x="493.97982495355666" y="474.43243139890774" width="40.28672123449769" height="17.90520943755456" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.011169933571428573" />
177
+ </g>
178
+ <g id="crosshair" style="display: none">
179
+ <line id="crosshair-h" y1="0" y2="640" stroke="#666" stroke-width="0.5" />
180
+ <line id="crosshair-v" x1="0" x2="640" stroke="#666" stroke-width="0.5" /><text id="coordinates" font-family="monospace" font-size="12" fill="#666"></text>
181
+ </g>
182
+ <script>
183
+ <![CDATA[
184
+ document.currentScript.parentElement.addEventListener('mousemove', (e) => {
185
+ const svg = e.currentTarget;
186
+ const rect = svg.getBoundingClientRect();
187
+ const x = e.clientX - rect.left;
188
+ const y = e.clientY - rect.top;
189
+ const crosshair = svg.getElementById('crosshair');
190
+ const h = svg.getElementById('crosshair-h');
191
+ const v = svg.getElementById('crosshair-v');
192
+ const coords = svg.getElementById('coordinates');
193
+
194
+ crosshair.style.display = 'block';
195
+ h.setAttribute('x1', '0');
196
+ h.setAttribute('x2', '640');
197
+ h.setAttribute('y1', y);
198
+ h.setAttribute('y2', y);
199
+ v.setAttribute('x1', x);
200
+ v.setAttribute('x2', x);
201
+ v.setAttribute('y1', '0');
202
+ v.setAttribute('y2', '640');
203
+
204
+ // Calculate real coordinates using inverse transformation
205
+ const matrix = {
206
+ "a": 89.52604718777262,
207
+ "c": 0,
208
+ "e": 336.63604454867266,
209
+ "b": 0,
210
+ "d": -89.52604718777262,
211
+ "f": 304.3329417421398
212
+ };
213
+ // Manually invert and apply the affine transform
214
+ // Since we only use translate and scale, we can directly compute:
215
+ // x' = (x - tx) / sx
216
+ // y' = (y - ty) / sy
217
+ const sx = matrix.a;
218
+ const sy = matrix.d;
219
+ const tx = matrix.e;
220
+ const ty = matrix.f;
221
+ const realPoint = {
222
+ x: (x - tx) / sx,
223
+ y: (y - ty) / sy // Flip y back since we used negative scale
224
+ }
225
+
226
+ coords.textContent = `(${realPoint.x.toFixed(2)}, ${realPoint.y.toFixed(2)})`;
227
+ coords.setAttribute('x', (x + 5).toString());
228
+ coords.setAttribute('y', (y - 5).toString());
229
+ });
230
+ document.currentScript.parentElement.addEventListener('mouseleave', () => {
231
+ document.currentScript.parentElement.getElementById('crosshair').style.display = 'none';
232
+ });
233
+ ]]>
234
+ </script>
235
+ </svg>
@@ -0,0 +1,187 @@
1
+ import { expect } from "bun:test"
2
+ import { test } from "bun:test"
3
+ import { SchematicTracePipelineSolver, type InputProblem } from "lib/index"
4
+ import "tests/fixtures/matcher"
5
+
6
+ const inputProblem = {
7
+ chips: [
8
+ {
9
+ chipId: "schematic_component_0",
10
+ center: {
11
+ x: 0,
12
+ y: 0,
13
+ },
14
+ width: 2,
15
+ height: 1.4,
16
+ pins: [
17
+ {
18
+ pinId: "U3.8",
19
+ x: -1.4,
20
+ y: 0.42500000000000004,
21
+ },
22
+ {
23
+ pinId: "U3.4",
24
+ x: -1.4,
25
+ y: -0.42500000000000004,
26
+ },
27
+ {
28
+ pinId: "U3.1",
29
+ x: 1.4,
30
+ y: 0.5,
31
+ },
32
+ {
33
+ pinId: "U3.6",
34
+ x: 1.4,
35
+ y: 0.30000000000000004,
36
+ },
37
+ {
38
+ pinId: "U3.5",
39
+ x: 1.4,
40
+ y: 0.10000000000000009,
41
+ },
42
+ {
43
+ pinId: "U3.2",
44
+ x: 1.4,
45
+ y: -0.09999999999999998,
46
+ },
47
+ {
48
+ pinId: "U3.3",
49
+ x: 1.4,
50
+ y: -0.3,
51
+ },
52
+ {
53
+ pinId: "U3.7",
54
+ x: 1.4,
55
+ y: -0.5,
56
+ },
57
+ ],
58
+ },
59
+ {
60
+ chipId: "schematic_component_1",
61
+ center: {
62
+ x: -2.3145833,
63
+ y: 0,
64
+ },
65
+ width: 0.5291665999999999,
66
+ height: 1.0583333000000001,
67
+ pins: [
68
+ {
69
+ pinId: "C20.1",
70
+ x: -2.3148566499999994,
71
+ y: 0.5512093000000002,
72
+ },
73
+ {
74
+ pinId: "C20.2",
75
+ x: -2.31430995,
76
+ y: -0.5512093000000002,
77
+ },
78
+ ],
79
+ },
80
+ {
81
+ chipId: "schematic_component_2",
82
+ center: {
83
+ x: 1.7577928249999983,
84
+ y: 1.7512907000000002,
85
+ },
86
+ width: 0.3155856499999966,
87
+ height: 1.0583332999999997,
88
+ pins: [
89
+ {
90
+ pinId: "R11.1",
91
+ x: 1.7580660749999977,
92
+ y: 2.3025814000000002,
93
+ },
94
+ {
95
+ pinId: "R11.2",
96
+ x: 1.757519574999999,
97
+ y: 1.2,
98
+ },
99
+ ],
100
+ },
101
+ {
102
+ chipId: "schematic_component_3",
103
+ center: {
104
+ x: -1.7577928249999983,
105
+ y: -2.7512907000000002,
106
+ },
107
+ width: 0.3155856499999966,
108
+ height: 1.0583332999999997,
109
+ pins: [
110
+ {
111
+ pinId: "R12.1",
112
+ x: -1.7580660749999977,
113
+ y: -3.3025814000000002,
114
+ },
115
+ {
116
+ pinId: "R12.2",
117
+ x: -1.757519574999999,
118
+ y: -2.2,
119
+ },
120
+ ],
121
+ },
122
+ {
123
+ chipId: "schematic_component_4",
124
+ center: {
125
+ x: 1.7577928249999983,
126
+ y: -2.7512907000000002,
127
+ },
128
+ width: 0.3155856499999966,
129
+ height: 1.0583332999999997,
130
+ pins: [
131
+ {
132
+ pinId: "R13.1",
133
+ x: 1.7580660749999977,
134
+ y: -3.3025814000000002,
135
+ },
136
+ {
137
+ pinId: "R13.2",
138
+ x: 1.757519574999999,
139
+ y: -2.2,
140
+ },
141
+ ],
142
+ },
143
+ ],
144
+ directConnections: [
145
+ {
146
+ pinIds: ["C20.1", "U3.8"],
147
+ netId: "capacitor.C20 > port.pin1 to .U3 > .VDD",
148
+ },
149
+ {
150
+ pinIds: ["C20.2", "U3.4"],
151
+ netId: "capacitor.C20 > port.pin2 to .U3 > .GND",
152
+ },
153
+ {
154
+ pinIds: ["R11.2", "U3.1"],
155
+ netId: "resistor.R11 > port.pin2 to .U3 > .N_CS",
156
+ },
157
+ {
158
+ pinIds: ["R13.2", "R12.2"],
159
+ netId: "resistor.R11 > port.pin1 to resistor.R12 > port.pin1",
160
+ },
161
+ ],
162
+ netConnections: [
163
+ {
164
+ netId: "V3_3",
165
+ pinIds: ["U3.8", "U3.3", "U3.7", "C20.1", "R11.1"],
166
+ },
167
+ {
168
+ netId: "GND",
169
+ pinIds: ["U3.4", "C20.2"],
170
+ },
171
+ {
172
+ netId: "FLASH_N_CS",
173
+ pinIds: ["U3.1", "R11.2"],
174
+ },
175
+ ],
176
+ availableNetLabelOrientations: {
177
+ V3_3: ["y+"],
178
+ GND: ["y-"],
179
+ },
180
+ maxMspPairDistance: 5,
181
+ } as InputProblem
182
+
183
+ test("example14", () => {
184
+ const solver = new SchematicTracePipelineSolver(inputProblem)
185
+ solver.solve()
186
+ expect(solver).toMatchSolverSnapshot(import.meta.path)
187
+ })