Files
OneCAD-Draft/src/app/update/util.rs
T

111 lines
3.9 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Small pure helpers split out of `update.rs`.
use crate::scene::Scene;
/// Parse a scale string like "1:50" or "2:1" into (numerator, denominator).
/// Returns (1.0, 1.0) for "Fit" or unknown formats.
/// Sync the model-space annotation scale into its named drawing variable and
/// numeric header mirror before a save.
pub(super) fn sync_annotation_scale_header(scene: &mut Scene) {
let anno = scene.annotation_scale;
let value = if anno.abs() > 1e-9 {
1.0 / anno as f64
} else {
1.0
};
let current = scene.document.header.current_annotation_scale.clone();
let current_matches = scene.scale_list().into_iter().any(|(name, factor, _)| {
name.eq_ignore_ascii_case(&current)
&& (factor - anno).abs() < 0.001 * anno.max(0.001)
});
let name = if current_matches {
current
} else {
scene
.scale_list()
.into_iter()
.find(|(_, factor, _)| (factor - anno).abs() < 0.001 * anno.max(0.001))
.map(|(name, _, _)| name)
.unwrap_or_else(|| format_annotation_scale_name(anno))
};
let hdr = &mut scene.document.header;
hdr.current_annotation_scale = name.clone();
hdr.annotation_scale_value = value;
crate::io::set_drawing_variable(&mut scene.document, "CANNOSCALE", &name);
}
/// Format an annotation-scale multiplier as a ratio name: 50.0 -> "1:50",
/// 0.5 -> "2:1", 1.0 -> "1:1".
fn format_annotation_scale_name(anno: f32) -> String {
if anno >= 1.0 {
format!("1:{}", anno.round() as i64)
} else if anno > 0.0 {
format!("{}:1", (1.0 / anno).round() as i64)
} else {
"1:1".to_string()
}
}
/// `<none>` / `<previous>` are pseudo-entries, not real page setups.
pub(super) fn is_special_entry(s: &str) -> bool {
s == crate::ui::window::plot::SETUP_NONE || s == crate::ui::window::plot::SETUP_PREV
}
/// A page-setup list entry wrapped in `*…*` is a layout (its embedded page
/// setup), not a standalone named page setup.
pub(super) fn is_layout_entry(s: &str) -> bool {
s.len() >= 2 && s.starts_with('*') && s.ends_with('*')
}
/// The layout name inside a `*name*` list entry.
pub(super) fn layout_entry_name(s: &str) -> &str {
s.trim_start_matches('*').trim_end_matches('*')
}
/// Infer the closest A-series paper label and orientation from sheet
/// dimensions (mm). Falls back to A4 when nothing is close.
pub(super) fn paper_label_from_dims(w: f64, h: f64) -> (String, String) {
use crate::io::paper_sizes::PaperSize;
let orient = if w >= h { "Landscape" } else { "Portrait" };
let (short, long) = if w <= h { (w, h) } else { (h, w) };
let mut best = ("A4".to_string(), f64::INFINITY);
for p in PaperSize::ALL {
let (pw, ph) = p.dimensions_mm(); // portrait: pw < ph
let err = (pw - short).abs() + (ph - long).abs();
if err < best.1 {
best = (p.label().to_string(), err);
}
}
(best.0, orient.to_string())
}
pub(super) fn parse_plot_scale(s: &str) -> (f64, f64) {
if s == "Fit" {
return (1.0, 1.0);
}
if let Some((a, b)) = s.split_once(':') {
let num: f64 = a.trim().parse().unwrap_or(1.0);
let den: f64 = b.trim().parse().unwrap_or(1.0);
if den > 0.0 {
return (num, den);
}
}
(1.0, 1.0)
}
/// Convert an internal `[r,g,b,a]` colour (0.0–1.0) to a persisted 0–255 RGB
/// triplet, dropping alpha (backgrounds are always opaque).
pub(super) fn f4_to_u3([r, g, b, _]: [f32; 4]) -> [u8; 3] {
[
(r * 255.0).round().clamp(0.0, 255.0) as u8,
(g * 255.0).round().clamp(0.0, 255.0) as u8,
(b * 255.0).round().clamp(0.0, 255.0) as u8,
]
}
/// Inverse of [`f4_to_u3`]: a persisted 0–255 RGB triplet back to an opaque
/// `[r,g,b,a]` colour.
pub(super) fn u3_to_f4([r, g, b]: [u8; 3]) -> [f32; 4] {
[r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0, 1.0]
}