@tscircuit/schematic-trace-solver 0.0.104 → 0.0.106
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 +133 -80
- package/lib/solvers/TraceCleanupSolver/minimizeTurnsWithFilteredLabels.ts +78 -23
- package/lib/solvers/TraceCleanupSolver/sameNetRailAlignment/geometry.ts +40 -20
- package/package.json +1 -1
- package/site/bug-reports/bug-report-20260721T221026Z.page.tsx +4 -0
- package/tests/bug-reports/bug-report-20260721T221026Z/__snapshots__/bug-report-20260721T221026Z.snap.svg +345 -0
- package/tests/bug-reports/bug-report-20260721T221026Z/bug-report-20260721T221026Z.json +1750 -0
- package/tests/bug-reports/bug-report-20260721T221026Z/bug-report-20260721T221026Z.test.ts +12 -0
- package/tests/examples/__snapshots__/example19.snap.svg +3 -3
- package/tests/examples/example19.test.ts +4 -0
- package/tests/repros/__snapshots__/bugreport-001-gnd-overlap.snap.svg +1 -1
- package/tests/repros/__snapshots__/repro-core-subcircuit-missing-ground.snap.svg +56 -0
- package/tests/repros/__snapshots__/repro-example35-minimize-trace-crossing.snap.svg +256 -0
- package/tests/repros/__snapshots__/repro-vcc-pin1-detour.snap.svg +47 -0
- package/tests/repros/assets/repro-core-subcircuit-missing-ground.input.json +103 -0
- package/tests/repros/assets/repro-example35-minimize-trace-crossing.input.json +1561 -0
- package/tests/repros/repro-core-subcircuit-missing-ground.test.ts +14 -0
- package/tests/repros/repro-example35-minimize-trace-crossing.test.ts +22 -0
- package/tests/repros/repro-vcc-pin1-detour.test.ts +166 -0
package/dist/index.js
CHANGED
|
@@ -4774,6 +4774,95 @@ var minimizeTurns = ({
|
|
|
4774
4774
|
return finalSimplifiedPath;
|
|
4775
4775
|
};
|
|
4776
4776
|
|
|
4777
|
+
// lib/solvers/TraceCleanupSolver/sameNetRailAlignment/geometry.ts
|
|
4778
|
+
var RAIL_ALIGNMENT_EPSILON = 2e-3;
|
|
4779
|
+
var nearlyEqual = (a, b) => Math.abs(a - b) < RAIL_ALIGNMENT_EPSILON;
|
|
4780
|
+
var isHorizontal2 = (a, b) => nearlyEqual(a.y, b.y);
|
|
4781
|
+
var isVertical2 = (a, b) => nearlyEqual(a.x, b.x);
|
|
4782
|
+
var isPositiveLength = (a, b) => Math.abs(a.x - b.x) >= RAIL_ALIGNMENT_EPSILON || Math.abs(a.y - b.y) >= RAIL_ALIGNMENT_EPSILON;
|
|
4783
|
+
var getRailOrientation = (a, b) => {
|
|
4784
|
+
if (!isPositiveLength(a, b)) return null;
|
|
4785
|
+
if (isVertical2(a, b)) return "vertical";
|
|
4786
|
+
if (isHorizontal2(a, b)) return "horizontal";
|
|
4787
|
+
return null;
|
|
4788
|
+
};
|
|
4789
|
+
var rangesTouchOrOverlap = (a, b) => Math.min(a.maxAlong, b.maxAlong) - Math.max(a.minAlong, b.minAlong) >= -RAIL_ALIGNMENT_EPSILON;
|
|
4790
|
+
var pointsEqual = (a, b) => nearlyEqual(a.x, b.x) && nearlyEqual(a.y, b.y);
|
|
4791
|
+
var getDistinctCoordinates = (coordinates) => {
|
|
4792
|
+
const distinct = [];
|
|
4793
|
+
for (const coordinate of coordinates) {
|
|
4794
|
+
if (!distinct.some((item) => nearlyEqual(item, coordinate))) {
|
|
4795
|
+
distinct.push(coordinate);
|
|
4796
|
+
}
|
|
4797
|
+
}
|
|
4798
|
+
return distinct;
|
|
4799
|
+
};
|
|
4800
|
+
var getMergedIntervalMetrics = (intervals) => {
|
|
4801
|
+
const groups = [];
|
|
4802
|
+
for (const interval of intervals) {
|
|
4803
|
+
const group = groups.find(
|
|
4804
|
+
(items) => nearlyEqual(items[0].coordinate, interval.coordinate)
|
|
4805
|
+
);
|
|
4806
|
+
if (group) group.push(interval);
|
|
4807
|
+
else groups.push([interval]);
|
|
4808
|
+
}
|
|
4809
|
+
return groups.reduce(
|
|
4810
|
+
(total, group) => {
|
|
4811
|
+
const sorted = [...group].sort((a, b) => a.min - b.min);
|
|
4812
|
+
let groupLength = 0;
|
|
4813
|
+
let segmentCount = 1;
|
|
4814
|
+
let currentMin = sorted[0].min;
|
|
4815
|
+
let currentMax = sorted[0].max;
|
|
4816
|
+
for (const interval of sorted.slice(1)) {
|
|
4817
|
+
if (interval.min <= currentMax + RAIL_ALIGNMENT_EPSILON) {
|
|
4818
|
+
currentMax = Math.max(currentMax, interval.max);
|
|
4819
|
+
} else {
|
|
4820
|
+
groupLength += currentMax - currentMin;
|
|
4821
|
+
segmentCount++;
|
|
4822
|
+
currentMin = interval.min;
|
|
4823
|
+
currentMax = interval.max;
|
|
4824
|
+
}
|
|
4825
|
+
}
|
|
4826
|
+
return {
|
|
4827
|
+
length: total.length + groupLength + currentMax - currentMin,
|
|
4828
|
+
segmentCount: total.segmentCount + segmentCount
|
|
4829
|
+
};
|
|
4830
|
+
},
|
|
4831
|
+
{ length: 0, segmentCount: 0 }
|
|
4832
|
+
);
|
|
4833
|
+
};
|
|
4834
|
+
var getVisibleTraceMetrics = (traces) => {
|
|
4835
|
+
const horizontal = [];
|
|
4836
|
+
const vertical = [];
|
|
4837
|
+
for (const trace of traces) {
|
|
4838
|
+
for (let index = 0; index < trace.tracePath.length - 1; index++) {
|
|
4839
|
+
const start = trace.tracePath[index];
|
|
4840
|
+
const end = trace.tracePath[index + 1];
|
|
4841
|
+
if (isHorizontal2(start, end)) {
|
|
4842
|
+
horizontal.push({
|
|
4843
|
+
coordinate: start.y,
|
|
4844
|
+
min: Math.min(start.x, end.x),
|
|
4845
|
+
max: Math.max(start.x, end.x)
|
|
4846
|
+
});
|
|
4847
|
+
} else if (isVertical2(start, end)) {
|
|
4848
|
+
vertical.push({
|
|
4849
|
+
coordinate: start.x,
|
|
4850
|
+
min: Math.min(start.y, end.y),
|
|
4851
|
+
max: Math.max(start.y, end.y)
|
|
4852
|
+
});
|
|
4853
|
+
}
|
|
4854
|
+
}
|
|
4855
|
+
}
|
|
4856
|
+
const horizontalMetrics = getMergedIntervalMetrics(horizontal);
|
|
4857
|
+
const verticalMetrics = getMergedIntervalMetrics(vertical);
|
|
4858
|
+
return {
|
|
4859
|
+
length: horizontalMetrics.length + verticalMetrics.length,
|
|
4860
|
+
segmentCount: horizontalMetrics.segmentCount + verticalMetrics.segmentCount
|
|
4861
|
+
};
|
|
4862
|
+
};
|
|
4863
|
+
var getVisibleTraceLength = (traces) => getVisibleTraceMetrics(traces).length;
|
|
4864
|
+
var getVisibleTraceSegmentCount = (traces) => getVisibleTraceMetrics(traces).segmentCount;
|
|
4865
|
+
|
|
4777
4866
|
// lib/solvers/TraceCleanupSolver/minimizeTurnsWithFilteredLabels.ts
|
|
4778
4867
|
var minimizeTurnsWithFilteredLabels = ({
|
|
4779
4868
|
targetMspConnectionPairId,
|
|
@@ -4789,11 +4878,16 @@ var minimizeTurnsWithFilteredLabels = ({
|
|
|
4789
4878
|
if (!targetTrace) {
|
|
4790
4879
|
throw new Error(`Target trace ${targetMspConnectionPairId} not found`);
|
|
4791
4880
|
}
|
|
4792
|
-
const
|
|
4793
|
-
|
|
4881
|
+
const targetPinIds = new Set(targetTrace.pinIds);
|
|
4882
|
+
const otherTraces = traces.filter(
|
|
4883
|
+
(trace) => trace.mspPairId !== targetMspConnectionPairId
|
|
4794
4884
|
);
|
|
4885
|
+
const relaxedObstacleTraces = otherTraces.filter((trace) => {
|
|
4886
|
+
const sharesEndpoint = trace.pinIds.some((pinId) => targetPinIds.has(pinId));
|
|
4887
|
+
return trace.globalConnNetId !== targetTrace.globalConnNetId || !sharesEndpoint;
|
|
4888
|
+
});
|
|
4795
4889
|
const TRACE_WIDTH = 0.01;
|
|
4796
|
-
const
|
|
4890
|
+
const getTraceObstacles2 = (obstacleTraces) => obstacleTraces.flatMap(
|
|
4797
4891
|
(trace, i) => trace.tracePath.slice(0, -1).map((p1, pi) => {
|
|
4798
4892
|
const p2 = trace.tracePath[pi + 1];
|
|
4799
4893
|
return {
|
|
@@ -4814,7 +4908,6 @@ var minimizeTurnsWithFilteredLabels = ({
|
|
|
4814
4908
|
maxX: obs.maxX + PADDING,
|
|
4815
4909
|
maxY: obs.maxY + PADDING
|
|
4816
4910
|
}));
|
|
4817
|
-
const combinedObstacles = [...staticObstacles, ...traceObstacles];
|
|
4818
4911
|
const originalPath = targetTrace.tracePath;
|
|
4819
4912
|
const filteredLabels = allLabelPlacements.filter((label) => {
|
|
4820
4913
|
const originalNetIds = mergedLabelNetIdMap[label.globalConnNetId];
|
|
@@ -4829,12 +4922,46 @@ var minimizeTurnsWithFilteredLabels = ({
|
|
|
4829
4922
|
minY: nl.center.y - nl.height / 2 - paddingBuffer,
|
|
4830
4923
|
maxY: nl.center.y + nl.height / 2 + paddingBuffer
|
|
4831
4924
|
}));
|
|
4832
|
-
const
|
|
4925
|
+
const strictPath = minimizeTurns({
|
|
4926
|
+
path: originalPath,
|
|
4927
|
+
obstacles: [...staticObstacles, ...getTraceObstacles2(otherTraces)],
|
|
4928
|
+
labelBounds,
|
|
4929
|
+
originalPath
|
|
4930
|
+
});
|
|
4931
|
+
const relaxedPath = minimizeTurns({
|
|
4833
4932
|
path: originalPath,
|
|
4834
|
-
obstacles:
|
|
4933
|
+
obstacles: [
|
|
4934
|
+
...staticObstacles,
|
|
4935
|
+
...getTraceObstacles2(relaxedObstacleTraces)
|
|
4936
|
+
],
|
|
4835
4937
|
labelBounds,
|
|
4836
4938
|
originalPath
|
|
4837
4939
|
});
|
|
4940
|
+
const sameNetTraces = otherTraces.filter(
|
|
4941
|
+
(trace) => trace.globalConnNetId === targetTrace.globalConnNetId
|
|
4942
|
+
);
|
|
4943
|
+
const getSameNetReadability = (tracePath) => {
|
|
4944
|
+
const tracesWithCandidate = [
|
|
4945
|
+
...sameNetTraces,
|
|
4946
|
+
{ ...targetTrace, tracePath }
|
|
4947
|
+
];
|
|
4948
|
+
return {
|
|
4949
|
+
segmentCount: getVisibleTraceSegmentCount(tracesWithCandidate),
|
|
4950
|
+
visibleLength: getVisibleTraceLength(tracesWithCandidate)
|
|
4951
|
+
};
|
|
4952
|
+
};
|
|
4953
|
+
const strictTurns = countTurns(strictPath);
|
|
4954
|
+
const relaxedTurns = countTurns(relaxedPath);
|
|
4955
|
+
let newPath = strictPath;
|
|
4956
|
+
if (relaxedTurns < strictTurns) {
|
|
4957
|
+
newPath = relaxedPath;
|
|
4958
|
+
} else if (relaxedTurns === strictTurns) {
|
|
4959
|
+
const strictReadability = getSameNetReadability(strictPath);
|
|
4960
|
+
const relaxedReadability = getSameNetReadability(relaxedPath);
|
|
4961
|
+
if (relaxedReadability.segmentCount < strictReadability.segmentCount || relaxedReadability.segmentCount === strictReadability.segmentCount && relaxedReadability.visibleLength < strictReadability.visibleLength - RAIL_ALIGNMENT_EPSILON) {
|
|
4962
|
+
newPath = relaxedPath;
|
|
4963
|
+
}
|
|
4964
|
+
}
|
|
4838
4965
|
return {
|
|
4839
4966
|
...targetTrace,
|
|
4840
4967
|
tracePath: newPath
|
|
@@ -4978,80 +5105,6 @@ var balanceZShapes = ({
|
|
|
4978
5105
|
};
|
|
4979
5106
|
};
|
|
4980
5107
|
|
|
4981
|
-
// lib/solvers/TraceCleanupSolver/sameNetRailAlignment/geometry.ts
|
|
4982
|
-
var RAIL_ALIGNMENT_EPSILON = 2e-3;
|
|
4983
|
-
var nearlyEqual = (a, b) => Math.abs(a - b) < RAIL_ALIGNMENT_EPSILON;
|
|
4984
|
-
var isHorizontal2 = (a, b) => nearlyEqual(a.y, b.y);
|
|
4985
|
-
var isVertical2 = (a, b) => nearlyEqual(a.x, b.x);
|
|
4986
|
-
var isPositiveLength = (a, b) => Math.abs(a.x - b.x) >= RAIL_ALIGNMENT_EPSILON || Math.abs(a.y - b.y) >= RAIL_ALIGNMENT_EPSILON;
|
|
4987
|
-
var getRailOrientation = (a, b) => {
|
|
4988
|
-
if (!isPositiveLength(a, b)) return null;
|
|
4989
|
-
if (isVertical2(a, b)) return "vertical";
|
|
4990
|
-
if (isHorizontal2(a, b)) return "horizontal";
|
|
4991
|
-
return null;
|
|
4992
|
-
};
|
|
4993
|
-
var rangesTouchOrOverlap = (a, b) => Math.min(a.maxAlong, b.maxAlong) - Math.max(a.minAlong, b.minAlong) >= -RAIL_ALIGNMENT_EPSILON;
|
|
4994
|
-
var pointsEqual = (a, b) => nearlyEqual(a.x, b.x) && nearlyEqual(a.y, b.y);
|
|
4995
|
-
var getDistinctCoordinates = (coordinates) => {
|
|
4996
|
-
const distinct = [];
|
|
4997
|
-
for (const coordinate of coordinates) {
|
|
4998
|
-
if (!distinct.some((item) => nearlyEqual(item, coordinate))) {
|
|
4999
|
-
distinct.push(coordinate);
|
|
5000
|
-
}
|
|
5001
|
-
}
|
|
5002
|
-
return distinct;
|
|
5003
|
-
};
|
|
5004
|
-
var getMergedIntervalLength = (intervals) => {
|
|
5005
|
-
const groups = [];
|
|
5006
|
-
for (const interval of intervals) {
|
|
5007
|
-
const group = groups.find(
|
|
5008
|
-
(items) => nearlyEqual(items[0].coordinate, interval.coordinate)
|
|
5009
|
-
);
|
|
5010
|
-
if (group) group.push(interval);
|
|
5011
|
-
else groups.push([interval]);
|
|
5012
|
-
}
|
|
5013
|
-
return groups.reduce((total, group) => {
|
|
5014
|
-
const sorted = [...group].sort((a, b) => a.min - b.min);
|
|
5015
|
-
let groupLength = 0;
|
|
5016
|
-
let currentMin = sorted[0].min;
|
|
5017
|
-
let currentMax = sorted[0].max;
|
|
5018
|
-
for (const interval of sorted.slice(1)) {
|
|
5019
|
-
if (interval.min <= currentMax + RAIL_ALIGNMENT_EPSILON) {
|
|
5020
|
-
currentMax = Math.max(currentMax, interval.max);
|
|
5021
|
-
} else {
|
|
5022
|
-
groupLength += currentMax - currentMin;
|
|
5023
|
-
currentMin = interval.min;
|
|
5024
|
-
currentMax = interval.max;
|
|
5025
|
-
}
|
|
5026
|
-
}
|
|
5027
|
-
return total + groupLength + currentMax - currentMin;
|
|
5028
|
-
}, 0);
|
|
5029
|
-
};
|
|
5030
|
-
var getVisibleTraceLength = (traces) => {
|
|
5031
|
-
const horizontal = [];
|
|
5032
|
-
const vertical = [];
|
|
5033
|
-
for (const trace of traces) {
|
|
5034
|
-
for (let index = 0; index < trace.tracePath.length - 1; index++) {
|
|
5035
|
-
const start = trace.tracePath[index];
|
|
5036
|
-
const end = trace.tracePath[index + 1];
|
|
5037
|
-
if (isHorizontal2(start, end)) {
|
|
5038
|
-
horizontal.push({
|
|
5039
|
-
coordinate: start.y,
|
|
5040
|
-
min: Math.min(start.x, end.x),
|
|
5041
|
-
max: Math.max(start.x, end.x)
|
|
5042
|
-
});
|
|
5043
|
-
} else if (isVertical2(start, end)) {
|
|
5044
|
-
vertical.push({
|
|
5045
|
-
coordinate: start.x,
|
|
5046
|
-
min: Math.min(start.y, end.y),
|
|
5047
|
-
max: Math.max(start.y, end.y)
|
|
5048
|
-
});
|
|
5049
|
-
}
|
|
5050
|
-
}
|
|
5051
|
-
}
|
|
5052
|
-
return getMergedIntervalLength(horizontal) + getMergedIntervalLength(vertical);
|
|
5053
|
-
};
|
|
5054
|
-
|
|
5055
5108
|
// lib/solvers/TraceCleanupSolver/sameNetRailAlignment/moveRailSegments.ts
|
|
5056
5109
|
var moveRailSegments = (trace, segments, coordinate) => {
|
|
5057
5110
|
const pointsToMove = /* @__PURE__ */ new Set();
|
|
@@ -3,13 +3,18 @@ import { minimizeTurns } from "./turnMinimization"
|
|
|
3
3
|
import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
4
4
|
import { getObstacleRects } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/rect"
|
|
5
5
|
import type { NetLabelPlacement } from "../NetLabelPlacementSolver/NetLabelPlacementSolver"
|
|
6
|
+
import { countTurns } from "./countTurns"
|
|
7
|
+
import {
|
|
8
|
+
getVisibleTraceLength,
|
|
9
|
+
getVisibleTraceSegmentCount,
|
|
10
|
+
RAIL_ALIGNMENT_EPSILON,
|
|
11
|
+
} from "./sameNetRailAlignment/geometry"
|
|
6
12
|
|
|
7
13
|
/**
|
|
8
|
-
* Minimizes
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* Finally, it uses a turn minimization algorithm to find a new path for the target trace that avoids these combined obstacles.
|
|
14
|
+
* Minimizes turns with a strict pass that treats every other trace as an
|
|
15
|
+
* obstacle and a relaxed pass that permits joining endpoint-sharing same-net
|
|
16
|
+
* branches. Equivalent-turn routes are compared by their rendered same-net
|
|
17
|
+
* complexity so aligned rails win without arbitrarily shifting local routes.
|
|
13
18
|
*/
|
|
14
19
|
export const minimizeTurnsWithFilteredLabels = ({
|
|
15
20
|
targetMspConnectionPairId,
|
|
@@ -33,23 +38,31 @@ export const minimizeTurnsWithFilteredLabels = ({
|
|
|
33
38
|
throw new Error(`Target trace ${targetMspConnectionPairId} not found`)
|
|
34
39
|
}
|
|
35
40
|
|
|
36
|
-
const
|
|
37
|
-
|
|
41
|
+
const targetPinIds = new Set(targetTrace.pinIds)
|
|
42
|
+
const otherTraces = traces.filter(
|
|
43
|
+
(trace) => trace.mspPairId !== targetMspConnectionPairId,
|
|
38
44
|
)
|
|
45
|
+
const relaxedObstacleTraces = otherTraces.filter((trace) => {
|
|
46
|
+
const sharesEndpoint = trace.pinIds.some((pinId) => targetPinIds.has(pinId))
|
|
47
|
+
return (
|
|
48
|
+
trace.globalConnNetId !== targetTrace.globalConnNetId || !sharesEndpoint
|
|
49
|
+
)
|
|
50
|
+
})
|
|
39
51
|
|
|
40
52
|
const TRACE_WIDTH = 0.01
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
+
const getTraceObstacles = (obstacleTraces: SolvedTracePath[]) =>
|
|
54
|
+
obstacleTraces.flatMap((trace, i) =>
|
|
55
|
+
trace.tracePath.slice(0, -1).map((p1, pi) => {
|
|
56
|
+
const p2 = trace.tracePath[pi + 1]!
|
|
57
|
+
return {
|
|
58
|
+
chipId: `trace-obstacle-${i}-${pi}`,
|
|
59
|
+
minX: Math.min(p1.x, p2.x) - TRACE_WIDTH / 2,
|
|
60
|
+
minY: Math.min(p1.y, p2.y) - TRACE_WIDTH / 2,
|
|
61
|
+
maxX: Math.max(p1.x, p2.x) + TRACE_WIDTH / 2,
|
|
62
|
+
maxY: Math.max(p1.y, p2.y) + TRACE_WIDTH / 2,
|
|
63
|
+
}
|
|
64
|
+
}),
|
|
65
|
+
)
|
|
53
66
|
|
|
54
67
|
const staticObstaclesRaw = getObstacleRects(inputProblem)
|
|
55
68
|
const PADDING = 0.01
|
|
@@ -61,8 +74,6 @@ export const minimizeTurnsWithFilteredLabels = ({
|
|
|
61
74
|
maxY: obs.maxY + PADDING,
|
|
62
75
|
}))
|
|
63
76
|
|
|
64
|
-
const combinedObstacles = [...staticObstacles, ...traceObstacles]
|
|
65
|
-
|
|
66
77
|
const originalPath = targetTrace.tracePath
|
|
67
78
|
const filteredLabels = allLabelPlacements.filter((label) => {
|
|
68
79
|
const originalNetIds = mergedLabelNetIdMap[label.globalConnNetId]
|
|
@@ -79,13 +90,57 @@ export const minimizeTurnsWithFilteredLabels = ({
|
|
|
79
90
|
maxY: nl.center.y + nl.height / 2 + paddingBuffer,
|
|
80
91
|
}))
|
|
81
92
|
|
|
82
|
-
const
|
|
93
|
+
const strictPath = minimizeTurns({
|
|
83
94
|
path: originalPath,
|
|
84
|
-
obstacles:
|
|
95
|
+
obstacles: [...staticObstacles, ...getTraceObstacles(otherTraces)],
|
|
85
96
|
labelBounds,
|
|
86
97
|
originalPath: originalPath,
|
|
87
98
|
})
|
|
88
99
|
|
|
100
|
+
const relaxedPath = minimizeTurns({
|
|
101
|
+
path: originalPath,
|
|
102
|
+
obstacles: [
|
|
103
|
+
...staticObstacles,
|
|
104
|
+
...getTraceObstacles(relaxedObstacleTraces),
|
|
105
|
+
],
|
|
106
|
+
labelBounds,
|
|
107
|
+
originalPath: originalPath,
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
const sameNetTraces = otherTraces.filter(
|
|
111
|
+
(trace) => trace.globalConnNetId === targetTrace.globalConnNetId,
|
|
112
|
+
)
|
|
113
|
+
const getSameNetReadability = (tracePath: SolvedTracePath["tracePath"]) => {
|
|
114
|
+
const tracesWithCandidate = [
|
|
115
|
+
...sameNetTraces,
|
|
116
|
+
{ ...targetTrace, tracePath },
|
|
117
|
+
]
|
|
118
|
+
return {
|
|
119
|
+
segmentCount: getVisibleTraceSegmentCount(tracesWithCandidate),
|
|
120
|
+
visibleLength: getVisibleTraceLength(tracesWithCandidate),
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const strictTurns = countTurns(strictPath)
|
|
125
|
+
const relaxedTurns = countTurns(relaxedPath)
|
|
126
|
+
let newPath = strictPath
|
|
127
|
+
|
|
128
|
+
if (relaxedTurns < strictTurns) {
|
|
129
|
+
newPath = relaxedPath
|
|
130
|
+
} else if (relaxedTurns === strictTurns) {
|
|
131
|
+
const strictReadability = getSameNetReadability(strictPath)
|
|
132
|
+
const relaxedReadability = getSameNetReadability(relaxedPath)
|
|
133
|
+
|
|
134
|
+
if (
|
|
135
|
+
relaxedReadability.segmentCount < strictReadability.segmentCount ||
|
|
136
|
+
(relaxedReadability.segmentCount === strictReadability.segmentCount &&
|
|
137
|
+
relaxedReadability.visibleLength <
|
|
138
|
+
strictReadability.visibleLength - RAIL_ALIGNMENT_EPSILON)
|
|
139
|
+
) {
|
|
140
|
+
newPath = relaxedPath
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
89
144
|
return {
|
|
90
145
|
...targetTrace,
|
|
91
146
|
tracePath: newPath,
|
|
@@ -48,7 +48,7 @@ interface Interval {
|
|
|
48
48
|
max: number
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
const
|
|
51
|
+
const getMergedIntervalMetrics = (intervals: Interval[]) => {
|
|
52
52
|
const groups: Interval[][] = []
|
|
53
53
|
for (const interval of intervals) {
|
|
54
54
|
const group = groups.find((items) =>
|
|
@@ -58,28 +58,35 @@ const getMergedIntervalLength = (intervals: Interval[]) => {
|
|
|
58
58
|
else groups.push([interval])
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
return groups.reduce(
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
61
|
+
return groups.reduce(
|
|
62
|
+
(total, group) => {
|
|
63
|
+
const sorted = [...group].sort((a, b) => a.min - b.min)
|
|
64
|
+
let groupLength = 0
|
|
65
|
+
let segmentCount = 1
|
|
66
|
+
let currentMin = sorted[0]!.min
|
|
67
|
+
let currentMax = sorted[0]!.max
|
|
68
|
+
|
|
69
|
+
for (const interval of sorted.slice(1)) {
|
|
70
|
+
if (interval.min <= currentMax + RAIL_ALIGNMENT_EPSILON) {
|
|
71
|
+
currentMax = Math.max(currentMax, interval.max)
|
|
72
|
+
} else {
|
|
73
|
+
groupLength += currentMax - currentMin
|
|
74
|
+
segmentCount++
|
|
75
|
+
currentMin = interval.min
|
|
76
|
+
currentMax = interval.max
|
|
77
|
+
}
|
|
74
78
|
}
|
|
75
|
-
}
|
|
76
79
|
|
|
77
|
-
|
|
78
|
-
|
|
80
|
+
return {
|
|
81
|
+
length: total.length + groupLength + currentMax - currentMin,
|
|
82
|
+
segmentCount: total.segmentCount + segmentCount,
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
{ length: 0, segmentCount: 0 },
|
|
86
|
+
)
|
|
79
87
|
}
|
|
80
88
|
|
|
81
|
-
|
|
82
|
-
export const getVisibleTraceLength = (traces: SolvedTracePath[]) => {
|
|
89
|
+
const getVisibleTraceMetrics = (traces: SolvedTracePath[]) => {
|
|
83
90
|
const horizontal: Interval[] = []
|
|
84
91
|
const vertical: Interval[] = []
|
|
85
92
|
|
|
@@ -104,5 +111,18 @@ export const getVisibleTraceLength = (traces: SolvedTracePath[]) => {
|
|
|
104
111
|
}
|
|
105
112
|
}
|
|
106
113
|
|
|
107
|
-
|
|
114
|
+
const horizontalMetrics = getMergedIntervalMetrics(horizontal)
|
|
115
|
+
const verticalMetrics = getMergedIntervalMetrics(vertical)
|
|
116
|
+
return {
|
|
117
|
+
length: horizontalMetrics.length + verticalMetrics.length,
|
|
118
|
+
segmentCount: horizontalMetrics.segmentCount + verticalMetrics.segmentCount,
|
|
119
|
+
}
|
|
108
120
|
}
|
|
121
|
+
|
|
122
|
+
/** Returns rendered length after overlapping collinear runs are merged. */
|
|
123
|
+
export const getVisibleTraceLength = (traces: SolvedTracePath[]) =>
|
|
124
|
+
getVisibleTraceMetrics(traces).length
|
|
125
|
+
|
|
126
|
+
/** Returns the number of distinct collinear runs visible on the schematic. */
|
|
127
|
+
export const getVisibleTraceSegmentCount = (traces: SolvedTracePath[]) =>
|
|
128
|
+
getVisibleTraceMetrics(traces).segmentCount
|
package/package.json
CHANGED