use super::{
NEW_DESIGN_CREATED,
steps::{EIGHT_FOLD_INDICES, STEPS},
};
use crate::{EditorModel, GuideModel, MainWindow, gui::editor::state::EditorState};
use indicatrix_cut_core::Design;
use slint::ComponentHandle;
const ANGLE_TOLERANCE_DEG: f64 = 0.05;
const INDEX_TOLERANCE: f64 = 1e-6;
pub(in crate::gui::editor) fn notify(ui: &MainWindow, key: &str) {
ui.global::<GuideModel>().invoke_notify(key.into());
}
pub(in crate::gui::editor) fn check_progress(ui: &MainWindow, state: &EditorState) {
check_design_progress(ui, &state.design);
}
pub(in crate::gui::editor) fn check_design_progress(ui: &MainWindow, design: &Design) {
let guide = ui.global::<GuideModel>();
if !guide.get_open() || guide.get_step_done() {
return;
}
let Some(step) = usize::try_from(guide.get_step_index())
.ok()
.and_then(|index| STEPS.get(index))
else {
return;
};
let model = ui.global::<EditorModel>();
let solved_closed = model.get_solve_state() == "solved" && !model.get_status_is_problem();
if goal_reached(step.completion, design, solved_closed) {
guide.invoke_notify(step.completion.into());
}
}
pub(super) fn goal_reached(key: &str, design: &Design, solved_closed: bool) -> bool {
match key {
"material:Diamond" => design
.material
.name
.as_deref()
.is_some_and(|name| name.trim().eq_ignore_ascii_case("Diamond")),
"solved_closed" => solved_closed && !design.tiers.is_empty(),
"yield_applied" => design.girdle_diameter_mm.is_some(),
NEW_DESIGN_CREATED => false,
_ => key
.strip_prefix("tier_named:")
.is_some_and(|name| has_expected_tier(design, name)),
}
}
fn expected_tier(name: &str) -> Option<(f64, &'static [f64])> {
match name {
"G1" => Some((90.0, &EIGHT_FOLD_INDICES)),
"P1" => Some((-40.0, &EIGHT_FOLD_INDICES)),
"C1" => Some((34.5, &EIGHT_FOLD_INDICES)),
"T" => Some((0.0, &[])),
_ => None,
}
}
fn has_expected_tier(design: &Design, name: &str) -> bool {
let Some((angle, indices)) = expected_tier(name) else {
return false;
};
let gear = f64::from(design.meta.gear_teeth_abs());
design.tiers.iter().any(|tier| {
tier.names()
.into_iter()
.any(|n| n.trim().eq_ignore_ascii_case(name))
&& (tier.angle_deg - angle).abs() <= ANGLE_TOLERANCE_DEG
&& same_index_set(&tier.indices, indices, gear)
})
}
pub(super) fn same_index_set(actual: &[f64], expected: &[f64], gear: f64) -> bool {
let same = |a: f64, b: f64| {
if gear > 0.0 {
let d = (a - b).rem_euclid(gear);
d.min(gear - d) <= INDEX_TOLERANCE
} else {
(a - b).abs() <= INDEX_TOLERANCE
}
};
if expected.is_empty() {
return match actual {
[] => true,
[only] => same(*only, 0.0),
_ => false,
};
}
actual.len() == expected.len()
&& expected.iter().all(|&e| actual.iter().any(|&a| same(a, e)))
&& actual.iter().all(|&a| expected.iter().any(|&e| same(a, e)))
}