@tscircuit/schematic-trace-solver 0.0.41 → 0.0.43

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.
Files changed (58) hide show
  1. package/dist/index.d.ts +118 -8
  2. package/dist/index.js +1067 -748
  3. package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +48 -16
  4. package/lib/solvers/TraceCleanupSolver/TraceCleanupSolver.ts +133 -0
  5. package/lib/solvers/TraceCleanupSolver/balanceZShapes.ts +200 -0
  6. package/lib/solvers/{TraceLabelOverlapAvoidanceSolver → TraceCleanupSolver}/hasCollisions.ts +1 -1
  7. package/lib/solvers/{TraceLabelOverlapAvoidanceSolver → TraceCleanupSolver}/hasCollisionsWithLabels.ts +1 -1
  8. package/lib/solvers/TraceCleanupSolver/minimizeTurnsWithFilteredLabels.ts +76 -0
  9. package/lib/solvers/{TraceLabelOverlapAvoidanceSolver → TraceCleanupSolver}/simplifyPath.ts +1 -1
  10. package/lib/solvers/{TraceLabelOverlapAvoidanceSolver → TraceCleanupSolver}/turnMinimization.ts +0 -2
  11. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/TraceLabelOverlapAvoidanceSolver.ts +74 -192
  12. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/rerouteCollidingTrace.ts +13 -24
  13. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/LabelMergingSolver/LabelMergingSolver.ts +205 -0
  14. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/OverlapAvoidanceStepSolver/OverlapAvoidanceStepSolver.ts +144 -0
  15. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/SingleOverlapSolver/SingleOverlapSolver.ts +117 -0
  16. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/tryFourPointDetour.ts +104 -0
  17. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/trySnipAndReconnect.ts +24 -154
  18. package/package.json +1 -1
  19. package/site/examples/example26.page.tsx +4 -0
  20. package/tests/assets/1.input.json +379 -0
  21. package/tests/assets/2.input.json +330 -0
  22. package/tests/assets/3.input.json +388 -0
  23. package/tests/assets/MergedNetLabelObstacles.test.input.json +154 -0
  24. package/tests/assets/OverlapAvoidanceStepSolver.test.input.json +682 -0
  25. package/tests/assets/SingleOverlapSolver.test.input.json +170 -0
  26. package/tests/assets/TraceCleanupSolver.test.input.json +216 -0
  27. package/tests/assets/TraceLabelOverlapAvoidanceSolver.test.input.json +424 -0
  28. package/tests/assets/example26.json +1206 -0
  29. package/tests/examples/__snapshots__/example02.snap.svg +1 -1
  30. package/tests/examples/__snapshots__/example09.snap.svg +34 -34
  31. package/tests/examples/__snapshots__/example11.snap.svg +13 -13
  32. package/tests/examples/__snapshots__/example12.snap.svg +3 -3
  33. package/tests/examples/__snapshots__/example13.snap.svg +12 -12
  34. package/tests/examples/__snapshots__/example16.snap.svg +4 -4
  35. package/tests/examples/__snapshots__/example19.snap.svg +9 -9
  36. package/tests/examples/__snapshots__/example28.snap.svg +3 -3
  37. package/tests/examples/__snapshots__/example29.snap.svg +1208 -0
  38. package/tests/examples/example29.test.ts +13 -0
  39. package/tests/solvers/TraceCleanupSolver/TraceCleanupSolver.test.ts +19 -0
  40. package/tests/solvers/TraceCleanupSolver/__snapshots__/TraceCleanupSolver.snap.svg +119 -0
  41. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/TraceLabelOverlapAvoidanceSolver.test.ts +18 -0
  42. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/__snapshots__/TraceLabelOverlapAvoidanceSolver.snap.svg +243 -0
  43. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/renderComparisonView/__snapshots__/renderComparisonView01.snap.svg +223 -0
  44. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/renderComparisonView/__snapshots__/renderComparisonView02.snap.svg +205 -0
  45. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/renderComparisonView/__snapshots__/renderComparisonView03.snap.svg +184 -0
  46. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/renderComparisonView/renderComparisonView01.test.ts +29 -0
  47. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/renderComparisonView/renderComparisonView02.test.ts +27 -0
  48. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/renderComparisonView/renderComparisonView03.test.ts +39 -0
  49. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/MergedNetLabelObstacles.test.ts +14 -0
  50. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/OverlapAvoidanceStepSolver.test.ts +20 -0
  51. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/SingleOverlapSolver.test.ts +10 -0
  52. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/__snapshots__/MergedNetLabelObstacles.snap.svg +140 -0
  53. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/__snapshots__/OverlapAvoidanceStepSolver.snap.svg +216 -0
  54. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/__snapshots__/SingleOverlapSolver.snap.svg +125 -0
  55. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/balanceLShapes.ts +0 -207
  56. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/minimizeTurnsWithFilteredLabels.ts +0 -66
  57. /package/lib/solvers/{TraceLabelOverlapAvoidanceSolver → TraceCleanupSolver}/countTurns.ts +0 -0
  58. /package/lib/solvers/{TraceLabelOverlapAvoidanceSolver → TraceCleanupSolver}/tryConnectPoints.ts +0 -0
@@ -0,0 +1,144 @@
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 type { InputProblem } from "lib/types/InputProblem"
7
+ import { detectTraceLabelOverlap } from "../../detectTraceLabelOverlap"
8
+ import { SingleOverlapSolver } from "../SingleOverlapSolver/SingleOverlapSolver"
9
+
10
+ type Overlap = ReturnType<typeof detectTraceLabelOverlap>[0]
11
+
12
+ // Define a type for the input of the internal overlap solver to avoid conflicts
13
+ interface OverlapCollectionSolverInput {
14
+ inputProblem: InputProblem
15
+ traces: SolvedTracePath[]
16
+ netLabelPlacements: NetLabelPlacement[]
17
+ mergedLabelNetIdMap: Record<string, Set<string>>
18
+ }
19
+
20
+ /**
21
+ * This is an internal solver that manages the step-by-step process of avoiding
22
+ * multiple overlaps. It follows the pattern of SchematicTraceLinesSolver.
23
+ */
24
+ export class OverlapAvoidanceStepSolver extends BaseSolver {
25
+ inputProblem: InputProblem
26
+ netLabelPlacements: NetLabelPlacement[]
27
+ mergedLabelNetIdMap: Record<string, Set<string>>
28
+
29
+ allTraces: SolvedTracePath[]
30
+ modifiedTraces: SolvedTracePath[] = []
31
+
32
+ private detourCountByLabel: Record<string, number> = {}
33
+ private readonly PADDING_BUFFER = 0.1
34
+
35
+ public override activeSubSolver: SingleOverlapSolver | null = null
36
+ private overlapQueue: Overlap[] = []
37
+ private recentlyFailed: Set<string> = new Set()
38
+
39
+ constructor(solverInput: OverlapCollectionSolverInput) {
40
+ super()
41
+ this.inputProblem = solverInput.inputProblem
42
+ this.netLabelPlacements = solverInput.netLabelPlacements
43
+ this.mergedLabelNetIdMap = solverInput.mergedLabelNetIdMap
44
+ this.allTraces = [...solverInput.traces]
45
+ }
46
+
47
+ override _step() {
48
+ if (this.activeSubSolver) {
49
+ this.activeSubSolver.step()
50
+
51
+ if (this.activeSubSolver.solved) {
52
+ const solvedPath = this.activeSubSolver.solvedTracePath
53
+ if (solvedPath) {
54
+ const traceIndex = this.allTraces.findIndex(
55
+ (t) => t.mspPairId === this.activeSubSolver!.initialTrace.mspPairId,
56
+ )
57
+ if (traceIndex !== -1) {
58
+ this.allTraces[traceIndex].tracePath = solvedPath
59
+ this.modifiedTraces.push(this.allTraces[traceIndex])
60
+ }
61
+ }
62
+ this.activeSubSolver = null
63
+ this.recentlyFailed.clear()
64
+ } else if (this.activeSubSolver.failed) {
65
+ const overlapId = `${this.activeSubSolver.initialTrace.mspPairId}-${this.activeSubSolver.label.globalConnNetId}`
66
+ this.recentlyFailed.add(overlapId)
67
+ this.activeSubSolver = null
68
+ }
69
+ return
70
+ }
71
+
72
+ const overlaps = detectTraceLabelOverlap(
73
+ this.allTraces,
74
+ this.netLabelPlacements,
75
+ ).filter((o) => {
76
+ const originalNetIds = this.mergedLabelNetIdMap[o.label.globalConnNetId]
77
+ if (originalNetIds) {
78
+ return !originalNetIds.has(o.trace.globalConnNetId)
79
+ }
80
+ return o.trace.globalConnNetId !== o.label.globalConnNetId
81
+ })
82
+
83
+ if (overlaps.length === 0) {
84
+ this.solved = true
85
+ return
86
+ }
87
+
88
+ const nonFailedOverlaps = overlaps.filter((o) => {
89
+ const overlapId = `${o.trace.mspPairId}-${o.label.globalConnNetId}`
90
+ return !this.recentlyFailed.has(overlapId)
91
+ })
92
+
93
+ if (nonFailedOverlaps.length === 0) {
94
+ this.solved = true // No more progress can be made
95
+ return
96
+ }
97
+
98
+ this.overlapQueue = nonFailedOverlaps
99
+
100
+ const nextOverlap = this.overlapQueue.shift()
101
+
102
+ if (nextOverlap) {
103
+ const traceToFix = this.allTraces.find(
104
+ (t) => t.mspPairId === nextOverlap.trace.mspPairId,
105
+ )
106
+ if (traceToFix) {
107
+ const labelId = nextOverlap.label.globalConnNetId
108
+ const detourCount = this.detourCountByLabel[labelId] || 0
109
+ this.detourCountByLabel[labelId] = detourCount + 1
110
+
111
+ this.activeSubSolver = new SingleOverlapSolver({
112
+ trace: traceToFix,
113
+ label: nextOverlap.label,
114
+ problem: this.inputProblem,
115
+ paddingBuffer: this.PADDING_BUFFER,
116
+ detourCount,
117
+ })
118
+ }
119
+ }
120
+ }
121
+
122
+ getOutput() {
123
+ return {
124
+ allTraces: this.allTraces,
125
+ modifiedTraces: this.modifiedTraces,
126
+ }
127
+ }
128
+
129
+ override visualize(): GraphicsObject {
130
+ if (this.activeSubSolver) {
131
+ return this.activeSubSolver.visualize()
132
+ }
133
+ // When idle, show all the traces
134
+ const graphics = visualizeInputProblem(this.inputProblem)
135
+ if (!graphics.lines) graphics.lines = []
136
+ for (const trace of this.allTraces) {
137
+ graphics.lines!.push({
138
+ points: trace.tracePath,
139
+ strokeColor: "purple",
140
+ })
141
+ }
142
+ return graphics
143
+ }
144
+ }
@@ -0,0 +1,117 @@
1
+ import type { Point } from "@tscircuit/math-utils"
2
+ import { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver"
3
+ import type { NetLabelPlacement } from "../../../NetLabelPlacementSolver/NetLabelPlacementSolver"
4
+ import type { InputProblem } from "lib/types/InputProblem"
5
+ import type { GraphicsObject } from "graphics-debug"
6
+ import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
7
+ import { isPathCollidingWithObstacles } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions"
8
+ import { getObstacleRects } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/rect"
9
+ import { visualizeInputProblem } from "lib/solvers/SchematicTracePipelineSolver/visualizeInputProblem"
10
+ import { generateRerouteCandidates } from "../../rerouteCollidingTrace"
11
+ import { simplifyPath } from "lib/solvers/TraceCleanupSolver/simplifyPath"
12
+
13
+ interface SingleOverlapSolverInput {
14
+ trace: SolvedTracePath
15
+ label: NetLabelPlacement
16
+ problem: InputProblem
17
+ paddingBuffer: number
18
+ detourCount: number
19
+ }
20
+
21
+ /**
22
+ * This solver attempts to find a valid rerouting for a single trace that is
23
+ * overlapping with a net label. It tries various candidate paths until it
24
+ * finds one that does not introduce new collisions.
25
+ */
26
+ export class SingleOverlapSolver extends BaseSolver {
27
+ queuedCandidatePaths: Point[][]
28
+ solvedTracePath: Point[] | null = null
29
+ initialTrace: SolvedTracePath
30
+ problem: InputProblem
31
+ obstacles: ReturnType<typeof getObstacleRects>
32
+ label: NetLabelPlacement
33
+
34
+ constructor(solverInput: SingleOverlapSolverInput) {
35
+ super()
36
+ this.initialTrace = solverInput.trace
37
+ this.problem = solverInput.problem
38
+ this.label = solverInput.label
39
+
40
+ const candidates = generateRerouteCandidates({
41
+ ...solverInput,
42
+ })
43
+
44
+ const getPathLength = (pts: Point[]) => {
45
+ let len = 0
46
+ for (let i = 0; i < pts.length - 1; i++) {
47
+ const dx = pts[i + 1].x - pts[i].x
48
+ const dy = pts[i + 1].y - pts[i].y
49
+ len += Math.sqrt(dx * dx + dy * dy)
50
+ }
51
+ return len
52
+ }
53
+
54
+ this.queuedCandidatePaths = candidates.sort(
55
+ (a, b) => getPathLength(a) - getPathLength(b),
56
+ )
57
+ this.obstacles = getObstacleRects(this.problem)
58
+ }
59
+
60
+ override _step() {
61
+ if (this.queuedCandidatePaths.length === 0) {
62
+ this.failed = true
63
+ return
64
+ }
65
+
66
+ const nextCandidatePath = this.queuedCandidatePaths.shift()!
67
+ const simplifiedPath = simplifyPath(nextCandidatePath)
68
+
69
+ if (!isPathCollidingWithObstacles(simplifiedPath, this.obstacles)) {
70
+ this.solvedTracePath = simplifiedPath
71
+ this.solved = true
72
+ }
73
+ }
74
+
75
+ override visualize(): GraphicsObject {
76
+ const graphics = visualizeInputProblem(this.problem, {
77
+ chipAlpha: 0.1,
78
+ connectionAlpha: 0.1,
79
+ })
80
+
81
+ if (!graphics.lines) graphics.lines = []
82
+ if (!graphics.rects) graphics.rects = []
83
+
84
+ // Draw initial trace
85
+ graphics.lines.push({
86
+ points: this.initialTrace.tracePath,
87
+ strokeColor: "red",
88
+ strokeDash: "4 4",
89
+ })
90
+
91
+ // Draw label
92
+ graphics.rects.push({
93
+ center: this.label.center,
94
+ width: this.label.width,
95
+ height: this.label.height,
96
+ fill: "rgba(255, 0, 0, 0.2)",
97
+ })
98
+
99
+ // Draw next candidate
100
+ if (this.queuedCandidatePaths.length > 0) {
101
+ graphics.lines.push({
102
+ points: this.queuedCandidatePaths[0],
103
+ strokeColor: "orange",
104
+ })
105
+ }
106
+
107
+ // Draw solved path
108
+ if (this.solvedTracePath) {
109
+ graphics.lines.push({
110
+ points: this.solvedTracePath,
111
+ strokeColor: "green",
112
+ })
113
+ }
114
+
115
+ return graphics
116
+ }
117
+ }
@@ -0,0 +1,104 @@
1
+ import type { Point } from "graphics-debug"
2
+ import type { NetLabelPlacement } from "../NetLabelPlacementSolver/NetLabelPlacementSolver"
3
+ import { getRectBounds } from "../NetLabelPlacementSolver/SingleNetLabelPlacementSolver/geometry"
4
+ import type { SolvedTracePath } from "../SchematicTraceLinesSolver/SchematicTraceLinesSolver"
5
+ import {
6
+ segmentIntersectsRect,
7
+ isVertical,
8
+ } from "../SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions"
9
+
10
+ export const generateFourPointDetourCandidates = ({
11
+ initialTrace,
12
+ label,
13
+ labelBounds,
14
+ paddingBuffer,
15
+ detourCount,
16
+ }: {
17
+ initialTrace: SolvedTracePath
18
+ label: NetLabelPlacement
19
+ labelBounds: any
20
+ paddingBuffer: number
21
+ detourCount: number
22
+ }): Point[][] => {
23
+ let collidingSegIndex = -1
24
+ for (let i = 0; i < initialTrace.tracePath.length - 1; i++) {
25
+ if (
26
+ segmentIntersectsRect(
27
+ initialTrace.tracePath[i],
28
+ initialTrace.tracePath[i + 1],
29
+ labelBounds,
30
+ )
31
+ ) {
32
+ collidingSegIndex = i
33
+ break
34
+ }
35
+ }
36
+
37
+ if (collidingSegIndex === -1) return []
38
+
39
+ const pA = initialTrace.tracePath[collidingSegIndex]
40
+ const pB = initialTrace.tracePath[collidingSegIndex + 1]
41
+
42
+ if (!pA || !pB) return []
43
+
44
+ const candidateDetours: Point[][] = []
45
+ const paddedLabelBounds = getRectBounds(
46
+ label.center,
47
+ label.width,
48
+ label.height,
49
+ )
50
+
51
+ const effectivePadding = paddingBuffer + detourCount * paddingBuffer
52
+
53
+ if (isVertical(pA, pB)) {
54
+ const xCandidates = [
55
+ paddedLabelBounds.maxX + effectivePadding,
56
+ paddedLabelBounds.minX - effectivePadding,
57
+ ]
58
+ for (const newX of xCandidates) {
59
+ candidateDetours.push(
60
+ pB.y > pA.y
61
+ ? [
62
+ { x: pA.x, y: paddedLabelBounds.minY - effectivePadding },
63
+ { x: newX, y: paddedLabelBounds.minY - effectivePadding },
64
+ { x: newX, y: paddedLabelBounds.maxY + effectivePadding },
65
+ { x: pB.x, y: paddedLabelBounds.maxY + effectivePadding },
66
+ ]
67
+ : [
68
+ { x: pA.x, y: paddedLabelBounds.maxY + effectivePadding },
69
+ { x: newX, y: paddedLabelBounds.maxY + effectivePadding },
70
+ { x: newX, y: paddedLabelBounds.minY - effectivePadding },
71
+ { x: pB.x, y: paddedLabelBounds.minY - effectivePadding },
72
+ ],
73
+ )
74
+ }
75
+ } else {
76
+ const yCandidates = [
77
+ paddedLabelBounds.maxY + effectivePadding,
78
+ paddedLabelBounds.minY - effectivePadding,
79
+ ]
80
+ for (const newY of yCandidates) {
81
+ candidateDetours.push(
82
+ pB.x > pA.x
83
+ ? [
84
+ { x: paddedLabelBounds.minX - effectivePadding, y: pA.y },
85
+ { x: paddedLabelBounds.minX - effectivePadding, y: newY },
86
+ { x: paddedLabelBounds.maxX + effectivePadding, y: newY },
87
+ { x: paddedLabelBounds.maxX + effectivePadding, y: pB.y },
88
+ ]
89
+ : [
90
+ { x: paddedLabelBounds.maxX + effectivePadding, y: pA.y },
91
+ { x: paddedLabelBounds.maxX + effectivePadding, y: newY },
92
+ { x: paddedLabelBounds.minX - effectivePadding, y: newY },
93
+ { x: paddedLabelBounds.minX - effectivePadding, y: pB.y },
94
+ ],
95
+ )
96
+ }
97
+ }
98
+
99
+ return candidateDetours.map((detourPoints) => [
100
+ ...initialTrace.tracePath.slice(0, collidingSegIndex + 1),
101
+ ...detourPoints,
102
+ ...initialTrace.tracePath.slice(collidingSegIndex + 1),
103
+ ])
104
+ }
@@ -1,32 +1,26 @@
1
1
  import type { Point } from "@tscircuit/math-utils"
2
- import { getRectBounds } from "../NetLabelPlacementSolver/SingleNetLabelPlacementSolver/geometry"
3
2
  import type { SolvedTracePath } from "../SchematicTraceLinesSolver/SchematicTraceLinesSolver"
4
- import {
5
- isPathCollidingWithObstacles,
6
- isVertical,
7
- segmentIntersectsRect,
8
- } from "../SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions"
9
- import type { NetLabelPlacement } from "../NetLabelPlacementSolver/NetLabelPlacementSolver"
10
- import { simplifyPath } from "./simplifyPath"
11
3
 
12
- export const trySnipAndReconnect = ({
4
+ export const generateSnipAndReconnectCandidates = ({
13
5
  initialTrace,
14
6
  firstInsideIndex,
15
7
  lastInsideIndex,
16
8
  labelBounds,
17
- obstacles,
9
+ paddingBuffer,
10
+ detourCount,
18
11
  }: {
19
12
  initialTrace: SolvedTracePath
20
13
  firstInsideIndex: number
21
14
  lastInsideIndex: number
22
15
  labelBounds: any
23
- obstacles: any[]
24
- }): SolvedTracePath | null => {
16
+ paddingBuffer: number
17
+ detourCount: number
18
+ }): Point[][] => {
25
19
  if (
26
20
  firstInsideIndex <= 0 ||
27
21
  lastInsideIndex >= initialTrace.tracePath.length - 1
28
22
  ) {
29
- return null
23
+ return []
30
24
  }
31
25
 
32
26
  const entryPoint = initialTrace.tracePath[firstInsideIndex - 1]
@@ -35,32 +29,19 @@ export const trySnipAndReconnect = ({
35
29
  const pathToEntry = initialTrace.tracePath.slice(0, firstInsideIndex)
36
30
  const pathFromExit = initialTrace.tracePath.slice(lastInsideIndex + 1)
37
31
 
38
- const candidateDetours: Point[][] = []
32
+ const allCandidateDetours: Point[][] = []
39
33
 
34
+ // Candidate type 1: simple elbow
40
35
  if (entryPoint.x !== exitPoint.x && entryPoint.y !== exitPoint.y) {
41
- candidateDetours.push([{ x: exitPoint.x, y: entryPoint.y }])
42
- candidateDetours.push([{ x: entryPoint.x, y: exitPoint.y }])
36
+ allCandidateDetours.push([{ x: exitPoint.x, y: entryPoint.y }])
37
+ allCandidateDetours.push([{ x: entryPoint.x, y: exitPoint.y }])
43
38
  } else if (entryPoint.x === exitPoint.x || entryPoint.y === exitPoint.y) {
44
- const newPath = [...pathToEntry, ...pathFromExit]
45
- const simplified = simplifyPath(newPath)
46
- if (!isPathCollidingWithObstacles(simplified, obstacles)) {
47
- return { ...initialTrace, tracePath: simplified }
48
- }
39
+ // Candidate type 2: direct connection (if points are aligned)
40
+ allCandidateDetours.push([]) // No detour points needed
49
41
  }
50
42
 
51
- for (let i = 0; i < candidateDetours.length; i++) {
52
- const detour = candidateDetours[i]
53
- const newFullPath = [...pathToEntry, ...detour, ...pathFromExit]
54
- const simplified = simplifyPath(newFullPath)
55
-
56
- if (!isPathCollidingWithObstacles(simplified, obstacles)) {
57
- return { ...initialTrace, tracePath: simplified }
58
- }
59
- }
60
-
61
- candidateDetours.length = 0
62
-
63
- const buffer = 0.1
43
+ // Candidate type 3: routing around the label bounds
44
+ const buffer = paddingBuffer + detourCount * paddingBuffer
64
45
  const leftX = labelBounds.minX - buffer
65
46
  const rightX = labelBounds.maxX + buffer
66
47
  const topY = labelBounds.maxY + buffer
@@ -71,7 +52,7 @@ export const trySnipAndReconnect = ({
71
52
  entryPoint.x < labelBounds.maxX &&
72
53
  exitPoint.x < labelBounds.maxX
73
54
  ) {
74
- candidateDetours.push([
55
+ allCandidateDetours.push([
75
56
  { x: leftX, y: entryPoint.y },
76
57
  { x: leftX, y: exitPoint.y },
77
58
  ])
@@ -82,7 +63,7 @@ export const trySnipAndReconnect = ({
82
63
  entryPoint.x > labelBounds.minX &&
83
64
  exitPoint.x > labelBounds.minX
84
65
  ) {
85
- candidateDetours.push([
66
+ allCandidateDetours.push([
86
67
  { x: rightX, y: entryPoint.y },
87
68
  { x: rightX, y: exitPoint.y },
88
69
  ])
@@ -93,7 +74,7 @@ export const trySnipAndReconnect = ({
93
74
  entryPoint.y > labelBounds.minY &&
94
75
  exitPoint.y > labelBounds.minY
95
76
  ) {
96
- candidateDetours.push([
77
+ allCandidateDetours.push([
97
78
  { x: entryPoint.x, y: topY },
98
79
  { x: exitPoint.x, y: topY },
99
80
  ])
@@ -104,126 +85,15 @@ export const trySnipAndReconnect = ({
104
85
  entryPoint.y < labelBounds.maxY &&
105
86
  exitPoint.y < labelBounds.maxY
106
87
  ) {
107
- candidateDetours.push([
88
+ allCandidateDetours.push([
108
89
  { x: entryPoint.x, y: bottomY },
109
90
  { x: exitPoint.x, y: bottomY },
110
91
  ])
111
92
  }
112
93
 
113
- for (let i = 0; i < candidateDetours.length; i++) {
114
- const detour = candidateDetours[i]
115
- const newFullPath = [...pathToEntry, ...detour, ...pathFromExit]
116
- const simplified = simplifyPath(newFullPath)
117
-
118
- if (!isPathCollidingWithObstacles(simplified, obstacles)) {
119
- return { ...initialTrace, tracePath: simplified }
120
- }
121
- }
122
-
123
- return null
124
- }
125
-
126
- export const tryFourPointDetour = ({
127
- initialTrace,
128
- label,
129
- labelBounds,
130
- obstacles,
131
- paddingBuffer,
132
- detourCount,
133
- }: {
134
- initialTrace: SolvedTracePath
135
- label: NetLabelPlacement
136
- labelBounds: any
137
- obstacles: any[]
138
- paddingBuffer: number
139
- detourCount: number
140
- }): SolvedTracePath | null => {
141
- let collidingSegIndex = -1
142
- for (let i = 0; i < initialTrace.tracePath.length - 1; i++) {
143
- if (
144
- segmentIntersectsRect(
145
- initialTrace.tracePath[i],
146
- initialTrace.tracePath[i + 1],
147
- labelBounds,
148
- )
149
- ) {
150
- collidingSegIndex = i
151
- break
152
- }
153
- }
154
-
155
- if (collidingSegIndex === -1) return initialTrace
156
-
157
- const pA = initialTrace.tracePath[collidingSegIndex]
158
- const pB = initialTrace.tracePath[collidingSegIndex + 1]
159
-
160
- if (!pA || !pB) return null
161
-
162
- const candidateDetours: Point[][] = []
163
- const paddedLabelBounds = getRectBounds(
164
- label.center,
165
- label.width,
166
- label.height,
167
- )
168
-
169
- const effectivePadding = paddingBuffer + detourCount * paddingBuffer
170
-
171
- if (isVertical(pA, pB)) {
172
- const xCandidates = [
173
- paddedLabelBounds.maxX + effectivePadding,
174
- paddedLabelBounds.minX - effectivePadding,
175
- ]
176
- for (const newX of xCandidates) {
177
- candidateDetours.push(
178
- pB.y > pA.y
179
- ? [
180
- { x: pA.x, y: paddedLabelBounds.minY - effectivePadding },
181
- { x: newX, y: paddedLabelBounds.minY - effectivePadding },
182
- { x: newX, y: paddedLabelBounds.maxY + effectivePadding },
183
- { x: pB.x, y: paddedLabelBounds.maxY + effectivePadding },
184
- ]
185
- : [
186
- { x: pA.x, y: paddedLabelBounds.maxY + effectivePadding },
187
- { x: newX, y: paddedLabelBounds.maxY + effectivePadding },
188
- { x: newX, y: paddedLabelBounds.minY - effectivePadding },
189
- { x: pB.x, y: paddedLabelBounds.minY - effectivePadding },
190
- ],
191
- )
192
- }
193
- } else {
194
- const yCandidates = [
195
- paddedLabelBounds.maxY + effectivePadding,
196
- paddedLabelBounds.minY - effectivePadding,
197
- ]
198
- for (const newY of yCandidates) {
199
- candidateDetours.push(
200
- pB.x > pA.x
201
- ? [
202
- { x: paddedLabelBounds.minX - effectivePadding, y: pA.y },
203
- { x: paddedLabelBounds.minX - effectivePadding, y: newY },
204
- { x: paddedLabelBounds.maxX + effectivePadding, y: newY },
205
- { x: paddedLabelBounds.maxX + effectivePadding, y: pB.y },
206
- ]
207
- : [
208
- { x: paddedLabelBounds.maxX + effectivePadding, y: pA.y },
209
- { x: paddedLabelBounds.maxX + effectivePadding, y: newY },
210
- { x: paddedLabelBounds.minX - effectivePadding, y: newY },
211
- { x: paddedLabelBounds.minX - effectivePadding, y: pB.y },
212
- ],
213
- )
214
- }
215
- }
216
-
217
- for (const detourPoints of candidateDetours) {
218
- const finalPath = [
219
- ...initialTrace.tracePath.slice(0, collidingSegIndex + 1),
220
- ...detourPoints,
221
- ...initialTrace.tracePath.slice(collidingSegIndex + 1),
222
- ]
223
- const simplifiedFinalPath = simplifyPath(finalPath)
224
- if (!isPathCollidingWithObstacles(simplifiedFinalPath, obstacles)) {
225
- return { ...initialTrace, tracePath: simplifiedFinalPath }
226
- }
227
- }
228
- return null
94
+ return allCandidateDetours.map((detour) => [
95
+ ...pathToEntry,
96
+ ...detour,
97
+ ...pathFromExit,
98
+ ])
229
99
  }
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.41",
4
+ "version": "0.0.43",
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/example26.json"
3
+
4
+ export default () => <PipelineDebugger inputProblem={inputProblem as any} />