use super::row_format::{
format_angle_cell, format_index_value, representative_crown_and_pavilion_angles_deg,
};
use crate::{AngleItem, gui::editor::material_lookup::EditorMaterialLookup};
use indicatrix::geometry::meet_solver::{Block, SolvedTier, classify_blocks};
use indicatrix_cut_core::Design;
pub(in crate::gui::editor) fn yield_report_texts(
design: &Design,
custom_sg: &[(String, f64)],
) -> (String, String, String, String) {
let Ok(solved) = design.solve() else {
return (String::new(), String::new(), String::new(), String::new());
};
yield_report_texts_from_solved(design, &solved, custom_sg)
}
pub(in crate::gui::editor) fn yield_report_texts_from_solved(
design: &Design,
solved: &[SolvedTier],
custom_sg: &[(String, f64)],
) -> (String, String, String, String) {
let catalogue = EditorMaterialLookup::new(&[]).with_specific_gravity(custom_sg);
let report = design.yield_report_with(solved, &catalogue);
let volumetric_yield_text = report
.volumetric_yield
.map(|y| format!("{:.2}%", y * 100.0))
.unwrap_or_default();
let carat_weight_text = report
.carat_weight
.map(|c| format!("{c:.4} ct (est.)"))
.unwrap_or_default();
let specific_gravity_used_text = report.specific_gravity_used.map_or_else(String::new, |sg| {
if design.material.specific_gravity_override.is_some() {
format!("{sg:.3} (override)")
} else {
format!("{sg:.3}")
}
});
let preform_fit_warning_text = report
.preform_fit
.map(|fit| fit.to_string())
.unwrap_or_default();
(
volumetric_yield_text,
carat_weight_text,
specific_gravity_used_text,
preform_fit_warning_text,
)
}
pub(in crate::gui::editor) fn proportions_texts(
design: &Design,
) -> (String, String, String, String, String) {
let dash = || "-".to_string();
if design.tiers.is_empty() {
return (dash(), dash(), dash(), dash(), dash());
}
let Ok(solved) = design.solve() else {
return (dash(), dash(), dash(), dash(), dash());
};
let Some(proportions) = design.stone_proportions(&solved) else {
return (dash(), dash(), dash(), dash(), dash());
};
let mm_per_unit = design.yield_report(&solved).mm_per_unit;
let proportions = mm_per_unit.map_or(proportions, |mm| proportions.to_mm(mm));
let unit = if mm_per_unit.is_some() { " mm" } else { "" };
let table_percent_text = proportions
.table_percent
.map_or_else(dash, |v| format!("{v:.1}%"));
let crown_height_text = proportions
.crown_height
.map_or_else(dash, |v| format!("{v:.3}{unit}"));
let pavilion_depth_text = proportions
.pavilion_depth
.map_or_else(dash, |v| format!("{v:.3}{unit}"));
let total_depth_text = format!("{:.3}{unit}", proportions.total_depth);
let length_to_width_text = proportions
.length_to_width
.map_or_else(dash, |v| format!("{v:.3}"));
(
table_percent_text,
crown_height_text,
pavilion_depth_text,
total_depth_text,
length_to_width_text,
)
}
#[must_use]
pub(in crate::gui::editor) fn girdle_and_ratio_texts(
design: &Design,
) -> (String, String, String, String) {
let dash = || "-".to_string();
if design.tiers.is_empty() {
return (dash(), dash(), dash(), dash());
}
let Ok(solved) = design.solve() else {
return (dash(), dash(), dash(), dash());
};
let Some(proportions) = design.stone_proportions(&solved) else {
return (dash(), dash(), dash(), dash());
};
let mm_per_unit = design.yield_report(&solved).mm_per_unit;
let proportions = mm_per_unit.map_or(proportions, |mm| proportions.to_mm(mm));
let unit = if mm_per_unit.is_some() { " mm" } else { "" };
let girdle_thickness_text = proportions
.girdle_thickness
.map_or_else(dash, |v| format!("{v:.3}{unit}"));
let crown_to_width_percent_text = proportions
.crown_to_width_percent
.map_or_else(dash, |v| format!("{v:.1}%"));
let pavilion_to_width_percent_text = proportions
.pavilion_to_width_percent
.map_or_else(dash, |v| format!("{v:.1}%"));
let girdle_to_width_percent_text = proportions
.girdle_to_width_percent
.map_or_else(dash, |v| format!("{v:.1}%"));
(
girdle_thickness_text,
crown_to_width_percent_text,
pavilion_to_width_percent_text,
girdle_to_width_percent_text,
)
}
pub(in crate::gui::editor) struct ProportionVerdict {
pub(in crate::gui::editor) level: i32,
pub(in crate::gui::editor) reason: String,
}
pub(in crate::gui::editor) struct ProportionVerdicts {
pub(in crate::gui::editor) table_pct: ProportionVerdict,
pub(in crate::gui::editor) crown_angle: ProportionVerdict,
pub(in crate::gui::editor) pavilion_angle: ProportionVerdict,
pub(in crate::gui::editor) total_depth_pct: ProportionVerdict,
pub(in crate::gui::editor) girdle_pct: ProportionVerdict,
}
const fn verdict_none() -> ProportionVerdict {
ProportionVerdict {
level: -1,
reason: String::new(),
}
}
fn verdict_from(
value: Option<f64>,
shape: indicatrix_cut_core::ShapeClass,
material: indicatrix_cut_core::MaterialClass,
metric: indicatrix_cut_core::ProportionMetric,
) -> ProportionVerdict {
let Some(v) = value else {
return verdict_none();
};
let Some((verdict, window)) =
indicatrix_cut_core::proportions_windows::verdict_for(shape, material, metric, v)
else {
return verdict_none();
};
let level = match verdict {
indicatrix_cut_core::Verdict::Within => 0,
indicatrix_cut_core::Verdict::Near => 1,
indicatrix_cut_core::Verdict::Outside => 2,
};
ProportionVerdict {
level,
reason: window.reason.to_string(),
}
}
const fn shape_class_for(
meta: &indicatrix_cut_core::ScheduleMeta,
) -> indicatrix_cut_core::ShapeClass {
if meta.symmetry_order >= 6 && meta.mirror {
indicatrix_cut_core::ShapeClass::Round
} else {
indicatrix_cut_core::ShapeClass::Other
}
}
#[must_use]
pub(in crate::gui::editor) fn proportion_verdicts(
design: &Design,
proportions: &indicatrix::geometry::stone_metrics::StoneProportions,
n_d: f64,
) -> ProportionVerdicts {
let shape = shape_class_for(&design.meta);
let material = indicatrix_cut_core::MaterialClass::from_ri(n_d);
let (crown_angle_deg, pavilion_angle_deg) =
representative_crown_and_pavilion_angles_deg(design);
let total_depth_pct = match (
proportions.crown_to_width_percent,
proportions.pavilion_to_width_percent,
proportions.girdle_to_width_percent,
) {
(Some(c), Some(p), Some(g)) => Some(c + p + g),
_ => None,
};
ProportionVerdicts {
table_pct: verdict_from(
proportions.table_percent,
shape,
material,
indicatrix_cut_core::ProportionMetric::TablePercent,
),
crown_angle: verdict_from(
crown_angle_deg,
shape,
material,
indicatrix_cut_core::ProportionMetric::CrownAngle,
),
pavilion_angle: verdict_from(
pavilion_angle_deg,
shape,
material,
indicatrix_cut_core::ProportionMetric::PavilionAngle,
),
total_depth_pct: verdict_from(
total_depth_pct,
shape,
material,
indicatrix_cut_core::ProportionMetric::TotalDepthPercent,
),
girdle_pct: verdict_from(
proportions.girdle_to_width_percent,
shape,
material,
indicatrix_cut_core::ProportionMetric::GirdleThicknessPercent,
),
}
}
#[must_use]
pub(in crate::gui::editor) fn preform_mm_texts(design: &Design) -> (String, String) {
let Ok(solved) = design.solve() else {
return (String::new(), String::new());
};
let Some(mm_per_unit) = design.yield_report(&solved).mm_per_unit else {
return (String::new(), String::new());
};
let preform = &design.preform;
(
format!("\u{2248} {:.3} mm", preform.half_width * mm_per_unit),
format!("\u{2248} {:.3} mm", preform.depth * mm_per_unit),
)
}
#[must_use]
pub(in crate::gui::editor) fn preform_y_offset_mm_text(
preform_y_offset: f64,
mm_per_unit: Option<f64>,
) -> String {
mm_per_unit.map_or_else(String::new, |mm_per_unit| {
format!("{:.2}", preform_y_offset * mm_per_unit)
})
}
pub(in crate::gui::editor) fn design_label_text(asc_filename: Option<&str>) -> String {
asc_filename.map_or_else(|| "Untitled design".to_string(), str::to_string)
}
pub(in crate::gui::editor) fn cutting_schedule_rows(
design: &Design,
solved: &[SolvedTier],
) -> Vec<AngleItem> {
let tier_blocks = classify_blocks(&design.meet_tier_inputs());
let schedule = match design.try_to_asc_schedule_from_solved(solved) {
Ok(schedule) => schedule,
Err(mismatch) => {
tracing::warn!(%mismatch, "cutting_schedule_rows: solved masts do not match this design's tier count");
return Vec::new();
}
};
schedule
.tiers
.into_iter()
.enumerate()
.map(|(order_idx, tier)| AngleItem {
order_idx: order_idx as i32,
side: match tier_blocks.get(order_idx) {
Some(Block::Crown) => 1,
Some(Block::Pavilion) => -1,
_ => 0,
},
facet: if tier.name.is_empty() {
format!("#{}", order_idx + 1)
} else {
tier.name
}
.into(),
angle: format_angle_cell(tier.angle_deg).into(),
index_val: tier
.indices
.into_iter()
.map(format_index_value)
.collect::<Vec<_>>()
.join(", ")
.into(),
notes: tier.notes.into(),
})
.collect()
}