ElkSharp: gateway face overflow redirect, under-node push-first routing, boundary-slot snap
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -53,7 +53,7 @@ internal static partial class ElkEdgeRouterIterative
|
||||
if (current.RetryState.ExcessiveDetourViolations > 0
|
||||
|| (!preferLowWaveRuntimePolish && current.RetryState.GatewaySourceExitViolations > 0))
|
||||
{
|
||||
current = ApplyWinnerDetourPolish(current, nodes, minLineClearance);
|
||||
current = ApplyWinnerDetourPolish(current, nodes, direction, minLineClearance);
|
||||
ElkLayoutDiagnostics.LogProgress($"Hybrid winner refinement after detour polish: {DescribeSolution(current)}");
|
||||
}
|
||||
|
||||
@@ -100,6 +100,10 @@ internal static partial class ElkEdgeRouterIterative
|
||||
var corridorCandidate = RerouteLongSweepsThroughCorridor(current.Edges, nodes, direction, minLineClearance);
|
||||
if (corridorCandidate is not null)
|
||||
{
|
||||
corridorCandidate = FinalizeHybridCorridorCandidate(
|
||||
corridorCandidate,
|
||||
nodes,
|
||||
minLineClearance);
|
||||
var corridorScore = ElkEdgeRoutingScoring.ComputeScore(corridorCandidate, nodes);
|
||||
if (corridorScore.Value > current.Score.Value
|
||||
&& corridorScore.NodeCrossings <= current.Score.NodeCrossings)
|
||||
@@ -166,9 +170,158 @@ internal static partial class ElkEdgeRouterIterative
|
||||
}
|
||||
}
|
||||
|
||||
if (current.RetryState.TargetApproachJoinViolations > 0)
|
||||
{
|
||||
var joinSeverity = new Dictionary<string, int>(StringComparer.Ordinal);
|
||||
ElkEdgeRoutingScoring.CountTargetApproachJoinViolations(current.Edges, nodes, joinSeverity, 10);
|
||||
var focusEdgeIds = joinSeverity
|
||||
.OrderByDescending(pair => pair.Value)
|
||||
.ThenBy(pair => pair.Key, StringComparer.Ordinal)
|
||||
.Take(MaxWinnerPolishBatchedRootEdges + 1)
|
||||
.Select(pair => pair.Key)
|
||||
.ToArray();
|
||||
focusEdgeIds = ExpandTargetApproachJoinRepairSet(
|
||||
focusEdgeIds,
|
||||
current.Edges,
|
||||
nodes,
|
||||
minLineClearance);
|
||||
if (focusEdgeIds.Length > 0)
|
||||
{
|
||||
var focusedJoinCandidate = ElkEdgePostProcessor.SpreadTargetApproachJoins(
|
||||
current.Edges,
|
||||
nodes,
|
||||
minLineClearance,
|
||||
focusEdgeIds,
|
||||
forceOutwardAxisSpacing: true);
|
||||
focusedJoinCandidate = ElkEdgePostProcessor.SpreadRectTargetApproachFeederBands(
|
||||
focusedJoinCandidate,
|
||||
nodes,
|
||||
minLineClearance,
|
||||
focusEdgeIds);
|
||||
focusedJoinCandidate = ElkEdgePostProcessor.RepairBoundaryAnglesAndTargetApproaches(
|
||||
focusedJoinCandidate,
|
||||
nodes,
|
||||
minLineClearance,
|
||||
focusEdgeIds);
|
||||
focusedJoinCandidate = ElkEdgePostProcessor.NormalizeBoundaryAngles(focusedJoinCandidate, nodes);
|
||||
focusedJoinCandidate = ElkEdgePostProcessor.NormalizeSourceExitAngles(focusedJoinCandidate, nodes);
|
||||
|
||||
var focusedJoinScore = ElkEdgeRoutingScoring.ComputeScore(focusedJoinCandidate, nodes);
|
||||
if (focusedJoinScore.Value > current.Score.Value
|
||||
&& focusedJoinScore.NodeCrossings <= current.Score.NodeCrossings)
|
||||
{
|
||||
var focusedJoinRetry = BuildRetryState(
|
||||
focusedJoinScore,
|
||||
HighwayProcessingEnabled
|
||||
? ElkEdgeRouterHighway.DetectRemainingBrokenHighways(focusedJoinCandidate, nodes).Count
|
||||
: 0);
|
||||
current = current with
|
||||
{
|
||||
Score = focusedJoinScore,
|
||||
RetryState = focusedJoinRetry,
|
||||
Edges = focusedJoinCandidate,
|
||||
};
|
||||
ElkLayoutDiagnostics.LogProgress($"Hybrid winner refinement after focused target-join polish: {DescribeSolution(current)}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (current.RetryState.UnderNodeViolations > 0
|
||||
|| current.RetryState.TargetApproachJoinViolations > 0)
|
||||
{
|
||||
current = ApplyFinalProtectedLocalBundlePolish(current, nodes, minLineClearance);
|
||||
current = ApplyFinalDirectUnderNodePolish(current, nodes, minLineClearance);
|
||||
ElkLayoutDiagnostics.LogProgress($"Hybrid winner refinement after late under-node polish: {DescribeSolution(current)}");
|
||||
}
|
||||
|
||||
if (current.RetryState.ExcessiveDetourViolations > 0)
|
||||
{
|
||||
current = ApplyWinnerDetourPolish(current, nodes, direction, minLineClearance);
|
||||
ElkLayoutDiagnostics.LogProgress($"Hybrid winner refinement after late detour polish: {DescribeSolution(current)}");
|
||||
}
|
||||
|
||||
current = ApplyFinalGatewayArtifactPolish(current, nodes, minLineClearance);
|
||||
ElkLayoutDiagnostics.LogProgress($"Hybrid winner refinement after gateway artifact polish: {DescribeSolution(current)}");
|
||||
|
||||
// Final boundary-slot snap: run AFTER the gateway artifact polish
|
||||
// so that normalization passes inside the gateway polish do not
|
||||
// shift endpoints off the slot lattice after snapping. The gateway
|
||||
// artifact polish ends with NormalizeBoundaryAngles +
|
||||
// NormalizeSourceExitAngles, which is the root cause of the
|
||||
// boundary-slot violations when snap ran before it.
|
||||
if (current.RetryState.BoundarySlotViolations > 0)
|
||||
{
|
||||
current = ApplyFinalBoundarySlotPolish(current, nodes, direction, minLineClearance, maxRounds: 1);
|
||||
ElkLayoutDiagnostics.LogProgress($"Hybrid winner refinement after final boundary-slot snap: {DescribeSolution(current)}");
|
||||
}
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
private static ElkRoutedEdge[] FinalizeHybridCorridorCandidate(
|
||||
ElkRoutedEdge[] candidate,
|
||||
ElkPositionedNode[] nodes,
|
||||
double minLineClearance)
|
||||
{
|
||||
var stabilized = ClampBelowGraphEdges(candidate, nodes);
|
||||
var focusSeverity = new Dictionary<string, int>(StringComparer.Ordinal);
|
||||
ElkEdgeRoutingScoring.CountUnderNodeViolations(stabilized, nodes, focusSeverity, 10);
|
||||
ElkEdgeRoutingScoring.CountTargetApproachJoinViolations(stabilized, nodes, focusSeverity, 10);
|
||||
if (focusSeverity.Count == 0)
|
||||
{
|
||||
return stabilized;
|
||||
}
|
||||
|
||||
var focusEdgeIds = focusSeverity
|
||||
.OrderByDescending(pair => pair.Value)
|
||||
.ThenBy(pair => pair.Key, StringComparer.Ordinal)
|
||||
.Take(MaxWinnerPolishBatchedRootEdges + 1)
|
||||
.Select(pair => pair.Key)
|
||||
.ToArray();
|
||||
focusEdgeIds = ExpandTargetApproachJoinRepairSet(
|
||||
focusEdgeIds,
|
||||
stabilized,
|
||||
nodes,
|
||||
minLineClearance);
|
||||
if (focusEdgeIds.Length == 0)
|
||||
{
|
||||
return stabilized;
|
||||
}
|
||||
|
||||
var focusedCandidate = ElkEdgePostProcessor.RepairBoundaryAnglesAndTargetApproaches(
|
||||
stabilized,
|
||||
nodes,
|
||||
minLineClearance,
|
||||
focusEdgeIds);
|
||||
focusedCandidate = ElkEdgePostProcessor.SpreadTargetApproachJoins(
|
||||
focusedCandidate,
|
||||
nodes,
|
||||
minLineClearance,
|
||||
focusEdgeIds,
|
||||
forceOutwardAxisSpacing: true);
|
||||
focusedCandidate = ElkEdgePostProcessor.SpreadRectTargetApproachFeederBands(
|
||||
focusedCandidate,
|
||||
nodes,
|
||||
minLineClearance,
|
||||
focusEdgeIds);
|
||||
focusedCandidate = ElkEdgePostProcessor.PolishTargetPeerConflicts(
|
||||
focusedCandidate,
|
||||
nodes,
|
||||
minLineClearance,
|
||||
focusEdgeIds);
|
||||
focusedCandidate = ClampBelowGraphEdges(focusedCandidate, nodes, focusEdgeIds);
|
||||
focusedCandidate = ElkEdgePostProcessor.SnapBoundarySlotAssignments(
|
||||
focusedCandidate,
|
||||
nodes,
|
||||
minLineClearance,
|
||||
focusEdgeIds,
|
||||
enforceAllNodeEndpoints: true);
|
||||
focusedCandidate = ElkEdgePostProcessor.NormalizeBoundaryAngles(focusedCandidate, nodes);
|
||||
focusedCandidate = ElkEdgePostProcessor.NormalizeSourceExitAngles(focusedCandidate, nodes);
|
||||
|
||||
return ChoosePreferredHardRuleLayout(stabilized, focusedCandidate, nodes);
|
||||
}
|
||||
|
||||
private static bool HasHybridHardRulePressure(RoutingRetryState retryState)
|
||||
{
|
||||
return retryState.RemainingShortHighways > 0
|
||||
|
||||
Reference in New Issue
Block a user