use std::collections::HashMap;
use brepkit_math::vec::Point3;
use brepkit_topology::Topology;
use brepkit_topology::wire::WireId;
use super::checks::{CheckId, EntityRef, Severity, ValidationIssue};
use crate::CheckError;
pub fn check_wire_empty(
topo: &Topology,
wire_id: WireId,
) -> Result<Vec<ValidationIssue>, CheckError> {
let wire = topo.wire(wire_id)?;
if wire.edges().is_empty() {
return Ok(vec![ValidationIssue {
check: CheckId::WireEmpty,
severity: Severity::Error,
entity: EntityRef::Wire(wire_id),
description: "wire contains no edges".into(),
deviation: None,
}]);
}
Ok(vec![])
}
pub fn check_wire_connected(
topo: &Topology,
wire_id: WireId,
) -> Result<Vec<ValidationIssue>, CheckError> {
let wire = topo.wire(wire_id)?;
let edges = wire.edges();
if edges.len() < 2 {
return Ok(vec![]);
}
let mut issues = Vec::new();
for i in 0..edges.len() - 1 {
let edge_a = topo.edge(edges[i].edge())?;
let edge_b = topo.edge(edges[i + 1].edge())?;
let end_a = edges[i].oriented_end(edge_a);
let start_b = edges[i + 1].oriented_start(edge_b);
if end_a != start_b {
issues.push(ValidationIssue {
check: CheckId::WireNotConnected,
severity: Severity::Error,
entity: EntityRef::Wire(wire_id),
description: format!("edges {} and {} not connected", i, i + 1),
deviation: None,
});
}
}
Ok(issues)
}
pub fn check_wire_closure(
topo: &Topology,
wire_id: WireId,
) -> Result<Vec<ValidationIssue>, CheckError> {
let wire = topo.wire(wire_id)?;
if !wire.is_closed() {
return Ok(vec![]);
}
let edges = wire.edges();
if edges.is_empty() {
return Ok(vec![]);
}
let first_edge = topo.edge(edges[0].edge())?;
let last_edge = topo.edge(edges[edges.len() - 1].edge())?;
let first_start = edges[0].oriented_start(first_edge);
let last_end = edges[edges.len() - 1].oriented_end(last_edge);
if first_start != last_end {
return Ok(vec![ValidationIssue {
check: CheckId::WireClosure3D,
severity: Severity::Error,
entity: EntityRef::Wire(wire_id),
description: "wire not closed: last edge end != first edge start".into(),
deviation: None,
}]);
}
Ok(vec![])
}
pub fn check_wire_redundant(
topo: &Topology,
wire_id: WireId,
) -> Result<Vec<ValidationIssue>, CheckError> {
let wire = topo.wire(wire_id)?;
let mut counts: HashMap<_, usize> = HashMap::new();
for oe in wire.edges() {
*counts.entry(oe.edge()).or_default() += 1;
}
let mut issues = Vec::new();
for (eid, count) in counts {
if count >= 3 {
issues.push(ValidationIssue {
check: CheckId::WireRedundantEdge,
severity: Severity::Error,
entity: EntityRef::Edge(eid),
description: format!("edge appears {count} times in wire"),
deviation: None,
});
}
}
Ok(issues)
}
#[allow(clippy::cast_precision_loss, clippy::too_many_lines)]
pub fn check_wire_self_intersection(
topo: &Topology,
wire_id: WireId,
tolerance: f64,
) -> Result<Vec<ValidationIssue>, CheckError> {
let wire = topo.wire(wire_id)?;
let edges = wire.edges();
if edges.len() < 3 {
return Ok(vec![]);
}
let samples_per_edge = 8usize;
let mut edge_segments: Vec<Vec<Point3>> = Vec::new();
for oe in edges {
let edge = topo.edge(oe.edge())?;
let p0 = topo.vertex(edge.start())?.point();
let p1 = topo.vertex(edge.end())?.point();
match edge.curve() {
brepkit_topology::edge::EdgeCurve::Line => {
edge_segments.push(vec![p0, p1]);
}
brepkit_topology::edge::EdgeCurve::Circle(c) => {
let is_closed = edge.start() == edge.end();
let (t0, t1) = if is_closed {
(0.0, std::f64::consts::TAU)
} else {
let mut ta = c.project(p0);
let mut tb = c.project(p1);
if !oe.is_forward() {
std::mem::swap(&mut ta, &mut tb);
}
if tb <= ta {
tb += std::f64::consts::TAU;
}
(ta, tb)
};
let mut pts = Vec::with_capacity(samples_per_edge + 1);
for k in 0..=samples_per_edge {
let t = t0 + (t1 - t0) * (k as f64) / (samples_per_edge as f64);
pts.push(c.evaluate(t));
}
if !oe.is_forward() {
pts.reverse();
}
edge_segments.push(pts);
}
brepkit_topology::edge::EdgeCurve::Ellipse(e) => {
let is_closed = edge.start() == edge.end();
let (t0, t1) = if is_closed {
(0.0, std::f64::consts::TAU)
} else {
let mut ta = e.project(p0);
let mut tb = e.project(p1);
if !oe.is_forward() {
std::mem::swap(&mut ta, &mut tb);
}
if tb <= ta {
tb += std::f64::consts::TAU;
}
(ta, tb)
};
let mut pts = Vec::with_capacity(samples_per_edge + 1);
for k in 0..=samples_per_edge {
let t = t0 + (t1 - t0) * (k as f64) / (samples_per_edge as f64);
pts.push(e.evaluate(t));
}
if !oe.is_forward() {
pts.reverse();
}
edge_segments.push(pts);
}
brepkit_topology::edge::EdgeCurve::NurbsCurve(nc) => {
let (t0, t1) = nc.domain();
let mut pts = Vec::with_capacity(samples_per_edge + 1);
for k in 0..=samples_per_edge {
let t = t0 + (t1 - t0) * (k as f64) / (samples_per_edge as f64);
pts.push(nc.evaluate(t));
}
if !oe.is_forward() {
pts.reverse();
}
edge_segments.push(pts);
}
}
}
let n_edges = edge_segments.len();
for i in 0..n_edges {
for j in (i + 2)..n_edges {
if j == n_edges - 1 && i == 0 {
continue;
}
for si in 0..edge_segments[i].len().saturating_sub(1) {
let a0 = edge_segments[i][si];
let a1 = edge_segments[i][si + 1];
for sj in 0..edge_segments[j].len().saturating_sub(1) {
let b0 = edge_segments[j][sj];
let b1 = edge_segments[j][sj + 1];
let (dist, _, _) =
crate::distance::edge::segment_segment_distance(a0, a1, b0, b1);
if dist < tolerance {
return Ok(vec![ValidationIssue {
check: CheckId::WireSelfIntersection,
severity: Severity::Error,
entity: EntityRef::Wire(wire_id),
description: format!(
"wire self-intersection between edges {i} and {j}"
),
deviation: Some(dist),
}]);
}
}
}
}
}
Ok(vec![])
}