diff --git a/crates/engine/src/cmd/modify.rs b/crates/engine/src/cmd/modify.rs index 7be2db3..79662ff 100644 --- a/crates/engine/src/cmd/modify.rs +++ b/crates/engine/src/cmd/modify.rs @@ -1182,8 +1182,9 @@ pub(crate) fn explode_kind(d: &cadcraft_doc::Drawing, e: &Entity) -> Option { - let style = d.dim_style(&dm.style).cloned().unwrap_or_default(); - let g = cadcraft_render::dimension_geometry(dm, &style, d.header.f64("DIMSCALE", 1.0)); + // The dimension as drawn: its overrides, DIMSCALE (0 = the drawing's) and text font. + let style = d.dim_style(&dm.style).cloned().unwrap_or_default().with_overrides(&dm.overrides); + let g = cadcraft_render::dimension_in(d, dm); let mut v: Vec = g .lines .iter() @@ -1208,21 +1209,22 @@ pub(crate) fn explode_kind(d: &cadcraft_doc::Drawing, e: &Entity) -> Option None, diff --git a/crates/engine/tests/dim_explode_text.rs b/crates/engine/tests/dim_explode_text.rs new file mode 100644 index 0000000..4a8a8ab --- /dev/null +++ b/crates/engine/tests/dim_explode_text.rs @@ -0,0 +1,45 @@ +//! EXPLODE turns a dimension's text into MTEXT that looks like the text that was drawn: the +//! dimension's overrides and DIMSCALE (0 = the drawing's) set its height, and aligned text keeps +//! its angle (issue #367). + +use cadcraft_engine::Session; +use cadcraft_engine::doc::{EntityKind, Handle, MText}; +use serde_json::{Value, json}; + +/// Draw a vertical dimension after `setup`, explode it, and return (drawn text height, drawn +/// text angle, the exploded MTEXT). +fn explode_vertical(setup: &[(&str, Value)]) -> (f64, f64, MText) { + let mut s = Session::new(); + for (cmd, p) in setup { + s.execute(cmd, p).unwrap(); + } + let r = s.execute("dimlinear", &json!({"p1": [0, 0], "p2": [0, 30], "at": [5, 15]})).unwrap(); + let h = Handle::parse_hex(r["handle"].as_str().unwrap()).unwrap(); + let drawn = match &s.doc().unwrap().entity(h).unwrap().kind { + EntityKind::Dimension(dm) => cadcraft_engine::render::dimension_in(s.doc().unwrap(), dm), + k => panic!("not a dimension: {k:?}"), + }; + s.execute("explode", &json!({"handles": [h.hex()]})).unwrap(); + let mut texts = s.doc().unwrap().model.iter().filter_map(|e| match &e.kind { + EntityKind::MText(m) => Some(m.clone()), + _ => None, + }); + let m = texts.next().expect("the exploded dimension has its text"); + assert!(texts.next().is_none()); + (drawn.text_height, drawn.text_angle, m) +} + +#[test] +fn exploded_dimension_text_matches_the_drawn_text() { + let near = |a: f64, b: f64| (a - b).abs() < 1e-9; + // SETVAR DIMSCALE is an override on the dimension: 10 × DIMTXT 0.18. + let (h, _, m) = explode_vertical(&[("setvar", json!({"name": "DIMSCALE", "value": 10}))]); + assert!(near(h, 1.8) && near(m.height, h), "drawn {h}, exploded {}", m.height); + // A style DIMSCALE of 0 draws at scale 1 in model space; the text must not shrink to ~0. + let (h, _, m) = explode_vertical(&[("dimstyle", json!({"name": "Standard", "DIMSCALE": 0}))]); + assert!(near(h, 0.18) && near(m.height, h), "drawn {h}, exploded {}", m.height); + // Aligned text (DIMTIH off) on a vertical dimension is drawn at 90° and stays there. + let (_, a, m) = explode_vertical(&[("dimstyle", json!({"name": "Standard", "DIMTIH": 0}))]); + assert!(near(a, std::f64::consts::FRAC_PI_2) && near(m.rotation, a), "drawn {a}, exploded {}", m.rotation); + assert_eq!(m.contents, "30.0000"); +} diff --git a/crates/io/src/dxf_write.rs b/crates/io/src/dxf_write.rs index 35a6a79..dd361d2 100644 --- a/crates/io/src/dxf_write.rs +++ b/crates/io/src/dxf_write.rs @@ -935,8 +935,9 @@ fn hatch_xdata(w: &mut W, h: &Hatch) { /// Anonymous dimension blocks (`*D1`…) with the rendered geometry, as consumers expect. fn dim_block_entities(d: &Drawing, dm: &Dimension, layer: &str) -> Vec { - let style = d.dim_style(&dm.style).cloned().unwrap_or_default(); - let g = cadcraft_render::dimension_geometry(dm, &style, d.header.f64("DIMSCALE", 1.0)); + // The dimension as drawn: its overrides, DIMSCALE (0 = the drawing's) and text font. + let style = d.dim_style(&dm.style).cloned().unwrap_or_default().with_overrides(&dm.overrides); + let g = cadcraft_render::dimension_in(d, dm); let mut out = Vec::new(); let c = Common { layer: layer.into(), color: Color::ByBlock, ..Common::default() }; for l in &g.lines { @@ -961,7 +962,7 @@ fn dim_block_entities(d: &Drawing, dm: &Dimension, layer: &str) -> Vec { common: c, kind: EntityKind::MText(MText { insert: g.text_pos.to3(0.0), - height: style.text_height * style.scale.max(1e-9), + height: g.text_height, width: 0.0, attach: 5, rotation: g.text_angle, diff --git a/crates/io/tests/dim_block_text.rs b/crates/io/tests/dim_block_text.rs new file mode 100644 index 0000000..517cc6a --- /dev/null +++ b/crates/io/tests/dim_block_text.rs @@ -0,0 +1,52 @@ +//! The anonymous `*D` block written for a dimension (what other programs draw) holds its text at +//! the height it is drawn at: the dimension's overrides and DIMSCALE (0 = the drawing's) count +//! (issue #367). + +use cadcraft_doc::{Common, DimKind, Dimension, Drawing, EntityKind, Space}; +use cadcraft_geom::Vec3; + +/// The group 40 (height) of the first MTEXT in the BLOCKS section. +fn block_mtext_height(dxf: &str) -> f64 { + let lines: Vec<&str> = dxf.lines().map(str::trim).collect(); + let blocks = lines.windows(2).position(|w| w == ["2", "BLOCKS"]).unwrap(); + let mtext = blocks + lines[blocks..].windows(2).position(|w| w == ["0", "MTEXT"]).unwrap(); + let h = mtext + lines[mtext..].chunks(2).position(|w| w[0] == "40").unwrap() * 2; + lines[h + 1].parse().unwrap() +} + +fn height_written(style_scale: f64, overrides: serde_json::Value) -> f64 { + let mut d = Drawing::new_imperial(); + d.dim_styles.iter_mut().for_each(|s| s.scale = style_scale); + let dm = Dimension { + kind: DimKind::Linear { rotation: 0.0 }, + defpt: Vec3::new(15.0, 5.0, 0.0), + text_mid: Vec3::ZERO, + p13: Vec3::ZERO, + p14: Vec3::new(30.0, 0.0, 0.0), + p15: Vec3::ZERO, + p16: Vec3::ZERO, + text: String::new(), + style: "Standard".into(), + measurement: 0.0, + text_rotation: 0.0, + user_text_pos: false, + block: None, + overrides: overrides.as_object().cloned().unwrap_or_default(), + assoc: Vec::new(), + }; + d.add(&Space::Model, Common::default(), EntityKind::Dimension(dm)).unwrap(); + block_mtext_height(&cadcraft_io::write_dxf(&d)) +} + +#[test] +fn dimension_block_text_has_the_drawn_height() { + let near = |a: f64, b: f64| (a - b).abs() < 1e-9; + let plain = height_written(1.0, serde_json::json!({})); + assert!(near(plain, 0.18), "{plain}"); + let scaled = height_written(1.0, serde_json::json!({"scale": 10.0})); + assert!(near(scaled, 1.8), "a DIMSCALE override scales the block text: {scaled}"); + let txt = height_written(1.0, serde_json::json!({"DIMTXT": 0.5})); + assert!(near(txt, 0.5), "a DIMTXT override sets the block text height: {txt}"); + let zero = height_written(0.0, serde_json::json!({})); + assert!(near(zero, 0.18), "DIMSCALE 0 in model space is scale 1, not ~0: {zero}"); +}