Exploded dimension text and DXF dimension blocks match the drawn text (fixes #367) (#369)

EXPLODE and the DXF *D block took the text height from the bare style's
DIMTXT × DIMSCALE, ignoring the dimension's overrides (SETVAR DIM*, DIMOVERRIDE),
a fixed-height text style and the DIMSCALE 0 fallback (text ~1e-10 high), and
EXPLODE always wrote rotation 0, so aligned text turned horizontal. Both now
use the geometry the renderer draws (dimension_in): its text height, angle and
the overridden text style; EXPLODE adds no MTEXT for suppressed text.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Valentin Klein 2026-10-11 00:09:15 +02:00 committed by GitHub
parent 409968ecbb
commit 8db51c2766
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 120 additions and 20 deletions

View File

@ -1182,8 +1182,9 @@ pub(crate) fn explode_kind(d: &cadcraft_doc::Drawing, e: &Entity) -> Option<Vec<
.collect(),
),
EntityKind::Dimension(dm) => {
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<Entity> = g
.lines
.iter()
@ -1208,21 +1209,22 @@ pub(crate) fn explode_kind(d: &cadcraft_doc::Drawing, e: &Entity) -> Option<Vec<
});
}
}
let th = style.text_height * style.scale.max(1e-9);
v.push(Entity {
handle: Handle(0),
common: e.common.clone(),
kind: EntityKind::MText(cadcraft_doc::MText {
insert: v3(g.text_pos),
height: th,
width: 0.0,
attach: 5,
rotation: 0.0,
style: style.text_style.clone(),
contents: g.value.clone(),
line_spacing: 1.0,
}),
});
if !g.value.is_empty() {
v.push(Entity {
handle: Handle(0),
common: e.common.clone(),
kind: EntityKind::MText(cadcraft_doc::MText {
insert: v3(g.text_pos),
height: g.text_height,
width: 0.0,
attach: 5,
rotation: g.text_angle,
style: style.text_style.clone(),
contents: g.value.clone(),
line_spacing: 1.0,
}),
});
}
Some(v)
}
_ => None,

View File

@ -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");
}

View File

@ -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<Entity> {
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<Entity> {
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,

View File

@ -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}");
}