@tscircuit/schematic-trace-solver 0.0.54 → 0.0.56

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,294 @@
1
+ import type { GraphicsObject } from "graphics-debug"
2
+ import { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver"
3
+ import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
4
+ import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
5
+ import { visualizeInputProblem } from "lib/solvers/SchematicTracePipelineSolver/visualizeInputProblem"
6
+ import { SingleOverlapSolver } from "lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/SingleOverlapSolver/SingleOverlapSolver"
7
+ import { getRectBounds } from "lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/geometry"
8
+ import { segmentIntersectsRect } from "lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/collisions"
9
+ import { getColorFromString } from "lib/utils/getColorFromString"
10
+ import type { InputProblem } from "lib/types/InputProblem"
11
+
12
+ /**
13
+ * Merges all labels that touch/overlap with each other (transitively) around
14
+ * a seed label into a single bounding-box label. This prevents the situation
15
+ * where fixing one label collision re-routes the trace into an adjacent label.
16
+ */
17
+ function buildMergedObstacleLabel(
18
+ seedLabel: NetLabelPlacement,
19
+ allLabels: NetLabelPlacement[],
20
+ ): NetLabelPlacement {
21
+ const TOUCH_MARGIN = 0.01
22
+
23
+ const getBounds = (l: NetLabelPlacement) =>
24
+ getRectBounds(l.center, l.width, l.height)
25
+
26
+ const touches = (a: NetLabelPlacement, b: NetLabelPlacement) => {
27
+ const ba = getBounds(a)
28
+ const bb = getBounds(b)
29
+ return (
30
+ ba.minX <= bb.maxX + TOUCH_MARGIN &&
31
+ ba.maxX >= bb.minX - TOUCH_MARGIN &&
32
+ ba.minY <= bb.maxY + TOUCH_MARGIN &&
33
+ ba.maxY >= bb.minY - TOUCH_MARGIN
34
+ )
35
+ }
36
+
37
+ const group = new Set<NetLabelPlacement>([seedLabel])
38
+ let changed = true
39
+ while (changed) {
40
+ changed = false
41
+ for (const l of allLabels) {
42
+ if (group.has(l)) continue
43
+ if ([...group].some((g) => touches(g, l))) {
44
+ group.add(l)
45
+ changed = true
46
+ }
47
+ }
48
+ }
49
+
50
+ let minX = Infinity,
51
+ minY = Infinity,
52
+ maxX = -Infinity,
53
+ maxY = -Infinity
54
+ for (const l of group) {
55
+ const b = getBounds(l)
56
+ if (b.minX < minX) minX = b.minX
57
+ if (b.minY < minY) minY = b.minY
58
+ if (b.maxX > maxX) maxX = b.maxX
59
+ if (b.maxY > maxY) maxY = b.maxY
60
+ }
61
+
62
+ const cx = (minX + maxX) / 2
63
+ const cy = (minY + maxY) / 2
64
+ const w = maxX - minX
65
+ const h = maxY - minY
66
+
67
+ return {
68
+ ...seedLabel,
69
+ center: { x: cx, y: cy },
70
+ width: w,
71
+ height: h,
72
+ }
73
+ }
74
+
75
+ interface TraceInnerLabelOverlap {
76
+ trace: SolvedTracePath
77
+ label: NetLabelPlacement
78
+ }
79
+
80
+ /**
81
+ * Detects overlaps where a trace's INTERIOR (non-endpoint) segments cross a label.
82
+ * Traces are allowed to boundary a label at their endpoints (pin locations),
83
+ * just not allowed to cross through it in the middle.
84
+ */
85
+ function detectInnerTraceLabelOverlaps(
86
+ traces: SolvedTracePath[],
87
+ labels: NetLabelPlacement[],
88
+ ): TraceInnerLabelOverlap[] {
89
+ const overlaps: TraceInnerLabelOverlap[] = []
90
+ for (const trace of traces) {
91
+ for (const label of labels) {
92
+ if (trace.globalConnNetId === label.globalConnNetId) continue
93
+ const bounds = getRectBounds(label.center, label.width, label.height)
94
+ const path = trace.tracePath
95
+ // Skip first and last segments — trace endpoints (pins) may legitimately
96
+ // sit inside or adjacent to labels of neighbouring nets.
97
+ const innerStart = 1
98
+ const innerEnd = path.length - 2
99
+ for (let i = innerStart; i < innerEnd; i++) {
100
+ if (segmentIntersectsRect(path[i]!, path[i + 1]!, bounds)) {
101
+ overlaps.push({ trace, label })
102
+ break
103
+ }
104
+ }
105
+ }
106
+ }
107
+ return overlaps
108
+ }
109
+
110
+ export interface NetLabelTraceCollisionSolverParams {
111
+ inputProblem: InputProblem
112
+ traces: SolvedTracePath[]
113
+ netLabelPlacements: NetLabelPlacement[]
114
+ }
115
+
116
+ const PADDING_BUFFER = 0.1
117
+ const MAX_DETOUR_ATTEMPTS = 3
118
+
119
+ export class NetLabelTraceCollisionSolver extends BaseSolver {
120
+ inputProblem: InputProblem
121
+ traces: SolvedTracePath[]
122
+ netLabelPlacements: NetLabelPlacement[]
123
+
124
+ outputTraces: SolvedTracePath[]
125
+ outputNetLabelPlacements: NetLabelPlacement[]
126
+
127
+ override activeSubSolver: SingleOverlapSolver | null = null
128
+ private recentlyFailed = new Set<string>()
129
+ private detourCounts = new Map<string, number>()
130
+
131
+ private currentOverlap: {
132
+ trace: SolvedTracePath
133
+ label: NetLabelPlacement
134
+ } | null = null
135
+
136
+ constructor(params: NetLabelTraceCollisionSolverParams) {
137
+ super()
138
+ this.inputProblem = params.inputProblem
139
+ this.traces = params.traces
140
+ this.netLabelPlacements = params.netLabelPlacements
141
+ this.outputTraces = [...params.traces]
142
+ this.outputNetLabelPlacements = [...params.netLabelPlacements]
143
+ }
144
+
145
+ override getConstructorParams(): ConstructorParameters<
146
+ typeof NetLabelTraceCollisionSolver
147
+ >[0] {
148
+ return {
149
+ inputProblem: this.inputProblem,
150
+ traces: this.traces,
151
+ netLabelPlacements: this.netLabelPlacements,
152
+ }
153
+ }
154
+
155
+ override _step() {
156
+ if (this.activeSubSolver) {
157
+ this.activeSubSolver.step()
158
+
159
+ if (this.activeSubSolver.solved) {
160
+ const solvedPath = this.activeSubSolver.solvedTracePath
161
+ if (solvedPath) {
162
+ const idx = this.outputTraces.findIndex(
163
+ (t) => t.mspPairId === this.activeSubSolver!.initialTrace.mspPairId,
164
+ )
165
+ if (idx !== -1) {
166
+ this.outputTraces[idx] = {
167
+ ...this.outputTraces[idx],
168
+ tracePath: solvedPath,
169
+ }
170
+ }
171
+ }
172
+ this.activeSubSolver = null
173
+ this.currentOverlap = null
174
+ } else if (this.activeSubSolver.failed) {
175
+ const key = this.getOverlapKey(
176
+ this.activeSubSolver.initialTrace,
177
+ this.activeSubSolver.label,
178
+ )
179
+ this.recentlyFailed.add(key)
180
+ this.activeSubSolver = null
181
+ this.currentOverlap = null
182
+ }
183
+ return
184
+ }
185
+
186
+ const overlaps = detectInnerTraceLabelOverlaps(
187
+ this.outputTraces,
188
+ this.outputNetLabelPlacements,
189
+ )
190
+
191
+ const actionable = overlaps.filter((o) => {
192
+ const key = this.getOverlapKey(o.trace, o.label)
193
+ return !this.recentlyFailed.has(key)
194
+ })
195
+
196
+ if (actionable.length === 0) {
197
+ this.solved = true
198
+ return
199
+ }
200
+
201
+ const next = actionable[0]
202
+
203
+ const traceToFix = this.outputTraces.find(
204
+ (t) => t.mspPairId === next.trace.mspPairId,
205
+ )!
206
+
207
+ this.currentOverlap = next
208
+
209
+ // Merge all touching/overlapping labels into one obstacle so that fixing
210
+ // one collision doesn't immediately create another with an adjacent label.
211
+ const mergedLabel = buildMergedObstacleLabel(
212
+ next.label,
213
+ this.outputNetLabelPlacements.filter(
214
+ (l) => l.globalConnNetId !== traceToFix.globalConnNetId,
215
+ ),
216
+ )
217
+
218
+ const mergeKey = `${next.trace.mspPairId}::merged::${mergedLabel.center.x},${mergedLabel.center.y},${mergedLabel.width},${mergedLabel.height}`
219
+ const detourCount = this.detourCounts.get(mergeKey) ?? 0
220
+ if (detourCount >= MAX_DETOUR_ATTEMPTS) {
221
+ this.recentlyFailed.add(this.getOverlapKey(next.trace, next.label))
222
+ this.currentOverlap = null
223
+ return
224
+ }
225
+ this.detourCounts.set(mergeKey, detourCount + 1)
226
+
227
+ this.activeSubSolver = new SingleOverlapSolver({
228
+ trace: traceToFix,
229
+ label: mergedLabel,
230
+ problem: this.inputProblem,
231
+ paddingBuffer: PADDING_BUFFER,
232
+ detourCount,
233
+ })
234
+ }
235
+
236
+ getOutput() {
237
+ return {
238
+ traces: this.outputTraces,
239
+ netLabelPlacements: this.outputNetLabelPlacements,
240
+ }
241
+ }
242
+
243
+ private getOverlapKey(trace: SolvedTracePath, label: NetLabelPlacement) {
244
+ return `${trace.mspPairId}::${label.globalConnNetId}`
245
+ }
246
+
247
+ override visualize(): GraphicsObject {
248
+ if (this.activeSubSolver) return this.activeSubSolver.visualize()
249
+
250
+ const graphics = visualizeInputProblem(this.inputProblem)
251
+ if (!graphics.lines) graphics.lines = []
252
+ if (!graphics.rects) graphics.rects = []
253
+ if (!graphics.points) graphics.points = []
254
+
255
+ for (const trace of this.outputTraces) {
256
+ graphics.lines.push({
257
+ points: trace.tracePath,
258
+ strokeColor: "purple",
259
+ } as any)
260
+ }
261
+
262
+ for (const label of this.outputNetLabelPlacements) {
263
+ graphics.rects.push({
264
+ center: label.center,
265
+ width: label.width,
266
+ height: label.height,
267
+ fill: getColorFromString(label.globalConnNetId, 0.35),
268
+ strokeColor: getColorFromString(label.globalConnNetId, 0.9),
269
+ label: `netId: ${label.netId}\nglobalConnNetId: ${label.globalConnNetId}`,
270
+ } as any)
271
+ graphics.points.push({
272
+ x: label.anchorPoint.x,
273
+ y: label.anchorPoint.y,
274
+ color: getColorFromString(label.globalConnNetId, 0.9),
275
+ } as any)
276
+ }
277
+
278
+ if (this.currentOverlap) {
279
+ graphics.lines.push({
280
+ points: this.currentOverlap.trace.tracePath,
281
+ strokeColor: "red",
282
+ } as any)
283
+ graphics.rects.push({
284
+ center: this.currentOverlap.label.center,
285
+ width: this.currentOverlap.label.width,
286
+ height: this.currentOverlap.label.height,
287
+ fill: "rgba(255,0,0,0.3)",
288
+ strokeColor: "red",
289
+ } as any)
290
+ }
291
+
292
+ return graphics
293
+ }
294
+ }
@@ -25,6 +25,7 @@ import { Example28Solver } from "../Example28Solver/Example28Solver"
25
25
  import { AvailableNetOrientationSolver } from "../AvailableNetOrientationSolver/AvailableNetOrientationSolver"
26
26
  import { VccNetLabelCornerPlacementSolver } from "../VccNetLabelCornerPlacementSolver/VccNetLabelCornerPlacementSolver"
27
27
  import { TraceAnchoredNetLabelOverlapSolver } from "../TraceAnchoredNetLabelOverlapSolver/TraceAnchoredNetLabelOverlapSolver"
28
+ import { NetLabelTraceCollisionSolver } from "../NetLabelTraceCollisionSolver/NetLabelTraceCollisionSolver"
28
29
 
29
30
  type PipelineStep<T extends new (...args: any[]) => BaseSolver> = {
30
31
  solverName: string
@@ -78,6 +79,7 @@ export class SchematicTracePipelineSolver extends BaseSolver {
78
79
  availableNetOrientationSolver?: AvailableNetOrientationSolver
79
80
  vccNetLabelCornerPlacementSolver?: VccNetLabelCornerPlacementSolver
80
81
  traceAnchoredNetLabelOverlapSolver?: TraceAnchoredNetLabelOverlapSolver
82
+ netLabelTraceCollisionSolver?: NetLabelTraceCollisionSolver
81
83
 
82
84
  startTimeOfPhase: Record<string, number>
83
85
  endTimeOfPhase: Record<string, number>
@@ -285,6 +287,19 @@ export class SchematicTracePipelineSolver extends BaseSolver {
285
287
  },
286
288
  ],
287
289
  ),
290
+ definePipelineStep(
291
+ "netLabelTraceCollisionSolver",
292
+ NetLabelTraceCollisionSolver,
293
+ (instance) => [
294
+ {
295
+ inputProblem: instance.inputProblem,
296
+ traces: instance.availableNetOrientationSolver!.traces,
297
+ netLabelPlacements:
298
+ instance.traceAnchoredNetLabelOverlapSolver!
299
+ .outputNetLabelPlacements,
300
+ },
301
+ ],
302
+ ),
288
303
  ]
289
304
 
290
305
  constructor(inputProblem: InputProblem) {
@@ -2,6 +2,7 @@ import type { GraphicsObject } from "graphics-debug"
2
2
  import type { PinId, InputPin, InputProblem } from "lib/types/InputProblem"
3
3
  import { getColorFromString } from "lib/utils/getColorFromString"
4
4
  import { getPinDirection } from "../SchematicTraceLinesSolver/SchematicTraceSingleLineSolver/getPinDirection"
5
+ import { arePinsInDifferentSchematicSections } from "../../utils/arePinsInDifferentSchematicSections"
5
6
 
6
7
  export const visualizeInputProblem = (
7
8
  inputProblem: InputProblem,
@@ -50,6 +51,9 @@ export const visualizeInputProblem = (
50
51
  const [pinId1, pinId2] = directConn.pinIds
51
52
  const pin1 = pinIdMap.get(pinId1)!
52
53
  const pin2 = pinIdMap.get(pinId2)!
54
+ if (arePinsInDifferentSchematicSections(inputProblem, pin1, pin2)) {
55
+ continue
56
+ }
53
57
  graphics.lines.push({
54
58
  points: [
55
59
  {
@@ -74,6 +78,9 @@ export const visualizeInputProblem = (
74
78
  for (let j = i + 1; j < pins.length; j++) {
75
79
  const pin1 = pins[i]!
76
80
  const pin2 = pins[j]!
81
+ if (arePinsInDifferentSchematicSections(inputProblem, pin1, pin2)) {
82
+ continue
83
+ }
77
84
  graphics.lines.push({
78
85
  points: [
79
86
  { x: pin1.x, y: pin1.y },
@@ -4,6 +4,7 @@ import type { FacingDirection } from "lib/utils/dir"
4
4
  export type ChipId = string
5
5
  export type PinId = string
6
6
  export type NetId = string
7
+ export type SectionId = string
7
8
 
8
9
  export interface InputPin {
9
10
  pinId: PinId
@@ -18,6 +19,7 @@ export interface InputChip {
18
19
  width: number
19
20
  height: number
20
21
  pins: Array<InputPin>
22
+ sectionId?: SectionId
21
23
  }
22
24
  export interface InputDirectConnection {
23
25
  pinIds: [PinId, PinId]
@@ -0,0 +1,39 @@
1
+ import type { InputProblem, InputPin } from "lib/types/InputProblem"
2
+
3
+ const getSectionNameForPin = (
4
+ sectionByChipId: Map<string, string>,
5
+ sectionByPinId: Map<string, string>,
6
+ pin: InputPin & { chipId?: string },
7
+ ) => {
8
+ if (pin.chipId) {
9
+ const chipSection = sectionByChipId.get(pin.chipId)
10
+ if (chipSection) return chipSection
11
+ }
12
+
13
+ return sectionByPinId.get(pin.pinId)
14
+ }
15
+
16
+ export const arePinsInDifferentSchematicSections = (
17
+ inputProblem: InputProblem,
18
+ p1: InputPin & { chipId?: string },
19
+ p2: InputPin & { chipId?: string },
20
+ ) => {
21
+ const sectionByChipId = new Map<string, string>()
22
+ const sectionByPinId = new Map<string, string>()
23
+
24
+ for (const chip of inputProblem.chips) {
25
+ if (!chip.sectionId) continue
26
+
27
+ sectionByChipId.set(chip.chipId, chip.sectionId)
28
+ for (const pin of chip.pins) {
29
+ sectionByPinId.set(pin.pinId, chip.sectionId)
30
+ }
31
+ }
32
+
33
+ if (sectionByChipId.size === 0) return false
34
+
35
+ const s1 = getSectionNameForPin(sectionByChipId, sectionByPinId, p1)
36
+ const s2 = getSectionNameForPin(sectionByChipId, sectionByPinId, p2)
37
+
38
+ return !!s1 && !!s2 && s1 !== s2
39
+ }
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.54",
4
+ "version": "0.0.56",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "start": "cosmos",
@@ -0,0 +1,4 @@
1
+ import { PipelineDebugger } from "site/components/PipelineDebugger"
2
+ import inputProblem from "../../tests/assets/example32.json"
3
+
4
+ export default () => <PipelineDebugger inputProblem={inputProblem as any} />
@@ -0,0 +1,4 @@
1
+ import { PipelineDebugger } from "site/components/PipelineDebugger"
2
+ import inputProblem from "../../tests/assets/example33.json"
3
+
4
+ export default () => <PipelineDebugger inputProblem={inputProblem as any} />
@@ -0,0 +1,148 @@
1
+ {
2
+ "chips": [
3
+ {
4
+ "chipId": "schematic_component_0",
5
+ "center": { "x": -4, "y": 3 },
6
+ "width": 0.95,
7
+ "height": 0.29,
8
+ "sectionId": "power",
9
+ "pins": [
10
+ { "pinId": "B1.1", "x": -4.45, "y": 2.9699999999999998 },
11
+ { "pinId": "B1.2", "x": -3.55, "y": 2.9699999999999998 }
12
+ ]
13
+ },
14
+ {
15
+ "chipId": "schematic_component_1",
16
+ "center": { "x": -2, "y": 3 },
17
+ "width": 0.76,
18
+ "height": 0.5,
19
+ "sectionId": "power",
20
+ "pins": [
21
+ { "pinId": "SW1.1", "x": -2.38, "y": 3 },
22
+ { "pinId": "SW1.2", "x": -1.62, "y": 3 }
23
+ ]
24
+ },
25
+ {
26
+ "chipId": "schematic_component_2",
27
+ "center": { "x": -3, "y": 1.5 },
28
+ "width": 1.1,
29
+ "height": 0.84,
30
+ "sectionId": "power",
31
+ "pins": [
32
+ { "pinId": "C3.1", "x": -3.55, "y": 1.5 },
33
+ { "pinId": "C3.2", "x": -2.45, "y": 1.5 }
34
+ ]
35
+ },
36
+ {
37
+ "chipId": "schematic_component_3",
38
+ "center": { "x": 1.5, "y": 2.5 },
39
+ "width": 1.6,
40
+ "height": 1,
41
+ "sectionId": "timer",
42
+ "pins": [
43
+ { "pinId": "U1.1", "x": 2.7, "y": 2.2 },
44
+ { "pinId": "U1.2", "x": 0.2999999999999998, "y": 2.2 },
45
+ { "pinId": "U1.3", "x": 2.7, "y": 2.6 },
46
+ { "pinId": "U1.4", "x": 0.2999999999999998, "y": 2.8 },
47
+ { "pinId": "U1.5", "x": 0.2999999999999998, "y": 2.6 },
48
+ { "pinId": "U1.6", "x": 0.2999999999999998, "y": 2.4 },
49
+ { "pinId": "U1.7", "x": 2.7, "y": 2.4 },
50
+ { "pinId": "U1.8", "x": 2.7, "y": 2.8 }
51
+ ]
52
+ },
53
+ {
54
+ "chipId": "schematic_component_4",
55
+ "center": { "x": 4, "y": 1.5 },
56
+ "width": 1.1,
57
+ "height": 0.84,
58
+ "sectionId": "timer",
59
+ "pins": [
60
+ { "pinId": "C2.1", "x": 3.45, "y": 1.5 },
61
+ { "pinId": "C2.2", "x": 4.55, "y": 1.5 }
62
+ ]
63
+ },
64
+ {
65
+ "chipId": "schematic_component_5",
66
+ "center": { "x": -4, "y": -1.5 },
67
+ "width": 1.1,
68
+ "height": 0.388910699999999,
69
+ "sectionId": "timing-network",
70
+ "pins": [
71
+ { "pinId": "R1.1", "x": -4.55, "y": -1.5 },
72
+ { "pinId": "R1.2", "x": -3.4499999999999997, "y": -1.5 }
73
+ ]
74
+ },
75
+ {
76
+ "chipId": "schematic_component_6",
77
+ "center": { "x": -2, "y": -1.5 },
78
+ "width": 1.1,
79
+ "height": 0.388910699999999,
80
+ "sectionId": "timing-network",
81
+ "pins": [
82
+ { "pinId": "R2.1", "x": -2.55, "y": -1.5 },
83
+ { "pinId": "R2.2", "x": -1.4500000000000002, "y": -1.5 }
84
+ ]
85
+ },
86
+ {
87
+ "chipId": "schematic_component_7",
88
+ "center": { "x": -3, "y": -3 },
89
+ "width": 1.1,
90
+ "height": 0.84,
91
+ "sectionId": "timing-network",
92
+ "pins": [
93
+ { "pinId": "C1.1", "x": -3.55, "y": -3 },
94
+ { "pinId": "C1.2", "x": -2.45, "y": -3 }
95
+ ]
96
+ },
97
+ {
98
+ "chipId": "schematic_component_8",
99
+ "center": { "x": 2.5, "y": -1.5 },
100
+ "width": 1.1,
101
+ "height": 0.388910699999999,
102
+ "sectionId": "output",
103
+ "pins": [
104
+ { "pinId": "R3.1", "x": 1.95, "y": -1.5 },
105
+ { "pinId": "R3.2", "x": 3.05, "y": -1.5 }
106
+ ]
107
+ },
108
+ {
109
+ "chipId": "schematic_component_9",
110
+ "center": { "x": 4.5, "y": -1.5 },
111
+ "width": 1.13,
112
+ "height": 0.65,
113
+ "sectionId": "output",
114
+ "pins": [
115
+ { "pinId": "D1.1", "x": 3.96, "y": -1.5 },
116
+ { "pinId": "D1.2", "x": 5.04, "y": -1.5 }
117
+ ]
118
+ }
119
+ ],
120
+ "directConnections": [
121
+ { "pinIds": ["B1.1", "SW1.1"], "netId": ".B1 > .pin1 to .SW1 > .pin1" },
122
+ { "pinIds": ["U1.5", "C2.1"], "netId": ".U1 > .CTRL to .C2 > .pin1" },
123
+ { "pinIds": ["U1.6", "U1.2"], "netId": ".U1 > .THRES to .U1 > .TRIG" },
124
+ { "pinIds": ["R1.2", "U1.7"], "netId": ".R1 > .pin2 to .U1 > .DISCH" },
125
+ { "pinIds": ["U1.7", "R2.1"], "netId": ".U1 > .DISCH to .R2 > .pin1" },
126
+ { "pinIds": ["R2.2", "U1.6"], "netId": ".R2 > .pin2 to .U1 > .THRES" },
127
+ { "pinIds": ["U1.6", "C1.1"], "netId": ".U1 > .THRES to .C1 > .pin1" },
128
+ { "pinIds": ["U1.3", "R3.1"], "netId": ".U1 > .OUT to .R3 > .pin1" },
129
+ { "pinIds": ["R3.2", "D1.1"], "netId": ".R3 > .pin2 to .D1 > .pin1" }
130
+ ],
131
+ "netConnections": [
132
+ {
133
+ "netId": "GND",
134
+ "pinIds": ["B1.2", "C3.2", "U1.1", "C2.2", "C1.2", "D1.2"],
135
+ "netLabelWidth": 0.3
136
+ },
137
+ {
138
+ "netId": "VCC",
139
+ "pinIds": ["SW1.2", "C3.1", "U1.4", "U1.8", "R1.1"],
140
+ "netLabelWidth": 0.3
141
+ }
142
+ ],
143
+ "availableNetLabelOrientations": {
144
+ "VCC": ["y+"],
145
+ "GND": ["y-"]
146
+ },
147
+ "maxMspPairDistance": 2.4
148
+ }