@tscircuit/schematic-trace-solver 0.0.63 → 0.0.65

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.
@@ -22,6 +22,7 @@ import {
22
22
  rectsOverlap,
23
23
  simplifyOrthogonalPath,
24
24
  traceCrossesBoundsInterior,
25
+ tracePathIntersectsBounds,
25
26
  tracePathCrossesAnyBounds,
26
27
  tracePathCrossesAnyTrace,
27
28
  } from "./geometry"
@@ -143,30 +144,7 @@ export class AvailableNetOrientationSolver extends BaseSolver {
143
144
  candidate: EvaluatedCandidate,
144
145
  labelIndex: number,
145
146
  ) {
146
- let tracePath: Point[]
147
-
148
- if (candidate.phase === "lateral-shift") {
149
- const orientDir = dir(candidate.orientation)
150
- const kickedSource = {
151
- x: label.anchorPoint.x - orientDir.x * LABEL_SEARCH_STEP,
152
- y: label.anchorPoint.y - orientDir.y * LABEL_SEARCH_STEP,
153
- }
154
- tracePath = simplifyOrthogonalPath([
155
- label.anchorPoint,
156
- ...getConnectorTracePath(
157
- kickedSource,
158
- candidate.anchorPoint,
159
- candidate.orientation,
160
- ),
161
- ])
162
- } else {
163
- tracePath = getConnectorTracePath(
164
- label.anchorPoint,
165
- candidate.anchorPoint,
166
- candidate.orientation,
167
- )
168
- }
169
-
147
+ const tracePath = this.getCandidateConnectorTrace(label, candidate)
170
148
  if (tracePath.length < 2) return
171
149
 
172
150
  const mspPairId = `available-net-orientation-${labelIndex}-${label.netId ?? label.globalConnNetId}`
@@ -417,8 +395,43 @@ export class AvailableNetOrientationSolver extends BaseSolver {
417
395
  distance,
418
396
  outwardDistance,
419
397
  selected: false,
420
- status: this.getCandidateStatus(candidate, label, labelIndex),
398
+ status: this.getCandidateStatus({
399
+ candidate,
400
+ label,
401
+ labelIndex,
402
+ phase,
403
+ }),
404
+ }
405
+ }
406
+
407
+ private getCandidateConnectorTrace(
408
+ label: NetLabelPlacement,
409
+ candidate: Pick<
410
+ EvaluatedCandidate,
411
+ "anchorPoint" | "orientation" | "phase"
412
+ >,
413
+ ) {
414
+ if (candidate.phase === "lateral-shift") {
415
+ const orientDir = dir(candidate.orientation)
416
+ const kickedSource = {
417
+ x: label.anchorPoint.x - orientDir.x * LABEL_SEARCH_STEP,
418
+ y: label.anchorPoint.y - orientDir.y * LABEL_SEARCH_STEP,
419
+ }
420
+ return simplifyOrthogonalPath([
421
+ label.anchorPoint,
422
+ ...getConnectorTracePath(
423
+ kickedSource,
424
+ candidate.anchorPoint,
425
+ candidate.orientation,
426
+ ),
427
+ ])
421
428
  }
429
+
430
+ return getConnectorTracePath(
431
+ label.anchorPoint,
432
+ candidate.anchorPoint,
433
+ candidate.orientation,
434
+ )
422
435
  }
423
436
 
424
437
  private getSearchStartAnchor(
@@ -583,22 +596,24 @@ export class AvailableNetOrientationSolver extends BaseSolver {
583
596
  )?.netLabelHeight
584
597
  }
585
598
 
586
- private getCandidateStatus(
587
- candidate: CandidateLabel,
588
- label: NetLabelPlacement,
589
- labelIndex: number,
590
- ) {
599
+ private getCandidateStatus(params: {
600
+ candidate: CandidateLabel
601
+ label: NetLabelPlacement
602
+ labelIndex: number
603
+ phase: CandidatePhase
604
+ }) {
605
+ const { candidate, label, labelIndex, phase } = params
591
606
  const boundsStatus = this.getBoundsStatus(
592
607
  getRectBounds(candidate.center, candidate.width, candidate.height),
593
608
  labelIndex,
594
609
  )
595
610
  if (boundsStatus !== "valid") return boundsStatus
596
611
 
597
- const connectorTrace = getConnectorTracePath(
598
- label.anchorPoint,
599
- candidate.anchorPoint,
600
- candidate.orientation,
601
- )
612
+ const connectorTrace = this.getCandidateConnectorTrace(label, {
613
+ anchorPoint: candidate.anchorPoint,
614
+ orientation: candidate.orientation,
615
+ phase,
616
+ })
602
617
 
603
618
  if (tracePathCrossesAnyTrace(connectorTrace, this.traceMap)) {
604
619
  return "trace-collision"
@@ -614,7 +629,7 @@ export class AvailableNetOrientationSolver extends BaseSolver {
614
629
  if (i === labelIndex) continue
615
630
  const otherLabel = this.outputNetLabelPlacements[i]!
616
631
  if (
617
- tracePathCrossesAnyBounds(
632
+ tracePathIntersectsBounds(
618
633
  connectorTrace,
619
634
  getRectBounds(otherLabel.center, otherLabel.width, otherLabel.height),
620
635
  )
@@ -2,6 +2,7 @@ import type { Point } from "@tscircuit/math-utils"
2
2
  import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
3
3
  import type { InputProblem } from "lib/types/InputProblem"
4
4
  import type { FacingDirection } from "lib/utils/dir"
5
+ import { segmentIntersectsRect } from "lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/collisions"
5
6
  import { EPS, TRACE_BOUNDARY_TOLERANCE } from "./constants"
6
7
  import type { Bounds, ChipSide } from "./types"
7
8
 
@@ -102,6 +103,18 @@ export const tracePathCrossesAnyBounds = (
102
103
  return false
103
104
  }
104
105
 
106
+ export const tracePathIntersectsBounds = (
107
+ tracePath: Point[],
108
+ bounds: Bounds,
109
+ ) => {
110
+ for (let i = 0; i < tracePath.length - 1; i++) {
111
+ if (segmentIntersectsRect(tracePath[i]!, tracePath[i + 1]!, bounds)) {
112
+ return true
113
+ }
114
+ }
115
+ return false
116
+ }
117
+
105
118
  export const tracePathCrossesAnyTrace = (
106
119
  tracePath: Point[],
107
120
  traceMap: Record<string, SolvedTracePath>,
@@ -72,31 +72,27 @@ function buildMergedObstacleLabel(
72
72
  }
73
73
  }
74
74
 
75
- interface TraceInnerLabelOverlap {
75
+ interface TraceLabelOverlap {
76
76
  trace: SolvedTracePath
77
77
  label: NetLabelPlacement
78
78
  }
79
79
 
80
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.
81
+ * Detects overlaps where a trace segment intersects a label from another net.
82
+ * Point-only contact is allowed by segmentIntersectsRect; positive overlap on
83
+ * a label edge is still a visual collision and should be rerouted.
84
84
  */
85
- function detectInnerTraceLabelOverlaps(
85
+ function detectTraceLabelOverlaps(
86
86
  traces: SolvedTracePath[],
87
87
  labels: NetLabelPlacement[],
88
- ): TraceInnerLabelOverlap[] {
89
- const overlaps: TraceInnerLabelOverlap[] = []
88
+ ): TraceLabelOverlap[] {
89
+ const overlaps: TraceLabelOverlap[] = []
90
90
  for (const trace of traces) {
91
91
  for (const label of labels) {
92
92
  if (trace.globalConnNetId === label.globalConnNetId) continue
93
93
  const bounds = getRectBounds(label.center, label.width, label.height)
94
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++) {
95
+ for (let i = 0; i < path.length - 1; i++) {
100
96
  if (segmentIntersectsRect(path[i]!, path[i + 1]!, bounds)) {
101
97
  overlaps.push({ trace, label })
102
98
  break
@@ -183,7 +179,7 @@ export class NetLabelTraceCollisionSolver extends BaseSolver {
183
179
  return
184
180
  }
185
181
 
186
- const overlaps = detectInnerTraceLabelOverlaps(
182
+ const overlaps = detectTraceLabelOverlaps(
187
183
  this.outputTraces,
188
184
  this.outputNetLabelPlacements,
189
185
  )
@@ -1,11 +1,16 @@
1
1
  import type { GraphicsObject } from "graphics-debug"
2
2
  import { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver"
3
3
  import type { MspConnectionPairId } from "lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver"
4
+ import { findFirstCollision } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions"
5
+ import { getObstacleRects } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/rect"
4
6
  import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
7
+ import type { InputProblem } from "lib/types/InputProblem"
5
8
  import { applyJogToTerminalSegment } from "./applyJogToTrace"
6
9
 
7
10
  type ConnNetId = string
8
11
 
12
+ const EPS = 1e-6
13
+
9
14
  export interface OverlappingTraceSegmentLocator {
10
15
  connNetId: string
11
16
  pathsWithOverlap: Array<{
@@ -17,18 +22,21 @@ export interface OverlappingTraceSegmentLocator {
17
22
  export class TraceOverlapIssueSolver extends BaseSolver {
18
23
  overlappingTraceSegments: OverlappingTraceSegmentLocator[]
19
24
  traceNetIslands: Record<ConnNetId, Array<SolvedTracePath>>
25
+ obstacleRects: ReturnType<typeof getObstacleRects>
20
26
 
21
27
  SHIFT_DISTANCE = 0.1
22
28
 
23
29
  correctedTraceMap: Record<MspConnectionPairId, SolvedTracePath> = {}
24
30
 
25
31
  constructor(params: {
32
+ inputProblem: InputProblem
26
33
  overlappingTraceSegments: OverlappingTraceSegmentLocator[]
27
34
  traceNetIslands: Record<ConnNetId, Array<SolvedTracePath>>
28
35
  }) {
29
36
  super()
30
37
  this.overlappingTraceSegments = params.overlappingTraceSegments
31
38
  this.traceNetIslands = params.traceNetIslands
39
+ this.obstacleRects = getObstacleRects(params.inputProblem)
32
40
 
33
41
  // Only add the relevant traces to the correctedTraceMap
34
42
  for (const { connNetId, pathsWithOverlap } of this
@@ -49,13 +57,14 @@ export class TraceOverlapIssueSolver extends BaseSolver {
49
57
  // Shift only the overlapping segments, and move the shared endpoints
50
58
  // (the last point of the previous segment and the first point of the next
51
59
  // segment) so the polyline remains orthogonal without self-overlap.
52
- const EPS = 1e-6
53
-
54
60
  // Compute offsets for each island involved: alternate directions
55
- const offsets = this.overlappingTraceSegments.map((_, idx) => {
61
+ const offsets = this.overlappingTraceSegments.map((group, idx) => {
56
62
  const n = Math.floor(idx / 2) + 1
57
63
  const signed = idx % 2 === 0 ? -n : n
58
- return signed * this.SHIFT_DISTANCE
64
+ return this.getObstacleAwareOffset({
65
+ group,
66
+ offset: signed * this.SHIFT_DISTANCE,
67
+ })
59
68
  })
60
69
 
61
70
  const eq = (a: number, b: number) => Math.abs(a - b) < EPS
@@ -82,8 +91,6 @@ export class TraceOverlapIssueSolver extends BaseSolver {
82
91
  const current = this.correctedTraceMap[original.mspPairId] ?? original
83
92
  const pts = current.tracePath.map((p) => ({ ...p }))
84
93
 
85
- const segIdxs = Array.from(segIdxSet).sort((a, b) => a - b)
86
-
87
94
  const segIdxsRev = Array.from(segIdxSet)
88
95
  .sort((a, b) => a - b)
89
96
  .reverse()
@@ -142,6 +149,82 @@ export class TraceOverlapIssueSolver extends BaseSolver {
142
149
  this.solved = true
143
150
  }
144
151
 
152
+ private getObstacleAwareOffset({
153
+ group,
154
+ offset,
155
+ }: {
156
+ group: OverlappingTraceSegmentLocator
157
+ offset: number
158
+ }) {
159
+ const blockingCollisions = this.getBlockingCollisions({ group, offset })
160
+ if (blockingCollisions.length === 0) return offset
161
+
162
+ return (
163
+ blockingCollisions
164
+ .flatMap(({ start, isVertical, obstacle }) => [
165
+ (isVertical ? obstacle.minX - start.x : obstacle.minY - start.y) -
166
+ this.SHIFT_DISTANCE,
167
+ (isVertical ? obstacle.maxX - start.x : obstacle.maxY - start.y) +
168
+ this.SHIFT_DISTANCE,
169
+ ])
170
+ .filter(
171
+ (candidateOffset) =>
172
+ this.getBlockingCollisions({ group, offset: candidateOffset })
173
+ .length === 0,
174
+ )
175
+ .sort((a, b) => Math.abs(a - offset) - Math.abs(b - offset))[0] ??
176
+ offset
177
+ )
178
+ }
179
+
180
+ private getBlockingCollisions({
181
+ group,
182
+ offset,
183
+ }: {
184
+ group: OverlappingTraceSegmentLocator
185
+ offset: number
186
+ }) {
187
+ const blockingCollisions: Array<{
188
+ start: SolvedTracePath["tracePath"][number]
189
+ isVertical: boolean
190
+ obstacle: ReturnType<typeof getObstacleRects>[number]
191
+ }> = []
192
+
193
+ for (const {
194
+ solvedTracePathIndex,
195
+ traceSegmentIndex,
196
+ } of group.pathsWithOverlap) {
197
+ const trace = this.traceNetIslands[group.connNetId][solvedTracePathIndex]!
198
+ const start = trace.tracePath[traceSegmentIndex]
199
+ const end = trace.tracePath[traceSegmentIndex + 1]
200
+ if (!start || !end) continue
201
+
202
+ const isVertical = Math.abs(start.x - end.x) < EPS
203
+ const isHorizontal = Math.abs(start.y - end.y) < EPS
204
+ if (!isVertical && !isHorizontal) continue
205
+
206
+ const shiftedSegment = isVertical
207
+ ? [
208
+ { ...start, x: start.x + offset },
209
+ { ...end, x: end.x + offset },
210
+ ]
211
+ : [
212
+ { ...start, y: start.y + offset },
213
+ { ...end, y: end.y + offset },
214
+ ]
215
+ const collision = findFirstCollision(shiftedSegment, this.obstacleRects)
216
+ if (collision) {
217
+ blockingCollisions.push({
218
+ start,
219
+ isVertical,
220
+ obstacle: collision.rect,
221
+ })
222
+ }
223
+ }
224
+
225
+ return blockingCollisions
226
+ }
227
+
145
228
  override visualize(): GraphicsObject {
146
229
  // Visualize overlapped segments and proposed corrections
147
230
  const graphics: GraphicsObject = {
@@ -309,6 +309,7 @@ export class TraceOverlapShiftSolver extends BaseSolver {
309
309
  const { overlappingTraceSegments } = overlapIssue
310
310
 
311
311
  this.activeSubSolver = new TraceOverlapIssueSolver({
312
+ inputProblem: this.inputProblem,
312
313
  overlappingTraceSegments,
313
314
  traceNetIslands: this.traceNetIslands,
314
315
  })
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.63",
4
+ "version": "0.0.65",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "start": "cosmos",
@@ -2,56 +2,60 @@
2
2
  <rect width="100%" height="100%" fill="white" />
3
3
  <g>
4
4
  <circle data-type="point" data-label="U1.1
5
- x+" data-x="1.1" data-y="0.30000000000000004" cx="487.83715012722644" cy="248.75318066157763" r="3" fill="hsl(319, 100%, 50%, 0.8)" />
5
+ x+" data-x="1.1" data-y="0.30000000000000004" cx="479.84291324526953" cy="250.0249910746162" r="3" fill="hsl(319, 100%, 50%, 0.8)" />
6
6
  </g>
7
7
  <g>
8
8
  <circle data-type="point" data-label="U1.2
9
- x+" data-x="1.1" data-y="0.09999999999999998" cx="487.83715012722644" cy="289.46564885496184" r="3" fill="hsl(320, 100%, 50%, 0.8)" />
9
+ x+" data-x="1.1" data-y="0.09999999999999998" cx="479.84291324526953" cy="290.0107104605498" r="3" fill="hsl(320, 100%, 50%, 0.8)" />
10
10
  </g>
11
11
  <g>
12
12
  <circle data-type="point" data-label="U1.3
13
- x+" data-x="1.1" data-y="-0.10000000000000003" cx="487.83715012722644" cy="330.17811704834605" r="3" fill="hsl(321, 100%, 50%, 0.8)" />
13
+ x+" data-x="1.1" data-y="-0.10000000000000003" cx="479.84291324526953" cy="329.9964298464834" r="3" fill="hsl(321, 100%, 50%, 0.8)" />
14
14
  </g>
15
15
  <g>
16
16
  <circle data-type="point" data-label="U1.4
17
- x+" data-x="1.1" data-y="-0.30000000000000004" cx="487.83715012722644" cy="370.89058524173026" r="3" fill="hsl(322, 100%, 50%, 0.8)" />
17
+ x+" data-x="1.1" data-y="-0.30000000000000004" cx="479.84291324526953" cy="369.982149232417" r="3" fill="hsl(322, 100%, 50%, 0.8)" />
18
18
  </g>
19
19
  <g>
20
- <circle data-type="point" data-label="" data-x="1.1" data-y="0.30000000000000004" cx="487.83715012722644" cy="248.75318066157763" r="3" fill="hsl(40, 100%, 50%, 0.9)" />
20
+ <circle data-type="point" data-label="anchorPoint
21
+ orientation: x+" data-x="1.1" data-y="0.30000000000000004" cx="479.84291324526953" cy="250.0249910746162" r="3" fill="hsl(40, 100%, 50%, 0.9)" />
21
22
  </g>
22
23
  <g>
23
- <circle data-type="point" data-label="" data-x="1.1" data-y="0.09999999999999998" cx="487.83715012722644" cy="289.46564885496184" r="3" fill="hsl(40, 100%, 50%, 0.9)" />
24
+ <circle data-type="point" data-label="anchorPoint
25
+ orientation: x+" data-x="1.1" data-y="0.09999999999999998" cx="479.84291324526953" cy="290.0107104605498" r="3" fill="hsl(40, 100%, 50%, 0.9)" />
24
26
  </g>
25
27
  <g>
26
- <circle data-type="point" data-label="" data-x="1.5510000000000002" data-y="0.4009999999999999" cx="579.6437659033079" cy="228.1933842239186" r="3" fill="hsl(40, 100%, 50%, 0.9)" />
28
+ <circle data-type="point" data-label="anchorPoint
29
+ orientation: y+" data-x="1.601" data-y="0.4009999999999999" cx="580.0071403070332" cy="229.83220278471975" r="3" fill="hsl(40, 100%, 50%, 0.9)" />
27
30
  </g>
28
31
  <g>
29
- <circle data-type="point" data-label="" data-x="1.201" data-y="-0.5010000000000001" cx="508.39694656488547" cy="411.80661577608146" r="3" fill="hsl(40, 100%, 50%, 0.9)" />
32
+ <circle data-type="point" data-label="anchorPoint
33
+ orientation: y-" data-x="1.201" data-y="-0.5010000000000001" cx="500.035701535166" cy="410.1677972152803" r="3" fill="hsl(40, 100%, 50%, 0.9)" />
30
34
  </g>
31
35
  <g>
32
- <polyline data-points="1.1,-0.10000000000000003 1.5510000000000002,-0.10000000000000003 1.5510000000000002,0.4009999999999999" data-type="line" data-label="" points="487.83715012722644,330.17811704834605 579.6437659033079,330.17811704834605 579.6437659033079,228.1933842239186" fill="none" stroke="purple" stroke-width="1" />
36
+ <polyline data-points="1.1,-0.10000000000000003 1.601,-0.10000000000000003 1.601,0.4009999999999999" data-type="line" data-label="" points="479.84291324526953,329.9964298464834 580.0071403070332,329.9964298464834 580.0071403070332,229.83220278471975" fill="none" stroke="purple" stroke-width="1" />
33
37
  </g>
34
38
  <g>
35
- <polyline data-points="1.1,-0.30000000000000004 1.201,-0.30000000000000004 1.201,-0.5010000000000001" data-type="line" data-label="" points="487.83715012722644,370.89058524173026 508.39694656488547,370.89058524173026 508.39694656488547,411.80661577608146" fill="none" stroke="purple" stroke-width="1" />
39
+ <polyline data-points="1.1,-0.30000000000000004 1.201,-0.30000000000000004 1.201,-0.5010000000000001" data-type="line" data-label="" points="479.84291324526953,369.982149232417 500.035701535166,369.982149232417 500.035701535166,410.1677972152803" fill="none" stroke="purple" stroke-width="1" />
36
40
  </g>
37
41
  <g>
38
- <rect data-type="rect" data-label="schematic_component_0" data-x="0" data-y="0" x="40" y="208.04071246819342" width="447.83715012722644" height="203.56234096692106" fill="hsl(24, 100%, 50%, 0.8)" stroke="black" stroke-width="0.004912500000000001" />
42
+ <rect data-type="rect" data-label="schematic_component_0" data-x="0" data-y="0" x="39.99999999999997" y="210.03927168868262" width="439.84291324526953" height="199.92859692966795" fill="hsl(24, 100%, 50%, 0.8)" stroke="black" stroke-width="0.005001785714285715" />
39
43
  </g>
40
44
  <g>
41
45
  <rect data-type="rect" data-label="netId: SCL
42
- globalConnNetId: connectivity_net0" data-x="1.326" data-y="0.30000000000000004" x="488.0407124681933" y="228.39694656488552" width="91.60305343511459" height="40.71246819338421" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.004912500000000001" />
46
+ globalConnNetId: connectivity_net0" data-x="1.326" data-y="0.30000000000000004" x="480.0428418421992" y="230.03213138164938" width="89.96786861835062" height="39.985719385933635" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.005001785714285715" />
43
47
  </g>
44
48
  <g>
45
49
  <rect data-type="rect" data-label="netId: SDA
46
- globalConnNetId: connectivity_net1" data-x="1.326" data-y="0.09999999999999998" x="488.0407124681933" y="269.10941475826974" width="91.60305343511459" height="40.71246819338421" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.004912500000000001" />
50
+ globalConnNetId: connectivity_net1" data-x="1.326" data-y="0.09999999999999998" x="480.0428418421992" y="270.017850767583" width="89.96786861835062" height="39.98571938593358" fill="hsl(40, 100%, 50%, 0.35)" stroke="black" stroke-width="0.005001785714285715" />
47
51
  </g>
48
52
  <g>
49
53
  <rect data-type="rect" data-label="netId: V3_3
50
- globalConnNetId: connectivity_net2" data-x="1.5510000000000002" data-y="0.6259999999999999" x="559.2875318066158" y="136.59033078880412" width="40.71246819338421" height="91.60305343511448" fill="#ef444466" stroke="#ef4444" stroke-width="0.004912500000000001" />
54
+ globalConnNetId: connectivity_net2" data-x="1.601" data-y="0.6259999999999999" x="560.0142806140664" y="139.86433416636916" width="39.985719385933635" height="89.9678686183506" fill="#ef444466" stroke="#ef4444" stroke-width="0.005001785714285715" />
51
55
  </g>
52
56
  <g>
53
57
  <rect data-type="rect" data-label="netId: GND
54
- globalConnNetId: connectivity_net3" data-x="1.201" data-y="-0.7260000000000001" x="488.0407124681933" y="411.80661577608146" width="40.71246819338421" height="91.60305343511448" fill="#00000066" stroke="#000000" stroke-width="0.004912500000000001" />
58
+ globalConnNetId: connectivity_net3" data-x="1.201" data-y="-0.7260000000000001" x="480.0428418421992" y="410.1677972152803" width="39.985719385933635" height="89.96786861835051" fill="#00000066" stroke="#000000" stroke-width="0.005001785714285715" />
55
59
  </g>
56
60
  <g id="crosshair" style="display: none">
57
61
  <line id="crosshair-h" y1="0" y2="640" stroke="#666" stroke-width="0.5" />
@@ -81,12 +85,12 @@ globalConnNetId: connectivity_net3" data-x="1.201" data-y="-0.7260000000000001"
81
85
 
82
86
  // Calculate real coordinates using inverse transformation
83
87
  const matrix = {
84
- "a": 203.56234096692108,
88
+ "a": 199.92859692966798,
85
89
  "c": 0,
86
- "e": 263.9185750636132,
90
+ "e": 259.92145662263476,
87
91
  "b": 0,
88
- "d": -203.56234096692108,
89
- "f": 309.82188295165395
92
+ "d": -199.92859692966798,
93
+ "f": 310.0035701535166
90
94
  };
91
95
  // Manually invert and apply the affine transform
92
96
  // Since we only use translate and scale, we can directly compute:
@@ -2,11 +2,19 @@ import { test, expect } from "bun:test"
2
2
  import { SchematicTracePipelineSolver } from "lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver"
3
3
  import inputProblem from "../assets/example34.json"
4
4
  import "tests/fixtures/matcher"
5
+ import { getTraceLabelCollisions } from "tests/fixtures/traceLabelCollisions"
5
6
 
6
7
  test("example34", () => {
7
8
  const solver = new SchematicTracePipelineSolver(inputProblem as any)
8
9
 
9
10
  solver.solve()
10
11
 
12
+ expect(
13
+ getTraceLabelCollisions(
14
+ solver.netLabelNetLabelCollisionSolver!.traces,
15
+ solver.netLabelNetLabelCollisionSolver!.outputNetLabelPlacements,
16
+ ),
17
+ ).toEqual([])
18
+
11
19
  expect(solver).toMatchSolverSnapshot(import.meta.path)
12
20
  })
@@ -0,0 +1,42 @@
1
+ import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
2
+ import { getRectBounds } from "lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/geometry"
3
+ import { segmentIntersectsRect } from "lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/collisions"
4
+ import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
5
+
6
+ export const getTraceLabelCollisions = (
7
+ traces: SolvedTracePath[],
8
+ labels: NetLabelPlacement[],
9
+ ) => {
10
+ const collisions: Array<{
11
+ traceId: string
12
+ traceNetId: string
13
+ labelNetId: string
14
+ segmentIndex: number
15
+ }> = []
16
+
17
+ for (const trace of traces) {
18
+ for (const label of labels) {
19
+ if (trace.globalConnNetId === label.globalConnNetId) continue
20
+
21
+ const bounds = getRectBounds(label.center, label.width, label.height)
22
+ for (let i = 0; i < trace.tracePath.length - 1; i++) {
23
+ if (
24
+ segmentIntersectsRect(
25
+ trace.tracePath[i]!,
26
+ trace.tracePath[i + 1]!,
27
+ bounds,
28
+ )
29
+ ) {
30
+ collisions.push({
31
+ traceId: trace.mspPairId,
32
+ traceNetId: trace.globalConnNetId,
33
+ labelNetId: label.globalConnNetId,
34
+ segmentIndex: i,
35
+ })
36
+ }
37
+ }
38
+ }
39
+ }
40
+
41
+ return collisions
42
+ }