Files
cadcraft/crates/render/tests/it/render_props.rs
T
Rahulandrahul05ranjan 03101e8d77 fix(io): a moved DIMENSION draws its block at its insertion point (group 12) (#2634) (#2636)
A DIMENSION moved by an older AutoCAD keeps its *D block contents where
they were and the move in DXF group 12, and is drawn translated by the
insertion point. Group 12 was never read into Dimension, so the block
rendered at its pre-move position and the extents reached there too.

Read group 12 into a new Dimension::insertion_point, draw the block
translated by it, move its bounds with it in the extents, test the
block's text where it draws, and write group 12 back whenever the block
written is the one read from the file. Regenerated geometry (edits drop
the block link) sits at its definition points, so no offset is kept or
written for it.

Co-authored-by: rahul05ranjan <11793455+rahul05ranjan@users.noreply.github.com>
2026-11-02 15:01:26 -07:00

188 lines
7.5 KiB
Rust

//! Dimensions take viewport layer colours, leader arrows their style's DIMSCALE, attributes
//! their own properties (issue #313).
use std::sync::Arc;
use cadcraft_color::{Color, Rgb};
use cadcraft_doc::{
Attrib, Block, Common, DimKind, DimStyle, Dimension, Drawing, Entity, EntityKind, HAlign, Handle, Insert, Leader, Line, Lineweight, Space, Text,
VAlign, Viewport,
};
use cadcraft_geom::{Bounds2, Vec2, Vec3};
use cadcraft_render::{Kind, Options, VIEWPORT_CONTENT, build};
fn rgb(i: u8) -> Rgb {
Color::Index(i).resolve(Color::Index(7), Color::Index(7))
}
fn layer(d: &mut Drawing, name: &str, color: u8, lw: Lineweight) {
d.ensure_layer(name);
let l = d.layer_mut(name).unwrap();
l.color = Color::Index(color);
l.lineweight = lw;
}
fn on(layer: &str) -> Common {
Common { layer: layer.into(), ..Default::default() }
}
#[test]
fn dimensions_take_the_viewport_layer_colour() {
let mut d = Drawing::new_imperial();
layer(&mut d, "A", 1, Lineweight::ByLayer);
let dim = Dimension {
kind: DimKind::Linear { rotation: 0.0 },
defpt: Vec3::new(0.0, 2.0, 0.0),
text_mid: Vec3::ZERO,
p13: Vec3::ZERO,
p14: Vec3::new(10.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: Default::default(),
assoc: Vec::new(),
marks: Default::default(),
insertion_point: Vec3::ZERO,
};
d.add(&Space::Model, on("A"), EntityKind::Dimension(dim)).unwrap();
d.add(&Space::Model, on("A"), EntityKind::Line(Line { a: Vec3::new(0.0, -1.0, 0.0), b: Vec3::new(10.0, -1.0, 0.0) })).unwrap();
let vp = Viewport {
center: Vec3::new(10.0, 10.0, 0.0),
width: 20.0,
height: 10.0,
view_center: Vec2::new(5.0, 1.0),
view_height: 10.0,
id: 2,
locked: false,
frozen_layers: Vec::new(),
layer_colors: vec![("a".into(), Color::Index(3))],
clip: None,
on: true,
};
d.add(&Space::Paper("Layout1".into()), Common::default(), EntityKind::Viewport(vp)).unwrap();
let l = build(&d, &Space::Paper("Layout1".into()), &Options::default());
let seen: Vec<Rgb> = l.prims.iter().filter(|p| p.handle == VIEWPORT_CONTENT).map(|p| p.color).collect();
assert!(seen.iter().filter(|c| **c == rgb(3)).count() > 3, "dimension lines, arrows and text: {seen:?}");
assert!(seen.iter().all(|c| *c == rgb(3)), "everything on layer A is green in the viewport: {seen:?}");
// Model space keeps the layer's own colour.
let m = build(&d, &Space::Model, &Options::default());
assert!(m.prims.iter().all(|p| p.color == rgb(1)));
}
/// Length of the leader's arrowhead along the leader (x).
fn arrow_length(d: &Drawing) -> f64 {
let l = build(d, &Space::Model, &Options::default());
let tris: Vec<Vec2> = l.prims.iter().filter(|p| p.kind == Kind::Tris).flat_map(|p| l.points(p).to_vec()).collect();
Bounds2::from_points(tris).width()
}
#[test]
fn leader_arrows_use_their_style_dimscale() {
let mut d = Drawing::new_imperial();
d.dim_styles.push(DimStyle { name: "BIG".into(), scale: 10.0, arrow_size: 0.18, ..DimStyle::default() });
let leader = Leader { vertices: vec![Vec3::ZERO, Vec3::new(5.0, 0.0, 0.0)], arrow: true, spline: false, style: "BIG".into() };
d.add(&Space::Model, Common::default(), EntityKind::Leader(leader)).unwrap();
d.header.set_f64("DIMSCALE", 1.0);
assert!((arrow_length(&d) - 1.8).abs() < 1e-6, "{}", arrow_length(&d));
// A style DIMSCALE of 0 falls back to the drawing's.
d.dim_styles.iter_mut().filter(|s| s.name == "BIG").for_each(|s| s.scale = 0.0);
d.header.set_f64("DIMSCALE", 2.0);
assert!((arrow_length(&d) - 0.36).abs() < 1e-6, "{}", arrow_length(&d));
// Both 0: never a zero-size arrow.
d.header.set_f64("DIMSCALE", 0.0);
assert!((arrow_length(&d) - 0.18).abs() < 1e-6, "{}", arrow_length(&d));
}
fn text(x: f64, value: &str) -> Text {
Text {
insert: Vec3::new(x, 0.0, 0.0),
align_pt: None,
height: 1.0,
value: value.into(),
rotation: 0.0,
width_factor: 1.0,
oblique: 0.0,
style: "Standard".into(),
halign: HAlign::Left,
valign: VAlign::Baseline,
}
}
fn attdef(handle: u64, tag: &str, x: f64, common: Common) -> Entity {
let a = Attrib {
tag: tag.into(),
text: text(x, tag),
invisible: false,
constant: false,
prompt: String::new(),
props: Default::default(),
multiline: None,
modes: Default::default(),
};
Entity { common, ..Entity::new(Handle(handle), EntityKind::AttDef(a)) }
}
/// Colour and lineweight of the attribute text drawn between x0 and x0 + 5 (none = not drawn).
fn attrib_at(d: &Drawing, x0: f64) -> Option<(Rgb, f32)> {
let l = build(d, &Space::Model, &Options { lineweights: true, ..Options::default() });
let mut it = l.prims.iter().filter(|p| l.points(p).iter().all(|q| q.x >= x0 - 0.5 && q.x < x0 + 5.0)).map(|p| (p.color, p.lw));
let first = it.next()?;
assert!(it.all(|x| x == first), "one colour and lineweight per attribute");
Some(first)
}
#[test]
fn attributes_draw_with_their_own_properties() {
let mut d = Drawing::new_imperial();
layer(&mut d, "TXT", 2, Lineweight::Mm100(50));
layer(&mut d, "INS", 5, Lineweight::Mm100(18));
let mut blk = Block::new("TAGS");
// ByLayer on its own layer; ByBlock; ByLayer on layer 0 (= the reference's layer); explicit.
blk.entities.push(attdef(0x10, "BYLAYER", 0.0, on("TXT")));
blk.entities.push(attdef(0x11, "BYBLOCK", 10.0, Common { color: Color::ByBlock, lineweight: Lineweight::ByBlock, ..on("0") }));
blk.entities.push(attdef(0x12, "LAYER0", 20.0, on("0")));
blk.entities.push(attdef(0x13, "OWN", 30.0, Common { color: Color::Index(6), lineweight: Lineweight::Mm100(70), ..on("TXT") }));
d.blocks.insert(blk.name.clone(), Arc::new(blk));
let attribs = [("bylayer", 0.0), ("ByBlock", 10.0), ("LAYER0", 20.0), ("OWN", 30.0), ("NODEF", 40.0)]
.map(|(tag, x)| Attrib {
tag: tag.into(),
text: text(x, "X"),
invisible: false,
constant: false,
prompt: String::new(),
props: Default::default(),
multiline: None,
modes: Default::default(),
})
.to_vec();
let ins = Insert {
block: "TAGS".into(),
insert: Vec3::ZERO,
scale: Vec3::new(1.0, 1.0, 1.0),
rotation: 0.0,
attribs,
cols: 1,
rows: 1,
col_spacing: 0.0,
row_spacing: 0.0,
};
let common = Common { color: Color::Index(1), lineweight: Lineweight::Mm100(35), ..on("INS") };
d.add(&Space::Model, common, EntityKind::Insert(ins)).unwrap();
assert_eq!(attrib_at(&d, 0.0), Some((rgb(2), 0.5)), "ByLayer: the attribute's layer");
assert_eq!(attrib_at(&d, 10.0), Some((rgb(1), 0.35)), "ByBlock: the block reference's");
assert_eq!(attrib_at(&d, 20.0), Some((rgb(5), 0.18)), "layer 0: the block reference's layer");
assert_eq!(attrib_at(&d, 30.0), Some((rgb(6), 0.7)), "explicit");
assert_eq!(attrib_at(&d, 40.0), Some((rgb(1), 0.35)), "no definition: the block reference's");
// An attribute on a layer that is off is hidden; the others stay.
d.layer_mut("TXT").unwrap().on = false;
assert_eq!(attrib_at(&d, 0.0), None);
assert_eq!(attrib_at(&d, 10.0), Some((rgb(1), 0.35)));
}