@tscircuit/schematic-trace-solver 0.0.101 → 0.0.102
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 +13 -6
- package/dist/index.js +213 -64
- package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2.ts +13 -5
- package/lib/solvers/TraceOverlapShiftSolver/TraceOverlapIssueSolver/TraceOverlapIssueSolver.ts +168 -24
- package/lib/solvers/TraceOverlapShiftSolver/TraceOverlapShiftSolver.ts +128 -48
- package/package.json +1 -1
- package/site/bug-reports/bug-report-20260716T144856Z.page.tsx +4 -0
- package/site/bug-reports/bug-report-20260717T022934Z.page.tsx +4 -0
- package/site/bug-reports/bug-report-20260717T031704Z.page.tsx +4 -0
- package/tests/bug-reports/bug-report-20260706T213649Z/__snapshots__/bug-report-20260706T213649Z.snap.svg +3 -3
- package/tests/bug-reports/bug-report-20260706T220324Z/__snapshots__/bug-report-20260706T220324Z.snap.svg +3 -3
- package/tests/bug-reports/bug-report-20260707T092615Z/__snapshots__/bug-report-20260707T092615Z.snap.svg +5 -5
- package/tests/bug-reports/bug-report-20260707T140410Z/__snapshots__/bug-report-20260707T140410Z.snap.svg +3 -3
- package/tests/bug-reports/bug-report-20260716T144856Z/__snapshots__/bug-report-20260716T144856Z.snap.svg +149 -0
- package/tests/bug-reports/bug-report-20260716T144856Z/bug-report-20260716T144856Z.json +693 -0
- package/tests/bug-reports/bug-report-20260716T144856Z/bug-report-20260716T144856Z.test.ts +12 -0
- package/tests/bug-reports/bug-report-20260717T022934Z/__snapshots__/bug-report-20260717T022934Z.snap.svg +130 -0
- package/tests/bug-reports/bug-report-20260717T022934Z/bug-report-20260717T022934Z.json +543 -0
- package/tests/bug-reports/bug-report-20260717T022934Z/bug-report-20260717T022934Z.test.ts +12 -0
- package/tests/bug-reports/bug-report-20260717T031704Z/__snapshots__/bug-report-20260717T031704Z.snap.svg +48 -0
- package/tests/bug-reports/bug-report-20260717T031704Z/bug-report-20260717T031704Z.json +77 -0
- package/tests/bug-reports/bug-report-20260717T031704Z/bug-report-20260717T031704Z.test.ts +12 -0
- package/tests/examples/__snapshots__/example11.snap.svg +19 -19
- package/tests/examples/__snapshots__/example19.snap.svg +3 -5
- package/tests/examples/__snapshots__/example29.snap.svg +13 -13
- package/tests/examples/__snapshots__/example37.snap.svg +4 -2
- package/tests/examples/example16.test.ts +22 -0
- package/tests/repros/__snapshots__/repro-rp2040-gamepad-trace-alignment.snap.svg +2 -8
- package/tests/repros/__snapshots__/repro47-endpoint-obstacle-detour.snap.svg +19 -19
- package/tests/repros/__snapshots__/trace-overlap-box-resistor.snap.svg +10 -10
- package/tests/repros/repro47-endpoint-obstacle-detour.test.ts +30 -0
- package/tests/solvers/TraceOverlapShiftSolver/TraceOverlapShiftSolver.test.ts +174 -0
package/lib/solvers/TraceOverlapShiftSolver/TraceOverlapIssueSolver/TraceOverlapIssueSolver.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { doSegmentsIntersect } from "@tscircuit/math-utils"
|
|
1
2
|
import type { GraphicsObject } from "graphics-debug"
|
|
2
3
|
import { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver"
|
|
3
4
|
import type { MspConnectionPairId } from "lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver"
|
|
@@ -19,8 +20,11 @@ export interface OverlappingTraceSegmentLocator {
|
|
|
19
20
|
}>
|
|
20
21
|
}
|
|
21
22
|
|
|
23
|
+
export type TraceInteractionKind = "overlap" | "point_contact"
|
|
24
|
+
|
|
22
25
|
export class TraceOverlapIssueSolver extends BaseSolver {
|
|
23
26
|
overlappingTraceSegments: OverlappingTraceSegmentLocator[]
|
|
27
|
+
interactionKind: TraceInteractionKind
|
|
24
28
|
traceNetIslands: Record<ConnNetId, Array<SolvedTracePath>>
|
|
25
29
|
obstacleRects: ReturnType<typeof getObstacleRects>
|
|
26
30
|
|
|
@@ -31,10 +35,12 @@ export class TraceOverlapIssueSolver extends BaseSolver {
|
|
|
31
35
|
constructor(params: {
|
|
32
36
|
inputProblem: InputProblem
|
|
33
37
|
overlappingTraceSegments: OverlappingTraceSegmentLocator[]
|
|
38
|
+
interactionKind: TraceInteractionKind
|
|
34
39
|
traceNetIslands: Record<ConnNetId, Array<SolvedTracePath>>
|
|
35
40
|
}) {
|
|
36
41
|
super()
|
|
37
42
|
this.overlappingTraceSegments = params.overlappingTraceSegments
|
|
43
|
+
this.interactionKind = params.interactionKind
|
|
38
44
|
this.traceNetIslands = params.traceNetIslands
|
|
39
45
|
this.obstacleRects = getObstacleRects(params.inputProblem)
|
|
40
46
|
|
|
@@ -76,17 +82,87 @@ export class TraceOverlapIssueSolver extends BaseSolver {
|
|
|
76
82
|
(shouldStay) => !shouldStay,
|
|
77
83
|
)
|
|
78
84
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
85
|
+
const getSeparationOffsets = (firstDirection: number) =>
|
|
86
|
+
this.overlappingTraceSegments.map((group, groupIndex) => {
|
|
87
|
+
if (someGroupCanShift && groupShouldStayInPlace[groupIndex]) return 0
|
|
88
|
+
const n = Math.floor(groupIndex / 2) + 1
|
|
89
|
+
const direction =
|
|
90
|
+
groupIndex % 2 === 0 ? firstDirection : -firstDirection
|
|
91
|
+
return this.getObstacleAwareOffset({
|
|
92
|
+
group,
|
|
93
|
+
offset: direction * n * this.SHIFT_DISTANCE,
|
|
94
|
+
})
|
|
87
95
|
})
|
|
96
|
+
|
|
97
|
+
const establishedOffsets = getSeparationOffsets(-1)
|
|
98
|
+
const isCompoundOverlap =
|
|
99
|
+
this.interactionKind === "overlap" &&
|
|
100
|
+
this.overlappingTraceSegments.some(
|
|
101
|
+
(group) => group.pathsWithOverlap.length > 1,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
// A compound overlap moves multiple rails as one correction. Preserve the
|
|
105
|
+
// established deterministic separation because a single candidate score
|
|
106
|
+
// cannot attribute downstream intersections to one participating rail.
|
|
107
|
+
if (isCompoundOverlap) {
|
|
108
|
+
this.correctedTraceMap = this.applyOffsets(establishedOffsets)
|
|
109
|
+
this.solved = true
|
|
110
|
+
return
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const offsetCandidates: number[][] =
|
|
114
|
+
this.interactionKind === "overlap"
|
|
115
|
+
? [establishedOffsets, getSeparationOffsets(1)]
|
|
116
|
+
: []
|
|
117
|
+
|
|
118
|
+
if (this.interactionKind === "point_contact") {
|
|
119
|
+
// A point contact needs only one island to move. Try later islands first
|
|
120
|
+
// so an established earlier trace remains stable when scores are equal.
|
|
121
|
+
const movableGroupIndexes = this.overlappingTraceSegments
|
|
122
|
+
.map((_, groupIndex) => groupIndex)
|
|
123
|
+
.filter(
|
|
124
|
+
(groupIndex) =>
|
|
125
|
+
!someGroupCanShift || !groupShouldStayInPlace[groupIndex],
|
|
126
|
+
)
|
|
127
|
+
.reverse()
|
|
128
|
+
for (const groupIndex of movableGroupIndexes) {
|
|
129
|
+
const group = this.overlappingTraceSegments[groupIndex]!
|
|
130
|
+
for (const direction of [-1, 1]) {
|
|
131
|
+
const offsets = this.overlappingTraceSegments.map(() => 0)
|
|
132
|
+
offsets[groupIndex] = this.getObstacleAwareOffset({
|
|
133
|
+
group,
|
|
134
|
+
offset: direction * this.SHIFT_DISTANCE,
|
|
135
|
+
})
|
|
136
|
+
offsetCandidates.push(offsets)
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const candidates = offsetCandidates.map((offsets) => {
|
|
142
|
+
const correctedTraceMap = this.applyOffsets(offsets)
|
|
143
|
+
return {
|
|
144
|
+
correctedTraceMap,
|
|
145
|
+
intersectionCount:
|
|
146
|
+
this.countDifferentNetIntersections(correctedTraceMap),
|
|
147
|
+
totalDisplacement: offsets.reduce(
|
|
148
|
+
(total, offset) => total + Math.abs(offset),
|
|
149
|
+
0,
|
|
150
|
+
),
|
|
151
|
+
}
|
|
88
152
|
})
|
|
89
153
|
|
|
154
|
+
candidates.sort(
|
|
155
|
+
(a, b) =>
|
|
156
|
+
a.intersectionCount - b.intersectionCount ||
|
|
157
|
+
a.totalDisplacement - b.totalDisplacement,
|
|
158
|
+
)
|
|
159
|
+
this.correctedTraceMap = candidates[0]!.correctedTraceMap
|
|
160
|
+
this.solved = true
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
private applyOffsets(offsets: number[]) {
|
|
164
|
+
const correctedTraceMap = { ...this.correctedTraceMap }
|
|
165
|
+
|
|
90
166
|
const eq = (a: number, b: number) => Math.abs(a - b) < EPS
|
|
91
167
|
const samePoint = (
|
|
92
168
|
p: { x: number; y: number } | undefined,
|
|
@@ -94,8 +170,8 @@ export class TraceOverlapIssueSolver extends BaseSolver {
|
|
|
94
170
|
) => !!p && !!q && eq(p.x, q.x) && eq(p.y, q.y)
|
|
95
171
|
|
|
96
172
|
// For each net island group, shift only its overlapping segments and adjust adjacent joints
|
|
97
|
-
this.overlappingTraceSegments.forEach((group,
|
|
98
|
-
const offset = offsets[
|
|
173
|
+
this.overlappingTraceSegments.forEach((group, groupIndex) => {
|
|
174
|
+
const offset = offsets[groupIndex]!
|
|
99
175
|
if (offset === 0) return
|
|
100
176
|
|
|
101
177
|
// Gather unique segment indices per path
|
|
@@ -109,12 +185,25 @@ export class TraceOverlapIssueSolver extends BaseSolver {
|
|
|
109
185
|
|
|
110
186
|
for (const [pathIdx, segIdxSet] of byPath) {
|
|
111
187
|
const original = this.traceNetIslands[group.connNetId][pathIdx]!
|
|
112
|
-
const current =
|
|
188
|
+
const current = correctedTraceMap[original.mspPairId] ?? original
|
|
113
189
|
const pts = current.tracePath.map((p) => ({ ...p }))
|
|
114
190
|
|
|
115
191
|
const segIdxsRev = Array.from(segIdxSet)
|
|
116
192
|
.sort((a, b) => a - b)
|
|
117
193
|
.reverse()
|
|
194
|
+
const segmentAxes = new Map<number, "vertical" | "horizontal">()
|
|
195
|
+
for (const si of segIdxsRev) {
|
|
196
|
+
const start = pts[si]
|
|
197
|
+
const end = pts[si + 1]
|
|
198
|
+
if (!start || !end) continue
|
|
199
|
+
if (Math.abs(start.x - end.x) < EPS) {
|
|
200
|
+
segmentAxes.set(si, "vertical")
|
|
201
|
+
} else if (Math.abs(start.y - end.y) < EPS) {
|
|
202
|
+
segmentAxes.set(si, "horizontal")
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
const shiftedXPointIndexes = new Set<number>()
|
|
206
|
+
const shiftedYPointIndexes = new Set<number>()
|
|
118
207
|
|
|
119
208
|
const JOG_SIZE = this.SHIFT_DISTANCE
|
|
120
209
|
|
|
@@ -134,17 +223,26 @@ export class TraceOverlapIssueSolver extends BaseSolver {
|
|
|
134
223
|
// Internal segment - shift both points
|
|
135
224
|
const start = pts[si]!
|
|
136
225
|
const end = pts[si + 1]!
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
if (
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
226
|
+
const axis = segmentAxes.get(si)
|
|
227
|
+
|
|
228
|
+
if (axis === "vertical") {
|
|
229
|
+
if (!shiftedXPointIndexes.has(si)) {
|
|
230
|
+
start.x += offset
|
|
231
|
+
shiftedXPointIndexes.add(si)
|
|
232
|
+
}
|
|
233
|
+
if (!shiftedXPointIndexes.has(si + 1)) {
|
|
234
|
+
end.x += offset
|
|
235
|
+
shiftedXPointIndexes.add(si + 1)
|
|
236
|
+
}
|
|
237
|
+
} else if (axis === "horizontal") {
|
|
238
|
+
if (!shiftedYPointIndexes.has(si)) {
|
|
239
|
+
start.y += offset
|
|
240
|
+
shiftedYPointIndexes.add(si)
|
|
241
|
+
}
|
|
242
|
+
if (!shiftedYPointIndexes.has(si + 1)) {
|
|
243
|
+
end.y += offset
|
|
244
|
+
shiftedYPointIndexes.add(si + 1)
|
|
245
|
+
}
|
|
148
246
|
}
|
|
149
247
|
}
|
|
150
248
|
}
|
|
@@ -160,14 +258,60 @@ export class TraceOverlapIssueSolver extends BaseSolver {
|
|
|
160
258
|
}
|
|
161
259
|
}
|
|
162
260
|
|
|
163
|
-
|
|
261
|
+
correctedTraceMap[original.mspPairId] = {
|
|
164
262
|
...current,
|
|
165
263
|
tracePath: cleaned,
|
|
166
264
|
}
|
|
167
265
|
}
|
|
168
266
|
})
|
|
169
267
|
|
|
170
|
-
|
|
268
|
+
return correctedTraceMap
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
private countDifferentNetIntersections(
|
|
272
|
+
correctedTraceMap: Record<MspConnectionPairId, SolvedTracePath>,
|
|
273
|
+
) {
|
|
274
|
+
const traces = Object.values(this.traceNetIslands).flatMap((island) =>
|
|
275
|
+
island.map((trace) => correctedTraceMap[trace.mspPairId] ?? trace),
|
|
276
|
+
)
|
|
277
|
+
let intersectionCount = 0
|
|
278
|
+
|
|
279
|
+
for (let traceIndex = 0; traceIndex < traces.length; traceIndex++) {
|
|
280
|
+
const trace = traces[traceIndex]!
|
|
281
|
+
for (
|
|
282
|
+
let otherTraceIndex = traceIndex + 1;
|
|
283
|
+
otherTraceIndex < traces.length;
|
|
284
|
+
otherTraceIndex++
|
|
285
|
+
) {
|
|
286
|
+
const otherTrace = traces[otherTraceIndex]!
|
|
287
|
+
if (trace.globalConnNetId === otherTrace.globalConnNetId) continue
|
|
288
|
+
|
|
289
|
+
for (
|
|
290
|
+
let segmentIndex = 0;
|
|
291
|
+
segmentIndex < trace.tracePath.length - 1;
|
|
292
|
+
segmentIndex++
|
|
293
|
+
) {
|
|
294
|
+
for (
|
|
295
|
+
let otherSegmentIndex = 0;
|
|
296
|
+
otherSegmentIndex < otherTrace.tracePath.length - 1;
|
|
297
|
+
otherSegmentIndex++
|
|
298
|
+
) {
|
|
299
|
+
if (
|
|
300
|
+
doSegmentsIntersect(
|
|
301
|
+
trace.tracePath[segmentIndex]!,
|
|
302
|
+
trace.tracePath[segmentIndex + 1]!,
|
|
303
|
+
otherTrace.tracePath[otherSegmentIndex]!,
|
|
304
|
+
otherTrace.tracePath[otherSegmentIndex + 1]!,
|
|
305
|
+
)
|
|
306
|
+
) {
|
|
307
|
+
intersectionCount++
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return intersectionCount
|
|
171
315
|
}
|
|
172
316
|
|
|
173
317
|
private getObstacleAwareOffset({
|
|
@@ -6,20 +6,22 @@ import type { ConnectivityMap } from "connectivity-map"
|
|
|
6
6
|
import {
|
|
7
7
|
TraceOverlapIssueSolver,
|
|
8
8
|
type OverlappingTraceSegmentLocator,
|
|
9
|
+
type TraceInteractionKind,
|
|
9
10
|
} from "./TraceOverlapIssueSolver/TraceOverlapIssueSolver"
|
|
10
11
|
import type { MspConnectionPairId } from "../MspConnectionPairSolver/MspConnectionPairSolver"
|
|
11
12
|
|
|
12
13
|
type ConnNetId = string
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
|
-
* This solver finds traces that overlap
|
|
16
|
-
* connected via the globalConnMap, then shifts them
|
|
17
|
-
*
|
|
16
|
+
* This solver finds traces that overlap or meet collinearly and aren't
|
|
17
|
+
* connected via the globalConnMap, then shifts them apart in the direction
|
|
18
|
+
* that minimizes the resulting intersections.
|
|
18
19
|
*
|
|
19
20
|
* All traces are orthogonal, so for traces to be considered overlapping, they
|
|
20
|
-
* need to each have a segment where both are horizontal or both are vertical
|
|
21
|
-
*
|
|
22
|
-
*
|
|
21
|
+
* need to each have a segment where both are horizontal or both are vertical,
|
|
22
|
+
* the segments must be on the same axis, and their ranges must overlap. A
|
|
23
|
+
* point contact is also handled when both segments are internal rails, where
|
|
24
|
+
* it can be separated without moving an anchored terminal segment.
|
|
23
25
|
*
|
|
24
26
|
* Each iteration, we find overlapping traces that aren't part of the same net.
|
|
25
27
|
* This is the same as finding two "trace net islands" that have an overlap.
|
|
@@ -89,6 +91,7 @@ export class TraceOverlapShiftSolver extends BaseSolver {
|
|
|
89
91
|
|
|
90
92
|
findNextOverlapIssue(): {
|
|
91
93
|
overlappingTraceSegments: Array<OverlappingTraceSegmentLocator>
|
|
94
|
+
interactionKind: TraceInteractionKind
|
|
92
95
|
} | null {
|
|
93
96
|
// Detect the next set of overlapping segments between two different net islands.
|
|
94
97
|
const EPS = 2e-3
|
|
@@ -111,23 +114,89 @@ export class TraceOverlapShiftSolver extends BaseSolver {
|
|
|
111
114
|
solvedTracePathIndex: number
|
|
112
115
|
traceSegmentIndex: number
|
|
113
116
|
}> = []
|
|
117
|
+
const pointContacts: Array<{
|
|
118
|
+
pathAIndex: number
|
|
119
|
+
segmentAIndex: number
|
|
120
|
+
pathBIndex: number
|
|
121
|
+
segmentBIndex: number
|
|
122
|
+
combinedLength: number
|
|
123
|
+
}> = []
|
|
114
124
|
|
|
115
125
|
// Track to avoid duplicates
|
|
116
126
|
const seenA = new Set<string>()
|
|
117
127
|
const seenB = new Set<string>()
|
|
118
128
|
|
|
119
|
-
const
|
|
129
|
+
const getRangeOverlap1D = (
|
|
120
130
|
a1: number,
|
|
121
131
|
a2: number,
|
|
122
132
|
b1: number,
|
|
123
133
|
b2: number,
|
|
124
|
-
)
|
|
134
|
+
) => {
|
|
125
135
|
const minA = Math.min(a1, a2)
|
|
126
136
|
const maxA = Math.max(a1, a2)
|
|
127
137
|
const minB = Math.min(b1, b2)
|
|
128
138
|
const maxB = Math.max(b1, b2)
|
|
129
|
-
|
|
130
|
-
|
|
139
|
+
return Math.min(maxA, maxB) - Math.max(minA, minB)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const recordCollinearInteraction = ({
|
|
143
|
+
pathAIndex,
|
|
144
|
+
segmentAIndex,
|
|
145
|
+
pathBIndex,
|
|
146
|
+
segmentBIndex,
|
|
147
|
+
rangeOverlap,
|
|
148
|
+
}: {
|
|
149
|
+
pathAIndex: number
|
|
150
|
+
segmentAIndex: number
|
|
151
|
+
pathBIndex: number
|
|
152
|
+
segmentBIndex: number
|
|
153
|
+
rangeOverlap: number
|
|
154
|
+
}) => {
|
|
155
|
+
if (rangeOverlap > EPS) {
|
|
156
|
+
const keyA = `${pathAIndex}:${segmentAIndex}`
|
|
157
|
+
const keyB = `${pathBIndex}:${segmentBIndex}`
|
|
158
|
+
if (!seenA.has(keyA)) {
|
|
159
|
+
overlapsA.push({
|
|
160
|
+
solvedTracePathIndex: pathAIndex,
|
|
161
|
+
traceSegmentIndex: segmentAIndex,
|
|
162
|
+
})
|
|
163
|
+
seenA.add(keyA)
|
|
164
|
+
}
|
|
165
|
+
if (!seenB.has(keyB)) {
|
|
166
|
+
overlapsB.push({
|
|
167
|
+
solvedTracePathIndex: pathBIndex,
|
|
168
|
+
traceSegmentIndex: segmentBIndex,
|
|
169
|
+
})
|
|
170
|
+
seenB.add(keyB)
|
|
171
|
+
}
|
|
172
|
+
return
|
|
173
|
+
}
|
|
174
|
+
if (rangeOverlap < -EPS) return
|
|
175
|
+
|
|
176
|
+
const pathA = pathsA[pathAIndex]!.tracePath
|
|
177
|
+
const pathB = pathsB[pathBIndex]!.tracePath
|
|
178
|
+
const segmentAStart = pathA[segmentAIndex]!
|
|
179
|
+
const segmentAEnd = pathA[segmentAIndex + 1]!
|
|
180
|
+
const segmentBStart = pathB[segmentBIndex]!
|
|
181
|
+
const segmentBEnd = pathB[segmentBIndex + 1]!
|
|
182
|
+
const hasTerminalSegment =
|
|
183
|
+
segmentAIndex === 0 ||
|
|
184
|
+
segmentAIndex === pathA.length - 2 ||
|
|
185
|
+
segmentBIndex === 0 ||
|
|
186
|
+
segmentBIndex === pathB.length - 2
|
|
187
|
+
if (hasTerminalSegment) return
|
|
188
|
+
|
|
189
|
+
pointContacts.push({
|
|
190
|
+
pathAIndex,
|
|
191
|
+
segmentAIndex,
|
|
192
|
+
pathBIndex,
|
|
193
|
+
segmentBIndex,
|
|
194
|
+
combinedLength:
|
|
195
|
+
Math.abs(segmentAStart.x - segmentAEnd.x) +
|
|
196
|
+
Math.abs(segmentAStart.y - segmentAEnd.y) +
|
|
197
|
+
Math.abs(segmentBStart.x - segmentBEnd.x) +
|
|
198
|
+
Math.abs(segmentBStart.y - segmentBEnd.y),
|
|
199
|
+
})
|
|
131
200
|
}
|
|
132
201
|
|
|
133
202
|
for (let pa = 0; pa < pathsA.length; pa++) {
|
|
@@ -150,48 +219,26 @@ export class TraceOverlapShiftSolver extends BaseSolver {
|
|
|
150
219
|
const bHorz = Math.abs(b1.y - b2.y) < EPS
|
|
151
220
|
if (!bVert && !bHorz) continue
|
|
152
221
|
|
|
153
|
-
// Only consider
|
|
222
|
+
// Only consider collinear, parallel interactions.
|
|
154
223
|
if (aVert && bVert) {
|
|
155
224
|
if (Math.abs(a1.x - b1.x) < EPS) {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
})
|
|
164
|
-
seenA.add(keyA)
|
|
165
|
-
}
|
|
166
|
-
if (!seenB.has(keyB)) {
|
|
167
|
-
overlapsB.push({
|
|
168
|
-
solvedTracePathIndex: pb,
|
|
169
|
-
traceSegmentIndex: sb,
|
|
170
|
-
})
|
|
171
|
-
seenB.add(keyB)
|
|
172
|
-
}
|
|
173
|
-
}
|
|
225
|
+
recordCollinearInteraction({
|
|
226
|
+
pathAIndex: pa,
|
|
227
|
+
segmentAIndex: sa,
|
|
228
|
+
pathBIndex: pb,
|
|
229
|
+
segmentBIndex: sb,
|
|
230
|
+
rangeOverlap: getRangeOverlap1D(a1.y, a2.y, b1.y, b2.y),
|
|
231
|
+
})
|
|
174
232
|
}
|
|
175
233
|
} else if (aHorz && bHorz) {
|
|
176
234
|
if (Math.abs(a1.y - b1.y) < EPS) {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
})
|
|
185
|
-
seenA.add(keyA)
|
|
186
|
-
}
|
|
187
|
-
if (!seenB.has(keyB)) {
|
|
188
|
-
overlapsB.push({
|
|
189
|
-
solvedTracePathIndex: pb,
|
|
190
|
-
traceSegmentIndex: sb,
|
|
191
|
-
})
|
|
192
|
-
seenB.add(keyB)
|
|
193
|
-
}
|
|
194
|
-
}
|
|
235
|
+
recordCollinearInteraction({
|
|
236
|
+
pathAIndex: pa,
|
|
237
|
+
segmentAIndex: sa,
|
|
238
|
+
pathBIndex: pb,
|
|
239
|
+
segmentBIndex: sb,
|
|
240
|
+
rangeOverlap: getRangeOverlap1D(a1.x, a2.x, b1.x, b2.x),
|
|
241
|
+
})
|
|
195
242
|
}
|
|
196
243
|
}
|
|
197
244
|
}
|
|
@@ -201,12 +248,44 @@ export class TraceOverlapShiftSolver extends BaseSolver {
|
|
|
201
248
|
|
|
202
249
|
if (overlapsA.length > 0 && overlapsB.length > 0) {
|
|
203
250
|
return {
|
|
251
|
+
interactionKind: "overlap",
|
|
204
252
|
overlappingTraceSegments: [
|
|
205
253
|
{ connNetId: netA, pathsWithOverlap: overlapsA },
|
|
206
254
|
{ connNetId: netB, pathsWithOverlap: overlapsB },
|
|
207
255
|
],
|
|
208
256
|
}
|
|
209
257
|
}
|
|
258
|
+
|
|
259
|
+
// An elbow can touch on both axes. Separating the longest internal
|
|
260
|
+
// rail changes one coordinate and avoids moving both sides of it.
|
|
261
|
+
const bestPointContact = pointContacts.sort(
|
|
262
|
+
(a, b) => b.combinedLength - a.combinedLength,
|
|
263
|
+
)[0]
|
|
264
|
+
if (bestPointContact) {
|
|
265
|
+
return {
|
|
266
|
+
interactionKind: "point_contact",
|
|
267
|
+
overlappingTraceSegments: [
|
|
268
|
+
{
|
|
269
|
+
connNetId: netA,
|
|
270
|
+
pathsWithOverlap: [
|
|
271
|
+
{
|
|
272
|
+
solvedTracePathIndex: bestPointContact.pathAIndex,
|
|
273
|
+
traceSegmentIndex: bestPointContact.segmentAIndex,
|
|
274
|
+
},
|
|
275
|
+
],
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
connNetId: netB,
|
|
279
|
+
pathsWithOverlap: [
|
|
280
|
+
{
|
|
281
|
+
solvedTracePathIndex: bestPointContact.pathBIndex,
|
|
282
|
+
traceSegmentIndex: bestPointContact.segmentBIndex,
|
|
283
|
+
},
|
|
284
|
+
],
|
|
285
|
+
},
|
|
286
|
+
],
|
|
287
|
+
}
|
|
288
|
+
}
|
|
210
289
|
}
|
|
211
290
|
}
|
|
212
291
|
|
|
@@ -306,10 +385,11 @@ export class TraceOverlapShiftSolver extends BaseSolver {
|
|
|
306
385
|
return
|
|
307
386
|
}
|
|
308
387
|
|
|
309
|
-
const { overlappingTraceSegments } = overlapIssue
|
|
388
|
+
const { interactionKind, overlappingTraceSegments } = overlapIssue
|
|
310
389
|
|
|
311
390
|
this.activeSubSolver = new TraceOverlapIssueSolver({
|
|
312
391
|
inputProblem: this.inputProblem,
|
|
392
|
+
interactionKind,
|
|
313
393
|
overlappingTraceSegments,
|
|
314
394
|
traceNetIslands: this.traceNetIslands,
|
|
315
395
|
})
|
package/package.json
CHANGED