@tscircuit/schematic-trace-solver 0.0.49 → 0.0.51

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 (23) hide show
  1. package/dist/index.d.ts +133 -1
  2. package/dist/index.js +1084 -41
  3. package/lib/solvers/AvailableNetOrientationSolver/AvailableNetOrientationSolver.ts +682 -0
  4. package/lib/solvers/AvailableNetOrientationSolver/constants.ts +6 -0
  5. package/lib/solvers/AvailableNetOrientationSolver/geometry.ts +192 -0
  6. package/lib/solvers/AvailableNetOrientationSolver/traces.ts +43 -0
  7. package/lib/solvers/AvailableNetOrientationSolver/types.ts +44 -0
  8. package/lib/solvers/AvailableNetOrientationSolver/visualize.ts +123 -0
  9. package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +30 -0
  10. package/lib/solvers/VccNetLabelCornerPlacementSolver/VccNetLabelCornerPlacementSolver.ts +268 -0
  11. package/lib/solvers/VccNetLabelCornerPlacementSolver/geometry.ts +59 -0
  12. package/lib/solvers/VccNetLabelCornerPlacementSolver/types.ts +37 -0
  13. package/lib/solvers/VccNetLabelCornerPlacementSolver/visualize.ts +114 -0
  14. package/package.json +1 -1
  15. package/tests/assets/example22.json +1 -1
  16. package/tests/examples/__snapshots__/example12.snap.svg +33 -30
  17. package/tests/examples/__snapshots__/example13.snap.svg +10 -4
  18. package/tests/examples/__snapshots__/example14.snap.svg +72 -66
  19. package/tests/examples/__snapshots__/example16.snap.svg +33 -27
  20. package/tests/examples/__snapshots__/example20.snap.svg +29 -23
  21. package/tests/examples/__snapshots__/example21.snap.svg +77 -65
  22. package/tests/examples/__snapshots__/example22.snap.svg +31 -25
  23. package/tests/examples/__snapshots__/example31.snap.svg +12 -12
@@ -0,0 +1,268 @@
1
+ import type { GraphicsObject } from "graphics-debug"
2
+ import { traceCrossesBoundsInterior } from "lib/solvers/AvailableNetOrientationSolver/geometry"
3
+ import { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver"
4
+ import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
5
+ import {
6
+ getCenterFromAnchor,
7
+ getRectBounds,
8
+ } from "lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/geometry"
9
+ import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
10
+ import type { InputProblem } from "lib/types/InputProblem"
11
+ import {
12
+ getDistance,
13
+ getTraceCorners,
14
+ isTraceLine,
15
+ rectsOverlap,
16
+ tracePathContainsPoint,
17
+ } from "./geometry"
18
+ import type {
19
+ Bounds,
20
+ CornerCandidateStatus,
21
+ EvaluatedCornerCandidate,
22
+ TraceCornerCandidate,
23
+ VccNetLabelCornerPlacementSolverParams,
24
+ } from "./types"
25
+ import { visualizeVccNetLabelCornerPlacementSolver } from "./visualize"
26
+
27
+ export class VccNetLabelCornerPlacementSolver extends BaseSolver {
28
+ inputProblem: InputProblem
29
+ traces: SolvedTracePath[]
30
+ netLabelPlacements: NetLabelPlacement[]
31
+
32
+ outputNetLabelPlacements: NetLabelPlacement[]
33
+ queuedLabelIndices: number[] = []
34
+ currentLabelIndex: number | null = null
35
+ currentLabel: NetLabelPlacement | null = null
36
+ currentCandidateResults: EvaluatedCornerCandidate[] = []
37
+
38
+ private queuedCornerCandidates: TraceCornerCandidate[] = []
39
+ private shouldAdvanceToNextLabel = false
40
+ private traceMap: Record<string, SolvedTracePath>
41
+
42
+ constructor(params: VccNetLabelCornerPlacementSolverParams) {
43
+ super()
44
+ this.inputProblem = params.inputProblem
45
+ this.traces = params.traces
46
+ this.netLabelPlacements = params.netLabelPlacements
47
+ this.outputNetLabelPlacements = [...params.netLabelPlacements]
48
+ this.traceMap = Object.fromEntries(
49
+ params.traces.map((trace) => [trace.mspPairId, trace]),
50
+ )
51
+ this.queuedLabelIndices = this.getProcessableLabelIndices()
52
+ this.prepareNextLabel()
53
+ }
54
+
55
+ override getConstructorParams(): ConstructorParameters<
56
+ typeof VccNetLabelCornerPlacementSolver
57
+ >[0] {
58
+ return {
59
+ inputProblem: this.inputProblem,
60
+ traces: this.traces,
61
+ netLabelPlacements: this.netLabelPlacements,
62
+ }
63
+ }
64
+
65
+ override _step() {
66
+ if (this.shouldAdvanceToNextLabel) {
67
+ this.advanceToNextLabel()
68
+ return
69
+ }
70
+
71
+ const label = this.currentLabel
72
+ const labelIndex = this.currentLabelIndex
73
+ if (label === null || labelIndex === null) {
74
+ this.finish()
75
+ return
76
+ }
77
+
78
+ const candidate = this.queuedCornerCandidates.shift()
79
+ if (!candidate) {
80
+ this.shouldAdvanceToNextLabel = true
81
+ return
82
+ }
83
+
84
+ const result = this.evaluateCornerCandidate(label, labelIndex, candidate)
85
+ this.currentCandidateResults.push(result)
86
+
87
+ if (result.status !== "valid") return
88
+
89
+ result.selected = true
90
+ this.applyCandidate(labelIndex, label, result)
91
+ this.shouldAdvanceToNextLabel = true
92
+ }
93
+
94
+ getOutput() {
95
+ return {
96
+ netLabelPlacements: this.outputNetLabelPlacements,
97
+ }
98
+ }
99
+
100
+ private getProcessableLabelIndices() {
101
+ const labelIndices: number[] = []
102
+
103
+ for (let i = 0; i < this.outputNetLabelPlacements.length; i++) {
104
+ const label = this.outputNetLabelPlacements[i]!
105
+ if (!this.shouldProcessLabel(label)) continue
106
+
107
+ labelIndices.push(i)
108
+ }
109
+
110
+ return labelIndices
111
+ }
112
+
113
+ private prepareNextLabel() {
114
+ const labelIndex = this.queuedLabelIndices.shift()
115
+ if (labelIndex === undefined) {
116
+ this.clearCurrentLabel()
117
+ return false
118
+ }
119
+
120
+ const label = this.outputNetLabelPlacements[labelIndex]!
121
+ this.currentLabelIndex = labelIndex
122
+ this.currentLabel = label
123
+ this.currentCandidateResults = []
124
+ this.queuedCornerCandidates = this.getCornerCandidatesForLabel(label)
125
+
126
+ return true
127
+ }
128
+
129
+ private advanceToNextLabel() {
130
+ this.shouldAdvanceToNextLabel = false
131
+ if (!this.prepareNextLabel()) this.finish()
132
+ }
133
+
134
+ private clearCurrentLabel() {
135
+ this.currentLabelIndex = null
136
+ this.currentLabel = null
137
+ this.currentCandidateResults = []
138
+ this.queuedCornerCandidates = []
139
+ }
140
+
141
+ private finish() {
142
+ this.clearCurrentLabel()
143
+ this.solved = true
144
+ }
145
+
146
+ private evaluateCornerCandidate(
147
+ label: NetLabelPlacement,
148
+ labelIndex: number,
149
+ candidate: TraceCornerCandidate,
150
+ ): EvaluatedCornerCandidate {
151
+ const center = getCenterFromAnchor(
152
+ candidate.anchorPoint,
153
+ label.orientation,
154
+ label.width,
155
+ label.height,
156
+ )
157
+ const bounds = getRectBounds(center, label.width, label.height)
158
+
159
+ return {
160
+ ...candidate,
161
+ center,
162
+ width: label.width,
163
+ height: label.height,
164
+ status: this.getCandidateStatus(bounds, labelIndex),
165
+ selected: false,
166
+ }
167
+ }
168
+
169
+ private getCandidateStatus(
170
+ bounds: Bounds,
171
+ labelIndex: number,
172
+ ): CornerCandidateStatus {
173
+ if (this.intersectsAnyChip(bounds)) return "chip-collision"
174
+ if (traceCrossesBoundsInterior(bounds, this.traceMap)) {
175
+ return "trace-collision"
176
+ }
177
+ if (this.intersectsAnyOtherNetLabel(bounds, labelIndex)) {
178
+ return "netlabel-collision"
179
+ }
180
+
181
+ return "valid"
182
+ }
183
+
184
+ private applyCandidate(
185
+ labelIndex: number,
186
+ label: NetLabelPlacement,
187
+ candidate: EvaluatedCornerCandidate,
188
+ ) {
189
+ this.outputNetLabelPlacements[labelIndex] = {
190
+ ...label,
191
+ anchorPoint: candidate.anchorPoint,
192
+ center: candidate.center,
193
+ }
194
+ }
195
+
196
+ private shouldProcessLabel(label: NetLabelPlacement) {
197
+ return (
198
+ label.netId === "VCC" &&
199
+ this.getCornerCandidatesForLabel(label).length > 0
200
+ )
201
+ }
202
+
203
+ private intersectsAnyChip(bounds: Bounds) {
204
+ return this.inputProblem.chips.some((chip) =>
205
+ rectsOverlap(bounds, getRectBounds(chip.center, chip.width, chip.height)),
206
+ )
207
+ }
208
+
209
+ private intersectsAnyOtherNetLabel(bounds: Bounds, labelIndex: number) {
210
+ return this.outputNetLabelPlacements.some((label, index) => {
211
+ if (index === labelIndex) return false
212
+ return rectsOverlap(
213
+ bounds,
214
+ getRectBounds(label.center, label.width, label.height),
215
+ )
216
+ })
217
+ }
218
+
219
+ private getCornerCandidatesForLabel(label: NetLabelPlacement) {
220
+ const candidates: TraceCornerCandidate[] = []
221
+ const seenCornerKeys = new Set<string>()
222
+
223
+ for (const trace of this.getTraceLinesForLabel(label)) {
224
+ for (const anchorPoint of getTraceCorners(trace.tracePath)) {
225
+ const key = `${anchorPoint.x}:${anchorPoint.y}`
226
+ if (seenCornerKeys.has(key)) continue
227
+ seenCornerKeys.add(key)
228
+ candidates.push({
229
+ anchorPoint,
230
+ traceId: trace.mspPairId,
231
+ distance: getDistance(anchorPoint, label.anchorPoint),
232
+ })
233
+ }
234
+ }
235
+
236
+ return candidates.sort((a, b) => a.distance - b.distance)
237
+ }
238
+
239
+ private getTraceLinesForLabel(label: NetLabelPlacement) {
240
+ return this.traces.filter(
241
+ (trace) => isTraceLine(trace) && this.isTraceForLabel(trace, label),
242
+ )
243
+ }
244
+
245
+ private isTraceForLabel(trace: SolvedTracePath, label: NetLabelPlacement) {
246
+ if (!tracePathContainsPoint(trace.tracePath, label.anchorPoint)) {
247
+ return false
248
+ }
249
+
250
+ const traceIds = new Set(label.mspConnectionPairIds)
251
+ if (traceIds.size > 0) {
252
+ return traceIds.has(trace.mspPairId)
253
+ }
254
+
255
+ return trace.globalConnNetId === label.globalConnNetId
256
+ }
257
+
258
+ override visualize(): GraphicsObject {
259
+ return visualizeVccNetLabelCornerPlacementSolver({
260
+ inputProblem: this.inputProblem,
261
+ traces: this.traces,
262
+ outputNetLabelPlacements: this.outputNetLabelPlacements,
263
+ currentLabel: this.currentLabel,
264
+ currentCandidateResults: this.currentCandidateResults,
265
+ solved: this.solved,
266
+ })
267
+ }
268
+ }
@@ -0,0 +1,59 @@
1
+ import type { Point } from "@tscircuit/math-utils"
2
+ import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
3
+ import type { Bounds } from "./types"
4
+
5
+ export const EPS = 1e-6
6
+
7
+ export const isTraceLine = (trace: SolvedTracePath) =>
8
+ getUniquePinCount(trace.pinIds) >= 2
9
+
10
+ export const rectsOverlap = (a: Bounds, b: Bounds) =>
11
+ Math.min(a.maxX, b.maxX) - Math.max(a.minX, b.minX) > EPS &&
12
+ Math.min(a.maxY, b.maxY) - Math.max(a.minY, b.minY) > EPS
13
+
14
+ export const getTraceCorners = (path: Point[]) => {
15
+ const corners: Point[] = []
16
+ for (let i = 1; i < path.length - 1; i++) {
17
+ const previousPoint = path[i - 1]!
18
+ const cornerPoint = path[i]!
19
+ const nextPoint = path[i + 1]!
20
+ if (isTraceCorner(previousPoint, cornerPoint, nextPoint)) {
21
+ corners.push(cornerPoint)
22
+ }
23
+ }
24
+ return corners
25
+ }
26
+
27
+ export const tracePathContainsPoint = (path: Point[], point: Point) => {
28
+ for (let i = 0; i < path.length - 1; i++) {
29
+ if (isPointOnSegment(point, path[i]!, path[i + 1]!)) return true
30
+ }
31
+ return false
32
+ }
33
+
34
+ export const getDistance = (a: Point, b: Point) =>
35
+ Math.abs(a.x - b.x) + Math.abs(a.y - b.y)
36
+
37
+ const getUniquePinCount = (pinIds: string[]) => new Set(pinIds).size
38
+
39
+ const isTraceCorner = (a: Point, b: Point, c: Point) =>
40
+ getSegmentOrientation(a, b) !== getSegmentOrientation(b, c)
41
+
42
+ const isPointOnSegment = (point: Point, start: Point, end: Point) => {
43
+ if (getSegmentOrientation(start, end) === "horizontal") {
44
+ return (
45
+ Math.abs(point.y - start.y) <= EPS &&
46
+ point.x >= Math.min(start.x, end.x) - EPS &&
47
+ point.x <= Math.max(start.x, end.x) + EPS
48
+ )
49
+ }
50
+
51
+ return (
52
+ Math.abs(point.x - start.x) <= EPS &&
53
+ point.y >= Math.min(start.y, end.y) - EPS &&
54
+ point.y <= Math.max(start.y, end.y) + EPS
55
+ )
56
+ }
57
+
58
+ const getSegmentOrientation = (a: Point, b: Point) =>
59
+ Math.abs(a.y - b.y) <= EPS ? "horizontal" : "vertical"
@@ -0,0 +1,37 @@
1
+ import type { Point } from "@tscircuit/math-utils"
2
+ import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
3
+ import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
4
+ import type { InputProblem } from "lib/types/InputProblem"
5
+
6
+ export interface VccNetLabelCornerPlacementSolverParams {
7
+ inputProblem: InputProblem
8
+ traces: SolvedTracePath[]
9
+ netLabelPlacements: NetLabelPlacement[]
10
+ }
11
+
12
+ export type Bounds = {
13
+ minX: number
14
+ minY: number
15
+ maxX: number
16
+ maxY: number
17
+ }
18
+
19
+ export type TraceCornerCandidate = {
20
+ anchorPoint: Point
21
+ traceId: string
22
+ distance: number
23
+ }
24
+
25
+ export type CornerCandidateStatus =
26
+ | "valid"
27
+ | "chip-collision"
28
+ | "trace-collision"
29
+ | "netlabel-collision"
30
+
31
+ export type EvaluatedCornerCandidate = TraceCornerCandidate & {
32
+ center: Point
33
+ width: number
34
+ height: number
35
+ status: CornerCandidateStatus
36
+ selected: boolean
37
+ }
@@ -0,0 +1,114 @@
1
+ import type { GraphicsObject } from "graphics-debug"
2
+ import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
3
+ import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
4
+ import { visualizeInputProblem } from "lib/solvers/SchematicTracePipelineSolver/visualizeInputProblem"
5
+ import type { InputProblem } from "lib/types/InputProblem"
6
+ import { getColorFromString } from "lib/utils/getColorFromString"
7
+ import type { EvaluatedCornerCandidate } from "./types"
8
+
9
+ const CANDIDATE_SELECTED_COLOR = "blue"
10
+ const CANDIDATE_REJECTED_COLOR = "red"
11
+
12
+ export const visualizeVccNetLabelCornerPlacementSolver = (state: {
13
+ inputProblem: InputProblem
14
+ traces: SolvedTracePath[]
15
+ outputNetLabelPlacements: NetLabelPlacement[]
16
+ currentLabel: NetLabelPlacement | null
17
+ currentCandidateResults: EvaluatedCornerCandidate[]
18
+ solved: boolean
19
+ }): GraphicsObject => {
20
+ const graphics = visualizeInputProblem(state.inputProblem)
21
+ ensureGraphicsArrays(graphics)
22
+
23
+ drawTraces(graphics, state.traces)
24
+ drawNetLabels(graphics, state.outputNetLabelPlacements)
25
+
26
+ if (!state.solved) {
27
+ drawCurrentLabel(graphics, state.currentLabel)
28
+ drawCurrentCandidates(graphics, state.currentCandidateResults)
29
+ }
30
+
31
+ return graphics
32
+ }
33
+
34
+ const drawTraces = (graphics: GraphicsObject, traces: SolvedTracePath[]) => {
35
+ for (const trace of traces) {
36
+ graphics.lines!.push({
37
+ points: trace.tracePath,
38
+ strokeColor: "purple",
39
+ } as any)
40
+ }
41
+ }
42
+
43
+ const drawNetLabels = (
44
+ graphics: GraphicsObject,
45
+ labels: NetLabelPlacement[],
46
+ ) => {
47
+ for (const label of labels) {
48
+ graphics.rects!.push({
49
+ center: label.center,
50
+ width: label.width,
51
+ height: label.height,
52
+ fill: getColorFromString(label.globalConnNetId, 0.35),
53
+ strokeColor: getColorFromString(label.globalConnNetId, 0.9),
54
+ label: `netId: ${label.netId}\nglobalConnNetId: ${label.globalConnNetId}`,
55
+ } as any)
56
+ graphics.points!.push({
57
+ x: label.anchorPoint.x,
58
+ y: label.anchorPoint.y,
59
+ color: getColorFromString(label.globalConnNetId, 0.9),
60
+ label: `anchorPoint\norientation: ${label.orientation}`,
61
+ } as any)
62
+ }
63
+ }
64
+
65
+ const drawCurrentLabel = (
66
+ graphics: GraphicsObject,
67
+ currentLabel: NetLabelPlacement | null,
68
+ ) => {
69
+ if (!currentLabel) return
70
+
71
+ graphics.rects!.push({
72
+ center: currentLabel.center,
73
+ width: currentLabel.width,
74
+ height: currentLabel.height,
75
+ fill: "rgba(255, 0, 0, 0.2)",
76
+ strokeColor: CANDIDATE_REJECTED_COLOR,
77
+ label: `trace-line VCC target\n${currentLabel.netId ?? currentLabel.globalConnNetId}`,
78
+ } as any)
79
+ }
80
+
81
+ const drawCurrentCandidates = (
82
+ graphics: GraphicsObject,
83
+ candidates: EvaluatedCornerCandidate[],
84
+ ) => {
85
+ for (const candidate of candidates) {
86
+ const color = candidate.selected
87
+ ? CANDIDATE_SELECTED_COLOR
88
+ : CANDIDATE_REJECTED_COLOR
89
+ graphics.rects!.push({
90
+ center: candidate.center,
91
+ width: candidate.width,
92
+ height: candidate.height,
93
+ fill: candidate.selected
94
+ ? "rgba(0, 0, 255, 0.2)"
95
+ : "rgba(255, 0, 0, 0.15)",
96
+ strokeColor: color,
97
+ strokeDash: candidate.selected ? undefined : "4 2",
98
+ label: `${candidate.selected ? "selected" : candidate.status} trace corner\ntrace: ${candidate.traceId}\ndistance: ${candidate.distance.toFixed(3)}`,
99
+ } as any)
100
+ graphics.points!.push({
101
+ ...candidate.anchorPoint,
102
+ color,
103
+ label: `candidate anchor\n${candidate.status}`,
104
+ } as any)
105
+ }
106
+ }
107
+
108
+ const ensureGraphicsArrays = (graphics: GraphicsObject) => {
109
+ if (!graphics.lines) graphics.lines = []
110
+ if (!graphics.points) graphics.points = []
111
+ if (!graphics.rects) graphics.rects = []
112
+ if (!graphics.circles) graphics.circles = []
113
+ if (!graphics.texts) graphics.texts = []
114
+ }
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.49",
4
+ "version": "0.0.51",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "start": "cosmos",
@@ -94,7 +94,7 @@
94
94
  }
95
95
  ],
96
96
  "availableNetLabelOrientations": {
97
- "VCC": ["y-"],
97
+ "VCC": ["y+"],
98
98
  "OUT": ["x-", "x+"],
99
99
  "GND": ["y-"],
100
100
  "MMM": ["x+", "x-"]
@@ -2,96 +2,99 @@
2
2
  <rect width="100%" height="100%" fill="white" />
3
3
  <g>
4
4
  <circle data-type="point" data-label="J1.1
5
- x+" data-x="1.1" data-y="0.2" cx="288.33702882483374" cy="207.43247369481963" r="3" fill="hsl(218, 100%, 50%, 0.8)" />
5
+ x+" data-x="1.1" data-y="0.2" cx="301.5156017830609" cy="201.4588202080238" r="3" fill="hsl(218, 100%, 50%, 0.8)" />
6
6
  </g>
7
7
  <g>
8
8
  <circle data-type="point" data-label="J1.2
9
- x+" data-x="1.1" data-y="0" cx="288.33702882483374" cy="230.00856722434997" r="3" fill="hsl(219, 100%, 50%, 0.8)" />
9
+ x+" data-x="1.1" data-y="0" cx="301.5156017830609" cy="225.2329658246657" r="3" fill="hsl(219, 100%, 50%, 0.8)" />
10
10
  </g>
11
11
  <g>
12
12
  <circle data-type="point" data-label="J1.3
13
- x+" data-x="1.1" data-y="-0.2" cx="288.33702882483374" cy="252.5846607538803" r="3" fill="hsl(220, 100%, 50%, 0.8)" />
13
+ x+" data-x="1.1" data-y="-0.2" cx="301.5156017830609" cy="249.00711144130761" r="3" fill="hsl(220, 100%, 50%, 0.8)" />
14
14
  </g>
15
15
  <g>
16
16
  <circle data-type="point" data-label="R1.1
17
- x-" data-x="0.5499999999999996" data-y="-1.7944553499999996" cx="226.25277161862525" cy="432.5675263051804" r="3" fill="hsl(226, 100%, 50%, 0.8)" />
17
+ x-" data-x="0.5499999999999996" data-y="-1.7944553499999996" cx="236.13670133729568" cy="438.5411797919762" r="3" fill="hsl(226, 100%, 50%, 0.8)" />
18
18
  </g>
19
19
  <g>
20
20
  <circle data-type="point" data-label="R1.2
21
- x+" data-x="1.6499999999999997" data-y="-1.7944553499999996" cx="350.4212860310421" cy="432.5675263051804" r="3" fill="hsl(227, 100%, 50%, 0.8)" />
21
+ x+" data-x="1.6499999999999997" data-y="-1.7944553499999996" cx="366.8945022288261" cy="438.5411797919762" r="3" fill="hsl(227, 100%, 50%, 0.8)" />
22
22
  </g>
23
23
  <g>
24
24
  <circle data-type="point" data-label="C1.1
25
- x-" data-x="2.3099999999999996" data-y="0.01999999999999985" cx="424.9223946784922" cy="227.75095787139696" r="3" fill="hsl(121, 100%, 50%, 0.8)" />
25
+ x-" data-x="2.3099999999999996" data-y="0.01999999999999985" cx="445.34918276374435" cy="222.85555126300153" r="3" fill="hsl(121, 100%, 50%, 0.8)" />
26
26
  </g>
27
27
  <g>
28
28
  <circle data-type="point" data-label="C1.2
29
- x+" data-x="3.41" data-y="0.01999999999999985" cx="549.090909090909" cy="227.75095787139696" r="3" fill="hsl(122, 100%, 50%, 0.8)" />
29
+ x+" data-x="3.41" data-y="0.01999999999999985" cx="576.1069836552749" cy="222.85555126300153" r="3" fill="hsl(122, 100%, 50%, 0.8)" />
30
30
  </g>
31
31
  <g>
32
32
  <circle data-type="point" data-label="anchorPoint
33
- orientation: y+" data-x="1.4024999999999999" data-y="0.2" cx="322.48337028824835" cy="207.43247369481963" r="3" fill="hsl(40, 100%, 50%, 0.9)" />
33
+ orientation: y+" data-x="1.7049999999999998" data-y="0.2" cx="373.4323922734027" cy="201.4588202080238" r="3" fill="hsl(40, 100%, 50%, 0.9)" />
34
34
  </g>
35
35
  <g>
36
36
  <circle data-type="point" data-label="anchorPoint
37
- orientation: x+" data-x="1.3" data-y="-0.4486138374999999" cx="310.9131223543641" cy="280.6483069945576" r="3" fill="hsl(40, 100%, 50%, 0.9)" />
37
+ orientation: x+" data-x="1.3" data-y="-0.4486138374999999" cx="325.28974739970283" cy="278.5600193164933" r="3" fill="hsl(40, 100%, 50%, 0.9)" />
38
38
  </g>
39
39
  <g>
40
40
  <circle data-type="point" data-label="anchorPoint
41
- orientation: y-" data-x="1.8499999999999996" data-y="-1.7944553499999996" cx="372.9973795605724" cy="432.5675263051804" r="3" fill="hsl(40, 100%, 50%, 0.9)" />
41
+ orientation: y-" data-x="1.8499999999999996" data-y="-1.7944553499999996" cx="390.668647845468" cy="438.5411797919762" r="3" fill="hsl(40, 100%, 50%, 0.9)" />
42
42
  </g>
43
43
  <g>
44
44
  <circle data-type="point" data-label="anchorPoint
45
- orientation: x+" data-x="3.41" data-y="0.01999999999999985" cx="549.090909090909" cy="227.75095787139696" r="3" fill="hsl(40, 100%, 50%, 0.9)" />
45
+ orientation: y-" data-x="3.511" data-y="-0.4310000000000001" cx="588.1129271916791" cy="276.466249628529" r="3" fill="hsl(40, 100%, 50%, 0.9)" />
46
46
  </g>
47
47
  <g>
48
- <polyline data-points="1.1,0.2 2.3099999999999996,0.01999999999999985" data-type="line" data-label="" points="288.33702882483374,207.43247369481963 424.9223946784922,227.75095787139696" fill="none" stroke="hsl(190, 100%, 50%, 0.8)" stroke-width="1" stroke-dasharray="4 2" />
48
+ <polyline data-points="1.1,0.2 2.3099999999999996,0.01999999999999985" data-type="line" data-label="" points="301.5156017830609,201.4588202080238 445.34918276374435,222.85555126300153" fill="none" stroke="hsl(190, 100%, 50%, 0.8)" stroke-width="1" stroke-dasharray="4 2" />
49
49
  </g>
50
50
  <g>
51
- <polyline data-points="1.1,0 0.5499999999999996,-1.7944553499999996" data-type="line" data-label="" points="288.33702882483374,230.00856722434997 226.25277161862525,432.5675263051804" fill="none" stroke="hsl(158, 100%, 50%, 0.8)" stroke-width="1" stroke-dasharray="4 2" />
51
+ <polyline data-points="1.1,0 0.5499999999999996,-1.7944553499999996" data-type="line" data-label="" points="301.5156017830609,225.2329658246657 236.13670133729568,438.5411797919762" fill="none" stroke="hsl(158, 100%, 50%, 0.8)" stroke-width="1" stroke-dasharray="4 2" />
52
52
  </g>
53
53
  <g>
54
- <polyline data-points="1.1,-0.2 1.6499999999999997,-1.7944553499999996" data-type="line" data-label="" points="288.33702882483374,252.5846607538803 350.4212860310421,432.5675263051804" fill="none" stroke="hsl(157, 100%, 50%, 0.8)" stroke-width="1" stroke-dasharray="4 2" />
54
+ <polyline data-points="1.1,-0.2 1.6499999999999997,-1.7944553499999996" data-type="line" data-label="" points="301.5156017830609,249.00711144130761 366.8945022288261,438.5411797919762" fill="none" stroke="hsl(157, 100%, 50%, 0.8)" stroke-width="1" stroke-dasharray="4 2" />
55
55
  </g>
56
56
  <g>
57
- <polyline data-points="1.1,-0.2 3.41,0.01999999999999985" data-type="line" data-label="" points="288.33702882483374,252.5846607538803 549.090909090909,227.75095787139696" fill="none" stroke="hsl(157, 100%, 50%, 0.8)" stroke-width="1" stroke-dasharray="4 2" />
57
+ <polyline data-points="1.1,-0.2 3.41,0.01999999999999985" data-type="line" data-label="" points="301.5156017830609,249.00711144130761 576.1069836552749,222.85555126300153" fill="none" stroke="hsl(157, 100%, 50%, 0.8)" stroke-width="1" stroke-dasharray="4 2" />
58
58
  </g>
59
59
  <g>
60
- <polyline data-points="1.6499999999999997,-1.7944553499999996 3.41,0.01999999999999985" data-type="line" data-label="" points="350.4212860310421,432.5675263051804 549.090909090909,227.75095787139696" fill="none" stroke="hsl(157, 100%, 50%, 0.8)" stroke-width="1" stroke-dasharray="4 2" />
60
+ <polyline data-points="1.6499999999999997,-1.7944553499999996 3.41,0.01999999999999985" data-type="line" data-label="" points="366.8945022288261,438.5411797919762 576.1069836552749,222.85555126300153" fill="none" stroke="hsl(157, 100%, 50%, 0.8)" stroke-width="1" stroke-dasharray="4 2" />
61
61
  </g>
62
62
  <g>
63
- <polyline data-points="1.1,0.2 1.7049999999999998,0.2 1.7049999999999998,0.01999999999999985 2.3099999999999996,0.01999999999999985" data-type="line" data-label="" points="288.33702882483374,207.43247369481963 356.62971175166297,207.43247369481963 356.62971175166297,227.75095787139696 424.9223946784922,227.75095787139696" fill="none" stroke="purple" stroke-width="1" />
63
+ <polyline data-points="1.1,0.2 1.7049999999999998,0.2 1.7049999999999998,0.01999999999999985 2.3099999999999996,0.01999999999999985" data-type="line" data-label="" points="301.5156017830609,201.4588202080238 373.4323922734027,201.4588202080238 373.4323922734027,222.85555126300153 445.34918276374435,222.85555126300153" fill="none" stroke="purple" stroke-width="1" />
64
64
  </g>
65
65
  <g>
66
- <polyline data-points="1.1,0 1.3,0 1.3,-0.8972276749999998 0.34999999999999964,-0.8972276749999998 0.34999999999999964,-1.7944553499999996 0.5499999999999996,-1.7944553499999996" data-type="line" data-label="" points="288.33702882483374,230.00856722434997 310.9131223543641,230.00856722434997 310.9131223543641,331.28804676476517 203.6766780890949,331.28804676476517 203.6766780890949,432.5675263051804 226.25277161862525,432.5675263051804" fill="none" stroke="purple" stroke-width="1" />
66
+ <polyline data-points="1.1,0 1.3,0 1.3,-0.8972276749999998 0.34999999999999964,-0.8972276749999998 0.34999999999999964,-1.7944553499999996 0.5499999999999996,-1.7944553499999996" data-type="line" data-label="" points="301.5156017830609,225.2329658246657 325.28974739970283,225.2329658246657 325.28974739970283,331.88707280832097 212.36255572065375,331.88707280832097 212.36255572065375,438.5411797919762 236.13670133729568,438.5411797919762" fill="none" stroke="purple" stroke-width="1" />
67
67
  </g>
68
68
  <g>
69
- <polyline data-points="1.6499999999999997,-1.7944553499999996 1.8499999999999996,-1.7944553499999996 1.8499999999999996,-0.2 1.1,-0.2" data-type="line" data-label="" points="350.4212860310421,432.5675263051804 372.9973795605724,432.5675263051804 372.9973795605724,252.5846607538803 288.33702882483374,252.5846607538803" fill="none" stroke="purple" stroke-width="1" />
69
+ <polyline data-points="1.6499999999999997,-1.7944553499999996 1.8499999999999996,-1.7944553499999996 1.8499999999999996,-0.2 1.1,-0.2" data-type="line" data-label="" points="366.8945022288261,438.5411797919762 390.668647845468,438.5411797919762 390.668647845468,249.00711144130761 301.5156017830609,249.00711144130761" fill="none" stroke="purple" stroke-width="1" />
70
70
  </g>
71
71
  <g>
72
- <rect data-type="rect" data-label="schematic_component_0" data-x="0" data-y="0" x="40.000000000000014" y="184.8563801652893" width="248.33702882483374" height="90.30437411812136" fill="hsl(24, 100%, 50%, 0.8)" stroke="black" stroke-width="0.008858928571428573" />
72
+ <polyline data-points="3.41,0.01999999999999985 3.511,0.01999999999999985 3.511,-0.4310000000000001" data-type="line" data-label="" points="576.1069836552749,222.85555126300153 588.1129271916791,222.85555126300153 588.1129271916791,276.466249628529" fill="none" stroke="purple" stroke-width="1" />
73
73
  </g>
74
74
  <g>
75
- <rect data-type="rect" data-label="schematic_component_1" data-x="1.0999999999999996" data-y="-1.7944553499999996" x="226.25277161862525" y="410.61731546059264" width="124.16851441241684" height="43.90042168917546" fill="hsl(24, 100%, 50%, 0.8)" stroke="black" stroke-width="0.008858928571428573" />
75
+ <rect data-type="rect" data-label="schematic_component_0" data-x="0" data-y="0" x="40.00000000000003" y="177.6846745913819" width="261.5156017830609" height="95.09658246656761" fill="hsl(24, 100%, 50%, 0.8)" stroke="black" stroke-width="0.008412500000000002" />
76
76
  </g>
77
77
  <g>
78
- <rect data-type="rect" data-label="schematic_component_2" data-x="2.86" data-y="0.01999999999999985" x="424.9223946784922" y="180.34116145938327" width="124.16851441241681" height="94.81959282402738" fill="hsl(24, 100%, 50%, 0.8)" stroke="black" stroke-width="0.008858928571428573" />
78
+ <rect data-type="rect" data-label="schematic_component_1" data-x="1.0999999999999996" data-y="-1.7944553499999996" x="236.13670133729568" y="415.42613075780093" width="130.75780089153045" height="46.230098068350514" fill="hsl(24, 100%, 50%, 0.8)" stroke="black" stroke-width="0.008412500000000002" />
79
+ </g>
80
+ <g>
81
+ <rect data-type="rect" data-label="schematic_component_2" data-x="2.86" data-y="0.01999999999999985" x="445.34918276374435" y="172.92984546805354" width="130.75780089153056" height="99.85141158989597" fill="hsl(24, 100%, 50%, 0.8)" stroke="black" stroke-width="0.008412500000000002" />
79
82
  </g>
80
83
  <g>
81
84
  <rect data-type="rect" data-label="netId: VCC
82
- globalConnNetId: connectivity_net0" data-x="1.4024999999999999" data-y="0.42500000000000004" x="311.1953235234831" y="156.6362632533764" width="22.57609352953034" height="50.796210441443236" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.008858928571428573" />
85
+ globalConnNetId: connectivity_net0" data-x="1.7049999999999998" data-y="0.42500000000000004" x="361.54531946508166" y="147.96699257057952" width="23.77414561664193" height="53.49182763744429" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.008412500000000002" />
83
86
  </g>
84
87
  <g>
85
88
  <rect data-type="rect" data-label="netId: OUT
86
- globalConnNetId: connectivity_net1" data-x="1.5250000000000001" data-y="-0.4486138374999999" x="310.9131223543641" y="269.3602602297924" width="50.796210441443236" height="22.57609352953034" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.008858928571428573" />
89
+ globalConnNetId: connectivity_net1" data-x="1.5250000000000001" data-y="-0.4486138374999999" x="325.28974739970283" y="266.6729465081724" width="53.49182763744432" height="23.774145616641874" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.008412500000000002" />
87
90
  </g>
88
91
  <g>
89
92
  <rect data-type="rect" data-label="netId: GND
90
- globalConnNetId: connectivity_net2" data-x="1.8499999999999996" data-y="-2.0194553499999994" x="361.7093327958072" y="432.56752630518037" width="22.576093529530397" height="50.79621044144329" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.008858928571428573" />
93
+ globalConnNetId: connectivity_net2" data-x="1.8499999999999996" data-y="-2.0194553499999994" x="378.78157503714704" y="438.54117979197616" width="23.77414561664193" height="53.49182763744432" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.008412500000000002" />
91
94
  </g>
92
95
  <g>
93
96
  <rect data-type="rect" data-label="netId: GND
94
- globalConnNetId: connectivity_net2" data-x="3.636" data-y="0.01999999999999985" x="549.2037895585568" y="216.4629111066318" width="50.796210441443236" height="22.57609352953034" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.008858928571428573" />
97
+ globalConnNetId: connectivity_net2" data-x="3.511" data-y="-0.6560000000000001" x="576.2258543833581" y="276.466249628529" width="23.77414561664193" height="53.49182763744432" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.008412500000000002" />
95
98
  </g>
96
99
  <g id="crosshair" style="display: none">
97
100
  <line id="crosshair-h" y1="0" y2="640" stroke="#666" stroke-width="0.5" />
@@ -121,12 +124,12 @@ globalConnNetId: connectivity_net2" data-x="3.636" data-y="0.01999999999999985"
121
124
 
122
125
  // Calculate real coordinates using inverse transformation
123
126
  const matrix = {
124
- "a": 112.88046764765167,
127
+ "a": 118.8707280832095,
125
128
  "c": 0,
126
- "e": 164.16851441241687,
129
+ "e": 170.75780089153048,
127
130
  "b": 0,
128
- "d": -112.88046764765167,
129
- "f": 230.00856722434997
131
+ "d": -118.8707280832095,
132
+ "f": 225.2329658246657
130
133
  };
131
134
  // Manually invert and apply the affine transform
132
135
  // Since we only use translate and scale, we can directly compute: