@tscircuit/schematic-trace-solver 0.0.50 → 0.0.52

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,125 @@
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 { LabelCandidate, LabelOverlap } from "./types"
8
+
9
+ const CANDIDATE_SELECTED_COLOR = "blue"
10
+ const CANDIDATE_REJECTED_COLOR = "red"
11
+
12
+ export const visualizeTraceAnchoredNetLabelOverlapSolver = (state: {
13
+ inputProblem: InputProblem
14
+ traces: SolvedTracePath[]
15
+ outputNetLabelPlacements: NetLabelPlacement[]
16
+ currentOverlap: LabelOverlap | null
17
+ currentCandidateResults: LabelCandidate[]
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
+ drawCurrentOverlap(graphics, state)
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 drawCurrentOverlap = (
66
+ graphics: GraphicsObject,
67
+ state: {
68
+ outputNetLabelPlacements: NetLabelPlacement[]
69
+ currentOverlap: LabelOverlap | null
70
+ },
71
+ ) => {
72
+ if (!state.currentOverlap) return
73
+
74
+ for (const labelIndex of [
75
+ state.currentOverlap.firstLabelIndex,
76
+ state.currentOverlap.secondLabelIndex,
77
+ ]) {
78
+ const label = state.outputNetLabelPlacements[labelIndex]
79
+ if (!label) continue
80
+
81
+ graphics.rects!.push({
82
+ center: label.center,
83
+ width: label.width,
84
+ height: label.height,
85
+ fill: "rgba(255, 0, 0, 0.2)",
86
+ strokeColor: CANDIDATE_REJECTED_COLOR,
87
+ label: `netlabel overlap target\n${label.netId ?? label.globalConnNetId}`,
88
+ } as any)
89
+ }
90
+ }
91
+
92
+ const drawCurrentCandidates = (
93
+ graphics: GraphicsObject,
94
+ candidates: LabelCandidate[],
95
+ ) => {
96
+ for (const candidate of candidates) {
97
+ const color = candidate.selected
98
+ ? CANDIDATE_SELECTED_COLOR
99
+ : CANDIDATE_REJECTED_COLOR
100
+ graphics.rects!.push({
101
+ center: candidate.center,
102
+ width: candidate.width,
103
+ height: candidate.height,
104
+ fill: candidate.selected
105
+ ? "rgba(0, 0, 255, 0.2)"
106
+ : "rgba(255, 0, 0, 0.15)",
107
+ strokeColor: color,
108
+ strokeDash: candidate.selected ? undefined : "4 2",
109
+ label: `${candidate.selected ? "selected" : candidate.status} netlabel overlap candidate\ntrace: ${candidate.traceId}\ndistance: ${candidate.distanceFromOriginal.toFixed(3)}`,
110
+ } as any)
111
+ graphics.points!.push({
112
+ ...candidate.anchorPoint,
113
+ color,
114
+ label: `candidate anchor\n${candidate.status}`,
115
+ } as any)
116
+ }
117
+ }
118
+
119
+ const ensureGraphicsArrays = (graphics: GraphicsObject) => {
120
+ if (!graphics.lines) graphics.lines = []
121
+ if (!graphics.points) graphics.points = []
122
+ if (!graphics.rects) graphics.rects = []
123
+ if (!graphics.circles) graphics.circles = []
124
+ if (!graphics.texts) graphics.texts = []
125
+ }
@@ -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.50",
4
+ "version": "0.0.52",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "start": "cosmos",
@@ -30,7 +30,7 @@ x+" data-x="3.41" data-y="0.01999999999999985" cx="576.1069836552749" cy="222.85
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="337.4739970282318" cy="201.4588202080238" 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
@@ -82,7 +82,7 @@ orientation: y-" data-x="3.511" data-y="-0.4310000000000001" cx="588.11292719167
82
82
  </g>
83
83
  <g>
84
84
  <rect data-type="rect" data-label="netId: VCC
85
- globalConnNetId: connectivity_net0" data-x="1.4024999999999999" data-y="0.42500000000000004" x="325.5869242199108" y="147.96699257057952" width="23.77414561664193" height="53.49182763744429" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.008412500000000002" />
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" />
86
86
  </g>
87
87
  <g>
88
88
  <rect data-type="rect" data-label="netId: OUT