@tscircuit/schematic-trace-solver 0.0.56 → 0.0.60

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.
@@ -21,6 +21,7 @@ import {
21
21
  rangesOverlap,
22
22
  rectsOverlap,
23
23
  traceCrossesBoundsInterior,
24
+ tracePathCrossesAnyBounds,
24
25
  tracePathCrossesAnyTrace,
25
26
  } from "./geometry"
26
27
  import { getPinMap, getTracePins, toNetLabelPlacementPatch } from "./traces"
@@ -430,9 +431,25 @@ export class AvailableNetOrientationSolver extends BaseSolver {
430
431
  }
431
432
 
432
433
  private getNetLabelWidth(label: NetLabelPlacement) {
433
- if (!label.netId) return undefined
434
- return this.inputProblem.netConnections.find(
435
- (connection) => connection.netId === label.netId,
434
+ if (label.netId) {
435
+ const ncWidth = this.inputProblem.netConnections.find(
436
+ (connection) => connection.netId === label.netId,
437
+ )?.netLabelWidth
438
+ if (ncWidth !== undefined) return ncWidth
439
+
440
+ const dcWidthByNetId = this.inputProblem.directConnections.find(
441
+ (dc) => dc.netId === label.netId,
442
+ )?.netLabelWidth
443
+ if (dcWidthByNetId !== undefined) return dcWidthByNetId
444
+ }
445
+
446
+ const dcWidthByPinId = this.inputProblem.directConnections.find((dc) =>
447
+ dc.pinIds.some((pid) => label.pinIds.includes(pid)),
448
+ )?.netLabelWidth
449
+ if (dcWidthByPinId !== undefined) return dcWidthByPinId
450
+
451
+ return this.inputProblem.netConnections.find((nc) =>
452
+ nc.pinIds.some((pid) => label.pinIds.includes(pid)),
436
453
  )?.netLabelWidth
437
454
  }
438
455
 
@@ -447,19 +464,29 @@ export class AvailableNetOrientationSolver extends BaseSolver {
447
464
  )
448
465
  if (boundsStatus !== "valid") return boundsStatus
449
466
 
450
- if (
451
- tracePathCrossesAnyTrace(
452
- getConnectorTracePath(
453
- label.anchorPoint,
454
- candidate.anchorPoint,
455
- candidate.orientation,
456
- ),
457
- this.traceMap,
458
- )
459
- ) {
467
+ const connectorTrace = getConnectorTracePath(
468
+ label.anchorPoint,
469
+ candidate.anchorPoint,
470
+ candidate.orientation,
471
+ )
472
+
473
+ if (tracePathCrossesAnyTrace(connectorTrace, this.traceMap)) {
460
474
  return "trace-collision"
461
475
  }
462
476
 
477
+ for (let i = 0; i < this.outputNetLabelPlacements.length; i++) {
478
+ if (i === labelIndex) continue
479
+ const otherLabel = this.outputNetLabelPlacements[i]!
480
+ if (
481
+ tracePathCrossesAnyBounds(
482
+ connectorTrace,
483
+ getRectBounds(otherLabel.center, otherLabel.width, otherLabel.height),
484
+ )
485
+ ) {
486
+ return "netlabel-collision"
487
+ }
488
+ }
489
+
463
490
  return "valid"
464
491
  }
465
492
 
@@ -88,6 +88,20 @@ const segmentCrossesBoundsInterior = (p1: Point, p2: Point, bounds: Bounds) => {
88
88
  return false
89
89
  }
90
90
 
91
+ export const tracePathCrossesAnyBounds = (
92
+ tracePath: Point[],
93
+ bounds: Bounds,
94
+ ) => {
95
+ for (let i = 0; i < tracePath.length - 1; i++) {
96
+ if (
97
+ segmentCrossesBoundsInterior(tracePath[i]!, tracePath[i + 1]!, bounds)
98
+ ) {
99
+ return true
100
+ }
101
+ }
102
+ return false
103
+ }
104
+
91
105
  export const tracePathCrossesAnyTrace = (
92
106
  tracePath: Point[],
93
107
  traceMap: Record<string, SolvedTracePath>,
@@ -1,5 +1,6 @@
1
1
  import type { Point } from "@tscircuit/math-utils"
2
2
  import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
3
+ import { segmentIntersectsRect as segmentIntersectsLabelRect } from "lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/collisions"
3
4
  import { getRectBounds } from "lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/geometry"
4
5
  import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
5
6
  import { simplifyPath } from "lib/solvers/TraceCleanupSolver/simplifyPath"
@@ -73,9 +74,39 @@ export const generateRerouteCandidateResults = ({
73
74
  outputNetLabelPlacements: NetLabelPlacement[]
74
75
  chipObstacles: ChipObstacle[]
75
76
  }) => {
76
- const rawCandidates = generateCandidatePaths(trace, label, inputProblem)
77
77
  const seen = new Set<string>()
78
78
  const candidateResults: RerouteCandidateResult[] = []
79
+ const horizontalSegmentPushCandidate = generateHorizontalSegmentPushCandidate(
80
+ trace,
81
+ label,
82
+ )
83
+
84
+ if (horizontalSegmentPushCandidate) {
85
+ candidateResults.push(
86
+ createCandidateResult({
87
+ trace,
88
+ obstacleLabel: label,
89
+ path: horizontalSegmentPushCandidate,
90
+ seen,
91
+ outputTraces,
92
+ outputNetLabelPlacements,
93
+ chipObstacles,
94
+ usesHorizontalSegmentPush: true,
95
+ }),
96
+ )
97
+ }
98
+
99
+ const rawCandidates = [
100
+ ...generateLabelHugCandidates(trace, label),
101
+ ...generateRerouteCandidates({
102
+ trace,
103
+ label,
104
+ problem: inputProblem,
105
+ paddingBuffer: LABEL_SIDE_CLEARANCE,
106
+ detourCount: 0,
107
+ }),
108
+ ...generateEndpointDetourCandidates(trace, label),
109
+ ]
79
110
 
80
111
  for (const rawCandidate of rawCandidates) {
81
112
  candidateResults.push(
@@ -94,22 +125,6 @@ export const generateRerouteCandidateResults = ({
94
125
  return candidateResults
95
126
  }
96
127
 
97
- const generateCandidatePaths = (
98
- trace: SolvedTracePath,
99
- label: NetLabelPlacement,
100
- inputProblem: InputProblem,
101
- ) => [
102
- ...generateLabelHugCandidates(trace, label),
103
- ...generateRerouteCandidates({
104
- trace,
105
- label,
106
- problem: inputProblem,
107
- paddingBuffer: LABEL_SIDE_CLEARANCE,
108
- detourCount: 0,
109
- }),
110
- ...generateEndpointDetourCandidates(trace, label),
111
- ]
112
-
113
128
  const createCandidateResult = ({
114
129
  trace,
115
130
  obstacleLabel,
@@ -118,6 +133,7 @@ const createCandidateResult = ({
118
133
  outputTraces,
119
134
  outputNetLabelPlacements,
120
135
  chipObstacles,
136
+ usesHorizontalSegmentPush,
121
137
  }: {
122
138
  trace: SolvedTracePath
123
139
  obstacleLabel: NetLabelPlacement
@@ -126,6 +142,7 @@ const createCandidateResult = ({
126
142
  outputTraces: SolvedTracePath[]
127
143
  outputNetLabelPlacements: NetLabelPlacement[]
128
144
  chipObstacles: ChipObstacle[]
145
+ usesHorizontalSegmentPush?: boolean
129
146
  }): RerouteCandidateResult => {
130
147
  const key = getPathKey(path)
131
148
 
@@ -133,6 +150,7 @@ const createCandidateResult = ({
133
150
  return {
134
151
  path,
135
152
  status: "duplicate",
153
+ usesHorizontalSegmentPush,
136
154
  selected: false,
137
155
  }
138
156
  }
@@ -143,6 +161,7 @@ const createCandidateResult = ({
143
161
  return {
144
162
  path,
145
163
  status: "chip-collision",
164
+ usesHorizontalSegmentPush,
146
165
  selected: false,
147
166
  }
148
167
  }
@@ -157,6 +176,7 @@ const createCandidateResult = ({
157
176
  outputNetLabelPlacements,
158
177
  }),
159
178
  status: "valid",
179
+ usesHorizontalSegmentPush,
160
180
  selected: false,
161
181
  }
162
182
  }
@@ -259,6 +279,13 @@ const selectBestReroutePath = ({
259
279
  outputNetLabelPlacements: NetLabelPlacement[]
260
280
  candidateResults: RerouteCandidateResult[]
261
281
  }) => {
282
+ for (const candidate of candidateResults) {
283
+ if (!candidate.usesHorizontalSegmentPush) continue
284
+ if (candidate.status !== "valid") continue
285
+ if (!candidate.score) continue
286
+ return candidate.path
287
+ }
288
+
262
289
  let bestPath: Point[] | null = null
263
290
  let bestScore = scoreTracePath({
264
291
  trace,
@@ -279,6 +306,99 @@ const selectBestReroutePath = ({
279
306
  return bestPath
280
307
  }
281
308
 
309
+ const generateHorizontalSegmentPushCandidate = (
310
+ trace: SolvedTracePath,
311
+ label: NetLabelPlacement,
312
+ ): Point[] | null => {
313
+ const labelDirection = dir(label.orientation)
314
+ if (labelDirection.x === 0) return null
315
+
316
+ const path = trace.tracePath
317
+ const bounds = getRectBounds(label.center, label.width, label.height)
318
+ const collidingVerticalSegmentIndex = path.findIndex((start, index) => {
319
+ const end = path[index + 1]
320
+ if (!end) return false
321
+ if (Math.abs(start.x - end.x) >= 1e-9) return false
322
+ return segmentIntersectsLabelRect(start, end, bounds)
323
+ })
324
+
325
+ if (
326
+ collidingVerticalSegmentIndex <= 0 ||
327
+ collidingVerticalSegmentIndex >= path.length - 2
328
+ ) {
329
+ return null
330
+ }
331
+
332
+ const previousAnchor = path[collidingVerticalSegmentIndex - 1]!
333
+ const verticalStart = path[collidingVerticalSegmentIndex]!
334
+ const verticalEnd = path[collidingVerticalSegmentIndex + 1]!
335
+ const nextAnchor = path[collidingVerticalSegmentIndex + 2]!
336
+
337
+ if (
338
+ Math.abs(previousAnchor.y - verticalStart.y) >= 1e-9 ||
339
+ Math.abs(verticalEnd.y - nextAnchor.y) >= 1e-9
340
+ ) {
341
+ return null
342
+ }
343
+
344
+ let segmentPushX = bounds.minX - LABEL_SIDE_CLEARANCE
345
+ if (labelDirection.x > 0) {
346
+ segmentPushX = bounds.maxX + LABEL_SIDE_CLEARANCE
347
+ }
348
+ const segmentPushStartY = getClearedHorizontalY({
349
+ start: previousAnchor,
350
+ end: { x: segmentPushX, y: verticalStart.y },
351
+ labelBounds: bounds,
352
+ })
353
+ const segmentPushEndY = getClearedHorizontalY({
354
+ start: { x: segmentPushX, y: verticalEnd.y },
355
+ end: nextAnchor,
356
+ labelBounds: bounds,
357
+ })
358
+
359
+ const segmentPushPath: Point[] = [
360
+ ...path.slice(0, collidingVerticalSegmentIndex - 1),
361
+ previousAnchor,
362
+ ]
363
+
364
+ if (Math.abs(previousAnchor.y - segmentPushStartY) >= 1e-9) {
365
+ segmentPushPath.push({ x: previousAnchor.x, y: segmentPushStartY })
366
+ }
367
+
368
+ segmentPushPath.push({ x: segmentPushX, y: segmentPushStartY })
369
+ segmentPushPath.push({ x: segmentPushX, y: segmentPushEndY })
370
+
371
+ if (Math.abs(nextAnchor.y - segmentPushEndY) >= 1e-9) {
372
+ segmentPushPath.push({ x: nextAnchor.x, y: segmentPushEndY })
373
+ }
374
+
375
+ segmentPushPath.push(
376
+ nextAnchor,
377
+ ...path.slice(collidingVerticalSegmentIndex + 3),
378
+ )
379
+
380
+ return simplifyPath(segmentPushPath)
381
+ }
382
+
383
+ const getClearedHorizontalY = ({
384
+ start,
385
+ end,
386
+ labelBounds,
387
+ }: {
388
+ start: Point
389
+ end: Point
390
+ labelBounds: { minX: number; minY: number; maxX: number; maxY: number }
391
+ }) => {
392
+ if (!segmentIntersectsLabelRect(start, end, labelBounds)) return start.y
393
+
394
+ const labelCenterY = (labelBounds.minY + labelBounds.maxY) / 2
395
+ if (start.y >= labelCenterY) {
396
+ return labelBounds.maxY + LABEL_HUG_CLEARANCE
397
+ }
398
+
399
+ return labelBounds.minY - LABEL_HUG_CLEARANCE
400
+ }
401
+
282
402
  const markSelectedCandidate = (
283
403
  candidateResults: RerouteCandidateResult[],
284
404
  bestPath: Point[] | null,
@@ -32,6 +32,7 @@ export type RerouteCandidateResult = {
32
32
  path: Point[]
33
33
  score?: TracePathScore
34
34
  status: "valid" | "duplicate" | "chip-collision"
35
+ usesHorizontalSegmentPush?: boolean
35
36
  selected: boolean
36
37
  }
37
38
 
@@ -250,6 +250,36 @@ export class NetLabelPlacementSolver extends BaseSolver {
250
250
  return groups
251
251
  }
252
252
 
253
+ private getNetLabelWidthForGroup(
254
+ group: OverlappingSameNetTraceGroup,
255
+ ): number | undefined {
256
+ if (group.netId) {
257
+ const ncWidth = this.inputProblem.netConnections.find(
258
+ (nc) => nc.netId === group.netId,
259
+ )?.netLabelWidth
260
+ if (ncWidth !== undefined) return ncWidth
261
+
262
+ const dcWidthByNetId = this.inputProblem.directConnections.find(
263
+ (dc) => dc.netId === group.netId,
264
+ )?.netLabelWidth
265
+ if (dcWidthByNetId !== undefined) return dcWidthByNetId
266
+ }
267
+
268
+ const pinIds = group.overlappingTraces?.pins.map((p) => p.pinId) ?? []
269
+ if (group.portOnlyPinId) {
270
+ pinIds.push(group.portOnlyPinId)
271
+ }
272
+
273
+ const dcWidthByPinId = this.inputProblem.directConnections.find((dc) =>
274
+ dc.pinIds.some((pid) => pinIds.includes(pid)),
275
+ )?.netLabelWidth
276
+ if (dcWidthByPinId !== undefined) return dcWidthByPinId
277
+
278
+ return this.inputProblem.netConnections.find((nc) =>
279
+ nc.pinIds.some((pid) => pinIds.includes(pid)),
280
+ )?.netLabelWidth
281
+ }
282
+
253
283
  override _step() {
254
284
  if (this.activeSubSolver?.solved) {
255
285
  this.netLabelPlacements.push(this.activeSubSolver.netLabelPlacement!)
@@ -273,11 +303,7 @@ export class NetLabelPlacementSolver extends BaseSolver {
273
303
  this.currentGroup
274
304
  ) {
275
305
  this.triedAnyOrientationFallbackForCurrentGroup = true
276
- const netLabelWidth = this.currentGroup.netId
277
- ? this.inputProblem.netConnections.find(
278
- (nc) => nc.netId === this.currentGroup!.netId,
279
- )?.netLabelWidth
280
- : undefined
306
+ const netLabelWidth = this.getNetLabelWidthForGroup(this.currentGroup)
281
307
  this.activeSubSolver = new SingleNetLabelPlacementSolver({
282
308
  inputProblem: this.inputProblem,
283
309
  inputTraceMap: this.inputTraceMap,
@@ -313,11 +339,7 @@ export class NetLabelPlacementSolver extends BaseSolver {
313
339
  this.currentGroup = nextOverlappingSameNetTraceGroup
314
340
  this.triedAnyOrientationFallbackForCurrentGroup = false
315
341
 
316
- const netLabelWidth = this.currentGroup.netId
317
- ? this.inputProblem.netConnections.find(
318
- (nc) => nc.netId === this.currentGroup!.netId,
319
- )?.netLabelWidth
320
- : undefined
342
+ const netLabelWidth = this.getNetLabelWidthForGroup(this.currentGroup)
321
343
 
322
344
  this.activeSubSolver = new SingleNetLabelPlacementSolver({
323
345
  inputProblem: this.inputProblem,
@@ -338,14 +338,26 @@ const getNetLabelWidth = (
338
338
  inputProblem: InputProblem,
339
339
  label: NetLabelPlacement,
340
340
  ) => {
341
- const configuredWidth = inputProblem.netConnections.find(
341
+ const ncWidthByNetId = inputProblem.netConnections.find(
342
342
  (connection) => connection.netId === label.netId,
343
343
  )?.netLabelWidth
344
- if (configuredWidth !== undefined) return configuredWidth
344
+ if (ncWidthByNetId !== undefined) return ncWidthByNetId
345
345
 
346
- return label.orientation === "y+" || label.orientation === "y-"
347
- ? label.height
348
- : label.width
346
+ const dcWidth = inputProblem.directConnections.find((dc) =>
347
+ dc.pinIds.some((pid) => label.pinIds.includes(pid)),
348
+ )?.netLabelWidth
349
+ if (dcWidth !== undefined) return dcWidth
350
+
351
+ const ncWidthByPinId = inputProblem.netConnections.find((nc) =>
352
+ nc.pinIds.some((pid) => label.pinIds.includes(pid)),
353
+ )?.netLabelWidth
354
+ if (ncWidthByPinId !== undefined) return ncWidthByPinId
355
+
356
+ if (label.orientation === "y+" || label.orientation === "y-") {
357
+ return label.height
358
+ }
359
+
360
+ return label.width
349
361
  }
350
362
 
351
363
  const roundDistance = (distance: number) => Number(distance.toFixed(6))
@@ -1,5 +1,10 @@
1
1
  import type { Point } from "@tscircuit/math-utils"
2
2
 
3
+ const EPS = 1e-6
4
+
5
+ const sameX = (a: Point, b: Point) => Math.abs(a.x - b.x) <= EPS
6
+ const sameY = (a: Point, b: Point) => Math.abs(a.y - b.y) <= EPS
7
+
3
8
  /**
4
9
  * Checks if a given path of four points forms a rectangle with horizontal and vertical segments.
5
10
  * It verifies if the path forms either an H-V-H "C" shape or a V-H-V "C" shape.
@@ -9,9 +14,9 @@ export const is4PointRectangle = (path: Point[]): boolean => {
9
14
  const [p0, p1, p2, p3] = path
10
15
  // H-V-H "C" shape
11
16
  const isHVHC =
12
- p0.y === p1.y && p1.x === p2.x && p2.y === p3.y && p0.x === p3.x
17
+ sameY(p0, p1) && sameX(p1, p2) && sameY(p2, p3) && sameX(p0, p3)
13
18
  // V-H-V "C" shape
14
19
  const isVHVC =
15
- p0.x === p1.x && p1.y === p2.y && p2.x === p3.x && p0.y === p3.y
20
+ sameX(p0, p1) && sameY(p1, p2) && sameX(p2, p3) && sameY(p0, p3)
16
21
  return isHVHC || isVHVC
17
22
  }
@@ -24,6 +24,7 @@ export interface InputChip {
24
24
  export interface InputDirectConnection {
25
25
  pinIds: [PinId, PinId]
26
26
  netId?: string
27
+ netLabelWidth?: number
27
28
  }
28
29
 
29
30
  export interface InputNetConnection {
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.56",
4
+ "version": "0.0.60",
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/example34.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/example35.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/example36.json"
3
+
4
+ export default () => <PipelineDebugger inputProblem={inputProblem as any} />
@@ -0,0 +1,61 @@
1
+ {
2
+ "chips": [
3
+ {
4
+ "chipId": "schematic_component_0",
5
+ "center": {
6
+ "x": 0,
7
+ "y": 0
8
+ },
9
+ "width": 1,
10
+ "height": 1,
11
+ "pins": [
12
+ {
13
+ "pinId": "U1.1",
14
+ "x": 1.1,
15
+ "y": 0.30000000000000004
16
+ },
17
+ {
18
+ "pinId": "U1.2",
19
+ "x": 1.1,
20
+ "y": 0.09999999999999998
21
+ },
22
+ {
23
+ "pinId": "U1.3",
24
+ "x": 1.1,
25
+ "y": -0.10000000000000003
26
+ },
27
+ {
28
+ "pinId": "U1.4",
29
+ "x": 1.1,
30
+ "y": -0.30000000000000004
31
+ }
32
+ ]
33
+ }
34
+ ],
35
+ "directConnections": [],
36
+ "netConnections": [
37
+ {
38
+ "netId": "SCL",
39
+ "pinIds": ["U1.1"]
40
+ },
41
+ {
42
+ "netId": "SDA",
43
+ "pinIds": ["U1.2"]
44
+ },
45
+ {
46
+ "netId": "V3_3",
47
+ "pinIds": ["U1.3"]
48
+ },
49
+ {
50
+ "netId": "GND",
51
+ "pinIds": ["U1.4"]
52
+ }
53
+ ],
54
+ "availableNetLabelOrientations": {
55
+ "SCL": ["x-", "x+"],
56
+ "SDA": ["x-", "x+"],
57
+ "V3_3": ["y+"],
58
+ "GND": ["y-"]
59
+ },
60
+ "maxMspPairDistance": 2.4
61
+ }
@@ -0,0 +1,127 @@
1
+ {
2
+ "chips": [
3
+ {
4
+ "chipId": "schematic_component_0",
5
+ "center": {
6
+ "x": 0,
7
+ "y": 0
8
+ },
9
+ "width": 1.3,
10
+ "height": 1,
11
+ "pins": [
12
+ {
13
+ "pinId": "U1.1",
14
+ "x": -1.05,
15
+ "y": 0.30000000000000004
16
+ },
17
+ {
18
+ "pinId": "U1.2",
19
+ "x": -1.05,
20
+ "y": 0.10000000000000003
21
+ },
22
+ {
23
+ "pinId": "U1.3",
24
+ "x": -1.05,
25
+ "y": -0.09999999999999998
26
+ },
27
+ {
28
+ "pinId": "U1.4",
29
+ "x": -1.05,
30
+ "y": -0.30000000000000004
31
+ },
32
+ {
33
+ "pinId": "U1.5",
34
+ "x": 1.05,
35
+ "y": -0.30000000000000004
36
+ },
37
+ {
38
+ "pinId": "U1.6",
39
+ "x": 1.05,
40
+ "y": -0.10000000000000003
41
+ },
42
+ {
43
+ "pinId": "U1.7",
44
+ "x": 1.05,
45
+ "y": 0.09999999999999998
46
+ },
47
+ {
48
+ "pinId": "U1.8",
49
+ "x": 1.05,
50
+ "y": 0.30000000000000004
51
+ }
52
+ ]
53
+ },
54
+ {
55
+ "chipId": "schematic_component_1",
56
+ "center": {
57
+ "x": 2,
58
+ "y": 2
59
+ },
60
+ "width": 1.3,
61
+ "height": 1,
62
+ "pins": [
63
+ {
64
+ "pinId": "U2.1",
65
+ "x": 0.95,
66
+ "y": 2.3
67
+ },
68
+ {
69
+ "pinId": "U2.2",
70
+ "x": 0.95,
71
+ "y": 2.1
72
+ },
73
+ {
74
+ "pinId": "U2.3",
75
+ "x": 0.95,
76
+ "y": 1.9
77
+ },
78
+ {
79
+ "pinId": "U2.4",
80
+ "x": 0.95,
81
+ "y": 1.7
82
+ },
83
+ {
84
+ "pinId": "U2.5",
85
+ "x": 3.05,
86
+ "y": 1.7
87
+ },
88
+ {
89
+ "pinId": "U2.6",
90
+ "x": 3.05,
91
+ "y": 1.9
92
+ },
93
+ {
94
+ "pinId": "U2.7",
95
+ "x": 3.05,
96
+ "y": 2.1
97
+ },
98
+ {
99
+ "pinId": "U2.8",
100
+ "x": 3.05,
101
+ "y": 2.3
102
+ }
103
+ ]
104
+ }
105
+ ],
106
+ "directConnections": [
107
+ {
108
+ "pinIds": ["U1.7", "U2.7"],
109
+ "netId": "U1.IO3 to U2.IO3"
110
+ },
111
+ {
112
+ "pinIds": ["U1.6", "U2.8"],
113
+ "netId": "U1.CLK to U2.VCC"
114
+ }
115
+ ],
116
+ "netConnections": [
117
+ {
118
+ "netId": "VCC",
119
+ "pinIds": ["U1.5", "U1.8"],
120
+ "netLabelWidth": 0.48
121
+ }
122
+ ],
123
+ "availableNetLabelOrientations": {
124
+ "VCC": ["y+"]
125
+ },
126
+ "maxMspPairDistance": 2.4
127
+ }