@tscircuit/schematic-trace-solver 0.0.40 → 0.0.42

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 (57) hide show
  1. package/dist/index.d.ts +155 -9
  2. package/dist/index.js +998 -479
  3. package/lib/solvers/LongDistancePairSolver/LongDistancePairSolver.ts +238 -0
  4. package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +61 -26
  5. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/TraceLabelOverlapAvoidanceSolver.ts +94 -192
  6. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/rerouteCollidingTrace.ts +13 -24
  7. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/LabelMergingSolver/LabelMergingSolver.ts +205 -0
  8. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/OverlapAvoidanceStepSolver/OverlapAvoidanceStepSolver.ts +144 -0
  9. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/SingleOverlapSolver/SingleOverlapSolver.ts +117 -0
  10. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/TraceCleanupSolver/TraceCleanupSolver.ts +132 -0
  11. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/{balanceLShapes.ts → sub-solvers/TraceCleanupSolver/balanceLShapes.ts} +12 -10
  12. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/{hasCollisions.ts → sub-solvers/TraceCleanupSolver/hasCollisions.ts} +1 -1
  13. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/{hasCollisionsWithLabels.ts → sub-solvers/TraceCleanupSolver/hasCollisionsWithLabels.ts} +1 -1
  14. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/{minimizeTurnsWithFilteredLabels.ts → sub-solvers/TraceCleanupSolver/minimizeTurnsWithFilteredLabels.ts} +6 -6
  15. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/{simplifyPath.ts → sub-solvers/TraceCleanupSolver/simplifyPath.ts} +1 -1
  16. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/{turnMinimization.ts → sub-solvers/TraceCleanupSolver/turnMinimization.ts} +0 -2
  17. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/tryFourPointDetour.ts +104 -0
  18. package/lib/solvers/TraceLabelOverlapAvoidanceSolver/trySnipAndReconnect.ts +24 -154
  19. package/lib/utils/does-trace-overlap-with-existing-traces.ts +33 -0
  20. package/package.json +1 -1
  21. package/site/examples/example25.page.tsx +4 -0
  22. package/tests/assets/1.input.json +379 -0
  23. package/tests/assets/2.input.json +330 -0
  24. package/tests/assets/3.input.json +388 -0
  25. package/tests/assets/MergedNetLabelObstacles.test.input.json +154 -0
  26. package/tests/assets/OverlapAvoidanceStepSolver.test.input.json +682 -0
  27. package/tests/assets/SingleOverlapSolver.test.input.json +170 -0
  28. package/tests/assets/TraceCleanupSolver.test.input.json +216 -0
  29. package/tests/assets/TraceLabelOverlapAvoidanceSolver.test.input.json +424 -0
  30. package/tests/assets/example25.json +96 -0
  31. package/tests/examples/__snapshots__/example03.snap.svg +67 -76
  32. package/tests/examples/__snapshots__/example10.snap.svg +24 -30
  33. package/tests/examples/__snapshots__/example13.snap.svg +81 -84
  34. package/tests/examples/__snapshots__/example15.snap.svg +5 -8
  35. package/tests/examples/__snapshots__/example21.snap.svg +68 -74
  36. package/tests/examples/__snapshots__/example25.snap.svg +3 -3
  37. package/tests/examples/__snapshots__/example26.snap.svg +3 -3
  38. package/tests/examples/__snapshots__/example28.snap.svg +273 -0
  39. package/tests/examples/example28.test.ts +13 -0
  40. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/TraceLabelOverlapAvoidanceSolver.test.ts +18 -0
  41. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/__snapshots__/TraceLabelOverlapAvoidanceSolver.snap.svg +243 -0
  42. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/renderComparisonView/__snapshots__/renderComparisonView01.snap.svg +223 -0
  43. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/renderComparisonView/__snapshots__/renderComparisonView02.snap.svg +205 -0
  44. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/renderComparisonView/__snapshots__/renderComparisonView03.snap.svg +184 -0
  45. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/renderComparisonView/renderComparisonView01.test.ts +29 -0
  46. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/renderComparisonView/renderComparisonView02.test.ts +27 -0
  47. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/renderComparisonView/renderComparisonView03.test.ts +39 -0
  48. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/MergedNetLabelObstacles.test.ts +14 -0
  49. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/OverlapAvoidanceStepSolver.test.ts +20 -0
  50. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/SingleOverlapSolver.test.ts +10 -0
  51. package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/TraceCleanupSolver.test.ts +19 -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/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/__snapshots__/TraceCleanupSolver.snap.svg +119 -0
  56. /package/lib/solvers/TraceLabelOverlapAvoidanceSolver/{countTurns.ts → sub-solvers/TraceCleanupSolver/countTurns.ts} +0 -0
  57. /package/lib/solvers/TraceLabelOverlapAvoidanceSolver/{tryConnectPoints.ts → sub-solvers/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 "../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,132 @@
1
+ import type { InputProblem } from "lib/types/InputProblem"
2
+ import type { GraphicsObject, Line } from "graphics-debug"
3
+ import type { NetLabelPlacement } from "../../../NetLabelPlacementSolver/NetLabelPlacementSolver"
4
+ import { minimizeTurnsWithFilteredLabels } from "./minimizeTurnsWithFilteredLabels"
5
+ import { balanceLShapes } from "./balanceLShapes"
6
+ import { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver"
7
+ import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
8
+ import { visualizeInputProblem } from "lib/solvers/SchematicTracePipelineSolver/visualizeInputProblem"
9
+
10
+ interface TraceCleanupSolverInput {
11
+ inputProblem: InputProblem
12
+ allTraces: SolvedTracePath[]
13
+ targetTraceIds: Set<string>
14
+ allLabelPlacements: NetLabelPlacement[]
15
+ mergedLabelNetIdMap: Record<string, Set<string>>
16
+ paddingBuffer: number
17
+ }
18
+
19
+ /**
20
+ * Cleans up traces by minimizing turns and balancing L-shapes to improve
21
+ * the overall aesthetics and readability of the schematic.
22
+ */
23
+ export class TraceCleanupSolver extends BaseSolver {
24
+ private input: TraceCleanupSolverInput
25
+ private outputTraces: SolvedTracePath[]
26
+ private pipelineStepIndex = 0
27
+ private tracesToProcess: SolvedTracePath[]
28
+ private minimizedTraces: SolvedTracePath[] | null = null
29
+ private balancedTraces: SolvedTracePath[] | null = null
30
+
31
+ constructor(solverInput: TraceCleanupSolverInput) {
32
+ super()
33
+ this.input = solverInput
34
+ this.outputTraces = [...solverInput.allTraces]
35
+ this.tracesToProcess = this.outputTraces.filter((t) =>
36
+ this.input.targetTraceIds.has(t.mspPairId),
37
+ )
38
+ }
39
+
40
+ override _step() {
41
+ const {
42
+ targetTraceIds,
43
+ inputProblem,
44
+ allLabelPlacements,
45
+ mergedLabelNetIdMap,
46
+ paddingBuffer,
47
+ } = this.input
48
+
49
+ if (targetTraceIds.size === 0) {
50
+ this.solved = true
51
+ return
52
+ }
53
+
54
+ switch (this.pipelineStepIndex) {
55
+ case 0: {
56
+ // Step 0: Minimize turns
57
+ const minimizedTracesResult = minimizeTurnsWithFilteredLabels({
58
+ traces: this.tracesToProcess,
59
+ inputProblem,
60
+ allLabelPlacements,
61
+ mergedLabelNetIdMap,
62
+ paddingBuffer,
63
+ })
64
+
65
+ this.minimizedTraces = minimizedTracesResult ?? this.tracesToProcess
66
+
67
+ const tracesMap = new Map(
68
+ this.input.allTraces.map((t) => [t.mspPairId, t]),
69
+ )
70
+ for (const trace of this.minimizedTraces) {
71
+ tracesMap.set(trace.mspPairId, trace)
72
+ }
73
+ this.outputTraces = Array.from(tracesMap.values())
74
+ this.pipelineStepIndex++
75
+ break
76
+ }
77
+ case 1: {
78
+ // Step 1: Balance L-shapes
79
+ const balancedTracesResult = balanceLShapes({
80
+ traces: this.minimizedTraces!,
81
+ inputProblem,
82
+ allLabelPlacements,
83
+ })
84
+
85
+ this.balancedTraces = balancedTracesResult ?? this.minimizedTraces
86
+
87
+ const tracesMap = new Map(
88
+ this.input.allTraces.map((t) => [t.mspPairId, t]),
89
+ )
90
+ for (const trace of this.balancedTraces!) {
91
+ tracesMap.set(trace.mspPairId, trace)
92
+ }
93
+ this.outputTraces = Array.from(tracesMap.values())
94
+ this.pipelineStepIndex++
95
+ break
96
+ }
97
+ case 2: {
98
+ // Step 2: Done
99
+ this.solved = true
100
+ break
101
+ }
102
+ }
103
+ }
104
+
105
+ getOutput() {
106
+ return {
107
+ traces: this.outputTraces,
108
+ }
109
+ }
110
+
111
+ override visualize(): GraphicsObject {
112
+ const graphics = visualizeInputProblem(this.input.inputProblem, {
113
+ chipAlpha: 0.1,
114
+ connectionAlpha: 0.1,
115
+ })
116
+
117
+ if (!graphics.lines) graphics.lines = []
118
+ if (!graphics.points) graphics.points = []
119
+ if (!graphics.rects) graphics.rects = []
120
+ if (!graphics.circles) graphics.circles = []
121
+ if (!graphics.texts) graphics.texts = []
122
+
123
+ for (const trace of this.outputTraces) {
124
+ const line: Line = {
125
+ points: trace.tracePath.map((p) => ({ x: p.x, y: p.y })),
126
+ strokeColor: "blue",
127
+ }
128
+ graphics.lines!.push(line)
129
+ }
130
+ return graphics
131
+ }
132
+ }
@@ -1,18 +1,18 @@
1
1
  import type { Point } from "graphics-debug"
2
2
  import type { InputProblem } from "lib/types/InputProblem"
3
- import type { NetLabelPlacement } from "../NetLabelPlacementSolver/NetLabelPlacementSolver"
4
- import { getObstacleRects } from "../SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/rect"
5
- import type { SolvedTracePath } from "../SchematicTraceLinesSolver/SchematicTraceLinesSolver"
6
- import { segmentIntersectsRect } from "../SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions"
3
+ import type { NetLabelPlacement } from "../../../NetLabelPlacementSolver/NetLabelPlacementSolver"
7
4
  import { simplifyPath } from "./simplifyPath"
5
+ import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
6
+ import { segmentIntersectsRect } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions"
7
+ import { getObstacleRects } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/rect"
8
8
 
9
9
  export const balanceLShapes = ({
10
10
  traces,
11
- problem,
11
+ inputProblem,
12
12
  allLabelPlacements,
13
13
  }: {
14
14
  traces: SolvedTracePath[]
15
- problem: InputProblem
15
+ inputProblem: InputProblem
16
16
  allLabelPlacements: NetLabelPlacement[]
17
17
  }): SolvedTracePath[] | null => {
18
18
  const TOLERANCE = 1e-5
@@ -46,7 +46,7 @@ export const balanceLShapes = ({
46
46
  }
47
47
  }
48
48
 
49
- const obstacles = getObstacleRects(problem).map((obs) => ({
49
+ const obstacles = getObstacleRects(inputProblem).map((obs) => ({
50
50
  ...obs,
51
51
  minX: obs.minX + TOLERANCE,
52
52
  maxX: obs.maxX - TOLERANCE,
@@ -81,7 +81,7 @@ export const balanceLShapes = ({
81
81
  }
82
82
 
83
83
  const newTraces = traces.map((trace) => {
84
- let newPath = [...trace.tracePath]
84
+ const newPath = [...trace.tracePath]
85
85
 
86
86
  if (newPath.length < 4) {
87
87
  return { ...trace }
@@ -94,7 +94,8 @@ export const balanceLShapes = ({
94
94
 
95
95
  if (newPath.length === 4) {
96
96
  const [p0, p1, p2, p3] = newPath
97
- let p1New: Point, p2New: Point
97
+ let p1New: Point
98
+ let p2New: Point
98
99
 
99
100
  const isHVHShape = p0.y === p1.y && p1.x === p2.x && p2.y === p3.y
100
101
 
@@ -154,7 +155,8 @@ export const balanceLShapes = ({
154
155
  continue
155
156
  }
156
157
 
157
- let p2New: Point, p3New: Point
158
+ let p2New: Point
159
+ let p3New: Point
158
160
  const len1Original = isHVHZShape
159
161
  ? Math.abs(p1.x - p2.x)
160
162
  : Math.abs(p1.y - p2.y)
@@ -1,5 +1,5 @@
1
1
  import type { Point } from "graphics-debug"
2
- import { segmentIntersectsRect } from "../SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions"
2
+ import { segmentIntersectsRect } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions"
3
3
 
4
4
  export const hasCollisions = (
5
5
  pathSegments: Point[],
@@ -1,5 +1,5 @@
1
1
  import type { Point } from "@tscircuit/math-utils"
2
- import { segmentIntersectsRect } from "../SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions"
2
+ import { segmentIntersectsRect } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions"
3
3
 
4
4
  export const hasCollisionsWithLabels = (
5
5
  pathSegments: Point[],
@@ -1,24 +1,24 @@
1
1
  import type { InputProblem } from "lib/types/InputProblem"
2
- import type { NetLabelPlacement } from "../NetLabelPlacementSolver/NetLabelPlacementSolver"
3
- import type { SolvedTracePath } from "../SchematicTraceLinesSolver/SchematicTraceLinesSolver"
4
- import { getObstacleRects } from "../SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/rect"
2
+ import type { NetLabelPlacement } from "../../../NetLabelPlacementSolver/NetLabelPlacementSolver"
5
3
  import { minimizeTurns } from "./turnMinimization"
4
+ import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
5
+ import { getObstacleRects } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/rect"
6
6
 
7
7
  export const minimizeTurnsWithFilteredLabels = ({
8
8
  traces,
9
- problem,
9
+ inputProblem,
10
10
  allLabelPlacements,
11
11
  mergedLabelNetIdMap,
12
12
  paddingBuffer,
13
13
  }: {
14
14
  traces: SolvedTracePath[]
15
- problem: InputProblem
15
+ inputProblem: InputProblem
16
16
  allLabelPlacements: NetLabelPlacement[]
17
17
  mergedLabelNetIdMap: Record<string, Set<string>>
18
18
  paddingBuffer: number
19
19
  }): SolvedTracePath[] | null => {
20
20
  let changesMade = false
21
- const obstacles = getObstacleRects(problem)
21
+ const obstacles = getObstacleRects(inputProblem)
22
22
 
23
23
  const newTraces = traces.map((trace) => {
24
24
  const originalPath = trace.tracePath
@@ -2,7 +2,7 @@ import type { Point } from "graphics-debug"
2
2
  import {
3
3
  isHorizontal,
4
4
  isVertical,
5
- } from "../SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions"
5
+ } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions"
6
6
 
7
7
  export const simplifyPath = (path: Point[]): Point[] => {
8
8
  if (path.length < 3) return path
@@ -105,8 +105,6 @@ export const minimizeTurns = ({
105
105
 
106
106
  if (!collidesWithObstacles && !collidesWithLabels) {
107
107
  const newTurns = countTurns(testPath)
108
- const turnsRemoved = stairEndIdx - startIdx - 1
109
-
110
108
  optimizedPath = testPath
111
109
  currentTurns = newTurns
112
110
  improved = true
@@ -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
+ }