@tscircuit/schematic-trace-solver 0.0.165 → 0.0.166
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.js +218 -5
- package/lib/solvers/SameNetJunctionAlignmentSolver/SameNetJunctionAlignmentSolver.ts +11 -5
- package/lib/solvers/SameNetJunctionAlignmentSolver/alignSameNetJunctions.ts +1 -1
- package/lib/solvers/SameNetJunctionAlignmentSolver/collapseSameNetCycles.ts +310 -0
- package/package.json +1 -1
- package/tests/bug-reports/bug-report-20260721T221026Z/__snapshots__/bug-report-20260721T221026Z.snap.svg +2 -2
- package/tests/repros/__snapshots__/board-1273-trace-overlap-cycle.snap.svg +2 -2
- package/tests/repros/__snapshots__/repro-isolated-rs485-isow7841.snap.svg +10 -10
- package/tests/repros/__snapshots__/repro-nrf52810-clock-routing.snap.svg +18 -18
- package/tests/repros/__snapshots__/repro-pmp11282-isolated-dcdc.snap.svg +18 -18
- package/tests/repros/repro-board-648-esp12f-section.test.ts +3 -0
- package/tests/repros/repro-isolated-rs485-isow7841.test.ts +33 -1
- package/tests/repros/repro-nrf52810-clock-routing.test.ts +26 -1
- package/tests/solvers/SameNetJunctionAlignmentSolver/collapse-same-net-cycles.test.ts +154 -0
package/dist/index.js
CHANGED
|
@@ -13009,6 +13009,214 @@ var alignSameNetJunctions = ({
|
|
|
13009
13009
|
};
|
|
13010
13010
|
};
|
|
13011
13011
|
|
|
13012
|
+
// lib/solvers/SameNetJunctionAlignmentSolver/collapseSameNetCycles.ts
|
|
13013
|
+
import { getSegmentIntersection as getSegmentIntersection4 } from "@tscircuit/math-utils/line-intersections";
|
|
13014
|
+
var TRACE_LENGTH_EPSILON = 1e-6;
|
|
13015
|
+
var getTracePathStartingAtPin = (trace, pin) => {
|
|
13016
|
+
const pathStart = trace.tracePath[0];
|
|
13017
|
+
const pathEnd = trace.tracePath.at(-1);
|
|
13018
|
+
if (!pathStart || !pathEnd) return null;
|
|
13019
|
+
if (pointsEqual(pathStart, pin)) return trace.tracePath;
|
|
13020
|
+
if (pointsEqual(pathEnd, pin)) return [...trace.tracePath].reverse();
|
|
13021
|
+
return null;
|
|
13022
|
+
};
|
|
13023
|
+
var getPerpendicularPathCrossings = (targetPath, donorPath) => {
|
|
13024
|
+
const crossings = [];
|
|
13025
|
+
const sharedEndpoint = targetPath[0];
|
|
13026
|
+
for (let targetSegmentIndex = 0; targetSegmentIndex < targetPath.length - 1; targetSegmentIndex++) {
|
|
13027
|
+
const targetStart = targetPath[targetSegmentIndex];
|
|
13028
|
+
const targetEnd = targetPath[targetSegmentIndex + 1];
|
|
13029
|
+
const targetOrientation = getRailOrientation(targetStart, targetEnd);
|
|
13030
|
+
if (!targetOrientation) continue;
|
|
13031
|
+
for (let donorSegmentIndex = 0; donorSegmentIndex < donorPath.length - 1; donorSegmentIndex++) {
|
|
13032
|
+
const donorStart = donorPath[donorSegmentIndex];
|
|
13033
|
+
const donorEnd = donorPath[donorSegmentIndex + 1];
|
|
13034
|
+
const donorOrientation = getRailOrientation(donorStart, donorEnd);
|
|
13035
|
+
if (!donorOrientation || donorOrientation === targetOrientation) continue;
|
|
13036
|
+
const intersectionPoint = getSegmentIntersection4(
|
|
13037
|
+
targetStart,
|
|
13038
|
+
targetEnd,
|
|
13039
|
+
donorStart,
|
|
13040
|
+
donorEnd
|
|
13041
|
+
);
|
|
13042
|
+
if (!intersectionPoint || pointsEqual(intersectionPoint, sharedEndpoint)) {
|
|
13043
|
+
continue;
|
|
13044
|
+
}
|
|
13045
|
+
crossings.push({
|
|
13046
|
+
intersectionPoint,
|
|
13047
|
+
targetSegmentIndex,
|
|
13048
|
+
donorSegmentIndex
|
|
13049
|
+
});
|
|
13050
|
+
}
|
|
13051
|
+
}
|
|
13052
|
+
return crossings;
|
|
13053
|
+
};
|
|
13054
|
+
var buildCollapsedCyclePath = ({
|
|
13055
|
+
targetTrace,
|
|
13056
|
+
targetPath,
|
|
13057
|
+
donorPath,
|
|
13058
|
+
crossing
|
|
13059
|
+
}) => {
|
|
13060
|
+
const pathFromSharedPin = simplifyPath([
|
|
13061
|
+
...donorPath.slice(0, crossing.donorSegmentIndex + 1),
|
|
13062
|
+
crossing.intersectionPoint,
|
|
13063
|
+
...targetPath.slice(crossing.targetSegmentIndex + 1)
|
|
13064
|
+
]);
|
|
13065
|
+
if (pointsEqual(targetTrace.tracePath[0], targetPath[0])) {
|
|
13066
|
+
return pathFromSharedPin;
|
|
13067
|
+
}
|
|
13068
|
+
return pathFromSharedPin.reverse();
|
|
13069
|
+
};
|
|
13070
|
+
var getNetLabelPlacementsForCycleCollapse = ({
|
|
13071
|
+
targetTrace,
|
|
13072
|
+
tracePath,
|
|
13073
|
+
netLabelPlacements
|
|
13074
|
+
}) => {
|
|
13075
|
+
const candidateNetLabelPlacements = moveAttachedLabelsToReroutedTrace({
|
|
13076
|
+
trace: targetTrace,
|
|
13077
|
+
originalTracePath: targetTrace.tracePath,
|
|
13078
|
+
reroutedTracePath: tracePath,
|
|
13079
|
+
netLabelPlacements
|
|
13080
|
+
});
|
|
13081
|
+
for (let labelIndex = 0; labelIndex < netLabelPlacements.length; labelIndex++) {
|
|
13082
|
+
const label = netLabelPlacements[labelIndex];
|
|
13083
|
+
if (label.globalConnNetId !== targetTrace.globalConnNetId) continue;
|
|
13084
|
+
if (!tracePathContainsPoint(targetTrace.tracePath, label.anchorPoint)) {
|
|
13085
|
+
continue;
|
|
13086
|
+
}
|
|
13087
|
+
const candidateLabel = candidateNetLabelPlacements[labelIndex];
|
|
13088
|
+
if (!tracePathContainsPoint(tracePath, candidateLabel.anchorPoint)) {
|
|
13089
|
+
return null;
|
|
13090
|
+
}
|
|
13091
|
+
}
|
|
13092
|
+
const otherNetLabelPlacements = candidateNetLabelPlacements.filter(
|
|
13093
|
+
(label) => label.globalConnNetId !== targetTrace.globalConnNetId
|
|
13094
|
+
);
|
|
13095
|
+
if (pathIntersectsAnyNetLabel({
|
|
13096
|
+
path: tracePath,
|
|
13097
|
+
netLabelPlacements: otherNetLabelPlacements
|
|
13098
|
+
})) {
|
|
13099
|
+
return null;
|
|
13100
|
+
}
|
|
13101
|
+
const sameNetLabelPlacements = candidateNetLabelPlacements.filter(
|
|
13102
|
+
(label) => label.globalConnNetId === targetTrace.globalConnNetId
|
|
13103
|
+
);
|
|
13104
|
+
if (pathEntersAnyNetLabel({
|
|
13105
|
+
path: tracePath,
|
|
13106
|
+
netLabelPlacements: sameNetLabelPlacements
|
|
13107
|
+
})) {
|
|
13108
|
+
return null;
|
|
13109
|
+
}
|
|
13110
|
+
return candidateNetLabelPlacements;
|
|
13111
|
+
};
|
|
13112
|
+
var candidateIsBetter = (candidate, bestCandidate) => {
|
|
13113
|
+
if (!bestCandidate) return true;
|
|
13114
|
+
const visibleLengthDelta = candidate.netVisibleLength - bestCandidate.netVisibleLength;
|
|
13115
|
+
if (Math.abs(visibleLengthDelta) > TRACE_LENGTH_EPSILON) {
|
|
13116
|
+
return visibleLengthDelta < 0;
|
|
13117
|
+
}
|
|
13118
|
+
return candidate.netVisibleSegmentCount < bestCandidate.netVisibleSegmentCount;
|
|
13119
|
+
};
|
|
13120
|
+
var getBestCycleCollapseCandidate = ({
|
|
13121
|
+
targetTrace,
|
|
13122
|
+
traces,
|
|
13123
|
+
netLabelPlacements
|
|
13124
|
+
}) => {
|
|
13125
|
+
const sameNetTraces = traces.filter(
|
|
13126
|
+
(trace) => trace.globalConnNetId === targetTrace.globalConnNetId
|
|
13127
|
+
);
|
|
13128
|
+
const baselineNetVisibleLength = getVisibleTraceLength(sameNetTraces);
|
|
13129
|
+
const baselineTargetVisibleLength = getVisibleTraceLength([targetTrace]);
|
|
13130
|
+
let bestCandidate = null;
|
|
13131
|
+
for (const donorTrace of sameNetTraces) {
|
|
13132
|
+
if (donorTrace.mspPairId === targetTrace.mspPairId) continue;
|
|
13133
|
+
const sharedPin = getSharedPin({
|
|
13134
|
+
donorTrace,
|
|
13135
|
+
branchTrace: targetTrace
|
|
13136
|
+
});
|
|
13137
|
+
if (!sharedPin) continue;
|
|
13138
|
+
const targetPath = getTracePathStartingAtPin(targetTrace, sharedPin);
|
|
13139
|
+
const donorPath = getTracePathStartingAtPin(donorTrace, sharedPin);
|
|
13140
|
+
if (!targetPath || !donorPath) continue;
|
|
13141
|
+
const crossings = getPerpendicularPathCrossings(targetPath, donorPath);
|
|
13142
|
+
for (const crossing of crossings) {
|
|
13143
|
+
const tracePath = buildCollapsedCyclePath({
|
|
13144
|
+
targetTrace,
|
|
13145
|
+
targetPath,
|
|
13146
|
+
donorPath,
|
|
13147
|
+
crossing
|
|
13148
|
+
});
|
|
13149
|
+
const candidateTrace = { ...targetTrace, tracePath };
|
|
13150
|
+
const candidateTargetVisibleLength = getVisibleTraceLength([
|
|
13151
|
+
candidateTrace
|
|
13152
|
+
]);
|
|
13153
|
+
if (candidateTargetVisibleLength > baselineTargetVisibleLength + TRACE_LENGTH_EPSILON) {
|
|
13154
|
+
continue;
|
|
13155
|
+
}
|
|
13156
|
+
const candidateNetLabelPlacements = getNetLabelPlacementsForCycleCollapse(
|
|
13157
|
+
{
|
|
13158
|
+
targetTrace,
|
|
13159
|
+
tracePath,
|
|
13160
|
+
netLabelPlacements
|
|
13161
|
+
}
|
|
13162
|
+
);
|
|
13163
|
+
if (!candidateNetLabelPlacements) continue;
|
|
13164
|
+
const candidateNetTraces = sameNetTraces.map((trace) => {
|
|
13165
|
+
if (trace.mspPairId === targetTrace.mspPairId) return candidateTrace;
|
|
13166
|
+
return trace;
|
|
13167
|
+
});
|
|
13168
|
+
const netVisibleLength = getVisibleTraceLength(candidateNetTraces);
|
|
13169
|
+
if (netVisibleLength >= baselineNetVisibleLength - TRACE_LENGTH_EPSILON) {
|
|
13170
|
+
continue;
|
|
13171
|
+
}
|
|
13172
|
+
const netVisibleSegmentCount = getVisibleTraceSegmentCount(candidateNetTraces);
|
|
13173
|
+
const candidate = {
|
|
13174
|
+
tracePath,
|
|
13175
|
+
netLabelPlacements: candidateNetLabelPlacements,
|
|
13176
|
+
netVisibleLength,
|
|
13177
|
+
netVisibleSegmentCount
|
|
13178
|
+
};
|
|
13179
|
+
if (candidateIsBetter(candidate, bestCandidate)) {
|
|
13180
|
+
bestCandidate = candidate;
|
|
13181
|
+
}
|
|
13182
|
+
}
|
|
13183
|
+
}
|
|
13184
|
+
return bestCandidate;
|
|
13185
|
+
};
|
|
13186
|
+
var collapseSameNetCycles = ({
|
|
13187
|
+
traces,
|
|
13188
|
+
netLabelPlacements
|
|
13189
|
+
}) => {
|
|
13190
|
+
const outputTraces = [...traces];
|
|
13191
|
+
let outputNetLabelPlacements = [...netLabelPlacements];
|
|
13192
|
+
let collapsedCycleCount = 0;
|
|
13193
|
+
let traceIndex = 0;
|
|
13194
|
+
while (traceIndex < outputTraces.length) {
|
|
13195
|
+
const targetTrace = outputTraces[traceIndex];
|
|
13196
|
+
const candidate = getBestCycleCollapseCandidate({
|
|
13197
|
+
targetTrace,
|
|
13198
|
+
traces: outputTraces,
|
|
13199
|
+
netLabelPlacements: outputNetLabelPlacements
|
|
13200
|
+
});
|
|
13201
|
+
if (!candidate) {
|
|
13202
|
+
traceIndex++;
|
|
13203
|
+
continue;
|
|
13204
|
+
}
|
|
13205
|
+
outputTraces[traceIndex] = {
|
|
13206
|
+
...targetTrace,
|
|
13207
|
+
tracePath: candidate.tracePath
|
|
13208
|
+
};
|
|
13209
|
+
outputNetLabelPlacements = candidate.netLabelPlacements;
|
|
13210
|
+
collapsedCycleCount++;
|
|
13211
|
+
traceIndex = 0;
|
|
13212
|
+
}
|
|
13213
|
+
return {
|
|
13214
|
+
traces: outputTraces,
|
|
13215
|
+
netLabelPlacements: outputNetLabelPlacements,
|
|
13216
|
+
collapsedCycleCount
|
|
13217
|
+
};
|
|
13218
|
+
};
|
|
13219
|
+
|
|
13012
13220
|
// lib/solvers/SameNetJunctionAlignmentSolver/placeGroundRailLabelsAtOuterEnd.ts
|
|
13013
13221
|
var placeGroundRailLabelsAtOuterEnd = ({
|
|
13014
13222
|
inputProblem,
|
|
@@ -13090,14 +13298,19 @@ var SameNetJunctionAlignmentSolver = class extends BaseSolver {
|
|
|
13090
13298
|
this.outputNetLabelPlacements = input.netLabelPlacements;
|
|
13091
13299
|
}
|
|
13092
13300
|
_step() {
|
|
13093
|
-
const
|
|
13094
|
-
|
|
13301
|
+
const alignment = alignSameNetJunctions(this.input);
|
|
13302
|
+
const cycleCollapse = collapseSameNetCycles({
|
|
13303
|
+
traces: alignment.traces,
|
|
13304
|
+
netLabelPlacements: alignment.netLabelPlacements
|
|
13305
|
+
});
|
|
13306
|
+
this.outputTraces = cycleCollapse.traces;
|
|
13095
13307
|
this.outputNetLabelPlacements = placeGroundRailLabelsAtOuterEnd({
|
|
13096
13308
|
inputProblem: this.input.inputProblem,
|
|
13097
|
-
traces:
|
|
13098
|
-
netLabelPlacements:
|
|
13309
|
+
traces: cycleCollapse.traces,
|
|
13310
|
+
netLabelPlacements: cycleCollapse.netLabelPlacements
|
|
13099
13311
|
});
|
|
13100
|
-
this.stats.alignedJunctionCount =
|
|
13312
|
+
this.stats.alignedJunctionCount = alignment.alignedJunctionCount;
|
|
13313
|
+
this.stats.collapsedCycleCount = cycleCollapse.collapsedCycleCount;
|
|
13101
13314
|
this.solved = true;
|
|
13102
13315
|
}
|
|
13103
13316
|
getOutput() {
|
|
@@ -6,6 +6,7 @@ import { visualizeInputProblem } from "lib/solvers/SchematicTracePipelineSolver/
|
|
|
6
6
|
import type { InputProblem } from "lib/types/InputProblem"
|
|
7
7
|
import { getColorFromString } from "lib/utils/getColorFromString"
|
|
8
8
|
import { alignSameNetJunctions } from "./alignSameNetJunctions"
|
|
9
|
+
import { collapseSameNetCycles } from "./collapseSameNetCycles"
|
|
9
10
|
import { placeGroundRailLabelsAtOuterEnd } from "./placeGroundRailLabelsAtOuterEnd"
|
|
10
11
|
|
|
11
12
|
interface SameNetJunctionAlignmentSolverInput {
|
|
@@ -28,14 +29,19 @@ export class SameNetJunctionAlignmentSolver extends BaseSolver {
|
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
override _step() {
|
|
31
|
-
const
|
|
32
|
-
|
|
32
|
+
const alignment = alignSameNetJunctions(this.input)
|
|
33
|
+
const cycleCollapse = collapseSameNetCycles({
|
|
34
|
+
traces: alignment.traces,
|
|
35
|
+
netLabelPlacements: alignment.netLabelPlacements,
|
|
36
|
+
})
|
|
37
|
+
this.outputTraces = cycleCollapse.traces
|
|
33
38
|
this.outputNetLabelPlacements = placeGroundRailLabelsAtOuterEnd({
|
|
34
39
|
inputProblem: this.input.inputProblem,
|
|
35
|
-
traces:
|
|
36
|
-
netLabelPlacements:
|
|
40
|
+
traces: cycleCollapse.traces,
|
|
41
|
+
netLabelPlacements: cycleCollapse.netLabelPlacements,
|
|
37
42
|
})
|
|
38
|
-
this.stats.alignedJunctionCount =
|
|
43
|
+
this.stats.alignedJunctionCount = alignment.alignedJunctionCount
|
|
44
|
+
this.stats.collapsedCycleCount = cycleCollapse.collapsedCycleCount
|
|
39
45
|
this.solved = true
|
|
40
46
|
}
|
|
41
47
|
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
import type { Point } from "@tscircuit/math-utils"
|
|
2
|
+
import { getSegmentIntersection } from "@tscircuit/math-utils/line-intersections"
|
|
3
|
+
import { moveAttachedLabelsToReroutedTrace } from "lib/solvers/Example28Solver/labelMovement"
|
|
4
|
+
import type { NetLabelPlacement } from "lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver"
|
|
5
|
+
import { tracePathContainsPoint } from "lib/solvers/RailNetLabelCornerPlacementSolver/geometry"
|
|
6
|
+
import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
7
|
+
import {
|
|
8
|
+
getRailOrientation,
|
|
9
|
+
getVisibleTraceLength,
|
|
10
|
+
getVisibleTraceSegmentCount,
|
|
11
|
+
pointsEqual,
|
|
12
|
+
} from "lib/solvers/TraceCleanupSolver/sameNetRailAlignment/geometry"
|
|
13
|
+
import { simplifyPath } from "lib/solvers/TraceCleanupSolver/simplifyPath"
|
|
14
|
+
import { getSharedPin } from "./alignSameNetJunctions"
|
|
15
|
+
import {
|
|
16
|
+
pathEntersAnyNetLabel,
|
|
17
|
+
pathIntersectsAnyNetLabel,
|
|
18
|
+
} from "./pathIntersectsAnyNetLabel"
|
|
19
|
+
|
|
20
|
+
interface CollapseSameNetCyclesInput {
|
|
21
|
+
traces: SolvedTracePath[]
|
|
22
|
+
netLabelPlacements: NetLabelPlacement[]
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface PathCrossing {
|
|
26
|
+
intersectionPoint: Point
|
|
27
|
+
targetSegmentIndex: number
|
|
28
|
+
donorSegmentIndex: number
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface CycleCollapseCandidate {
|
|
32
|
+
tracePath: Point[]
|
|
33
|
+
netLabelPlacements: NetLabelPlacement[]
|
|
34
|
+
netVisibleLength: number
|
|
35
|
+
netVisibleSegmentCount: number
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const TRACE_LENGTH_EPSILON = 1e-6
|
|
39
|
+
|
|
40
|
+
const getTracePathStartingAtPin = (
|
|
41
|
+
trace: SolvedTracePath,
|
|
42
|
+
pin: Point,
|
|
43
|
+
): Point[] | null => {
|
|
44
|
+
const pathStart = trace.tracePath[0]
|
|
45
|
+
const pathEnd = trace.tracePath.at(-1)
|
|
46
|
+
if (!pathStart || !pathEnd) return null
|
|
47
|
+
if (pointsEqual(pathStart, pin)) return trace.tracePath
|
|
48
|
+
if (pointsEqual(pathEnd, pin)) return [...trace.tracePath].reverse()
|
|
49
|
+
return null
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const getPerpendicularPathCrossings = (
|
|
53
|
+
targetPath: Point[],
|
|
54
|
+
donorPath: Point[],
|
|
55
|
+
): PathCrossing[] => {
|
|
56
|
+
const crossings: PathCrossing[] = []
|
|
57
|
+
const sharedEndpoint = targetPath[0]!
|
|
58
|
+
|
|
59
|
+
for (
|
|
60
|
+
let targetSegmentIndex = 0;
|
|
61
|
+
targetSegmentIndex < targetPath.length - 1;
|
|
62
|
+
targetSegmentIndex++
|
|
63
|
+
) {
|
|
64
|
+
const targetStart = targetPath[targetSegmentIndex]!
|
|
65
|
+
const targetEnd = targetPath[targetSegmentIndex + 1]!
|
|
66
|
+
const targetOrientation = getRailOrientation(targetStart, targetEnd)
|
|
67
|
+
if (!targetOrientation) continue
|
|
68
|
+
|
|
69
|
+
for (
|
|
70
|
+
let donorSegmentIndex = 0;
|
|
71
|
+
donorSegmentIndex < donorPath.length - 1;
|
|
72
|
+
donorSegmentIndex++
|
|
73
|
+
) {
|
|
74
|
+
const donorStart = donorPath[donorSegmentIndex]!
|
|
75
|
+
const donorEnd = donorPath[donorSegmentIndex + 1]!
|
|
76
|
+
const donorOrientation = getRailOrientation(donorStart, donorEnd)
|
|
77
|
+
if (!donorOrientation || donorOrientation === targetOrientation) continue
|
|
78
|
+
|
|
79
|
+
const intersectionPoint = getSegmentIntersection(
|
|
80
|
+
targetStart,
|
|
81
|
+
targetEnd,
|
|
82
|
+
donorStart,
|
|
83
|
+
donorEnd,
|
|
84
|
+
)
|
|
85
|
+
if (
|
|
86
|
+
!intersectionPoint ||
|
|
87
|
+
pointsEqual(intersectionPoint, sharedEndpoint)
|
|
88
|
+
) {
|
|
89
|
+
continue
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
crossings.push({
|
|
93
|
+
intersectionPoint,
|
|
94
|
+
targetSegmentIndex,
|
|
95
|
+
donorSegmentIndex,
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return crossings
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const buildCollapsedCyclePath = ({
|
|
104
|
+
targetTrace,
|
|
105
|
+
targetPath,
|
|
106
|
+
donorPath,
|
|
107
|
+
crossing,
|
|
108
|
+
}: {
|
|
109
|
+
targetTrace: SolvedTracePath
|
|
110
|
+
targetPath: Point[]
|
|
111
|
+
donorPath: Point[]
|
|
112
|
+
crossing: PathCrossing
|
|
113
|
+
}) => {
|
|
114
|
+
const pathFromSharedPin = simplifyPath([
|
|
115
|
+
...donorPath.slice(0, crossing.donorSegmentIndex + 1),
|
|
116
|
+
crossing.intersectionPoint,
|
|
117
|
+
...targetPath.slice(crossing.targetSegmentIndex + 1),
|
|
118
|
+
])
|
|
119
|
+
if (pointsEqual(targetTrace.tracePath[0]!, targetPath[0]!)) {
|
|
120
|
+
return pathFromSharedPin
|
|
121
|
+
}
|
|
122
|
+
return pathFromSharedPin.reverse()
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const getNetLabelPlacementsForCycleCollapse = ({
|
|
126
|
+
targetTrace,
|
|
127
|
+
tracePath,
|
|
128
|
+
netLabelPlacements,
|
|
129
|
+
}: {
|
|
130
|
+
targetTrace: SolvedTracePath
|
|
131
|
+
tracePath: Point[]
|
|
132
|
+
netLabelPlacements: NetLabelPlacement[]
|
|
133
|
+
}): NetLabelPlacement[] | null => {
|
|
134
|
+
const candidateNetLabelPlacements = moveAttachedLabelsToReroutedTrace({
|
|
135
|
+
trace: targetTrace,
|
|
136
|
+
originalTracePath: targetTrace.tracePath,
|
|
137
|
+
reroutedTracePath: tracePath,
|
|
138
|
+
netLabelPlacements,
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
for (
|
|
142
|
+
let labelIndex = 0;
|
|
143
|
+
labelIndex < netLabelPlacements.length;
|
|
144
|
+
labelIndex++
|
|
145
|
+
) {
|
|
146
|
+
const label = netLabelPlacements[labelIndex]!
|
|
147
|
+
if (label.globalConnNetId !== targetTrace.globalConnNetId) continue
|
|
148
|
+
if (!tracePathContainsPoint(targetTrace.tracePath, label.anchorPoint)) {
|
|
149
|
+
continue
|
|
150
|
+
}
|
|
151
|
+
const candidateLabel = candidateNetLabelPlacements[labelIndex]!
|
|
152
|
+
if (!tracePathContainsPoint(tracePath, candidateLabel.anchorPoint)) {
|
|
153
|
+
return null
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const otherNetLabelPlacements = candidateNetLabelPlacements.filter(
|
|
158
|
+
(label) => label.globalConnNetId !== targetTrace.globalConnNetId,
|
|
159
|
+
)
|
|
160
|
+
if (
|
|
161
|
+
pathIntersectsAnyNetLabel({
|
|
162
|
+
path: tracePath,
|
|
163
|
+
netLabelPlacements: otherNetLabelPlacements,
|
|
164
|
+
})
|
|
165
|
+
) {
|
|
166
|
+
return null
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const sameNetLabelPlacements = candidateNetLabelPlacements.filter(
|
|
170
|
+
(label) => label.globalConnNetId === targetTrace.globalConnNetId,
|
|
171
|
+
)
|
|
172
|
+
if (
|
|
173
|
+
pathEntersAnyNetLabel({
|
|
174
|
+
path: tracePath,
|
|
175
|
+
netLabelPlacements: sameNetLabelPlacements,
|
|
176
|
+
})
|
|
177
|
+
) {
|
|
178
|
+
return null
|
|
179
|
+
}
|
|
180
|
+
return candidateNetLabelPlacements
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const candidateIsBetter = (
|
|
184
|
+
candidate: CycleCollapseCandidate,
|
|
185
|
+
bestCandidate: CycleCollapseCandidate | null,
|
|
186
|
+
) => {
|
|
187
|
+
if (!bestCandidate) return true
|
|
188
|
+
const visibleLengthDelta =
|
|
189
|
+
candidate.netVisibleLength - bestCandidate.netVisibleLength
|
|
190
|
+
if (Math.abs(visibleLengthDelta) > TRACE_LENGTH_EPSILON) {
|
|
191
|
+
return visibleLengthDelta < 0
|
|
192
|
+
}
|
|
193
|
+
return candidate.netVisibleSegmentCount < bestCandidate.netVisibleSegmentCount
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const getBestCycleCollapseCandidate = ({
|
|
197
|
+
targetTrace,
|
|
198
|
+
traces,
|
|
199
|
+
netLabelPlacements,
|
|
200
|
+
}: {
|
|
201
|
+
targetTrace: SolvedTracePath
|
|
202
|
+
traces: SolvedTracePath[]
|
|
203
|
+
netLabelPlacements: NetLabelPlacement[]
|
|
204
|
+
}): CycleCollapseCandidate | null => {
|
|
205
|
+
const sameNetTraces = traces.filter(
|
|
206
|
+
(trace) => trace.globalConnNetId === targetTrace.globalConnNetId,
|
|
207
|
+
)
|
|
208
|
+
const baselineNetVisibleLength = getVisibleTraceLength(sameNetTraces)
|
|
209
|
+
const baselineTargetVisibleLength = getVisibleTraceLength([targetTrace])
|
|
210
|
+
let bestCandidate: CycleCollapseCandidate | null = null
|
|
211
|
+
|
|
212
|
+
for (const donorTrace of sameNetTraces) {
|
|
213
|
+
if (donorTrace.mspPairId === targetTrace.mspPairId) continue
|
|
214
|
+
const sharedPin = getSharedPin({
|
|
215
|
+
donorTrace,
|
|
216
|
+
branchTrace: targetTrace,
|
|
217
|
+
})
|
|
218
|
+
if (!sharedPin) continue
|
|
219
|
+
const targetPath = getTracePathStartingAtPin(targetTrace, sharedPin)
|
|
220
|
+
const donorPath = getTracePathStartingAtPin(donorTrace, sharedPin)
|
|
221
|
+
if (!targetPath || !donorPath) continue
|
|
222
|
+
|
|
223
|
+
const crossings = getPerpendicularPathCrossings(targetPath, donorPath)
|
|
224
|
+
for (const crossing of crossings) {
|
|
225
|
+
const tracePath = buildCollapsedCyclePath({
|
|
226
|
+
targetTrace,
|
|
227
|
+
targetPath,
|
|
228
|
+
donorPath,
|
|
229
|
+
crossing,
|
|
230
|
+
})
|
|
231
|
+
const candidateTrace = { ...targetTrace, tracePath }
|
|
232
|
+
const candidateTargetVisibleLength = getVisibleTraceLength([
|
|
233
|
+
candidateTrace,
|
|
234
|
+
])
|
|
235
|
+
if (
|
|
236
|
+
candidateTargetVisibleLength >
|
|
237
|
+
baselineTargetVisibleLength + TRACE_LENGTH_EPSILON
|
|
238
|
+
) {
|
|
239
|
+
continue
|
|
240
|
+
}
|
|
241
|
+
const candidateNetLabelPlacements = getNetLabelPlacementsForCycleCollapse(
|
|
242
|
+
{
|
|
243
|
+
targetTrace,
|
|
244
|
+
tracePath,
|
|
245
|
+
netLabelPlacements,
|
|
246
|
+
},
|
|
247
|
+
)
|
|
248
|
+
if (!candidateNetLabelPlacements) continue
|
|
249
|
+
|
|
250
|
+
const candidateNetTraces = sameNetTraces.map((trace) => {
|
|
251
|
+
if (trace.mspPairId === targetTrace.mspPairId) return candidateTrace
|
|
252
|
+
return trace
|
|
253
|
+
})
|
|
254
|
+
const netVisibleLength = getVisibleTraceLength(candidateNetTraces)
|
|
255
|
+
if (netVisibleLength >= baselineNetVisibleLength - TRACE_LENGTH_EPSILON) {
|
|
256
|
+
continue
|
|
257
|
+
}
|
|
258
|
+
const netVisibleSegmentCount =
|
|
259
|
+
getVisibleTraceSegmentCount(candidateNetTraces)
|
|
260
|
+
const candidate = {
|
|
261
|
+
tracePath,
|
|
262
|
+
netLabelPlacements: candidateNetLabelPlacements,
|
|
263
|
+
netVisibleLength,
|
|
264
|
+
netVisibleSegmentCount,
|
|
265
|
+
}
|
|
266
|
+
if (candidateIsBetter(candidate, bestCandidate)) {
|
|
267
|
+
bestCandidate = candidate
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return bestCandidate
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export const collapseSameNetCycles = ({
|
|
276
|
+
traces,
|
|
277
|
+
netLabelPlacements,
|
|
278
|
+
}: CollapseSameNetCyclesInput) => {
|
|
279
|
+
const outputTraces = [...traces]
|
|
280
|
+
let outputNetLabelPlacements = [...netLabelPlacements]
|
|
281
|
+
let collapsedCycleCount = 0
|
|
282
|
+
let traceIndex = 0
|
|
283
|
+
|
|
284
|
+
while (traceIndex < outputTraces.length) {
|
|
285
|
+
const targetTrace = outputTraces[traceIndex]!
|
|
286
|
+
const candidate = getBestCycleCollapseCandidate({
|
|
287
|
+
targetTrace,
|
|
288
|
+
traces: outputTraces,
|
|
289
|
+
netLabelPlacements: outputNetLabelPlacements,
|
|
290
|
+
})
|
|
291
|
+
if (!candidate) {
|
|
292
|
+
traceIndex++
|
|
293
|
+
continue
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
outputTraces[traceIndex] = {
|
|
297
|
+
...targetTrace,
|
|
298
|
+
tracePath: candidate.tracePath,
|
|
299
|
+
}
|
|
300
|
+
outputNetLabelPlacements = candidate.netLabelPlacements
|
|
301
|
+
collapsedCycleCount++
|
|
302
|
+
traceIndex = 0
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return {
|
|
306
|
+
traces: outputTraces,
|
|
307
|
+
netLabelPlacements: outputNetLabelPlacements,
|
|
308
|
+
collapsedCycleCount,
|
|
309
|
+
}
|
|
310
|
+
}
|