@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.
- package/dist/index.d.ts +118 -8
- package/dist/index.js +1067 -748
- package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +48 -16
- package/lib/solvers/TraceCleanupSolver/TraceCleanupSolver.ts +133 -0
- package/lib/solvers/TraceCleanupSolver/balanceZShapes.ts +200 -0
- package/lib/solvers/{TraceLabelOverlapAvoidanceSolver → TraceCleanupSolver}/hasCollisions.ts +1 -1
- package/lib/solvers/{TraceLabelOverlapAvoidanceSolver → TraceCleanupSolver}/hasCollisionsWithLabels.ts +1 -1
- package/lib/solvers/TraceCleanupSolver/minimizeTurnsWithFilteredLabels.ts +76 -0
- package/lib/solvers/{TraceLabelOverlapAvoidanceSolver → TraceCleanupSolver}/simplifyPath.ts +1 -1
- package/lib/solvers/{TraceLabelOverlapAvoidanceSolver → TraceCleanupSolver}/turnMinimization.ts +0 -2
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/TraceLabelOverlapAvoidanceSolver.ts +74 -192
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/rerouteCollidingTrace.ts +13 -24
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/LabelMergingSolver/LabelMergingSolver.ts +205 -0
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/OverlapAvoidanceStepSolver/OverlapAvoidanceStepSolver.ts +144 -0
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/SingleOverlapSolver/SingleOverlapSolver.ts +117 -0
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/tryFourPointDetour.ts +104 -0
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/trySnipAndReconnect.ts +24 -154
- package/package.json +1 -1
- package/site/examples/example26.page.tsx +4 -0
- package/tests/assets/1.input.json +379 -0
- package/tests/assets/2.input.json +330 -0
- package/tests/assets/3.input.json +388 -0
- package/tests/assets/MergedNetLabelObstacles.test.input.json +154 -0
- package/tests/assets/OverlapAvoidanceStepSolver.test.input.json +682 -0
- package/tests/assets/SingleOverlapSolver.test.input.json +170 -0
- package/tests/assets/TraceCleanupSolver.test.input.json +216 -0
- package/tests/assets/TraceLabelOverlapAvoidanceSolver.test.input.json +424 -0
- package/tests/assets/example26.json +1206 -0
- package/tests/examples/__snapshots__/example02.snap.svg +1 -1
- package/tests/examples/__snapshots__/example09.snap.svg +34 -34
- package/tests/examples/__snapshots__/example11.snap.svg +13 -13
- package/tests/examples/__snapshots__/example12.snap.svg +3 -3
- package/tests/examples/__snapshots__/example13.snap.svg +12 -12
- package/tests/examples/__snapshots__/example16.snap.svg +4 -4
- package/tests/examples/__snapshots__/example19.snap.svg +9 -9
- package/tests/examples/__snapshots__/example28.snap.svg +3 -3
- package/tests/examples/__snapshots__/example29.snap.svg +1208 -0
- package/tests/examples/example29.test.ts +13 -0
- package/tests/solvers/TraceCleanupSolver/TraceCleanupSolver.test.ts +19 -0
- package/tests/solvers/TraceCleanupSolver/__snapshots__/TraceCleanupSolver.snap.svg +119 -0
- package/tests/solvers/TraceLabelOverlapAvoidanceSolver/TraceLabelOverlapAvoidanceSolver.test.ts +18 -0
- package/tests/solvers/TraceLabelOverlapAvoidanceSolver/__snapshots__/TraceLabelOverlapAvoidanceSolver.snap.svg +243 -0
- package/tests/solvers/TraceLabelOverlapAvoidanceSolver/renderComparisonView/__snapshots__/renderComparisonView01.snap.svg +223 -0
- package/tests/solvers/TraceLabelOverlapAvoidanceSolver/renderComparisonView/__snapshots__/renderComparisonView02.snap.svg +205 -0
- package/tests/solvers/TraceLabelOverlapAvoidanceSolver/renderComparisonView/__snapshots__/renderComparisonView03.snap.svg +184 -0
- package/tests/solvers/TraceLabelOverlapAvoidanceSolver/renderComparisonView/renderComparisonView01.test.ts +29 -0
- package/tests/solvers/TraceLabelOverlapAvoidanceSolver/renderComparisonView/renderComparisonView02.test.ts +27 -0
- package/tests/solvers/TraceLabelOverlapAvoidanceSolver/renderComparisonView/renderComparisonView03.test.ts +39 -0
- package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/MergedNetLabelObstacles.test.ts +14 -0
- package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/OverlapAvoidanceStepSolver.test.ts +20 -0
- package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/SingleOverlapSolver.test.ts +10 -0
- package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/__snapshots__/MergedNetLabelObstacles.snap.svg +140 -0
- package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/__snapshots__/OverlapAvoidanceStepSolver.snap.svg +216 -0
- package/tests/solvers/TraceLabelOverlapAvoidanceSolver/sub-solver/__snapshots__/SingleOverlapSolver.snap.svg +125 -0
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/balanceLShapes.ts +0 -207
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/minimizeTurnsWithFilteredLabels.ts +0 -66
- /package/lib/solvers/{TraceLabelOverlapAvoidanceSolver → TraceCleanupSolver}/countTurns.ts +0 -0
- /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
|
|
4
|
+
export const generateSnipAndReconnectCandidates = ({
|
|
13
5
|
initialTrace,
|
|
14
6
|
firstInsideIndex,
|
|
15
7
|
lastInsideIndex,
|
|
16
8
|
labelBounds,
|
|
17
|
-
|
|
9
|
+
paddingBuffer,
|
|
10
|
+
detourCount,
|
|
18
11
|
}: {
|
|
19
12
|
initialTrace: SolvedTracePath
|
|
20
13
|
firstInsideIndex: number
|
|
21
14
|
lastInsideIndex: number
|
|
22
15
|
labelBounds: any
|
|
23
|
-
|
|
24
|
-
|
|
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
|
|
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
|
|
32
|
+
const allCandidateDetours: Point[][] = []
|
|
39
33
|
|
|
34
|
+
// Candidate type 1: simple elbow
|
|
40
35
|
if (entryPoint.x !== exitPoint.x && entryPoint.y !== exitPoint.y) {
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
45
|
-
|
|
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
|
-
|
|
52
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
88
|
+
allCandidateDetours.push([
|
|
108
89
|
{ x: entryPoint.x, y: bottomY },
|
|
109
90
|
{ x: exitPoint.x, y: bottomY },
|
|
110
91
|
])
|
|
111
92
|
}
|
|
112
93
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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