experiment(layout): diagnose mandatory leads and test bounded flat separation
This commit is contained in:
@@ -6,7 +6,9 @@ use crate::{
|
||||
RelationBody, RelationKind, Size,
|
||||
};
|
||||
mod attachments;
|
||||
mod leads;
|
||||
pub use attachments::{attachment_from_point, attachment_point, semantic_port_endpoint};
|
||||
pub use leads::{LeadBlocker, LeadEndpoint, LeadIncompatibility, lead_incompatibilities};
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::{BTreeMap, BTreeSet, BinaryHeap};
|
||||
use std::fmt;
|
||||
@@ -420,6 +422,73 @@ fn endpoints(
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn effective_route<'a>(
|
||||
project: &Project,
|
||||
diagram: &Diagram,
|
||||
edge: &ProjectedEdge,
|
||||
stored: Option<&'a crate::EdgeRoute>,
|
||||
) -> (
|
||||
bool,
|
||||
Option<&'a crate::EdgeRoute>,
|
||||
[Option<EdgeAttachment>; 2],
|
||||
) {
|
||||
let promoted = projection::edge_endpoint_placements(project, diagram, edge.id)
|
||||
.is_some_and(|(source, target)| source != edge.source || target != edge.target);
|
||||
let route = if promoted { None } else { stored };
|
||||
let attachments = [EdgeEnd::Source, EdgeEnd::Target].map(|end| {
|
||||
semantic_port_endpoint(project, edge.id, end)
|
||||
.is_none()
|
||||
.then(|| route.and_then(|r| r.attachment(end)))
|
||||
.flatten()
|
||||
});
|
||||
(promoted, route, attachments)
|
||||
}
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn edge_endpoints(
|
||||
project: &Project,
|
||||
diagram: &Diagram,
|
||||
edge: &ProjectedEdge,
|
||||
source: &ProjectedNode,
|
||||
target: &ProjectedNode,
|
||||
view: &ProjectedView,
|
||||
attachments: [Option<EdgeAttachment>; 2],
|
||||
) -> [Vec<Endpoint>; 2] {
|
||||
[
|
||||
endpoints(
|
||||
project,
|
||||
diagram,
|
||||
source,
|
||||
semantic_port_endpoint(project, edge.id, EdgeEnd::Source).or(edge.source_port),
|
||||
view,
|
||||
attachments[0],
|
||||
),
|
||||
endpoints(
|
||||
project,
|
||||
diagram,
|
||||
target,
|
||||
semantic_port_endpoint(project, edge.id, EdgeEnd::Target).or(edge.target_port),
|
||||
view,
|
||||
attachments[1],
|
||||
),
|
||||
]
|
||||
}
|
||||
fn allowed_endpoint_pair(edge: &ProjectedEdge, a: Endpoint, b: Endpoint, coincident: bool) -> bool {
|
||||
!(edge.source == edge.target
|
||||
&& edge.source_port.is_none()
|
||||
&& edge.target_port.is_none()
|
||||
&& a.point == b.point
|
||||
&& a.stub == b.stub
|
||||
&& !coincident)
|
||||
}
|
||||
fn lead_pair_clear(edge: &ProjectedEdge, a: Endpoint, b: Endpoint, obstacles: &[Rect]) -> bool {
|
||||
!obstacles
|
||||
.iter()
|
||||
.any(|r| r.id != edge.source && r.crosses(a.point, a.stub))
|
||||
&& !obstacles
|
||||
.iter()
|
||||
.any(|r| r.id != edge.target && r.crosses(b.point, b.stub))
|
||||
}
|
||||
|
||||
fn ancestors(id: Id, nodes: &BTreeMap<Id, &ProjectedNode>, out: &mut BTreeSet<Id>) {
|
||||
let mut current = nodes.get(&id).and_then(|n| n.visual_parent);
|
||||
let mut seen = BTreeSet::new();
|
||||
@@ -512,16 +581,8 @@ fn route_diagram_impl(
|
||||
continue;
|
||||
};
|
||||
let stored = authored.get(&edge.id).copied();
|
||||
let promoted = projection::edge_endpoint_placements(project, diagram, edge.id)
|
||||
.is_some_and(|(source, target)| source != edge.source || target != edge.target);
|
||||
let custom_suspended = promoted;
|
||||
let route = if promoted { None } else { stored };
|
||||
let attachments = [EdgeEnd::Source, EdgeEnd::Target].map(|end| {
|
||||
semantic_port_endpoint(project, edge.id, end)
|
||||
.is_none()
|
||||
.then(|| route.and_then(|r| r.attachment(end)))
|
||||
.flatten()
|
||||
});
|
||||
let (custom_suspended, route, attachments) =
|
||||
effective_route(project, diagram, edge, stored);
|
||||
let pins = route.map_or_else(Vec::new, |r| r.waypoints.clone());
|
||||
let obstacles = edge_obstacles(edge, &nodes, &rectangles, &headers);
|
||||
|
||||
@@ -559,24 +620,11 @@ fn route_diagram_impl(
|
||||
});
|
||||
}
|
||||
}
|
||||
let ends = edge_endpoints(project, diagram, edge, source, target, view, attachments);
|
||||
let (points, pin_lengths, failure) = route_edge(
|
||||
edge,
|
||||
&endpoints(
|
||||
project,
|
||||
diagram,
|
||||
source,
|
||||
semantic_port_endpoint(project, edge.id, EdgeEnd::Source).or(edge.source_port),
|
||||
view,
|
||||
attachments[0],
|
||||
),
|
||||
&endpoints(
|
||||
project,
|
||||
diagram,
|
||||
target,
|
||||
semantic_port_endpoint(project, edge.id, EdgeEnd::Target).or(edge.target_port),
|
||||
view,
|
||||
attachments[1],
|
||||
),
|
||||
&ends[0],
|
||||
&ends[1],
|
||||
&pins,
|
||||
&obstacles,
|
||||
&previous,
|
||||
@@ -793,13 +841,7 @@ fn route_edge(
|
||||
for &b in target_ends {
|
||||
// A real self-relation must leave and return at distinct anchors.
|
||||
// Reusing one anchor makes the cheapest path a degenerate stub.
|
||||
if edge.source == edge.target
|
||||
&& edge.source_port.is_none()
|
||||
&& edge.target_port.is_none()
|
||||
&& a.point == b.point
|
||||
&& a.stub == b.stub
|
||||
&& !coincident
|
||||
{
|
||||
if !allowed_endpoint_pair(edge, a, b, coincident) {
|
||||
continue;
|
||||
}
|
||||
let estimate = manhattan(a.stub, pins.first().copied().unwrap_or(b.stub))
|
||||
@@ -823,13 +865,7 @@ fn route_edge(
|
||||
}) {
|
||||
continue;
|
||||
}
|
||||
if obstacles
|
||||
.iter()
|
||||
.any(|r| r.id != edge.source && r.crosses(a.point, a.stub))
|
||||
|| obstacles
|
||||
.iter()
|
||||
.any(|r| r.id != edge.target && r.crosses(b.point, b.stub))
|
||||
{
|
||||
if !lead_pair_clear(edge, a, b, obstacles) {
|
||||
continue;
|
||||
}
|
||||
let mut points = vec![a.point, a.stub];
|
||||
|
||||
Reference in New Issue
Block a user