use crate::gui::library::detail::reconstruct_planes;
use glam::DVec3;
use indicatrix::geometry::{GpuFacetPlane, cuts::FacetSpec, girdle, stone_metrics};
use indicatrix_vault::{
db::sqlite::DEFAULT_SHAPES,
model::{angle::AngleSetting, detail::FacetingDiagramDetail, entry::FullDiagramRecord},
};
pub fn apply_measured_metadata(detail: &mut FacetingDiagramDetail) {
measure_flat_planes(detail);
apply_concave_rows(detail);
}
pub fn apply_concave_rows(detail: &mut FacetingDiagramDetail) {
detail.angle_settings_table.retain(|row| row.tool.is_none());
detail.concave_tiers = 0;
detail.concave_facets = 0;
let Some(position) = indicatrix_vault::local::native_design_attachment_position(
detail.attached_files.iter().map(|f| f.name.as_str()),
) else {
return;
};
let Ok(text) = std::str::from_utf8(&detail.attached_files[position].content) else {
return;
};
if !text.contains("[[concave_tiers]]") {
return;
}
let Ok(loaded) = indicatrix_cut_core::native::design_from_str(text) else {
return;
};
let first = detail.angle_settings_table.len();
for (i, tier) in loaded.design.concave_tiers.iter().enumerate() {
detail.angle_settings_table.push(AngleSetting {
order_index: (first + i) as u32,
facet: tier.name.clone(),
angle: format!("{}\u{b0}", tier.angle_deg),
index: tier
.indices
.iter()
.map(f64::to_string)
.collect::<Vec<_>>()
.join(", "),
notes: tier.instructions.clone(),
tool: Some(tier.tool.code().to_owned()),
tool_line: Some(indicatrix_editor::view_model::rows::concave_tool_line(tier)),
});
detail.concave_facets += tier.indices.len() as u32;
}
detail.concave_tiers = loaded.design.concave_tiers.len() as u32;
}
fn measure_flat_planes(detail: &mut FacetingDiagramDetail) {
if detail.angle_settings_table.is_empty() {
return;
}
let facet_specs: Vec<FacetSpec> = detail
.angle_settings_table
.iter()
.filter(|a| a.tool.is_none())
.map(|a| FacetSpec {
facet: a.facet.clone(),
angle: a.angle.clone(),
index: a.index.clone(),
notes: a.notes.clone(),
})
.collect();
let planes = reconstruct_planes(None, detail.index_gear.as_deref(), &facet_specs);
let dvec_planes: Vec<(DVec3, f64)> = planes
.iter()
.map(|p| {
(
DVec3::new(
f64::from(p.normal[0]),
f64::from(p.normal[1]),
f64::from(p.normal[2]),
),
-f64::from(p.d),
)
})
.collect();
let Some(m) = stone_metrics::measure_solid(&dvec_planes) else {
return;
};
let w = m.width_axis;
if w < 1e-9 {
return;
}
detail.lw_ratio = Some((m.length_axis / w).to_string());
detail.hw_ratio = Some((m.total_height / w).to_string());
detail.cw_ratio = m.crown_height.map(|c| (c / w).to_string());
detail.pw_ratio = m.pavilion_depth.map(|p| (p / w).to_string());
detail.volume = Some((m.volume / (w * w * w)).to_string());
detail.shape = detail
.shape
.take()
.or_else(|| classify_shape(&planes, m.length_axis / w));
}
pub fn merge_reimport_metadata(fresh: &mut FacetingDiagramDetail, existing: &FullDiagramRecord) {
if fresh.page_url.is_empty() {
fresh.page_url.clone_from(&existing.page_url);
}
fresh.diagram_image_name = fresh
.diagram_image_name
.take()
.or_else(|| existing.diagram_image_name.clone());
fresh.diagram_image_data = fresh
.diagram_image_data
.take()
.or_else(|| existing.diagram_image_data.clone());
fresh.competition_diagram = fresh
.competition_diagram
.take()
.or_else(|| existing.competition_diagram.clone());
fresh.lw_ratio = fresh.lw_ratio.take().or_else(|| existing.lw_ratio.clone());
fresh.refractive_index = fresh
.refractive_index
.take()
.or_else(|| existing.refractive_index.clone());
fresh.index_gear = fresh
.index_gear
.take()
.or_else(|| existing.index_gear.clone());
fresh.volume = fresh.volume.take().or_else(|| existing.volume.clone());
fresh.facets_count = fresh
.facets_count
.take()
.or_else(|| existing.facets_count.clone());
fresh.shape = fresh.shape.take().or_else(|| existing.shape.clone());
fresh.designer_info = fresh
.designer_info
.take()
.or_else(|| existing.designer_info.clone());
fresh.hw_ratio = fresh.hw_ratio.take().or_else(|| existing.hw_ratio.clone());
fresh.tw_ratio = fresh.tw_ratio.take().or_else(|| existing.tw_ratio.clone());
fresh.uw_ratio = fresh.uw_ratio.take().or_else(|| existing.uw_ratio.clone());
fresh.pw_ratio = fresh.pw_ratio.take().or_else(|| existing.pw_ratio.clone());
fresh.cw_ratio = fresh.cw_ratio.take().or_else(|| existing.cw_ratio.clone());
fresh.symmetry_order = fresh
.symmetry_order
.take()
.or_else(|| existing.symmetry_order.clone());
fresh.mirror_symmetry = fresh.mirror_symmetry.or(existing.mirror_symmetry);
fresh.designer = fresh.designer.take().or_else(|| existing.designer.clone());
fresh.source_citation = fresh
.source_citation
.take()
.or_else(|| existing.source_citation.clone());
fresh.pdf_file = fresh.pdf_file.take().or_else(|| existing.pdf_file.clone());
fresh.gem_file = fresh.gem_file.take().or_else(|| existing.gem_file.clone());
fresh.shape_category = fresh
.shape_category
.take()
.or_else(|| existing.shape_category.clone());
}
pub(super) fn classify_shape(planes: &[GpuFacetPlane], lw: f64) -> Option<String> {
const ROUND_LW_MAX: f64 = 1.03;
const POLYGON_LW_MAX: f64 = 1.20;
const ROUND_MIN_SIDES: usize = 12;
let sides = girdle::classify_girdle_plane_indices(planes).len();
let name = match sides {
3 if lw <= POLYGON_LW_MAX => "Triangle",
4 if lw <= ROUND_LW_MAX => "Square",
6 if lw <= POLYGON_LW_MAX => "Hexagon",
8 if lw <= ROUND_LW_MAX => "Octagon",
n if n >= ROUND_MIN_SIDES && lw <= ROUND_LW_MAX => "Round",
_ => return None,
};
DEFAULT_SHAPES
.iter()
.find(|s| **s == name)
.map(|s| (*s).to_string())
}