use super::row_format::tier_label;
use crate::gui::editor::auto_solve;
use indicatrix::geometry::{
GpuFacetPlane,
meet_solver::SolvedTier,
stone_metrics::{SolidMetrics, SolidStatus, build_solid_mesh, measure_solid},
};
use indicatrix_cut_core::{Design, degenerate_suspects};
fn escaping_tier_text(design: &Design, solved: &[SolvedTier], escaping: &[usize]) -> String {
escaping
.iter()
.map(|&plane_index| {
design
.tier_for_plane_index(solved, plane_index)
.map_or_else(
|| format!("plane {plane_index}"),
|tier_index| tier_label(design, tier_index),
)
})
.collect::<Vec<_>>()
.join(", ")
}
fn degenerate_suspects_note(design: &Design, solved: &[SolvedTier]) -> String {
let suspects = degenerate_suspects(solved);
if suspects.is_empty() {
return String::new();
}
let names: Vec<String> = suspects
.into_iter()
.map(|index| tier_label(design, index))
.collect();
format!(" -- check {}", names.join(", "))
}
pub(super) fn external_proportions_note(m: &SolidMetrics, mm_per_unit: Option<f64>) -> String {
if m.width_axis <= 0.0 {
return String::new();
}
let mut parts = vec![
format!("L/W {:.3}", m.length_axis / m.width_axis),
format!("H/W {:.3}", m.total_height / m.width_axis),
];
if let Some(c) = m.crown_height {
parts.push(format!("C/W {:.3}", c / m.width_axis));
}
if let Some(p) = m.pavilion_depth {
parts.push(format!("P/W {:.3}", p / m.width_axis));
}
if let Some(mm) = mm_per_unit {
parts.push(format!(
"{:.2} x {:.2} mm",
m.width_axis * mm,
m.total_height * mm
));
}
format!(", {}", parts.join(", "))
}
pub(in crate::gui::editor) fn status_text_and_is_problem(design: &Design) -> (String, bool) {
match design.status() {
Ok(SolidStatus::Closed(_)) => {
let volume_note = design
.measure()
.ok()
.flatten()
.map(|m| {
let mm_per_unit = design
.solve()
.ok()
.and_then(|solved| design.yield_report(&solved).mm_per_unit);
format!(
" -- volume {:.4} (model units^3){}",
m.volume,
external_proportions_note(&m, mm_per_unit)
)
})
.unwrap_or_default();
(format!("Closed solid{volume_note}."), false)
}
Ok(SolidStatus::Degenerate {
vertex_count,
volume,
}) => {
let volume_text = volume.map_or_else(
|| "non-finite".to_string(),
|v| format!("{v:.4} (model units^3)"),
);
let solved = design.solve().ok();
if solved
.as_deref()
.is_some_and(auto_solve::likely_hit_plane_cap)
&& let Some(message) = auto_solve::too_many_planes_message(design)
{
return (message, true);
}
let suspects_note = solved.as_ref().map_or(String::new(), |solved| {
degenerate_suspects_note(design, solved)
});
(
format!(
"Degenerate: only {vertex_count} distinct vertex(es), volume \
{volume_text}{suspects_note}."
),
true,
)
}
Ok(SolidStatus::Unbounded { escaping }) => {
let solved = design.solve().ok();
if solved
.as_deref()
.is_some_and(auto_solve::likely_hit_plane_cap)
&& let Some(message) = auto_solve::too_many_planes_message(design)
{
return (message, true);
}
let tier_text = solved.as_ref().map_or_else(
|| format!("plane(s) {escaping:?}"),
|solved| escaping_tier_text(design, solved, &escaping),
);
(
format!("Unbounded: {tier_text} never close the solid."),
true,
)
}
Err(missing) => (missing.to_string(), true),
}
}
pub(in crate::gui::editor) fn status_text_and_is_problem_from_solved(
design: &Design,
solved: &[SolvedTier],
) -> (String, bool) {
let planes = design.planes_from_solved(solved);
match build_solid_mesh(&planes) {
SolidStatus::Closed(_) => {
let mm_per_unit = design.yield_report(solved).mm_per_unit;
let volume_note = measure_solid(&planes)
.map(|m| {
format!(
" -- volume {:.4} (model units^3){}",
m.volume,
external_proportions_note(&m, mm_per_unit)
)
})
.unwrap_or_default();
(format!("Closed solid{volume_note}."), false)
}
SolidStatus::Degenerate {
vertex_count,
volume,
} => {
if auto_solve::likely_hit_plane_cap(solved)
&& let Some(message) = auto_solve::too_many_planes_message(design)
{
return (message, true);
}
let volume_text = volume.map_or_else(
|| "non-finite".to_string(),
|v| format!("{v:.4} (model units^3)"),
);
let suspects_note = degenerate_suspects_note(design, solved);
(
format!(
"Degenerate: only {vertex_count} distinct vertex(es), volume \
{volume_text}{suspects_note}."
),
true,
)
}
SolidStatus::Unbounded { escaping } => {
if auto_solve::likely_hit_plane_cap(solved)
&& let Some(message) = auto_solve::too_many_planes_message(design)
{
return (message, true);
}
let tier_text = escaping_tier_text(design, solved, &escaping);
(
format!("Unbounded: {tier_text} never close the solid."),
true,
)
}
}
}
pub(in crate::gui::editor) fn design_to_gpu_planes(design: &Design) -> Vec<GpuFacetPlane> {
design
.planes()
.unwrap_or_default()
.into_iter()
.map(|(normal, offset)| GpuFacetPlane::new(normal.as_vec3(), -offset as f32))
.collect()
}
#[must_use]
pub(in crate::gui::editor) fn tier_matches_filter(haystack: &str, filter: &str) -> bool {
let filter = filter.trim();
if filter.is_empty() {
return true;
}
haystack.to_lowercase().contains(&filter.to_lowercase())
}