@tscircuit/schematic-trace-solver 0.0.64 → 0.0.66
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 +10 -0
- package/dist/index.js +254 -115
- package/lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver.ts +8 -2
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/TraceLabelOverlapAvoidanceSolver.ts +6 -0
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/OverlapAvoidanceStepSolver/OverlapAvoidanceStepSolver.ts +6 -0
- package/lib/solvers/TraceLabelOverlapAvoidanceSolver/sub-solvers/SingleOverlapSolver/SingleOverlapSolver.ts +68 -1
- package/lib/solvers/TraceOverlapShiftSolver/TraceOverlapIssueSolver/TraceOverlapIssueSolver.ts +109 -5
- package/lib/solvers/TraceOverlapShiftSolver/TraceOverlapIssueSolver/applyJogToTrace.ts +30 -0
- package/lib/solvers/TraceOverlapShiftSolver/TraceOverlapShiftSolver.ts +1 -0
- package/package.json +1 -1
- package/tests/examples/__snapshots__/example27.snap.svg +86 -78
- package/tests/examples/__snapshots__/example37.snap.svg +60 -156
- package/tests/examples/__snapshots__/example40.snap.svg +103 -63
- package/tests/repros/__snapshots__/repro130-bq27441-fuel-gauge-trace-through-c1.snap.svg +460 -0
- package/tests/repros/__snapshots__/trace-overlap-box-resistor.snap.svg +3 -3
- package/tests/repros/assets/repro130-bq27441-fuel-gauge.input.json +350 -0
- package/tests/repros/repro130-bq27441-fuel-gauge-trace-through-c1.test.ts +60 -0
|
@@ -74,6 +74,7 @@ export class NetLabelPlacementSolver extends BaseSolver {
|
|
|
74
74
|
declare activeSubSolver: SingleNetLabelPlacementSolver | null
|
|
75
75
|
|
|
76
76
|
netLabelPlacements: Array<NetLabelPlacement> = []
|
|
77
|
+
failedGroups: Array<OverlappingSameNetTraceGroup> = []
|
|
77
78
|
currentGroup: OverlappingSameNetTraceGroup | null = null
|
|
78
79
|
triedAnyOrientationFallbackForCurrentGroup = false
|
|
79
80
|
|
|
@@ -336,8 +337,13 @@ export class NetLabelPlacementSolver extends BaseSolver {
|
|
|
336
337
|
return
|
|
337
338
|
}
|
|
338
339
|
|
|
339
|
-
this
|
|
340
|
-
this.
|
|
340
|
+
// Record the failure for this group and continue to the next one
|
|
341
|
+
if (this.currentGroup) {
|
|
342
|
+
this.failedGroups.push(this.currentGroup)
|
|
343
|
+
}
|
|
344
|
+
this.activeSubSolver = null
|
|
345
|
+
this.currentGroup = null
|
|
346
|
+
this.triedAnyOrientationFallbackForCurrentGroup = false
|
|
341
347
|
return
|
|
342
348
|
}
|
|
343
349
|
|
|
@@ -42,10 +42,13 @@ export class TraceLabelOverlapAvoidanceSolver extends BaseSolver {
|
|
|
42
42
|
|
|
43
43
|
labelMergingSolver?: MergedNetLabelObstacleSolver
|
|
44
44
|
|
|
45
|
+
allInputTraces: SolvedTracePath[]
|
|
46
|
+
|
|
45
47
|
constructor(solverInput: TraceLabelOverlapAvoidanceSolverInput) {
|
|
46
48
|
super()
|
|
47
49
|
this.inputProblem = solverInput.inputProblem
|
|
48
50
|
this.unprocessedTraces = [...solverInput.traces]
|
|
51
|
+
this.allInputTraces = solverInput.traces
|
|
49
52
|
this.netLabelPlacements = solverInput.netLabelPlacements
|
|
50
53
|
this.cleanTraces = []
|
|
51
54
|
this.subSolvers = []
|
|
@@ -88,6 +91,9 @@ export class TraceLabelOverlapAvoidanceSolver extends BaseSolver {
|
|
|
88
91
|
mergedNetLabelPlacements: mergingOutput.netLabelPlacements,
|
|
89
92
|
mergedLabelNetIdMap: mergingOutput.mergedLabelNetIdMap,
|
|
90
93
|
detourCounts: this.detourCounts,
|
|
94
|
+
tracesToAvoidOverlapping: this.allInputTraces.filter(
|
|
95
|
+
(t) => t.mspPairId !== currentTargetTrace.mspPairId,
|
|
96
|
+
),
|
|
91
97
|
})
|
|
92
98
|
this.subSolvers.push(subSolver)
|
|
93
99
|
}
|
|
@@ -19,6 +19,7 @@ export interface OverlapCollectionSolverInput {
|
|
|
19
19
|
mergedNetLabelPlacements: NetLabelPlacement[]
|
|
20
20
|
mergedLabelNetIdMap: Record<string, Set<string>>
|
|
21
21
|
detourCounts: Map<string, number>
|
|
22
|
+
tracesToAvoidOverlapping?: SolvedTracePath[]
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
/**
|
|
@@ -32,6 +33,7 @@ export class OverlapAvoidanceStepSolver extends BaseSolver {
|
|
|
32
33
|
mergedLabelNetIdMap: Record<string, Set<string>>
|
|
33
34
|
|
|
34
35
|
allTraces: SolvedTracePath[]
|
|
36
|
+
tracesToAvoidOverlapping: SolvedTracePath[]
|
|
35
37
|
modifiedTraces: SolvedTracePath[] = []
|
|
36
38
|
|
|
37
39
|
private readonly PADDING_BUFFER = 0.1
|
|
@@ -51,6 +53,7 @@ export class OverlapAvoidanceStepSolver extends BaseSolver {
|
|
|
51
53
|
this.mergedNetLabelPlacements = solverInput.mergedNetLabelPlacements
|
|
52
54
|
this.mergedLabelNetIdMap = solverInput.mergedLabelNetIdMap
|
|
53
55
|
this.allTraces = [...solverInput.traces]
|
|
56
|
+
this.tracesToAvoidOverlapping = solverInput.tracesToAvoidOverlapping ?? []
|
|
54
57
|
this.detourCounts = solverInput.detourCounts
|
|
55
58
|
}
|
|
56
59
|
|
|
@@ -150,6 +153,7 @@ export class OverlapAvoidanceStepSolver extends BaseSolver {
|
|
|
150
153
|
problem: this.inputProblem,
|
|
151
154
|
paddingBuffer: this.PADDING_BUFFER,
|
|
152
155
|
detourCount: detourCount,
|
|
156
|
+
tracesToAvoidOverlapping: this.tracesToAvoidOverlapping,
|
|
153
157
|
})
|
|
154
158
|
} else {
|
|
155
159
|
const overlapId = `${traceToFix.mspPairId}-${labelToAvoid.globalConnNetId}`
|
|
@@ -192,6 +196,7 @@ export class OverlapAvoidanceStepSolver extends BaseSolver {
|
|
|
192
196
|
problem: this.inputProblem,
|
|
193
197
|
paddingBuffer: this.PADDING_BUFFER,
|
|
194
198
|
detourCount: detourCount,
|
|
199
|
+
tracesToAvoidOverlapping: this.tracesToAvoidOverlapping,
|
|
195
200
|
})
|
|
196
201
|
} else {
|
|
197
202
|
const overlapId = `${traceToFix.mspPairId}-${labelToAvoid.globalConnNetId}`
|
|
@@ -211,6 +216,7 @@ export class OverlapAvoidanceStepSolver extends BaseSolver {
|
|
|
211
216
|
problem: this.inputProblem,
|
|
212
217
|
paddingBuffer: this.PADDING_BUFFER,
|
|
213
218
|
detourCount: detourCount,
|
|
219
|
+
tracesToAvoidOverlapping: this.tracesToAvoidOverlapping,
|
|
214
220
|
})
|
|
215
221
|
}
|
|
216
222
|
}
|
|
@@ -16,9 +16,69 @@ interface SingleOverlapSolverInput {
|
|
|
16
16
|
problem: InputProblem
|
|
17
17
|
paddingBuffer: number
|
|
18
18
|
detourCount: number
|
|
19
|
+
tracesToAvoidOverlapping?: SolvedTracePath[]
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
const MAX_TRIES = 5
|
|
23
|
+
const COINCIDENT_EPS = 2e-3
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Returns true if any segment of the path is parallel and coincident
|
|
27
|
+
* (within COINCIDENT_EPS) with a segment of one of the given traces.
|
|
28
|
+
* Perpendicular crossings are allowed.
|
|
29
|
+
*/
|
|
30
|
+
const doesPathCoincideWithTraces = (
|
|
31
|
+
path: Point[],
|
|
32
|
+
traces: SolvedTracePath[],
|
|
33
|
+
): boolean => {
|
|
34
|
+
const rangesOverlap1D = (a1: number, a2: number, b1: number, b2: number) =>
|
|
35
|
+
Math.min(Math.max(a1, a2), Math.max(b1, b2)) -
|
|
36
|
+
Math.max(Math.min(a1, a2), Math.min(b1, b2)) >
|
|
37
|
+
COINCIDENT_EPS
|
|
38
|
+
|
|
39
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
40
|
+
const pathSegStart = path[i]!
|
|
41
|
+
const pathSegEnd = path[i + 1]!
|
|
42
|
+
const isVertical = Math.abs(pathSegStart.x - pathSegEnd.x) < COINCIDENT_EPS
|
|
43
|
+
const isHorizontal =
|
|
44
|
+
Math.abs(pathSegStart.y - pathSegEnd.y) < COINCIDENT_EPS
|
|
45
|
+
if (!isVertical && !isHorizontal) continue
|
|
46
|
+
|
|
47
|
+
// For a vertical segment, coincidence is measured in x and overlap in y
|
|
48
|
+
// (and vice versa for horizontal)
|
|
49
|
+
const crossAxis = isVertical ? "x" : "y"
|
|
50
|
+
const alongAxis = isVertical ? "y" : "x"
|
|
51
|
+
|
|
52
|
+
for (const trace of traces) {
|
|
53
|
+
for (let j = 0; j < trace.tracePath.length - 1; j++) {
|
|
54
|
+
const traceSegStart = trace.tracePath[j]!
|
|
55
|
+
const traceSegEnd = trace.tracePath[j + 1]!
|
|
56
|
+
|
|
57
|
+
const isParallel =
|
|
58
|
+
Math.abs(traceSegStart[crossAxis] - traceSegEnd[crossAxis]) <
|
|
59
|
+
COINCIDENT_EPS
|
|
60
|
+
if (!isParallel) continue
|
|
61
|
+
|
|
62
|
+
const isCoincident =
|
|
63
|
+
Math.abs(pathSegStart[crossAxis] - traceSegStart[crossAxis]) <
|
|
64
|
+
COINCIDENT_EPS
|
|
65
|
+
if (!isCoincident) continue
|
|
66
|
+
|
|
67
|
+
if (
|
|
68
|
+
rangesOverlap1D(
|
|
69
|
+
pathSegStart[alongAxis],
|
|
70
|
+
pathSegEnd[alongAxis],
|
|
71
|
+
traceSegStart[alongAxis],
|
|
72
|
+
traceSegEnd[alongAxis],
|
|
73
|
+
)
|
|
74
|
+
) {
|
|
75
|
+
return true
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return false
|
|
81
|
+
}
|
|
22
82
|
|
|
23
83
|
/**
|
|
24
84
|
* This solver attempts to find a valid rerouting for a single trace that is
|
|
@@ -32,6 +92,7 @@ export class SingleOverlapSolver extends BaseSolver {
|
|
|
32
92
|
problem: InputProblem
|
|
33
93
|
obstacles: ReturnType<typeof getObstacleRects>
|
|
34
94
|
label: NetLabelPlacement
|
|
95
|
+
tracesToAvoidOverlapping: SolvedTracePath[]
|
|
35
96
|
_tried: number = 0
|
|
36
97
|
|
|
37
98
|
constructor(solverInput: SingleOverlapSolverInput) {
|
|
@@ -39,6 +100,9 @@ export class SingleOverlapSolver extends BaseSolver {
|
|
|
39
100
|
this.initialTrace = solverInput.trace
|
|
40
101
|
this.problem = solverInput.problem
|
|
41
102
|
this.label = solverInput.label
|
|
103
|
+
this.tracesToAvoidOverlapping = (
|
|
104
|
+
solverInput.tracesToAvoidOverlapping ?? []
|
|
105
|
+
).filter((t) => t.globalConnNetId !== solverInput.trace.globalConnNetId)
|
|
42
106
|
|
|
43
107
|
// Calculate an effective padding for this specific run based on the detourCount.
|
|
44
108
|
const effectivePadding =
|
|
@@ -77,7 +141,10 @@ export class SingleOverlapSolver extends BaseSolver {
|
|
|
77
141
|
const nextCandidatePath = this.queuedCandidatePaths.shift()!
|
|
78
142
|
const simplifiedPath = simplifyPath(nextCandidatePath)
|
|
79
143
|
|
|
80
|
-
if (
|
|
144
|
+
if (
|
|
145
|
+
!isPathCollidingWithObstacles(simplifiedPath, this.obstacles) &&
|
|
146
|
+
!doesPathCoincideWithTraces(simplifiedPath, this.tracesToAvoidOverlapping)
|
|
147
|
+
) {
|
|
81
148
|
this.solvedTracePath = simplifiedPath
|
|
82
149
|
this.solved = true
|
|
83
150
|
}
|
package/lib/solvers/TraceOverlapShiftSolver/TraceOverlapIssueSolver/TraceOverlapIssueSolver.ts
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import type { GraphicsObject } from "graphics-debug"
|
|
2
2
|
import { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver"
|
|
3
3
|
import type { MspConnectionPairId } from "lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver"
|
|
4
|
+
import { findFirstCollision } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/collisions"
|
|
5
|
+
import { getObstacleRects } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/rect"
|
|
4
6
|
import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
7
|
+
import type { InputProblem } from "lib/types/InputProblem"
|
|
5
8
|
import { applyJogToTerminalSegment } from "./applyJogToTrace"
|
|
6
9
|
|
|
7
10
|
type ConnNetId = string
|
|
8
11
|
|
|
12
|
+
const EPS = 1e-6
|
|
13
|
+
|
|
9
14
|
export interface OverlappingTraceSegmentLocator {
|
|
10
15
|
connNetId: string
|
|
11
16
|
pathsWithOverlap: Array<{
|
|
@@ -17,18 +22,21 @@ export interface OverlappingTraceSegmentLocator {
|
|
|
17
22
|
export class TraceOverlapIssueSolver extends BaseSolver {
|
|
18
23
|
overlappingTraceSegments: OverlappingTraceSegmentLocator[]
|
|
19
24
|
traceNetIslands: Record<ConnNetId, Array<SolvedTracePath>>
|
|
25
|
+
obstacleRects: ReturnType<typeof getObstacleRects>
|
|
20
26
|
|
|
21
27
|
SHIFT_DISTANCE = 0.1
|
|
22
28
|
|
|
23
29
|
correctedTraceMap: Record<MspConnectionPairId, SolvedTracePath> = {}
|
|
24
30
|
|
|
25
31
|
constructor(params: {
|
|
32
|
+
inputProblem: InputProblem
|
|
26
33
|
overlappingTraceSegments: OverlappingTraceSegmentLocator[]
|
|
27
34
|
traceNetIslands: Record<ConnNetId, Array<SolvedTracePath>>
|
|
28
35
|
}) {
|
|
29
36
|
super()
|
|
30
37
|
this.overlappingTraceSegments = params.overlappingTraceSegments
|
|
31
38
|
this.traceNetIslands = params.traceNetIslands
|
|
39
|
+
this.obstacleRects = getObstacleRects(params.inputProblem)
|
|
32
40
|
|
|
33
41
|
// Only add the relevant traces to the correctedTraceMap
|
|
34
42
|
for (const { connNetId, pathsWithOverlap } of this
|
|
@@ -49,13 +57,34 @@ export class TraceOverlapIssueSolver extends BaseSolver {
|
|
|
49
57
|
// Shift only the overlapping segments, and move the shared endpoints
|
|
50
58
|
// (the last point of the previous segment and the first point of the next
|
|
51
59
|
// segment) so the polyline remains orthogonal without self-overlap.
|
|
52
|
-
|
|
60
|
+
// Groups containing a straight pin-to-pin trace (2 points, both endpoints
|
|
61
|
+
// are pins) keep their position when another group can shift instead:
|
|
62
|
+
// shifting such a trace would force jogs on an otherwise straight line.
|
|
63
|
+
const containsStraightPinToPinTrace = (
|
|
64
|
+
group: OverlappingTraceSegmentLocator,
|
|
65
|
+
) =>
|
|
66
|
+
group.pathsWithOverlap.some(({ solvedTracePathIndex }) => {
|
|
67
|
+
const path =
|
|
68
|
+
this.traceNetIslands[group.connNetId][solvedTracePathIndex]!
|
|
69
|
+
return path.tracePath.length === 2
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
const groupShouldStayInPlace = this.overlappingTraceSegments.map(
|
|
73
|
+
containsStraightPinToPinTrace,
|
|
74
|
+
)
|
|
75
|
+
const someGroupCanShift = groupShouldStayInPlace.some(
|
|
76
|
+
(shouldStay) => !shouldStay,
|
|
77
|
+
)
|
|
53
78
|
|
|
54
79
|
// Compute offsets for each island involved: alternate directions
|
|
55
|
-
const offsets = this.overlappingTraceSegments.map((
|
|
80
|
+
const offsets = this.overlappingTraceSegments.map((group, idx) => {
|
|
81
|
+
if (someGroupCanShift && groupShouldStayInPlace[idx]) return 0
|
|
56
82
|
const n = Math.floor(idx / 2) + 1
|
|
57
83
|
const signed = idx % 2 === 0 ? -n : n
|
|
58
|
-
return
|
|
84
|
+
return this.getObstacleAwareOffset({
|
|
85
|
+
group,
|
|
86
|
+
offset: signed * this.SHIFT_DISTANCE,
|
|
87
|
+
})
|
|
59
88
|
})
|
|
60
89
|
|
|
61
90
|
const eq = (a: number, b: number) => Math.abs(a - b) < EPS
|
|
@@ -67,6 +96,7 @@ export class TraceOverlapIssueSolver extends BaseSolver {
|
|
|
67
96
|
// For each net island group, shift only its overlapping segments and adjust adjacent joints
|
|
68
97
|
this.overlappingTraceSegments.forEach((group, gidx) => {
|
|
69
98
|
const offset = offsets[gidx]!
|
|
99
|
+
if (offset === 0) return
|
|
70
100
|
|
|
71
101
|
// Gather unique segment indices per path
|
|
72
102
|
const byPath: Map<number, Set<number>> = new Map()
|
|
@@ -82,8 +112,6 @@ export class TraceOverlapIssueSolver extends BaseSolver {
|
|
|
82
112
|
const current = this.correctedTraceMap[original.mspPairId] ?? original
|
|
83
113
|
const pts = current.tracePath.map((p) => ({ ...p }))
|
|
84
114
|
|
|
85
|
-
const segIdxs = Array.from(segIdxSet).sort((a, b) => a - b)
|
|
86
|
-
|
|
87
115
|
const segIdxsRev = Array.from(segIdxSet)
|
|
88
116
|
.sort((a, b) => a - b)
|
|
89
117
|
.reverse()
|
|
@@ -142,6 +170,82 @@ export class TraceOverlapIssueSolver extends BaseSolver {
|
|
|
142
170
|
this.solved = true
|
|
143
171
|
}
|
|
144
172
|
|
|
173
|
+
private getObstacleAwareOffset({
|
|
174
|
+
group,
|
|
175
|
+
offset,
|
|
176
|
+
}: {
|
|
177
|
+
group: OverlappingTraceSegmentLocator
|
|
178
|
+
offset: number
|
|
179
|
+
}) {
|
|
180
|
+
const blockingCollisions = this.getBlockingCollisions({ group, offset })
|
|
181
|
+
if (blockingCollisions.length === 0) return offset
|
|
182
|
+
|
|
183
|
+
return (
|
|
184
|
+
blockingCollisions
|
|
185
|
+
.flatMap(({ start, isVertical, obstacle }) => [
|
|
186
|
+
(isVertical ? obstacle.minX - start.x : obstacle.minY - start.y) -
|
|
187
|
+
this.SHIFT_DISTANCE,
|
|
188
|
+
(isVertical ? obstacle.maxX - start.x : obstacle.maxY - start.y) +
|
|
189
|
+
this.SHIFT_DISTANCE,
|
|
190
|
+
])
|
|
191
|
+
.filter(
|
|
192
|
+
(candidateOffset) =>
|
|
193
|
+
this.getBlockingCollisions({ group, offset: candidateOffset })
|
|
194
|
+
.length === 0,
|
|
195
|
+
)
|
|
196
|
+
.sort((a, b) => Math.abs(a - offset) - Math.abs(b - offset))[0] ??
|
|
197
|
+
offset
|
|
198
|
+
)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
private getBlockingCollisions({
|
|
202
|
+
group,
|
|
203
|
+
offset,
|
|
204
|
+
}: {
|
|
205
|
+
group: OverlappingTraceSegmentLocator
|
|
206
|
+
offset: number
|
|
207
|
+
}) {
|
|
208
|
+
const blockingCollisions: Array<{
|
|
209
|
+
start: SolvedTracePath["tracePath"][number]
|
|
210
|
+
isVertical: boolean
|
|
211
|
+
obstacle: ReturnType<typeof getObstacleRects>[number]
|
|
212
|
+
}> = []
|
|
213
|
+
|
|
214
|
+
for (const {
|
|
215
|
+
solvedTracePathIndex,
|
|
216
|
+
traceSegmentIndex,
|
|
217
|
+
} of group.pathsWithOverlap) {
|
|
218
|
+
const trace = this.traceNetIslands[group.connNetId][solvedTracePathIndex]!
|
|
219
|
+
const start = trace.tracePath[traceSegmentIndex]
|
|
220
|
+
const end = trace.tracePath[traceSegmentIndex + 1]
|
|
221
|
+
if (!start || !end) continue
|
|
222
|
+
|
|
223
|
+
const isVertical = Math.abs(start.x - end.x) < EPS
|
|
224
|
+
const isHorizontal = Math.abs(start.y - end.y) < EPS
|
|
225
|
+
if (!isVertical && !isHorizontal) continue
|
|
226
|
+
|
|
227
|
+
const shiftedSegment = isVertical
|
|
228
|
+
? [
|
|
229
|
+
{ ...start, x: start.x + offset },
|
|
230
|
+
{ ...end, x: end.x + offset },
|
|
231
|
+
]
|
|
232
|
+
: [
|
|
233
|
+
{ ...start, y: start.y + offset },
|
|
234
|
+
{ ...end, y: end.y + offset },
|
|
235
|
+
]
|
|
236
|
+
const collision = findFirstCollision(shiftedSegment, this.obstacleRects)
|
|
237
|
+
if (collision) {
|
|
238
|
+
blockingCollisions.push({
|
|
239
|
+
start,
|
|
240
|
+
isVertical,
|
|
241
|
+
obstacle: collision.rect,
|
|
242
|
+
})
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return blockingCollisions
|
|
247
|
+
}
|
|
248
|
+
|
|
145
249
|
override visualize(): GraphicsObject {
|
|
146
250
|
// Visualize overlapped segments and proposed corrections
|
|
147
251
|
const graphics: GraphicsObject = {
|
|
@@ -29,6 +29,36 @@ export const applyJogToTerminalSegment = ({
|
|
|
29
29
|
? 1
|
|
30
30
|
: -1
|
|
31
31
|
|
|
32
|
+
if (pts.length === 2) {
|
|
33
|
+
// The segment is both the first and last segment: both endpoints are
|
|
34
|
+
// pins, so jog inward on both sides to keep the endpoints anchored
|
|
35
|
+
if (isVertical) {
|
|
36
|
+
const jogYNearStart = start.y + segDir * JOG_SIZE
|
|
37
|
+
const jogYNearEnd = end.y - segDir * JOG_SIZE
|
|
38
|
+
pts.splice(
|
|
39
|
+
1,
|
|
40
|
+
0,
|
|
41
|
+
{ x: start.x, y: jogYNearStart },
|
|
42
|
+
{ x: start.x + offset, y: jogYNearStart },
|
|
43
|
+
{ x: end.x + offset, y: jogYNearEnd },
|
|
44
|
+
{ x: end.x, y: jogYNearEnd },
|
|
45
|
+
)
|
|
46
|
+
} else {
|
|
47
|
+
// Horizontal
|
|
48
|
+
const jogXNearStart = start.x + segDir * JOG_SIZE
|
|
49
|
+
const jogXNearEnd = end.x - segDir * JOG_SIZE
|
|
50
|
+
pts.splice(
|
|
51
|
+
1,
|
|
52
|
+
0,
|
|
53
|
+
{ x: jogXNearStart, y: start.y },
|
|
54
|
+
{ x: jogXNearStart, y: start.y + offset },
|
|
55
|
+
{ x: jogXNearEnd, y: end.y + offset },
|
|
56
|
+
{ x: jogXNearEnd, y: end.y },
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
return
|
|
60
|
+
}
|
|
61
|
+
|
|
32
62
|
if (si === 0) {
|
|
33
63
|
if (isVertical) {
|
|
34
64
|
const jogY = start.y + segDir * JOG_SIZE
|
|
@@ -309,6 +309,7 @@ export class TraceOverlapShiftSolver extends BaseSolver {
|
|
|
309
309
|
const { overlappingTraceSegments } = overlapIssue
|
|
310
310
|
|
|
311
311
|
this.activeSubSolver = new TraceOverlapIssueSolver({
|
|
312
|
+
inputProblem: this.inputProblem,
|
|
312
313
|
overlappingTraceSegments,
|
|
313
314
|
traceNetIslands: this.traceNetIslands,
|
|
314
315
|
})
|