fix(dimstyle): preview respects per-end arrowheads

DimensionPreview drew identical generic triangles regardless of dimblk/dimblk1/dimblk2. It now resolves each end through the shared arrow_from_block_name mapping (widened to pub(crate)) and draws per-end glyphs, keeping DIMTSZ tick precedence. Tests: arrow_name_mapping_covers_preview_kinds pins the shared contract; 2 preview_arrow_tests pin per-end resolution (RED first). Full suite: 1989 passed / 0 failed / 25 ignored.
This commit is contained in:
Karim Jerbi committed 2026-10-06 22:47:34 +01:00
1 parent 50e20a91c4
commit 29392de267
2 files changed
+186 -15

No files matched your search

+34 -1
View File
@@ -2781,7 +2781,10 @@ pub(crate) fn arrow_from_block_with_deferred_hatch(
.unwrap_or_else(|| arrow_from_block_name(None, dimasz))
}
fn arrow_from_block_name(name: Option<&str>, dimasz: f32) -> ArrowKind {
/// Canonical block-name → arrow-kind mapping shared by the renderer and the
/// dimstyle preview so both agree on what a named arrowhead looks like.
/// Unknown / empty names fall back to the standard filled triangle.
pub(crate) fn arrow_from_block_name(name: Option<&str>, dimasz: f32) -> ArrowKind {
name.and_then(|name| builtin_arrow_from_block_name(name, dimasz))
.unwrap_or(ArrowKind::Triangle {
size: dimasz,
@@ -3453,6 +3456,36 @@ pub(crate) fn normalized_or(v: Vec3, fallback: Vec3) -> Vec3 {
mod tests {
use super::*;
#[test]
fn arrow_name_mapping_covers_preview_kinds() {
// Pins the shared name→kind contract the dimstyle preview relies on.
assert!(matches!(
arrow_from_block_name(Some("OPEN"), 1.0),
ArrowKind::Open { .. }
));
assert!(matches!(
arrow_from_block_name(Some("DOT"), 1.0),
ArrowKind::Dot { filled: true, .. }
));
assert!(matches!(
arrow_from_block_name(Some("ARCHTICK"), 1.0),
ArrowKind::Tick { .. }
));
assert!(matches!(
arrow_from_block_name(Some("NONE"), 1.0),
ArrowKind::None
));
// Unknown / empty names fall back to the standard filled triangle.
assert!(matches!(
arrow_from_block_name(Some("NOPE"), 1.0),
ArrowKind::Triangle { filled: true, .. }
));
assert!(matches!(
arrow_from_block_name(None, 1.0),
ArrowKind::Triangle { filled: true, .. }
));
}
#[test]
fn test_split_mixed_polyline_straight_lines_have_taper_widths() {
let tg_line = TangentGeom::Line {
+152 -14
View File
@@ -1,6 +1,7 @@
//! Dimension Style Manager window — fills the entire OS window.
use crate::app::{ColorPickTarget, DsField, Message};
use crate::scene::convert::tessellate::{arrow_from_block_name, ArrowKind};
use iced::widget::{
button, canvas, checkbox, column, container, row, scrollable, text, text_input, Space,
};
@@ -171,11 +172,32 @@ struct DimensionPreview {
dim2: bool,
tick: bool,
arrow_size: f32,
arrow1: ArrowKind,
arrow2: ArrowKind,
text_above: bool,
basic: bool,
text: String,
}
/// Resolve the staged arrowhead block names to per-end preview kinds: the
/// shared `dimblk` when Dimsah is off, the per-end blocks when on — the same
/// selection the renderer makes. `dimasz` is the preview arrow size so
/// size-scaled kinds (SMALL, DOTSMALL) keep their proportions.
fn preview_arrow_kinds(
dimsah: bool,
dimblk: &str,
dimblk1: &str,
dimblk2: &str,
dimasz: f32,
) -> (ArrowKind, ArrowKind) {
let kind = |name: &str| arrow_from_block_name(Some(name), dimasz);
if dimsah {
(kind(dimblk1), kind(dimblk2))
} else {
(kind(dimblk), kind(dimblk))
}
}
impl canvas::Program<Message> for DimensionPreview {
type State = ();
@@ -218,23 +240,107 @@ impl canvas::Program<Message> for DimensionPreview {
}
let size = self.arrow_size.clamp(5.0, 14.0);
for (tip_x, sign) in [(x1, 1.0_f32), (x2, -1.0_f32)] {
// Per-end arrowheads: each end draws its own resolved kind so split
// (Dimsah) configurations read correctly. A global DIMTSZ tick still
// overrides both ends, matching AutoCAD precedence.
let tick_path = |tip_x: f32, tick_size: f32| {
canvas::Path::line(
Point::new(tip_x - tick_size * 0.55, y + tick_size * 0.75),
Point::new(tip_x + tick_size * 0.55, y - tick_size * 0.75),
)
};
for (kind, tip_x, sign) in [(&self.arrow1, x1, 1.0_f32), (&self.arrow2, x2, -1.0_f32)] {
if self.tick {
frame.stroke(
&line(
Point::new(tip_x - size * 0.55, y + size * 0.75),
Point::new(tip_x + size * 0.55, y - size * 0.75),
),
&tick_path(tip_x, size),
canvas::Stroke::default().with_color(guide).with_width(1.6),
);
} else {
let arrow = canvas::Path::new(|path| {
path.move_to(Point::new(tip_x, y));
path.line_to(Point::new(tip_x + sign * size, y - size * 0.42));
path.line_to(Point::new(tip_x + sign * size, y + size * 0.42));
path.close();
});
frame.fill(&arrow, guide);
continue;
}
match kind {
ArrowKind::None => {}
ArrowKind::Triangle { size, filled, size_mul } => {
let s = size * size_mul;
let arrow = canvas::Path::new(|path| {
path.move_to(Point::new(tip_x, y));
path.line_to(Point::new(tip_x + sign * s, y - s * 0.42));
path.line_to(Point::new(tip_x + sign * s, y + s * 0.42));
path.close();
});
if *filled {
frame.fill(&arrow, guide);
} else {
frame.stroke(&arrow, canvas::Stroke::default().with_color(guide).with_width(1.4));
}
}
ArrowKind::Open { size, half_angle } => {
let (sin, cos) = half_angle.sin_cos();
for side in [-1.0_f32, 1.0_f32] {
frame.stroke(
&line(
Point::new(tip_x, y),
Point::new(tip_x + sign * size * cos, y + side * size * sin),
),
canvas::Stroke::default().with_color(guide).with_width(1.4),
);
}
}
ArrowKind::Dot { size, filled } => {
let dot = canvas::Path::circle(Point::new(tip_x + sign * size * 0.5, y), size * 0.5);
if *filled {
frame.fill(&dot, guide);
} else {
frame.stroke(&dot, canvas::Stroke::default().with_color(guide).with_width(1.4));
}
}
ArrowKind::Tick { size } => {
frame.stroke(
&tick_path(tip_x, *size),
canvas::Stroke::default().with_color(guide).with_width(1.6),
);
}
ArrowKind::Box_ { size, filled } => {
let half = size * 0.45;
let square = canvas::Path::rectangle(
Point::new(tip_x + sign * half - half, y - half),
iced::Size::new(half * 2.0, half * 2.0),
);
if *filled {
frame.fill(&square, guide);
} else {
frame.stroke(&square, canvas::Stroke::default().with_color(guide).with_width(1.4));
}
}
// Datum/Origin have dedicated CAD geometry; the preview keeps
// the schematic triangle/dot respectively.
ArrowKind::Datum { size, filled } => {
let arrow = canvas::Path::new(|path| {
path.move_to(Point::new(tip_x, y));
path.line_to(Point::new(tip_x + sign * size, y - size * 0.42));
path.line_to(Point::new(tip_x + sign * size, y + size * 0.42));
path.close();
});
if *filled {
frame.fill(&arrow, guide);
} else {
frame.stroke(&arrow, canvas::Stroke::default().with_color(guide).with_width(1.4));
}
}
ArrowKind::Origin { size } => {
let dot = canvas::Path::circle(Point::new(tip_x + sign * size * 0.5, y), size * 0.4);
frame.stroke(&dot, canvas::Stroke::default().with_color(guide).with_width(1.4));
}
// Custom DWG block geometry cannot render in the schematic
// preview; fall back to the standard filled triangle.
ArrowKind::Custom { .. } => {
let arrow = canvas::Path::new(|path| {
path.move_to(Point::new(tip_x, y));
path.line_to(Point::new(tip_x + sign * size, y - size * 0.42));
path.line_to(Point::new(tip_x + sign * size, y + size * 0.42));
path.close();
});
frame.fill(&arrow, guide);
}
}
}
@@ -1127,13 +1233,23 @@ pub fn view_window<'a>(
preview_text.push_str(&format!(" +{} −{}", vals.dimtp.trim(), vals.dimtm.trim()));
}
}
let arrow_size = vals.dimasz.trim().parse::<f32>().unwrap_or(1.0).abs() * 5.0 + 5.0;
let (arrow1, arrow2) = preview_arrow_kinds(
vals.dimsah,
&vals.dimblk_name,
&vals.dimblk1_name,
&vals.dimblk2_name,
arrow_size.clamp(5.0, 14.0),
);
let preview = canvas(DimensionPreview {
ext1: !vals.dimse1,
ext2: !vals.dimse2,
dim1: !vals.dimsd1,
dim2: !vals.dimsd2,
tick: vals.dimtsz.trim().parse::<f32>().unwrap_or(0.0) > 0.0,
arrow_size: vals.dimasz.trim().parse::<f32>().unwrap_or(1.0).abs() * 5.0 + 5.0,
arrow_size,
arrow1,
arrow2,
text_above: vals.dimtad.trim() != "0",
basic: vals.dimgap.trim().starts_with('-'),
text: preview_text,
@@ -1231,3 +1347,25 @@ pub fn view_window<'a>(
editor: right_panel.into(),
})
}
#[cfg(test)]
mod preview_arrow_tests {
// Preview must resolve each end independently: shared block when Dimsah
// is off, per-end blocks when on.
use super::preview_arrow_kinds;
use crate::scene::convert::tessellate::ArrowKind;
#[test]
fn shared_block_feeds_both_ends_when_dimsah_off() {
let (first, second) = preview_arrow_kinds(false, "OPEN", "DOT", "NONE", 10.0);
assert!(matches!(first, ArrowKind::Open { .. }));
assert!(matches!(second, ArrowKind::Open { .. }));
}
#[test]
fn per_end_blocks_feed_each_end_when_dimsah_on() {
let (first, second) = preview_arrow_kinds(true, "OPEN", "DOT", "NONE", 10.0);
assert!(matches!(first, ArrowKind::Dot { .. }));
assert!(matches!(second, ArrowKind::None));
}
}