@tscircuit/schematic-trace-solver 0.0.137 → 0.0.140

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.
@@ -17,7 +17,12 @@ import type {
17
17
  } from "lib/types/InputProblem"
18
18
  import { dir, type FacingDirection } from "lib/utils/dir"
19
19
  import { rectIntersectsAnyTextBox } from "lib/utils/textBoxBounds"
20
- import { EPS, LABEL_SEARCH_STEP, WICK_CLEARANCE } from "./constants"
20
+ import {
21
+ EPS,
22
+ LABEL_SEARCH_STEP,
23
+ MAX_RECORDED_CANDIDATES,
24
+ WICK_CLEARANCE,
25
+ } from "./constants"
21
26
  import {
22
27
  getConnectorTracePath,
23
28
  getMaxSearchDistance,
@@ -27,8 +32,6 @@ import {
27
32
  rangesOverlap,
28
33
  rectsOverlap,
29
34
  simplifyOrthogonalPath,
30
- traceCrossesBoundsInterior,
31
- tracePathCrossesAnyBounds,
32
35
  tracePathIntersectsBounds,
33
36
  } from "./geometry"
34
37
  import { orderRoutedLabelsBeforeOverlappingPortLabels } from "./orderRoutedLabelsBeforeOverlappingPortLabels"
@@ -43,6 +46,7 @@ import type {
43
46
  EvaluatedCandidate,
44
47
  } from "./types"
45
48
  import { visualizeAvailableNetOrientationSolver } from "./visualize"
49
+ import { AvailableNetOrientationObstacleIndex } from "./AvailableNetOrientationObstacleIndex"
46
50
 
47
51
  const LABEL_TRACE_CLEARANCE = 0.1
48
52
 
@@ -64,6 +68,7 @@ export class AvailableNetOrientationSolver extends BaseSolver {
64
68
  private chipObstacleSpatialIndex: ChipObstacleSpatialIndex
65
69
  private maxSearchDistance: number
66
70
  private pinMap: Record<string, InputPin & { chipId: string }>
71
+ private obstacleIndex: AvailableNetOrientationObstacleIndex
67
72
 
68
73
  constructor(params: AvailableNetOrientationSolverParams) {
69
74
  super()
@@ -79,6 +84,11 @@ export class AvailableNetOrientationSolver extends BaseSolver {
79
84
  params.inputProblem._chipObstacleSpatialIndex ??
80
85
  new ChipObstacleSpatialIndex(params.inputProblem.chips)
81
86
  this.maxSearchDistance = getMaxSearchDistance(params.inputProblem)
87
+ this.obstacleIndex = new AvailableNetOrientationObstacleIndex({
88
+ chipObstacleSpatialIndex: this.chipObstacleSpatialIndex,
89
+ netLabelPlacements: this.outputNetLabelPlacements,
90
+ traces: this.traces,
91
+ })
82
92
  this.crowdedPortOnlyLabelIndices = this.getCrowdedPortOnlyLabelIndices()
83
93
  this.queuedLabelIndices = this.getProcessableLabelIndices()
84
94
  this.setCurrentLabel(this.queuedLabelIndices[0] ?? null)
@@ -309,6 +319,10 @@ export class AvailableNetOrientationSolver extends BaseSolver {
309
319
  ...toNetLabelPlacementPatch(candidate),
310
320
  }
311
321
  this.addConnectorTrace(label, candidate, labelIndex)
322
+ this.obstacleIndex.rebuild({
323
+ netLabelPlacements: this.outputNetLabelPlacements,
324
+ traces: this.traces,
325
+ })
312
326
  }
313
327
 
314
328
  private addConnectorTrace(
@@ -553,7 +567,7 @@ export class AvailableNetOrientationSolver extends BaseSolver {
553
567
  anchorY - label.anchorPoint.y,
554
568
  anchorX - label.anchorPoint.x,
555
569
  )
556
- this.currentCandidateResults.push(result)
570
+ this.recordCandidateResult(result)
557
571
  if (result.status !== "valid") continue
558
572
 
559
573
  result.selected = true
@@ -605,7 +619,7 @@ export class AvailableNetOrientationSolver extends BaseSolver {
605
619
  labelIndex,
606
620
  "rotate",
607
621
  )
608
- this.currentCandidateResults.push(result)
622
+ this.recordCandidateResult(result)
609
623
  if (result.status === "valid") {
610
624
  result.selected = true
611
625
  return result
@@ -637,7 +651,7 @@ export class AvailableNetOrientationSolver extends BaseSolver {
637
651
  labelIndex,
638
652
  "trace-anchor",
639
653
  )
640
- this.currentCandidateResults.push(result)
654
+ this.recordCandidateResult(result)
641
655
 
642
656
  if (result.status === "valid") {
643
657
  result.selected = true
@@ -682,7 +696,7 @@ export class AvailableNetOrientationSolver extends BaseSolver {
682
696
  labelIndex,
683
697
  "outward-trace-anchor",
684
698
  )
685
- this.currentCandidateResults.push(preservedColumnResult)
699
+ this.recordCandidateResult(preservedColumnResult)
686
700
  if (preservedColumnResult.status === "valid") {
687
701
  preservedColumnResult.selected = true
688
702
  return preservedColumnResult
@@ -716,7 +730,7 @@ export class AvailableNetOrientationSolver extends BaseSolver {
716
730
  "outward-trace-anchor",
717
731
  distance,
718
732
  )
719
- this.currentCandidateResults.push(result)
733
+ this.recordCandidateResult(result)
720
734
 
721
735
  if (result.status === "valid") {
722
736
  result.selected = true
@@ -891,7 +905,7 @@ export class AvailableNetOrientationSolver extends BaseSolver {
891
905
  distance,
892
906
  outwardDistance,
893
907
  )
894
- this.currentCandidateResults.push(result)
908
+ this.recordCandidateResult(result)
895
909
 
896
910
  if (result.status === "valid") {
897
911
  result.selected = true
@@ -926,6 +940,19 @@ export class AvailableNetOrientationSolver extends BaseSolver {
926
940
  }
927
941
  }
928
942
 
943
+ private recordCandidateResult(candidate: EvaluatedCandidate) {
944
+ this.stats.candidateEvaluations = (this.stats.candidateEvaluations ?? 0) + 1
945
+ if (this.currentCandidateResults.length < MAX_RECORDED_CANDIDATES) {
946
+ this.currentCandidateResults.push(candidate)
947
+ } else if (candidate.status === "valid") {
948
+ this.currentCandidateResults[MAX_RECORDED_CANDIDATES - 1] = candidate
949
+ }
950
+ this.stats.maxRecordedCandidates = Math.max(
951
+ this.stats.maxRecordedCandidates ?? 0,
952
+ this.currentCandidateResults.length,
953
+ )
954
+ }
955
+
929
956
  private getCandidateConnectorTrace(
930
957
  label: NetLabelPlacement,
931
958
  candidate: Pick<
@@ -1269,13 +1296,13 @@ export class AvailableNetOrientationSolver extends BaseSolver {
1269
1296
  labelIndex,
1270
1297
  )
1271
1298
 
1272
- for (const chip of this.chipObstacleSpatialIndex.chips) {
1273
- if (tracePathCrossesAnyBounds(connectorTrace, chip.bounds)) {
1274
- return "chip-collision"
1275
- }
1299
+ if (this.obstacleIndex.doesTracePathCrossChip(connectorTrace)) {
1300
+ return "chip-collision"
1276
1301
  }
1277
1302
 
1278
- for (let i = 0; i < this.outputNetLabelPlacements.length; i++) {
1303
+ for (const i of this.obstacleIndex.getLabelIndicesNearTracePath(
1304
+ connectorTrace,
1305
+ )) {
1279
1306
  if (i === labelIndex) continue
1280
1307
  if (this.shouldIgnorePendingCrowdedLabel(labelIndex, i)) continue
1281
1308
  const otherLabel = this.outputNetLabelPlacements[i]!
@@ -1345,7 +1372,7 @@ export class AvailableNetOrientationSolver extends BaseSolver {
1345
1372
  if (rectIntersectsAnyTextBox(bounds, this.inputProblem)) {
1346
1373
  return "text-collision"
1347
1374
  }
1348
- if (traceCrossesBoundsInterior(bounds, this.traceMap)) {
1375
+ if (this.obstacleIndex.doesTraceCrossBoundsInterior(bounds)) {
1349
1376
  return "trace-collision"
1350
1377
  }
1351
1378
  if (this.isTraceTooCloseToLabel(bounds, label)) {
@@ -1361,14 +1388,10 @@ export class AvailableNetOrientationSolver extends BaseSolver {
1361
1388
  if (!this.shouldCheckTraceClearanceForLabel(label)) return false
1362
1389
 
1363
1390
  const clearanceBounds = this.getLabelTraceClearanceBounds(bounds)
1364
- for (const trace of Object.values(this.traceMap)) {
1365
- if (trace.globalConnNetId === label.globalConnNetId) continue
1366
- if (tracePathIntersectsBounds(trace.tracePath, clearanceBounds)) {
1367
- return true
1368
- }
1369
- }
1370
-
1371
- return false
1391
+ return this.obstacleIndex.doesTraceIntersectBounds({
1392
+ bounds: clearanceBounds,
1393
+ excludedGlobalConnNetId: label.globalConnNetId,
1394
+ })
1372
1395
  }
1373
1396
 
1374
1397
  private getLabelTraceClearanceBounds(bounds: Bounds): Bounds {
@@ -1496,7 +1519,17 @@ export class AvailableNetOrientationSolver extends BaseSolver {
1496
1519
  }
1497
1520
 
1498
1521
  private intersectsAnyOtherNetLabel(bounds: Bounds, labelIndex: number) {
1499
- for (let i = 0; i < this.outputNetLabelPlacements.length; i++) {
1522
+ const searchBounds = {
1523
+ minX: bounds.minX - LABEL_SEARCH_STEP,
1524
+ minY: bounds.minY - LABEL_SEARCH_STEP,
1525
+ maxX: bounds.maxX + LABEL_SEARCH_STEP,
1526
+ maxY: bounds.maxY + LABEL_SEARCH_STEP,
1527
+ }
1528
+ const nearbyLabelIndices = this.obstacleIndex
1529
+ .getLabelIndicesInBounds(searchBounds)
1530
+ .sort((a, b) => a - b)
1531
+
1532
+ for (const i of nearbyLabelIndices) {
1500
1533
  if (i === labelIndex) continue
1501
1534
  if (this.shouldIgnorePendingCrowdedLabel(labelIndex, i)) continue
1502
1535
  const label = this.outputNetLabelPlacements[i]!
@@ -1515,7 +1548,16 @@ export class AvailableNetOrientationSolver extends BaseSolver {
1515
1548
  }
1516
1549
 
1517
1550
  private sharesChipBoundary(bounds: Bounds) {
1518
- for (const chip of this.chipObstacleSpatialIndex.chips) {
1551
+ const boundarySearchBounds = {
1552
+ minX: bounds.minX - WICK_CLEARANCE - EPS,
1553
+ minY: bounds.minY - WICK_CLEARANCE - EPS,
1554
+ maxX: bounds.maxX + WICK_CLEARANCE + EPS,
1555
+ maxY: bounds.maxY + WICK_CLEARANCE + EPS,
1556
+ }
1557
+ const nearbyChips =
1558
+ this.chipObstacleSpatialIndex.getChipsInBounds(boundarySearchBounds)
1559
+
1560
+ for (const chip of nearbyChips) {
1519
1561
  const chipBounds = chip.bounds
1520
1562
  const adjacentToVerticalSide =
1521
1563
  Math.abs(bounds.minX - chipBounds.maxX) <= WICK_CLEARANCE + EPS ||
@@ -1,4 +1,6 @@
1
1
  export const LABEL_SEARCH_STEP = 0.05
2
+ // Keep failed searches from retaining an unbounded visualization history.
3
+ export const MAX_RECORDED_CANDIDATES = 2_000
2
4
  export const WICK_CLEARANCE = 0.001
3
5
  export const EPS = 1e-9
4
6
  export const TRACE_BOUNDARY_TOLERANCE = WICK_CLEARANCE + EPS
@@ -1,6 +1,7 @@
1
1
  import type { Point } from "@tscircuit/math-utils"
2
2
  import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
3
3
  import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
4
+ import { tracePathContainsPoint } from "lib/solvers/RailNetLabelCornerPlacementSolver/geometry"
4
5
  import { getMovedAnchorPointForReroute } from "./getMovedAnchorPointForReroute"
5
6
  import { isLabelAttachedToTrace } from "./isLabelAttachedToTrace"
6
7
 
@@ -39,3 +40,73 @@ export const moveAttachedLabelsToReroutedTrace = ({
39
40
  },
40
41
  }
41
42
  })
43
+
44
+ export const moveNetLabelConnectorsToReroutedTraces = ({
45
+ originalTraces,
46
+ reroutedTraces,
47
+ netLabelPlacements,
48
+ }: {
49
+ originalTraces: SolvedTracePath[]
50
+ reroutedTraces: SolvedTracePath[]
51
+ netLabelPlacements: NetLabelPlacement[]
52
+ }) => {
53
+ const traces = [...reroutedTraces]
54
+ const reroutedTraceMap = new Map(
55
+ reroutedTraces.map((trace) => [trace.mspPairId, trace]),
56
+ )
57
+
58
+ const labels = netLabelPlacements.map((label) => {
59
+ const originalHostTrace = originalTraces.find((trace) => {
60
+ if (!label.mspConnectionPairIds.includes(trace.mspPairId)) return false
61
+ const reroutedTrace = reroutedTraceMap.get(trace.mspPairId)
62
+ if (!reroutedTrace) return false
63
+ return reroutedTrace.tracePath !== trace.tracePath
64
+ })
65
+ if (!originalHostTrace) return label
66
+
67
+ const reroutedHostTrace = reroutedTraceMap.get(originalHostTrace.mspPairId)!
68
+ const connectorIndex = originalTraces.findIndex((trace) => {
69
+ if (trace.mspPairId === originalHostTrace.mspPairId) return false
70
+ if (trace.globalConnNetId !== label.globalConnNetId) return false
71
+ if (!tracePathContainsPoint(trace.tracePath, label.anchorPoint)) {
72
+ return false
73
+ }
74
+ return label.pinIds.every((pinId) => trace.pinIds.includes(pinId))
75
+ })
76
+ if (connectorIndex < 0) return label
77
+
78
+ const connector = originalTraces[connectorIndex]!
79
+ const originalJunction = connector.tracePath.find((point) =>
80
+ tracePathContainsPoint(originalHostTrace.tracePath, point),
81
+ )
82
+ if (!originalJunction) return label
83
+
84
+ const reroutedJunction = getMovedAnchorPointForReroute(
85
+ originalJunction,
86
+ originalHostTrace.tracePath,
87
+ reroutedHostTrace.tracePath,
88
+ )
89
+ if (!reroutedJunction) return label
90
+
91
+ const delta = {
92
+ x: reroutedJunction.x - originalJunction.x,
93
+ y: reroutedJunction.y - originalJunction.y,
94
+ }
95
+ const movedConnector = {
96
+ ...connector,
97
+ tracePath: connector.tracePath.map((point) => ({
98
+ x: point.x + delta.x,
99
+ y: point.y + delta.y,
100
+ })),
101
+ }
102
+ traces[connectorIndex] = movedConnector
103
+ return moveAttachedLabelsToReroutedTrace({
104
+ trace: connector,
105
+ originalTracePath: connector.tracePath,
106
+ reroutedTracePath: movedConnector.tracePath,
107
+ netLabelPlacements: [label],
108
+ })[0]!
109
+ })
110
+
111
+ return { traces, netLabelPlacements: labels }
112
+ }
@@ -22,7 +22,10 @@ import { LongDistancePairSolver } from "../LongDistancePairSolver/LongDistancePa
22
22
  import { MergedNetLabelObstacleSolver } from "../TraceLabelOverlapAvoidanceSolver/sub-solvers/LabelMergingSolver/LabelMergingSolver"
23
23
  import { TraceCleanupSolver } from "../TraceCleanupSolver/TraceCleanupSolver"
24
24
  import { Example28Solver } from "../Example28Solver/Example28Solver"
25
- import { moveAttachedLabelsToReroutedTrace } from "../Example28Solver/labelMovement"
25
+ import {
26
+ moveAttachedLabelsToReroutedTrace,
27
+ moveNetLabelConnectorsToReroutedTraces,
28
+ } from "../Example28Solver/labelMovement"
26
29
  import { AvailableNetOrientationSolver } from "../AvailableNetOrientationSolver/AvailableNetOrientationSolver"
27
30
  import { RailNetLabelCornerPlacementSolver } from "../RailNetLabelCornerPlacementSolver/RailNetLabelCornerPlacementSolver"
28
31
  import { TraceAnchoredNetLabelOverlapSolver } from "../TraceAnchoredNetLabelOverlapSolver/TraceAnchoredNetLabelOverlapSolver"
@@ -426,12 +429,17 @@ export class SchematicTracePipelineSolver extends BaseSolver {
426
429
  const previousOutput =
427
430
  instance.preAlignmentNetLabelTraceCollisionSolver!.getOutput()
428
431
  const alignmentOutput = instance.traceCleanupSolver2!.getOutput()
432
+ const connectorMovement = moveNetLabelConnectorsToReroutedTraces({
433
+ originalTraces: previousOutput.traces,
434
+ reroutedTraces: alignmentOutput.traces,
435
+ netLabelPlacements: previousOutput.netLabelPlacements,
436
+ })
429
437
  const previousTraceMap = new Map(
430
438
  previousOutput.traces.map((trace) => [trace.mspPairId, trace]),
431
439
  )
432
- let netLabelPlacements = previousOutput.netLabelPlacements
440
+ let netLabelPlacements = connectorMovement.netLabelPlacements
433
441
 
434
- for (const trace of alignmentOutput.traces) {
442
+ for (const trace of connectorMovement.traces) {
435
443
  const previousTrace = previousTraceMap.get(trace.mspPairId)
436
444
  if (!previousTrace || previousTrace.tracePath === trace.tracePath) {
437
445
  continue
@@ -455,7 +463,7 @@ export class SchematicTracePipelineSolver extends BaseSolver {
455
463
  return [
456
464
  {
457
465
  inputProblem: instance.inputProblem,
458
- traces: alignmentOutput.traces,
466
+ traces: connectorMovement.traces,
459
467
  netLabelPlacements,
460
468
  },
461
469
  ]
package/package.json CHANGED
@@ -1,7 +1,11 @@
1
1
  {
2
2
  "name": "@tscircuit/schematic-trace-solver",
3
+ "repository": {
4
+ "type": "git",
5
+ "url": "https://github.com/tscircuit/schematic-trace-solver.git"
6
+ },
3
7
  "main": "dist/index.js",
4
- "version": "0.0.137",
8
+ "version": "0.0.140",
5
9
  "type": "module",
6
10
  "scripts": {
7
11
  "start": "cosmos",