@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
@@ -7,16 +7,19 @@ import type { GraphicsObject } from "graphics-debug"
7
7
  import { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver"
8
8
  import type { InputProblem } from "lib/types/InputProblem"
9
9
  import { MspConnectionPairSolver } from "../MspConnectionPairSolver/MspConnectionPairSolver"
10
- import { SchematicTraceLinesSolver } from "../SchematicTraceLinesSolver/SchematicTraceLinesSolver"
10
+ import {
11
+ SchematicTraceLinesSolver,
12
+ type SolvedTracePath,
13
+ } from "../SchematicTraceLinesSolver/SchematicTraceLinesSolver"
11
14
  import { TraceOverlapShiftSolver } from "../TraceOverlapShiftSolver/TraceOverlapShiftSolver"
12
15
  import { NetLabelPlacementSolver } from "../NetLabelPlacementSolver/NetLabelPlacementSolver"
13
16
  import { visualizeInputProblem } from "./visualizeInputProblem"
14
- import { GuidelinesSolver } from "../GuidelinesSolver/GuidelinesSolver"
15
17
  import { TraceLabelOverlapAvoidanceSolver } from "../TraceLabelOverlapAvoidanceSolver/TraceLabelOverlapAvoidanceSolver"
16
- import { getInputChipBounds } from "../GuidelinesSolver/getInputChipBounds"
17
18
  import { correctPinsInsideChips } from "./correctPinsInsideChip"
18
19
  import { expandChipsToFitPins } from "./expandChipsToFitPins"
19
20
  import { LongDistancePairSolver } from "../LongDistancePairSolver/LongDistancePairSolver"
21
+ import { MergedNetLabelObstacleSolver } from "../TraceLabelOverlapAvoidanceSolver/sub-solvers/LabelMergingSolver/LabelMergingSolver"
22
+ import { TraceCleanupSolver } from "../TraceCleanupSolver/TraceCleanupSolver"
20
23
 
21
24
  type PipelineStep<T extends new (...args: any[]) => BaseSolver> = {
22
25
  solverName: string
@@ -63,7 +66,9 @@ export class SchematicTracePipelineSolver extends BaseSolver {
63
66
  longDistancePairSolver?: LongDistancePairSolver
64
67
  traceOverlapShiftSolver?: TraceOverlapShiftSolver
65
68
  netLabelPlacementSolver?: NetLabelPlacementSolver
69
+ labelMergingSolver?: MergedNetLabelObstacleSolver
66
70
  traceLabelOverlapAvoidanceSolver?: TraceLabelOverlapAvoidanceSolver
71
+ traceCleanupSolver?: TraceCleanupSolver
67
72
 
68
73
  startTimeOfPhase: Record<string, number>
69
74
  endTimeOfPhase: Record<string, number>
@@ -182,18 +187,42 @@ export class SchematicTracePipelineSolver extends BaseSolver {
182
187
  },
183
188
  ]
184
189
  },
185
- {
186
- onSolved: (instance) => {
187
- if (
188
- instance.traceLabelOverlapAvoidanceSolver &&
189
- instance.netLabelPlacementSolver
190
- ) {
191
- const { netLabelPlacements } =
192
- instance.traceLabelOverlapAvoidanceSolver.getOutput()
193
- instance.netLabelPlacementSolver.netLabelPlacements =
194
- netLabelPlacements
195
- }
190
+ ),
191
+ definePipelineStep("traceCleanupSolver", TraceCleanupSolver, (instance) => {
192
+ const prevSolverOutput =
193
+ instance.traceLabelOverlapAvoidanceSolver!.getOutput()
194
+ const traces = prevSolverOutput.traces
195
+
196
+ const labelMergingOutput =
197
+ instance.traceLabelOverlapAvoidanceSolver!.labelMergingSolver!.getOutput()
198
+
199
+ return [
200
+ {
201
+ inputProblem: instance.inputProblem,
202
+ allTraces: traces,
203
+ targetTraceIds: new Set(traces.map((t) => t.mspPairId)),
204
+ allLabelPlacements: labelMergingOutput.netLabelPlacements,
205
+ mergedLabelNetIdMap: labelMergingOutput.mergedLabelNetIdMap,
206
+ paddingBuffer: 0.1,
196
207
  },
208
+ ]
209
+ }),
210
+ definePipelineStep(
211
+ "netLabelPlacementSolver",
212
+ NetLabelPlacementSolver,
213
+ (instance) => {
214
+ const traces =
215
+ instance.traceCleanupSolver?.getOutput().traces ??
216
+ instance.traceLabelOverlapAvoidanceSolver!.getOutput().traces
217
+
218
+ return [
219
+ {
220
+ inputProblem: instance.inputProblem,
221
+ inputTraceMap: Object.fromEntries(
222
+ traces.map((trace: SolvedTracePath) => [trace.mspPairId, trace]),
223
+ ),
224
+ },
225
+ ]
197
226
  },
198
227
  ),
199
228
  ]
@@ -303,16 +332,19 @@ export class SchematicTracePipelineSolver extends BaseSolver {
303
332
  }) as GraphicsObject[]),
304
333
  ]
305
334
 
306
- if (visualizations.length === 1) return visualizations[0]!
335
+ if (visualizations.length === 1) {
336
+ return visualizations[0]!
337
+ }
307
338
 
308
339
  // Simple combination of visualizations
309
- return {
340
+ const finalGraphics = {
310
341
  points: visualizations.flatMap((v) => v.points || []),
311
342
  rects: visualizations.flatMap((v) => v.rects || []),
312
343
  lines: visualizations.flatMap((v) => v.lines || []),
313
344
  circles: visualizations.flatMap((v) => v.circles || []),
314
345
  texts: visualizations.flatMap((v) => v.texts || []),
315
346
  }
347
+ return finalGraphics
316
348
  }
317
349
 
318
350
  /**
@@ -0,0 +1,133 @@
1
+ import type { InputProblem } from "lib/types/InputProblem"
2
+ import type { GraphicsObject, Line } from "graphics-debug"
3
+ import { minimizeTurnsWithFilteredLabels } from "./minimizeTurnsWithFilteredLabels"
4
+ import { balanceZShapes } from "./balanceZShapes"
5
+ import { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver"
6
+ import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
7
+ import { visualizeInputProblem } from "lib/solvers/SchematicTracePipelineSolver/visualizeInputProblem"
8
+ import type { NetLabelPlacement } from "../NetLabelPlacementSolver/NetLabelPlacementSolver"
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
+ type PipelineStep = "minimizing_turns" | "balancing_l_shapes"
20
+
21
+ /**
22
+ * Cleans up traces by minimizing turns and balancing L-shapes to improve
23
+ * the overall aesthetics and readability of the schematic.
24
+ */
25
+ export class TraceCleanupSolver extends BaseSolver {
26
+ private input: TraceCleanupSolverInput
27
+ private outputTraces: SolvedTracePath[]
28
+ private traceIdQueue: string[]
29
+ private tracesMap: Map<string, SolvedTracePath>
30
+ private pipelineStep: PipelineStep = "minimizing_turns"
31
+ private activeTraceId: string | null = null // New property
32
+
33
+ constructor(solverInput: TraceCleanupSolverInput) {
34
+ super()
35
+ this.input = solverInput
36
+ this.outputTraces = [...solverInput.allTraces]
37
+ this.tracesMap = new Map(this.outputTraces.map((t) => [t.mspPairId, t]))
38
+ this.traceIdQueue = Array.from(solverInput.targetTraceIds)
39
+ }
40
+
41
+ override _step() {
42
+ if (
43
+ this.pipelineStep === "minimizing_turns" &&
44
+ this.traceIdQueue.length === 0
45
+ ) {
46
+ this.pipelineStep = "balancing_l_shapes"
47
+ this.traceIdQueue = Array.from(this.input.targetTraceIds)
48
+ }
49
+
50
+ if (
51
+ this.pipelineStep === "balancing_l_shapes" &&
52
+ this.traceIdQueue.length === 0
53
+ ) {
54
+ this.solved = true
55
+ return
56
+ }
57
+
58
+ const targetMspConnectionPairId = this.traceIdQueue.shift()!
59
+ this.activeTraceId = targetMspConnectionPairId // Set active trace ID
60
+ const originalTrace = this.tracesMap.get(targetMspConnectionPairId)!
61
+
62
+ const { tracePath } = originalTrace
63
+
64
+ // Skip if the trace is a perfect rectangle
65
+ const is4PointRectangle = (path: typeof tracePath): boolean => {
66
+ if (path.length !== 4) return false
67
+ const [p0, p1, p2, p3] = path
68
+ // H-V-H "C" shape
69
+ const isHVHC =
70
+ p0.y === p1.y && p1.x === p2.x && p2.y === p3.y && p0.x === p3.x
71
+ // V-H-V "C" shape
72
+ const isVHVC =
73
+ p0.x === p1.x && p1.y === p2.y && p2.x === p3.x && p0.y === p3.y
74
+ return isHVHC || isVHVC
75
+ }
76
+
77
+ if (is4PointRectangle(tracePath)) {
78
+ return
79
+ }
80
+
81
+ const allTraces = Array.from(this.tracesMap.values())
82
+
83
+ let updatedTrace: SolvedTracePath
84
+
85
+ if (this.pipelineStep === "minimizing_turns") {
86
+ updatedTrace = minimizeTurnsWithFilteredLabels({
87
+ ...this.input,
88
+ targetMspConnectionPairId,
89
+ traces: allTraces,
90
+ })
91
+ } else {
92
+ updatedTrace = balanceZShapes({
93
+ ...this.input,
94
+ targetMspConnectionPairId,
95
+ traces: allTraces,
96
+ })
97
+ }
98
+
99
+ // Update the state in the central map
100
+ this.tracesMap.set(targetMspConnectionPairId, updatedTrace)
101
+
102
+ // Update the output for visualization
103
+ this.outputTraces = Array.from(this.tracesMap.values())
104
+ }
105
+
106
+ getOutput() {
107
+ return {
108
+ traces: this.outputTraces,
109
+ }
110
+ }
111
+
112
+ override visualize(): GraphicsObject {
113
+ const graphics = visualizeInputProblem(this.input.inputProblem, {
114
+ chipAlpha: 0.1,
115
+ connectionAlpha: 0.1,
116
+ })
117
+
118
+ if (!graphics.lines) graphics.lines = []
119
+ if (!graphics.points) graphics.points = []
120
+ if (!graphics.rects) graphics.rects = []
121
+ if (!graphics.circles) graphics.circles = []
122
+ if (!graphics.texts) graphics.texts = []
123
+
124
+ for (const trace of this.outputTraces) {
125
+ const line: Line = {
126
+ points: trace.tracePath.map((p) => ({ x: p.x, y: p.y })),
127
+ strokeColor: trace.mspPairId === this.activeTraceId ? "red" : "blue", // Highlight active trace
128
+ }
129
+ graphics.lines!.push(line)
130
+ }
131
+ return graphics
132
+ }
133
+ }
@@ -0,0 +1,200 @@
1
+ import type { Point } from "graphics-debug"
2
+ import type { InputProblem } from "lib/types/InputProblem"
3
+ import { simplifyPath } from "./simplifyPath"
4
+ import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
5
+ import { segmentIntersectsRect } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions"
6
+ import { getObstacleRects } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/rect"
7
+ import type { NetLabelPlacement } from "../NetLabelPlacementSolver/NetLabelPlacementSolver"
8
+
9
+ export const balanceZShapes = ({
10
+ targetMspConnectionPairId,
11
+ traces,
12
+ inputProblem,
13
+ allLabelPlacements,
14
+ mergedLabelNetIdMap,
15
+ paddingBuffer,
16
+ }: {
17
+ targetMspConnectionPairId: string
18
+ traces: SolvedTracePath[]
19
+ inputProblem: InputProblem
20
+ allLabelPlacements: NetLabelPlacement[]
21
+ mergedLabelNetIdMap: Record<string, Set<string>>
22
+ paddingBuffer: number
23
+ }): SolvedTracePath => {
24
+ const targetTrace = traces.find(
25
+ (t) => t.mspPairId === targetMspConnectionPairId,
26
+ )
27
+
28
+ if (!targetTrace) {
29
+ throw new Error(`Target trace ${targetMspConnectionPairId} not found`)
30
+ }
31
+
32
+ const TOLERANCE = 1e-5
33
+
34
+ const obstacleTraces = traces.filter(
35
+ (t) => t.mspPairId !== targetMspConnectionPairId,
36
+ )
37
+
38
+ const TRACE_WIDTH = 0.01
39
+ const traceObstacles = obstacleTraces.flatMap((trace, i) =>
40
+ trace.tracePath.slice(0, -1).map((p1, pi) => {
41
+ const p2 = trace.tracePath[pi + 1]!
42
+ return {
43
+ chipId: `trace-obstacle-${i}-${pi}`,
44
+ minX: Math.min(p1.x, p2.x) - TRACE_WIDTH / 2,
45
+ minY: Math.min(p1.y, p2.y) - TRACE_WIDTH / 2,
46
+ maxX: Math.max(p1.x, p2.x) + TRACE_WIDTH / 2,
47
+ maxY: Math.max(p1.y, p2.y) + TRACE_WIDTH / 2,
48
+ }
49
+ }),
50
+ )
51
+
52
+ const staticObstacles = getObstacleRects(inputProblem).map((obs) => ({
53
+ ...obs,
54
+ minX: obs.minX + TOLERANCE,
55
+ maxX: obs.maxX - TOLERANCE,
56
+ minY: obs.minY + TOLERANCE,
57
+ maxY: obs.maxY - TOLERANCE,
58
+ }))
59
+
60
+ const combinedObstacles = [...staticObstacles, ...traceObstacles]
61
+
62
+ const segmentIntersectsAnyRect = (
63
+ p1: Point,
64
+ p2: Point,
65
+ rects: any[],
66
+ ): boolean => {
67
+ for (const rect of rects) {
68
+ if (segmentIntersectsRect(p1, p2, rect)) {
69
+ return true
70
+ }
71
+ }
72
+ return false
73
+ }
74
+
75
+ const filteredLabels = allLabelPlacements.filter((label) => {
76
+ const originalNetIds = mergedLabelNetIdMap[label.globalConnNetId]
77
+ if (originalNetIds) {
78
+ return !originalNetIds.has(targetTrace.globalConnNetId)
79
+ }
80
+ return label.globalConnNetId !== targetTrace.globalConnNetId
81
+ })
82
+
83
+ const labelBounds = filteredLabels.map((nl) => ({
84
+ minX: nl.center.x - nl.width / 2 + TOLERANCE,
85
+ maxX: nl.center.x + nl.width / 2 - TOLERANCE,
86
+ minY: nl.center.y - nl.height / 2 + TOLERANCE,
87
+ maxY: nl.center.y + nl.height / 2 - TOLERANCE,
88
+ }))
89
+
90
+ const newPath = [...targetTrace.tracePath]
91
+
92
+ if (newPath.length < 4) {
93
+ return { ...targetTrace }
94
+ }
95
+
96
+ if (newPath.length === 4) {
97
+ const [p0, p1, p2, p3] = newPath
98
+ let p1New: Point
99
+ let p2New: Point
100
+
101
+ const isHVHShape = p0.y === p1.y && p1.x === p2.x && p2.y === p3.y
102
+
103
+ if (isHVHShape) {
104
+ const idealX = (p0.x + p3.x) / 2
105
+ p1New = { x: idealX, y: p1.y }
106
+ p2New = { x: idealX, y: p2.y }
107
+ } else {
108
+ const idealY = (p0.y + p3.y) / 2
109
+ p1New = { x: p1.x, y: idealY }
110
+ p2New = { x: p2.x, y: idealY }
111
+ }
112
+
113
+ const collides =
114
+ segmentIntersectsAnyRect(p0, p1New, combinedObstacles) ||
115
+ segmentIntersectsAnyRect(p1New, p2New, combinedObstacles) ||
116
+ segmentIntersectsAnyRect(p2New, p3, combinedObstacles) ||
117
+ segmentIntersectsAnyRect(p0, p1New, labelBounds) ||
118
+ segmentIntersectsAnyRect(p1New, p2New, labelBounds) ||
119
+ segmentIntersectsAnyRect(p2New, p3, labelBounds)
120
+
121
+ if (!collides) {
122
+ newPath[1] = p1New
123
+ newPath[2] = p2New
124
+ }
125
+
126
+ return { ...targetTrace, tracePath: simplifyPath(newPath) }
127
+ }
128
+
129
+ for (let i = 1; i < newPath.length - 4; i++) {
130
+ const p1 = newPath[i]!
131
+ const p2 = newPath[i + 1]!
132
+ const p3 = newPath[i + 2]!
133
+ const p4 = newPath[i + 3]!
134
+
135
+ const isHVHZShape = p1.y === p2.y && p2.x === p3.x && p3.y === p4.y
136
+ const isVHVZShape = p1.x === p2.x && p2.y === p3.y && p3.x === p4.x
137
+
138
+ const isCollinearHorizontal =
139
+ p1.y === p2.y && p2.y === p3.y && p3.y === p4.y
140
+ const isCollinearVertical = p1.x === p2.x && p2.x === p3.x && p3.x === p4.x
141
+ const isCollinear = isCollinearHorizontal || isCollinearVertical
142
+
143
+ let isSameDirection = false
144
+ if (isHVHZShape) {
145
+ isSameDirection = Math.sign(p2.x - p1.x) === Math.sign(p4.x - p3.x)
146
+ } else if (isVHVZShape) {
147
+ isSameDirection = Math.sign(p2.y - p1.y) === Math.sign(p4.y - p3.y)
148
+ }
149
+
150
+ const isValidZShape =
151
+ (isHVHZShape || isVHVZShape) && !isCollinear && isSameDirection
152
+
153
+ if (!isValidZShape) {
154
+ continue
155
+ }
156
+
157
+ let p2New: Point
158
+ let p3New: Point
159
+ const len1Original = isHVHZShape
160
+ ? Math.abs(p1.x - p2.x)
161
+ : Math.abs(p1.y - p2.y)
162
+ const len2Original = isHVHZShape
163
+ ? Math.abs(p3.x - p4.x)
164
+ : Math.abs(p3.y - p4.y)
165
+
166
+ if (Math.abs(len1Original - len2Original) < 0.001) {
167
+ continue
168
+ }
169
+
170
+ if (isHVHZShape) {
171
+ const idealX = (p1.x + p4.x) / 2
172
+ p2New = { x: idealX, y: p2.y }
173
+ p3New = { x: idealX, y: p3.y }
174
+ } else {
175
+ const idealY = (p1.y + p4.y) / 2
176
+ p2New = { x: p2.x, y: idealY }
177
+ p3New = { x: p3.x, y: idealY }
178
+ }
179
+
180
+ const collides =
181
+ segmentIntersectsAnyRect(p1, p2New, combinedObstacles) ||
182
+ segmentIntersectsAnyRect(p2New, p3New, combinedObstacles) ||
183
+ segmentIntersectsAnyRect(p3New, p4, combinedObstacles) ||
184
+ segmentIntersectsAnyRect(p1, p2New, labelBounds) ||
185
+ segmentIntersectsAnyRect(p2New, p3New, labelBounds) ||
186
+ segmentIntersectsAnyRect(p3New, p4, labelBounds)
187
+
188
+ if (!collides) {
189
+ newPath[i + 1] = p2New
190
+ newPath[i + 2] = p3New
191
+ i = 0
192
+ }
193
+ }
194
+
195
+ const finalSimplifiedPath = simplifyPath(newPath)
196
+ return {
197
+ ...targetTrace,
198
+ tracePath: finalSimplifiedPath,
199
+ }
200
+ }
@@ -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[],
@@ -0,0 +1,76 @@
1
+ import type { InputProblem } from "lib/types/InputProblem"
2
+ import { minimizeTurns } from "./turnMinimization"
3
+ import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
4
+ import { getObstacleRects } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/rect"
5
+ import type { NetLabelPlacement } from "../NetLabelPlacementSolver/NetLabelPlacementSolver"
6
+
7
+ export const minimizeTurnsWithFilteredLabels = ({
8
+ targetMspConnectionPairId,
9
+ traces,
10
+ inputProblem,
11
+ allLabelPlacements,
12
+ mergedLabelNetIdMap,
13
+ paddingBuffer,
14
+ }: {
15
+ targetMspConnectionPairId: string
16
+ traces: SolvedTracePath[]
17
+ inputProblem: InputProblem
18
+ allLabelPlacements: NetLabelPlacement[]
19
+ mergedLabelNetIdMap: Record<string, Set<string>>
20
+ paddingBuffer: number
21
+ }): SolvedTracePath => {
22
+ const targetTrace = traces.find(
23
+ (t) => t.mspPairId === targetMspConnectionPairId,
24
+ )
25
+ if (!targetTrace) {
26
+ throw new Error(`Target trace ${targetMspConnectionPairId} not found`)
27
+ }
28
+
29
+ const obstacleTraces = traces.filter(
30
+ (t) => t.mspPairId !== targetMspConnectionPairId,
31
+ )
32
+
33
+ const TRACE_WIDTH = 0.01
34
+ const traceObstacles = obstacleTraces.flatMap((trace, i) =>
35
+ trace.tracePath.slice(0, -1).map((p1, pi) => {
36
+ const p2 = trace.tracePath[pi + 1]!
37
+ return {
38
+ chipId: `trace-obstacle-${i}-${pi}`,
39
+ minX: Math.min(p1.x, p2.x) - TRACE_WIDTH / 2,
40
+ minY: Math.min(p1.y, p2.y) - TRACE_WIDTH / 2,
41
+ maxX: Math.max(p1.x, p2.x) + TRACE_WIDTH / 2,
42
+ maxY: Math.max(p1.y, p2.y) + TRACE_WIDTH / 2,
43
+ }
44
+ }),
45
+ )
46
+
47
+ const staticObstacles = getObstacleRects(inputProblem)
48
+ const combinedObstacles = [...staticObstacles, ...traceObstacles]
49
+
50
+ const originalPath = targetTrace.tracePath
51
+ const filteredLabels = allLabelPlacements.filter((label) => {
52
+ const originalNetIds = mergedLabelNetIdMap[label.globalConnNetId]
53
+ if (originalNetIds) {
54
+ return !originalNetIds.has(targetTrace.globalConnNetId)
55
+ }
56
+ return label.globalConnNetId !== targetTrace.globalConnNetId
57
+ })
58
+
59
+ const labelBounds = filteredLabels.map((nl) => ({
60
+ minX: nl.center.x - nl.width / 2 - paddingBuffer,
61
+ maxX: nl.center.x + nl.width / 2 + paddingBuffer,
62
+ minY: nl.center.y - nl.height / 2 - paddingBuffer,
63
+ maxY: nl.center.y + nl.height / 2 + paddingBuffer,
64
+ }))
65
+
66
+ const newPath = minimizeTurns({
67
+ path: originalPath,
68
+ obstacles: combinedObstacles,
69
+ labelBounds,
70
+ })
71
+
72
+ return {
73
+ ...targetTrace,
74
+ tracePath: newPath,
75
+ }
76
+ }
@@ -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