use super::{
editing::install_base,
host::{Host, on_idle_host},
locate::open_locate,
mesh_task,
obj_import::read_mesh_file,
saved::{show_error, show_status},
};
use crate::{
RoughPlanModel,
gui::pickers::{PickerFilter, PickerKind, PickerRequest, pick},
mesh_io::{
MeshUnit, NOT_A_MESH_ROUGH, add_inclusion, inclusion_in_rough_frame, inclusion_parts,
inclusion_row_text, parse_margin_mm,
},
};
use indicatrix_cut_core::rough_plan::{
RoughBase,
shape::{
RoughMesh,
hull::{self, inclusion_list, remove_inclusion},
},
};
use slint::{ComponentHandle, ModelRc, SharedString, VecModel};
use std::{path::PathBuf, rc::Rc};
const ROUGH_CHANGED_MESSAGE: &str =
"The rough changed while the inclusion was being read. Do it again.";
pub(super) fn setup_inclusion_callbacks(host: &Rc<Host>) {
let model = host.window.global::<RoughPlanModel>();
model.on_add_inclusion(|| on_idle_host(start_add));
model.on_remove_inclusion(|row| on_idle_host(|host| remove(host, row)));
model.on_locate_inclusion(|| on_idle_host(open_locate));
}
fn mesh_base(host: &Host) -> Option<RoughBase> {
let base = host.session.borrow().model.base;
matches!(base, RoughBase::Hull { .. }).then_some(base)
}
pub(super) fn push(host: &Host) {
let base = host.session.borrow().model.base;
let rows: Vec<SharedString> = match base {
RoughBase::Hull { id, .. } => inclusion_list(id)
.iter()
.enumerate()
.map(|(i, info)| inclusion_row_text(i, info.extents_mm, info.volume_mm3).into())
.collect(),
_ => Vec::new(),
};
let has_mesh = matches!(base, RoughBase::Hull { id, .. } if hull::mesh(id).is_some());
let model = host.window.global::<RoughPlanModel>();
model.set_inclusions_possible(matches!(base, RoughBase::Hull { .. }));
model.set_locate_possible(has_mesh);
model.set_inclusion_rows(ModelRc::new(VecModel::from(rows)));
}
fn start_add(host: &Rc<Host>) {
show_error(host, "");
if mesh_base(host).is_none() {
show_error(host, NOT_A_MESH_ROUGH);
return;
}
let model = host.window.global::<RoughPlanModel>();
let margin_mm = match parse_margin_mm(model.get_inclusion_margin().as_str()) {
Ok(margin_mm) => margin_mm,
Err(message) => {
show_error(host, &message);
return;
}
};
let unit = MeshUnit::from_index(model.get_mesh_unit_index());
let Some(main) = host.main.upgrade() else {
return;
};
let request = PickerRequest {
kind: PickerKind::OpenFile,
title: Some("Add an inclusion from a mesh file".to_string()),
filters: vec![PickerFilter {
label: "Mesh (OBJ, STL, PLY)".to_string(),
extensions: ["obj", "stl", "ply"].map(str::to_string).to_vec(),
}],
default_file_name: None,
starting_dir: None,
};
pick(&main, request, move |_, path| {
let Some(path) = path else {
return;
};
on_idle_host(move |host| read_inclusion(host, path, unit, margin_mm));
});
}
fn read_inclusion(host: &Rc<Host>, path: PathBuf, unit: MeshUnit, margin_mm: f64) {
let Some(base) = mesh_base(host) else {
show_error(host, NOT_A_MESH_ROUGH);
return;
};
show_error(host, "");
mesh_task::spawn(
host,
"Reading the inclusion...",
move || {
let bytes = read_mesh_file(&path)?;
let extension = path.extension().and_then(|e| e.to_str());
let (points, triangles) = inclusion_parts(&bytes, extension, unit)?;
inclusion_in_rough_frame(&base, &points, &triangles)
},
move |host, result| match result {
Ok(inclusion) => add_inclusion_to_rough(host, inclusion, margin_mm),
Err(message) => show_error(host, &format!("Inclusion not added: {message}")),
},
);
}
pub(super) fn add_inclusion_to_rough(host: &Rc<Host>, inclusion: RoughMesh, margin_mm: f64) {
add_inclusion_notify(host, inclusion, margin_mm, |_, _| {});
}
pub(super) fn add_inclusion_notify(
host: &Rc<Host>,
inclusion: RoughMesh,
margin_mm: f64,
notify: impl FnOnce(&Rc<Host>, Result<(), String>) + Send + 'static,
) {
let Some(base) = mesh_base(host) else {
show_error(host, NOT_A_MESH_ROUGH);
return;
};
mesh_task::spawn(
host,
"Adding the inclusion...",
move || add_inclusion(&base, &inclusion, margin_mm),
move |host, result| {
let outcome = finish(
host,
base,
result,
"Inclusion not added:",
"Inclusion added. Stones keep clear of it, and the weight and the yield count it \
as material.",
);
notify(host, outcome);
},
);
}
fn remove(host: &Rc<Host>, row: i32) {
let (Ok(index), Some(base)) = (usize::try_from(row), mesh_base(host)) else {
return;
};
show_error(host, "");
mesh_task::spawn(
host,
"Removing the inclusion...",
move || remove_inclusion(&base, index).map_err(|e| e.to_string()),
move |host, result| {
let _ = finish(
host,
base,
result,
"Inclusion not removed:",
"Inclusion removed.",
);
},
);
}
fn finish(
host: &Rc<Host>,
before: RoughBase,
result: Result<RoughBase, String>,
failure: &str,
success: &str,
) -> Result<(), String> {
let base = match result {
Ok(base) => base,
Err(message) => {
show_error(host, &format!("{failure} {message}"));
return Err(message);
}
};
if host.session.borrow().model.base != before {
show_error(host, ROUGH_CHANGED_MESSAGE);
return Err(ROUGH_CHANGED_MESSAGE.to_owned());
}
install_base(host, base);
show_status(host, success);
Ok(())
}