174 lines
5.8 KiB
Rust
174 lines
5.8 KiB
Rust
//! Necessary terminal-lead checks shared with experimental placement consumers.
|
|
use super::*;
|
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
pub struct LeadBlocker {
|
|
pub id: Id,
|
|
/// The actual routing obstacle: clearance inflated, ancestor body excluded.
|
|
pub bounds: RoutingBounds,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
pub struct LeadEndpoint {
|
|
pub owner: Id,
|
|
pub tip: Position,
|
|
pub stub: Position,
|
|
pub blockers: Vec<LeadBlocker>,
|
|
/// Separate information only; not part of the router's early lead guard.
|
|
pub stub_containers: Vec<LeadBlocker>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq)]
|
|
pub struct LeadIncompatibility {
|
|
pub edge: Id,
|
|
pub source: Vec<LeadEndpoint>,
|
|
pub target: Vec<LeadEndpoint>,
|
|
pub allowed_pair_count: usize,
|
|
pub lead_clear_pair_count: usize,
|
|
}
|
|
|
|
fn blocker(r: &Rect) -> LeadBlocker {
|
|
LeadBlocker {
|
|
id: r.id,
|
|
bounds: RoutingBounds {
|
|
position: pos(r.left, r.top),
|
|
size: Size {
|
|
width: r.right - r.left,
|
|
height: r.bottom - r.top,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
fn describe(owner: Id, endpoint: Endpoint, obstacles: &[Rect]) -> LeadEndpoint {
|
|
LeadEndpoint {
|
|
owner,
|
|
tip: endpoint.point,
|
|
stub: endpoint.stub,
|
|
blockers: obstacles
|
|
.iter()
|
|
.filter(|r| r.id != owner && r.crosses(endpoint.point, endpoint.stub))
|
|
.map(blocker)
|
|
.collect(),
|
|
stub_containers: obstacles
|
|
.iter()
|
|
.filter(|r| r.contains(endpoint.stub))
|
|
.map(blocker)
|
|
.collect(),
|
|
}
|
|
}
|
|
|
|
/// Return only edges for which every allowed endpoint pair fails the router's
|
|
/// existing mandatory-lead guard. Empty output is NECESSARY ONLY: it does not
|
|
/// establish a clear route, clear pins, successful search, or label clearance.
|
|
/// Uses the supplied final projection; does not modify the Project or choose
|
|
/// different ports/attachments. Stub containment is reported separately and
|
|
/// does not change the lead guard or its historical pinned/degenerate behavior.
|
|
pub fn lead_incompatibilities(
|
|
project: &Project,
|
|
diagram: &Diagram,
|
|
view: &ProjectedView,
|
|
) -> Vec<LeadIncompatibility> {
|
|
let nodes: BTreeMap<_, _> = view.nodes.iter().map(|n| (n.id, n)).collect();
|
|
let rectangles: Vec<_> = nodes.values().map(|n| Rect::node(n)).collect();
|
|
let authored: BTreeMap<_, _> = diagram.routes.iter().map(|r| (r.edge, r)).collect();
|
|
let headers: BTreeMap<_, _> = nodes
|
|
.values()
|
|
.map(|node| {
|
|
(
|
|
node.id,
|
|
if diagram.projection.state_machine().is_some() && !node.collapsed {
|
|
projection::state_header_height(project, node.element)
|
|
} else {
|
|
HEADER
|
|
},
|
|
)
|
|
})
|
|
.collect();
|
|
let mut ordered: Vec<_> = view.edges.iter().collect();
|
|
ordered.sort_by_key(|e| e.id);
|
|
let mut result = Vec::new();
|
|
for edge in ordered {
|
|
let (Some(source), Some(target)) = (nodes.get(&edge.source), nodes.get(&edge.target))
|
|
else {
|
|
continue;
|
|
};
|
|
let (_, _, attachments) =
|
|
effective_route(project, diagram, edge, authored.get(&edge.id).copied());
|
|
let ends = edge_endpoints(project, diagram, edge, source, target, view, attachments);
|
|
let obstacles = edge_obstacles(edge, &nodes, &rectangles, &headers);
|
|
let coincident = edge.source == edge.target
|
|
&& ends[0].len() == 1
|
|
&& ends[1].len() == 1
|
|
&& ends[0][0].point == ends[1][0].point
|
|
&& ends[0][0].stub == ends[1][0].stub;
|
|
let mut allowed_pair_count = 0;
|
|
let mut lead_clear_pair_count = 0;
|
|
for &a in &ends[0] {
|
|
for &b in &ends[1] {
|
|
if !allowed_endpoint_pair(edge, a, b, coincident) {
|
|
continue;
|
|
}
|
|
allowed_pair_count += 1;
|
|
if lead_pair_clear(edge, a, b, &obstacles) {
|
|
lead_clear_pair_count += 1;
|
|
}
|
|
}
|
|
}
|
|
if lead_clear_pair_count == 0 {
|
|
result.push(LeadIncompatibility {
|
|
edge: edge.id,
|
|
source: ends[0]
|
|
.iter()
|
|
.map(|&e| describe(edge.source, e, &obstacles))
|
|
.collect(),
|
|
target: ends[1]
|
|
.iter()
|
|
.map(|&e| describe(edge.target, e, &obstacles))
|
|
.collect(),
|
|
allowed_pair_count,
|
|
lead_clear_pair_count,
|
|
});
|
|
}
|
|
}
|
|
result
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn coincident_selfloop_exception_and_stub_containment_remain_separate() {
|
|
let owner = Id::from_u128(1);
|
|
let edge = ProjectedEdge {
|
|
id: Id::from_u128(2),
|
|
source: owner,
|
|
target: owner,
|
|
source_port: None,
|
|
target_port: None,
|
|
trace: false,
|
|
oriented: true,
|
|
};
|
|
let endpoint = Endpoint {
|
|
point: pos(0., 0.),
|
|
stub: pos(18., 0.),
|
|
};
|
|
assert!(!allowed_endpoint_pair(&edge, endpoint, endpoint, false));
|
|
assert!(allowed_endpoint_pair(&edge, endpoint, endpoint, true));
|
|
let obstacles = [Rect {
|
|
id: owner,
|
|
left: -1.,
|
|
right: 20.,
|
|
top: -1.,
|
|
bottom: 1.,
|
|
}];
|
|
assert!(lead_pair_clear(&edge, endpoint, endpoint, &obstacles));
|
|
let diagnostic = describe(owner, endpoint, &obstacles);
|
|
assert!(diagnostic.blockers.is_empty());
|
|
assert_eq!(diagnostic.stub_containers.len(), 1);
|
|
// This diagnostic must not be folded into the historical early guard.
|
|
assert_eq!(diagnostic.stub_containers[0].id, owner);
|
|
}
|
|
}
|