use super::BrepSolid;
use crate::{DiagnosticSeverity, EntityTolerances, KernelDiagnostics, KernelStage};
pub const STATED_PRECISION_INCONSISTENCY_RATIO: f64 = 10.0;
pub const STATED_PRECISION_MM: &str = "import.stated_precision_mm";
pub const STATED_PRECISION_TIGHTEST_MM: &str = "import.stated_precision_tightest_mm";
pub const MEASURED_EDGE_DEVIATION_MM: &str = "import.measured_edge_deviation_mm";
pub const MEASURED_VERTEX_GAP_MM: &str = "import.measured_vertex_gap_mm";
pub const STATED_PRECISION_INCONSISTENT: &str = "import.stated_precision_inconsistent";
pub const MEASURED_BODIES: &str = "import.stated_precision.measured_bodies";
pub(super) fn stated_precision_diagnostics(
stated_mm: &[f64],
solids: &[BrepSolid],
) -> KernelDiagnostics {
let mut diagnostics = KernelDiagnostics::default();
let (Some(&tightest), Some(&loosest)) = (stated_mm.first(), stated_mm.last()) else {
return diagnostics;
};
diagnostics.measure_max(STATED_PRECISION_MM, loosest);
diagnostics.measure_max(STATED_PRECISION_TIGHTEST_MM, tightest);
let mut worst_edge: Option<(f64, usize, u64)> = None;
let mut worst_vertex: Option<(f64, usize, u64)> = None;
let mut scale = 0.0f64;
for (index, solid) in solids.iter().enumerate() {
diagnostics.count(MEASURED_BODIES);
scale = scale.max(crate::solid_scale(solid));
let mut tolerances = EntityTolerances::with_floor(solid, 0.0);
for edge in &solid.edges {
if edge.degenerate {
continue;
}
if let Some(deviation) = tolerances.measured_edge_deviation(edge.id) {
if worst_edge.is_none_or(|(w, _, _)| deviation > w) {
worst_edge = Some((deviation, index, edge.id));
}
}
}
for vertex in &solid.vertices {
if let Some(gap) = tolerances.measured_vertex_gap(vertex.id) {
if worst_vertex.is_none_or(|(w, _, _)| gap > w) {
worst_vertex = Some((gap, index, vertex.id));
}
}
}
}
if let Some((deviation, _, _)) = worst_edge {
diagnostics.measure_max(MEASURED_EDGE_DEVIATION_MM, deviation);
}
if let Some((gap, _, _)) = worst_vertex {
diagnostics.measure_max(MEASURED_VERTEX_GAP_MM, gap);
}
let worst = match (worst_edge, worst_vertex) {
(Some(edge), Some(vertex)) => {
if edge.0 >= vertex.0 {
Some(("edge", edge))
} else {
Some(("vertex", vertex))
}
}
(Some(edge), None) => Some(("edge", edge)),
(None, Some(vertex)) => Some(("vertex", vertex)),
(None, None) => None,
};
let Some((kind, (measured, index, id))) = worst else {
return diagnostics;
};
if !(measured > loosest * STATED_PRECISION_INCONSISTENCY_RATIO) {
return diagnostics;
}
let ratio = measured / loosest;
let declarations = if stated_mm.len() > 1 {
format!(
" (the loosest of {} distinct figures, tightest {:.3e} mm)",
stated_mm.len(),
tightest
)
} else {
String::new()
};
let resolution = f64::EPSILON * scale;
let boilerplate = if loosest < resolution {
format!(
" The stated figure is below double resolution at this part's size ({:.1e} mm), so it cannot describe any geometry.",
resolution
)
} else {
String::new()
};
diagnostics.event(
DiagnosticSeverity::Warning,
KernelStage::Collect,
STATED_PRECISION_INCONSISTENT,
format!(
"the file states a precision of {loosest:.3e} mm{declarations}, but its own geometry disagrees with itself by up to {measured:.3e} mm ({kind} {id} of body {index}), {ratio:.3e}× the stated figure: the stated precision does not describe this geometry.{boilerplate} Acceptance is unchanged."
),
);
diagnostics
}