Files
cadcraft/crates/doc/src/header.rs
T
PedroGuilleandClaude Opus 5.5 f967dc7aca Persist SNAPUNIT and GRIDUNIT in DXF (fix #607) (#614)
Squash of PR #614 by @PedroGuille.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 14:28:56 -04:00

169 lines
5.6 KiB
Rust

//! Header (drawing-scoped system) variables.
use std::collections::BTreeMap;
use cadcraft_geom::{Vec2, Vec3};
use serde::{Deserialize, Serialize};
/// A header variable value.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum HVal {
Int(i64),
Real(f64),
Str(String),
Point(Vec3),
}
impl HVal {
pub fn as_f64(&self) -> Option<f64> {
match self {
HVal::Int(i) => Some(*i as f64),
HVal::Real(r) => Some(*r),
HVal::Str(s) => s.trim().parse().ok(),
HVal::Point(_) => None,
}
}
pub fn as_i64(&self) -> Option<i64> {
match self {
HVal::Int(i) => Some(*i),
HVal::Real(r) if r.is_finite() => Some(r.round() as i64),
HVal::Str(s) => s.trim().parse().ok(),
_ => None,
}
}
pub fn as_str(&self) -> Option<&str> {
match self {
HVal::Str(s) => Some(s),
_ => None,
}
}
pub fn display(&self) -> String {
match self {
HVal::Int(i) => i.to_string(),
HVal::Real(r) => format!("{r:.4}"),
HVal::Str(s) => format!("\"{s}\""),
HVal::Point(p) => format!("{:.4},{:.4},{:.4}", p.x, p.y, p.z),
}
}
}
/// Header variables keyed by name without the `$` (e.g. `LTSCALE`), uppercase.
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
pub struct Header {
pub vars: BTreeMap<String, HVal>,
}
impl Header {
pub fn get(&self, name: &str) -> Option<&HVal> {
self.vars.get(&name.trim_start_matches('$').to_ascii_uppercase())
}
pub fn set(&mut self, name: &str, v: HVal) {
self.vars.insert(name.trim_start_matches('$').to_ascii_uppercase(), v);
}
pub fn f64(&self, name: &str, default: f64) -> f64 {
self.get(name).and_then(HVal::as_f64).filter(|v| v.is_finite()).unwrap_or(default)
}
pub fn i64(&self, name: &str, default: i64) -> i64 {
self.get(name).and_then(HVal::as_i64).unwrap_or(default)
}
pub fn str(&self, name: &str, default: &str) -> String {
self.get(name).and_then(HVal::as_str).map(str::to_string).unwrap_or_else(|| default.to_string())
}
pub fn point(&self, name: &str) -> Option<Vec3> {
match self.get(name)? {
HVal::Point(p) => Some(*p),
_ => None,
}
}
/// Drawing snap spacing; malformed or missing values use the drafting default.
pub fn snap_unit(&self) -> Vec2 {
self.spacing("SNAPUNIT", false)
}
/// Drawing grid spacing. A zero component follows the corresponding snap spacing.
pub fn grid_unit(&self) -> Vec2 {
self.spacing("GRIDUNIT", true)
}
fn spacing(&self, name: &str, allow_zero: bool) -> Vec2 {
self.point(name)
.map(|p| p.xy())
.filter(|p| p.is_finite() && if allow_zero { p.x >= 0.0 && p.y >= 0.0 } else { p.x > 0.0 && p.y > 0.0 })
.unwrap_or(Vec2::new(0.5, 0.5))
}
pub fn set_f64(&mut self, name: &str, v: f64) {
self.set(name, HVal::Real(v));
}
pub fn set_i64(&mut self, name: &str, v: i64) {
self.set(name, HVal::Int(v));
}
pub fn set_str(&mut self, name: &str, v: &str) {
self.set(name, HVal::Str(v.into()));
}
fn common() -> Header {
let mut h = Header::default();
h.set_str("ACADVER", "AC1032");
h.set_str("CLAYER", "0");
h.set_i64("CECOLOR", 256);
h.set_str("CELTYPE", "ByLayer");
h.set_i64("CELWEIGHT", -1);
h.set_f64("CELTSCALE", 1.0);
h.set_f64("LTSCALE", 1.0);
h.set_i64("PSLTSCALE", 1);
h.set_str("TEXTSTYLE", "Standard");
h.set_str("DIMSTYLE", "Standard");
h.set_str("CMLEADERSTYLE", "Standard");
h.set_str("CTABLESTYLE", "Standard");
h.set_i64("LUNITS", 2);
h.set_i64("LUPREC", 4);
h.set_i64("AUNITS", 0);
h.set_i64("AUPREC", 0);
h.set_f64("ANGBASE", 0.0);
h.set_i64("ANGDIR", 0);
h.set_i64("PDMODE", 0);
h.set_f64("PDSIZE", 0.0);
h.set_i64("FILLMODE", 1);
h.set_i64("MIRRTEXT", 0);
h.set_i64("ATTMODE", 1);
h.set_f64("THICKNESS", 0.0);
h.set_f64("ELEVATION", 0.0);
h.set_i64("LWDISPLAY", 0);
h.set("INSBASE", HVal::Point(Vec3::ZERO));
// Snap grid origin and rotation (SNAPANG in degrees).
h.set("SNAPBASE", HVal::Point(Vec3::ZERO));
h.set_f64("SNAPANG", 0.0);
h.set("SNAPUNIT", HVal::Point(Vec3::new(0.5, 0.5, 0.0)));
h.set("GRIDUNIT", HVal::Point(Vec3::new(0.5, 0.5, 0.0)));
// Annotation scale of model space; annotative objects of other scales show (1) or not.
h.set_str("CANNOSCALE", "1:1");
h.set_i64("ANNOALLVISIBLE", 1);
h
}
pub fn imperial() -> Header {
let mut h = Header::common();
h.set_i64("MEASUREMENT", 0);
h.set_i64("INSUNITS", 1);
h.set_f64("TEXTSIZE", 0.2);
h.set_f64("DIMSCALE", 1.0);
h.set_f64("FILLETRAD", 0.0);
h.set_f64("CHAMFERA", 0.0);
h.set_f64("CHAMFERB", 0.0);
h.set("LIMMIN", HVal::Point(Vec3::ZERO));
h.set("LIMMAX", HVal::Point(Vec3::new(12.0, 9.0, 0.0)));
h
}
pub fn metric() -> Header {
let mut h = Header::common();
h.set_i64("MEASUREMENT", 1);
h.set_i64("INSUNITS", 4);
h.set_f64("TEXTSIZE", 2.5);
h.set_f64("DIMSCALE", 1.0);
h.set_f64("FILLETRAD", 0.0);
h.set("LIMMIN", HVal::Point(Vec3::ZERO));
h.set("LIMMAX", HVal::Point(Vec3::new(420.0, 297.0, 0.0)));
h
}
}