feat(dimension): flip arrowheads outside when they don't fit (#94)

When the dimension line is too short for the arrowheads, they are now
placed outside the extension lines (tip on the line, body outward) with a
short stub to sit on, instead of overlapping inside. DIMSOXD suppresses
those outer stubs. Ticks are unaffected (they always fit).

DIMATFIT's exact mode ordering and DIMUPT remain for a later pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Hakan Seven 2026-06-14 02:13:00 +03:00
commit b211ca07a9

View file

@ -1017,6 +1017,7 @@ fn tessellate_dimension_inner(
dimsoxd, dimsoxd,
dimcen, dimcen,
ticks: dimtsz_raw > 1e-9, ticks: dimtsz_raw > 1e-9,
arrow_len: dimasz,
}, },
SuppressFlags { SuppressFlags {
ext1: dimse1, ext1: dimse1,
@ -1039,13 +1040,13 @@ fn tessellate_dimension_inner(
} }
} }
} }
// Text fit: DIMTIX (force text inside) is honoured in the text-placement // Fit: text that doesn't fit between the extension lines is slid outside
// pass (dimension_text_pos_f64), which slides centred text outside the // in the text-placement pass (unless DIMTIX), and arrowheads that don't
// extension lines when it doesn't fit. DIMTOFL (force the dim line // fit are flipped outside in append_linear_dimension. DIMTOFL (force the
// inside) is already satisfied for linear/aligned, whose dim line is // dim line inside) is already satisfied for linear/aligned, whose dim
// always drawn between the extension points. DIMATFIT's arrow-outside // line is always drawn between the extension points. DIMATFIT's exact
// modes and DIMUPT (reposition-on-create) still need an arrow autofit // mode ordering (arrows-first vs text-first) and DIMUPT (reposition on
// pass — read here for round-trip until then. // create) aren't differentiated yet — read here for round-trip.
let _ = (s.dimtofl, s.dimatfit, s.dimupt); let _ = (s.dimtofl, s.dimatfit, s.dimupt);
// DIMTXTDIRECTION (RTL) needs per-instance text mirroring on the Text // DIMTXTDIRECTION (RTL) needs per-instance text mirroring on the Text
// entity, which the current text struct can't carry. Tracked: read // entity, which the current text struct can't carry. Tracked: read
@ -1444,6 +1445,8 @@ struct DimLineParams {
dimsoxd: bool, dimsoxd: bool,
dimcen: f32, dimcen: f32,
ticks: bool, ticks: bool,
/// Arrowhead length (DIMASZ, scaled) — used to decide arrow-outside fit.
arrow_len: f32,
} }
fn dimension_geometry( fn dimension_geometry(
dim: &Dimension, dim: &Dimension,
@ -1638,16 +1641,33 @@ fn append_linear_dimension(
let dir_d1_to_d2 = normalized_or(d2 - d1, axis); let dir_d1_to_d2 = normalized_or(d2 - d1, axis);
let d1_out = d1 - dir_d1_to_d2 * dle; let d1_out = d1 - dir_d1_to_d2 * dle;
let d2_out = d2 + dir_d1_to_d2 * dle; let d2_out = d2 + dir_d1_to_d2 * dle;
// DIMATFIT fit: when the arrowheads don't fit between the extension lines
// they're flipped to the outside (point still on the ext line, body
// outside) with a short stub for them to sit on. DIMSOXD suppresses those
// outer stubs. Ticks always fit, so this only affects arrowheads.
let gap = (d2 - d1).length();
let arrows_outside = !params.ticks && params.arrow_len > 1e-6 && gap < 2.0 * params.arrow_len;
// DIMSD1/DIMSD2: when *both* set, omit the dim line entirely. AutoCAD // DIMSD1/DIMSD2: when *both* set, omit the dim line entirely. AutoCAD
// splits at text otherwise — without that pivot info, leave as-is. // splits at text otherwise — without that pivot info, leave as-is.
let _ = params.dimsoxd; // DIMSOXD: only meaningful when text is auto-placed
// outside the ext lines; we honour the saved
// text_middle_point so this is a no-op for files.
if !(suppress.dim1 && suppress.dim2) { if !(suppress.dim1 && suppress.dim2) {
add_segment(&mut g.dim_lines, d1_out, d2_out); add_segment(&mut g.dim_lines, d1_out, d2_out);
if arrows_outside && !params.dimsoxd {
let stub = params.arrow_len * 2.0;
add_segment(&mut g.dim_lines, d1 - dir_d1_to_d2 * stub, d1);
add_segment(&mut g.dim_lines, d2, d2 + dir_d1_to_d2 * stub);
}
}
if arrows_outside {
// Tip on the ext line, body pointing outward.
append_arrow(g, d1, normalized_or(d1 - d2, -axis), arrow1);
append_arrow(g, d2, normalized_or(d2 - d1, axis), arrow2);
} else {
append_arrow(g, d1, normalized_or(d2 - d1, axis), arrow1);
append_arrow(g, d2, normalized_or(d1 - d2, -axis), arrow2);
} }
append_arrow(g, d1, normalized_or(d2 - d1, axis), arrow1);
append_arrow(g, d2, normalized_or(d1 - d2, -axis), arrow2);
} }
/// Draw a center mark for radius/diameter dimensions. /// Draw a center mark for radius/diameter dimensions.