diff --git a/src/app/properties.rs b/src/app/properties.rs index ebeda6228..1bfa1bc9e 100644 --- a/src/app/properties.rs +++ b/src/app/properties.rs @@ -1828,6 +1828,16 @@ impl OpenCADStudio { pub(super) fn invalidate_property_targets(&mut self, i: usize, handles: &[Handle]) { let mut context_object_changed = false; for &handle in handles { + // A dimension is drawn from the block holding its picture, and that + // picture was made under the settings just edited. Drop it so the + // dimension is drawn afresh — otherwise the edit changes the stored + // variables and nothing on screen. + if matches!( + self.tabs[i].scene.document.get_entity(handle), + Some(acadrust::EntityType::Dimension(_)) + ) { + self.tabs[i].scene.invalidate_dim_block_recorded(handle); + } context_object_changed |= self.tabs[i] .scene .sync_displayed_annotation_context(handle); diff --git a/src/app/update/style.rs b/src/app/update/style.rs index be8781683..bff4cd52d 100644 --- a/src/app/update/style.rs +++ b/src/app/update/style.rs @@ -467,6 +467,29 @@ impl OpenCADStudio { ds.dimtofl = self.ds_dimtofl; ds.dimalt = self.ds_dimalt; ds.dimapost = self.ds_dimapost.clone(); + + // Same reason as a per-dimension edit: every dimension on this style is + // drawn from a block made under the settings that just changed, so the + // pictures are stale. Drop them and let each be drawn again. Without + // this an edit here would move the numbers and leave the drawing alone. + let edited = self.dimstyle_selected.clone(); + let stale: Vec = self.tabs[tab] + .scene + .document + .entities() + .filter_map(|entity| match entity { + acadrust::EntityType::Dimension(dim) + if dim.base().style_name.eq_ignore_ascii_case(&edited) => + { + Some(entity.common().handle) + } + _ => None, + }) + .collect(); + for handle in stale { + self.tabs[tab].scene.invalidate_dim_block_recorded(handle); + } + self.command_line .push_output(crate::tf!("DimStyle '{}' updated.", self.dimstyle_selected).as_ref()); } diff --git a/src/scene/convert/tess.rs b/src/scene/convert/tess.rs index 69b038bc8..117d953dd 100644 --- a/src/scene/convert/tess.rs +++ b/src/scene/convert/tess.rs @@ -737,6 +737,114 @@ pub(crate) fn tessellate_entity( }]; } + // ── Dimension baked-block fast path ───────────────────────────────────── + // + // A DIMENSION carries the block "that contains the entities that make up + // the dimension picture" (DXF group 2), and that block IS the picture: + // AutoCAD requires it and draws it, BricsCAD draws it when present and only + // falls back to rendering from the dimension variables when it is missing. + // OCS re-derived the picture from DIMVARS every time instead, which means a + // drawing whose style disagrees with what it actually drew comes out wrong + // — a DIMTXT stored in different units from the DIMSCALE applied to it, or + // a per-object override that is already in drawing units, and the text and + // extension lines land hundreds of times too large. + // + // Drawing the block puts OCS on the same footing as the CAD that wrote the + // file: it shows what the file says it looks like. Re-deriving stays as the + // fallback, for a dimension with no block (one OCS just created, or one + // whose block was dropped because it was edited). + // + // Annotative dimensions keep the old path: their several representations + // are separate blocks, and choosing between them is what the annotation + // machinery already does. The doctrine above assumes one picture. + if let EntityType::Dimension(dim) = e { + let baked = Some(dim.base().block_name.trim()) + .filter(|name| !name.is_empty()) + .filter(|_| !crate::scene::annotative::is_annotative(document, e)) + .and_then(|name| { + document + .block_records + .iter() + .find(|record| record.name.eq_ignore_ascii_case(name)) + }) + .filter(|record| !record.entity_handles.is_empty()); + if let Some(record) = baked { + // The block's contents are already in world space and its base + // point is the origin, so the instance that carries them is the + // identity — it exists only to reuse the graph's ByBlock / layer-0 + // colour resolution and its handling of anything nested inside. + let mut insert = acadrust::entities::Insert::new( + record.name.clone(), + acadrust::types::Vector3::ZERO, + ); + insert.common = dim.base().common.clone(); + + let depths = rustc_hash::FxHashMap::default(); + let graph = crate::scene::render_graph::RenderSceneGraph::new( + document, + None, + annotation_scale_handle, + true, + &depths, + ); + let mut wires = Vec::new(); + graph.walk_insert( + &insert, + h, + |_, _| true, + |sub, context| { + let has_book_color = view::render::has_resolved_book_color(document, sub); + let color_byblock = + !has_book_color && sub.common().color == acadrust::types::Color::ByBlock; + let color_layer0 = !has_book_color + && view::render::is_effective_layer_zero(&sub.common().layer) + && sub.common().color == acadrust::types::Color::ByLayer; + let style = context.style_for(document, sub); + let mut placed = sub.clone(); + placed.apply_transform(&context.transform); + let sub_wires = tessellate_entity( + document, + selected, + active_viewport, + bg_color, + anno_scale, + annotation_scale_handle, + &placed, + block_cache, + view_aabb, + world_per_pixel, + paper_space, + ); + for mut wire in sub_wires { + // Every wire answers to the dimension, so a pick selects + // the dimension rather than one of its pieces. + wire.name = h.value().to_string(); + if sel { + wire.selected = true; + wire.color = WireModel::SELECTED; + } else if color_byblock || color_layer0 { + wire.color = view::render::adapt_to_bg(style.0, bg_color); + wire.aci = style.4; + } + wires.push(wire); + } + }, + ); + if !wires.is_empty() { + let aabb = entity_aabb(e); + for wire in &mut wires { + // As below: only stroke / fill wires take the whole-dimension + // box. An SDF-text wire keeps its own tight glyph box so the + // text pick area hugs the text. + if !wire.points.is_empty() || !wire.fill_tris.is_empty() { + set_wire_aabb(wire, aabb); + } + } + return wires; + } + } + } + if let EntityType::Dimension(dim) = e { let aabb = entity_aabb(e); use crate::entities::dimension::DimensionTess;