diff --git a/docs/screenshots/dimension-preview/after-dimangular-placement.png b/docs/screenshots/dimension-preview/after-dimangular-placement.png new file mode 100644 index 000000000..974845828 Binary files /dev/null and b/docs/screenshots/dimension-preview/after-dimangular-placement.png differ diff --git a/docs/screenshots/dimension-preview/after-dimlinear-placement.png b/docs/screenshots/dimension-preview/after-dimlinear-placement.png new file mode 100644 index 000000000..da048d2db Binary files /dev/null and b/docs/screenshots/dimension-preview/after-dimlinear-placement.png differ diff --git a/docs/screenshots/dimension-preview/after-dimlinear-second-point.png b/docs/screenshots/dimension-preview/after-dimlinear-second-point.png new file mode 100644 index 000000000..6328cc973 Binary files /dev/null and b/docs/screenshots/dimension-preview/after-dimlinear-second-point.png differ diff --git a/docs/screenshots/dimension-preview/before-dimangular-placement.png b/docs/screenshots/dimension-preview/before-dimangular-placement.png new file mode 100644 index 000000000..c335d74d5 Binary files /dev/null and b/docs/screenshots/dimension-preview/before-dimangular-placement.png differ diff --git a/docs/screenshots/dimension-preview/before-dimlinear-placement.png b/docs/screenshots/dimension-preview/before-dimlinear-placement.png new file mode 100644 index 000000000..e655e5629 Binary files /dev/null and b/docs/screenshots/dimension-preview/before-dimlinear-placement.png differ diff --git a/docs/screenshots/dimension-preview/before-dimlinear-second-point.png b/docs/screenshots/dimension-preview/before-dimlinear-second-point.png new file mode 100644 index 000000000..1d4e7a1af Binary files /dev/null and b/docs/screenshots/dimension-preview/before-dimlinear-second-point.png differ diff --git a/src/app/dim_viewport.rs b/src/app/dim_viewport.rs index 841b784e1..b5ddc83d2 100644 --- a/src/app/dim_viewport.rs +++ b/src/app/dim_viewport.rs @@ -2,7 +2,7 @@ //! supplies a viewport scale; sheet-only dimensions retain paper units. use super::OpenCADStudio; -use crate::command::DimensionAssociationSource; +use crate::command::{DimensionAssociationSource, DimensionPreview}; use crate::entities::dim_override; use crate::scene::viewport_ref::{AcceptedSnap, MeasurementScale, SnapSourceRef, ViewportFrame}; use acadrust::entities::Dimension; @@ -27,12 +27,25 @@ impl OpenCADStudio { } pub(crate) fn dimension_measure_space(&self, i: usize) -> DimensionMeasureSpace { + self.dimension_measure_space_with_candidate(i, None) + } + + fn dimension_measure_space_with_candidate( + &self, + i: usize, + candidate: Option, + ) -> DimensionMeasureSpace { let scene = &self.tabs[i].scene; if scene.current_layout == "Model" || scene.active_viewport.is_some() { return DimensionMeasureSpace::Direct; } let mut frame: Option = None; - for f in self.accepted_snaps().iter().filter_map(|snap| snap.frame) { + for f in self + .accepted_snaps() + .iter() + .filter_map(|snap| snap.frame) + .chain(candidate) + { match frame { None => frame = Some(f), Some(previous) if previous.viewport == f.viewport => {} @@ -122,13 +135,10 @@ impl OpenCADStudio { i: usize, entity: &mut EntityType, ) -> bool { - let EntityType::Dimension(dimension) = entity else { + if !matches!(entity, EntityType::Dimension(_)) { return true; - }; - let angular = matches!( - dimension, - Dimension::Angular2Ln(_) | Dimension::Angular3Pt(_) - ); + } + let angular = is_angular_dimension(entity); let frame = match self.dimension_measure_space(i) { DimensionMeasureSpace::Direct => return true, DimensionMeasureSpace::ConflictingViewports => { @@ -140,18 +150,106 @@ impl OpenCADStudio { if angular { return true; } + let Some(scale) = self.viewport_dimension_scale(i, entity, frame) else { + return false; + }; + // Owner is needed even by DIMASSOC=0's explode path, before insertion. + entity.common_mut().owner_handle = self.tabs[i].scene.current_layout_block_handle_pub(); + scale.write_to_entity(entity); + true + } + + fn viewport_dimension_scale( + &self, + i: usize, + entity: &EntityType, + frame: ViewportFrame, + ) -> Option { let dimlfac = self.pending_dimension_style_dimlfac(i, entity); let scale = MeasurementScale { user_lfac: MeasurementScale::user_lfac_for_space(dimlfac, true), viewport_compensation: frame.paper_to_model_length_factor(), }; - if !scale.paper_factor().is_finite() || scale.paper_factor() <= 0.0 { - return false; + (scale.paper_factor().is_finite() && scale.paper_factor() > 0.0).then_some(scale) + } + + /// Styled dimensions for the placement preview at `cursor`, carrying the + /// same style and viewport measurement the commit path would apply. + /// `None` when the active command has no committable dimension at this + /// stage; an empty list when the candidate geometry or scale is invalid. + pub(crate) fn dimension_preview_entities( + &self, + i: usize, + cursor: DVec3, + ) -> Option> { + let command = self.tabs[i].active_cmd.as_ref()?; + let previews = command.dimension_preview(cursor)?; + let scene = &self.tabs[i].scene; + let measure_space = if command.measures_through_viewports() { + // The hovered model snap supplies a provisional viewport only while + // the second definition point is still being acquired. + let candidate = if command.dimension_placement_pending() { + None + } else { + self.vp_snap_frame.filter(|frame| { + self.tabs[i] + .snap_result + .is_some_and(|hit| hit.viewport == Some(frame.viewport)) + }) + }; + self.dimension_measure_space_with_candidate(i, candidate) + } else { + DimensionMeasureSpace::Direct + }; + if matches!(measure_space, DimensionMeasureSpace::ConflictingViewports) { + return Some(Vec::new()); } - // Owner is needed even by DIMASSOC=0's explode path, before insertion. - entity.common_mut().owner_handle = self.tabs[i].scene.current_layout_block_handle_pub(); - scale.write_to_entity(entity); - true + let mut entities = Vec::with_capacity(previews.len()); + for DimensionPreview { + mut entity, + preserve_base_style, + } in previews + { + if !preserve_base_style { + crate::scene::creation_style::apply_current_creation_styles( + &scene.document, + &mut entity, + ); + } + entity.common_mut().owner_handle = scene.current_layout_block_handle_pub(); + if let DimensionMeasureSpace::Viewport(frame) = measure_space { + if !is_angular_dimension(&entity) { + let Some(scale) = self.viewport_dimension_scale(i, &entity, frame) else { + return Some(Vec::new()); + }; + scale.write_to_entity(&mut entity); + } + } + entities.push(entity); + } + Some(entities) + } + + pub(crate) fn dimension_preview_wires( + &self, + i: usize, + cursor: DVec3, + ) -> Option> { + let entities = self.dimension_preview_entities(i, cursor)?; + // Degenerate geometry, an invalid scale, or conflicting viewports + // produce no styled dimension. Returning None lets the command's + // plain rubber-band preview run, which shows no value to mistrust. + if entities.is_empty() { + return None; + } + let mut wires = self.tabs[i].scene.wires_for_entities(&entities); + for wire in &mut wires { + wire.color = crate::scene::WireModel::CYAN; + for vertex in &mut wire.text_verts { + vertex.color = crate::scene::WireModel::CYAN; + } + } + Some(wires) } pub(crate) fn try_dimension_viewport_entity_pick( @@ -254,3 +352,10 @@ impl OpenCADStudio { .bump_entities(&[(dimension, crate::scene::ChangeKind::Modified)]); } } + +fn is_angular_dimension(entity: &EntityType) -> bool { + matches!( + entity, + EntityType::Dimension(Dimension::Angular2Ln(_) | Dimension::Angular3Pt(_)) + ) +} diff --git a/src/app/dimension_preview_tests.rs b/src/app/dimension_preview_tests.rs new file mode 100644 index 000000000..8268d0979 --- /dev/null +++ b/src/app/dimension_preview_tests.rs @@ -0,0 +1,225 @@ +//! Styled placement previews must show the text and geometry the command +//! commits at the same stage. Viewport measurement cases live in +//! `viewport_dimension_tests`; these cover the remaining creation commands +//! in model space, style inheritance, batches, and degenerate stages. + +use super::viewport_dimension_tests::preview_text as text; +use super::OpenCADStudio; +use crate::command::StepInput; +use acadrust::entities::{Arc, Circle, Line}; +use acadrust::types::{Handle, Vector3}; +use acadrust::EntityType; +use glam::DVec3; + +fn app() -> (OpenCADStudio, usize) { + let mut app = OpenCADStudio::new_for_test(); + app.automation_op(r#"{"op":"new"}"#); + let i = app.active_tab; + (app, i) +} + +fn style_name(entity: &EntityType) -> String { + let EntityType::Dimension(dimension) = entity else { + panic!("expected dimension") + }; + dimension.base().style_name.clone() +} + +fn committed(app: &OpenCADStudio) -> Vec { + app.tabs[app.active_tab] + .scene + .document + .entities() + .filter(|entity| matches!(entity, EntityType::Dimension(_))) + .cloned() + .collect() +} + +/// Styled preview entities at `cursor`, asserting they also tessellate. +fn preview(app: &OpenCADStudio, i: usize, cursor: DVec3) -> Vec { + let entities = app.dimension_preview_entities(i, cursor).unwrap(); + assert!(!entities.is_empty(), "no styled preview at {cursor}"); + assert!(!app.dimension_preview_wires(i, cursor).unwrap().is_empty()); + entities +} + +fn commit_and_compare(app: &mut OpenCADStudio, i: usize, cursor: DVec3) -> EntityType { + let previewed = preview(app, i, cursor); + assert_eq!(previewed.len(), 1); + let before = committed(app).len(); + let _ = app.feed_command(StepInput::Point(cursor)); + let after = committed(app); + assert_eq!(after.len(), before + 1, "commit did not add one dimension"); + let entity = after.last().unwrap().clone(); + assert_eq!(text(app, &entity), text(app, &previewed[0])); + assert_eq!(style_name(&entity), style_name(&previewed[0])); + entity +} + +#[test] +fn ordinate_preview_matches_commit() { + let (mut app, i) = app(); + let _ = app.dispatch_command("DIMORDINATE"); + assert!(app.dimension_preview_entities(i, DVec3::ZERO).is_none()); + let _ = app.feed_command(StepInput::Point(DVec3::new(12.5, 40.0, 0.0))); + let entity = commit_and_compare(&mut app, i, DVec3::new(12.5, 90.0, 0.0)); + assert_eq!(text(&app, &entity), "12.5"); +} + +#[test] +fn arc_length_preview_matches_commit_for_full_and_partial_arcs() { + let (mut app, i) = app(); + let mut arc = Arc::new(); + arc.center = Vector3::ZERO; + arc.radius = 50.0; + arc.start_angle = 0.0; + arc.end_angle = std::f64::consts::FRAC_PI_2; + let handle = app.tabs[i].scene.add_entity(EntityType::Arc(arc)); + let on_arc = |degrees: f64| { + let radians = degrees.to_radians(); + DVec3::new(50.0 * radians.cos(), 50.0 * radians.sin(), 0.0) + }; + + let _ = app.dispatch_command("DIMARC"); + let _ = app.feed_command(StepInput::EntityPick(handle, on_arc(45.0))); + let full = commit_and_compare(&mut app, i, DVec3::new(60.0, 60.0, 0.0)); + + let _ = app.dispatch_command("DIMARC"); + let _ = app.feed_command(StepInput::EntityPick(handle, on_arc(45.0))); + let _ = app.feed_command(StepInput::Text("P".into())); + let _ = app.feed_command(StepInput::Point(on_arc(10.0))); + // While the second partial point is acquired the command has no + // committable dimension; the plain arc preview stays in charge. + assert!(app.dimension_preview_entities(i, on_arc(80.0)).is_none()); + let _ = app.feed_command(StepInput::Point(on_arc(80.0))); + let partial = commit_and_compare(&mut app, i, DVec3::new(60.0, 60.0, 0.0)); + assert_ne!(text(&app, &full), text(&app, &partial)); +} + +#[test] +fn jogged_radius_preview_matches_commit_at_text_and_jog_stages() { + let (mut app, i) = app(); + let mut circle = Circle::new(); + circle.center = Vector3::ZERO; + circle.radius = 100.0; + let handle = app.tabs[i].scene.add_entity(EntityType::Circle(circle)); + let _ = app.dispatch_command("DIMJOGGED"); + let _ = app.feed_command(StepInput::EntityPick(handle, DVec3::new(100.0, 0.0, 0.0))); + assert!(app + .dimension_preview_entities(i, DVec3::new(30.0, 0.0, 0.0)) + .is_none()); + let _ = app.feed_command(StepInput::Point(DVec3::new(30.0, 0.0, 0.0))); + let text_position = DVec3::new(150.0, 40.0, 0.0); + let staged = preview(&app, i, text_position); + assert_eq!(staged.len(), 1); + let _ = app.feed_command(StepInput::Point(text_position)); + let entity = commit_and_compare(&mut app, i, DVec3::new(90.0, 20.0, 0.0)); + assert_eq!(text(&app, &entity), text(&app, &staged[0])); + assert_eq!(text(&app, &entity), "R100"); +} + +#[test] +fn angular_three_point_preview_matches_commit() { + let (mut app, i) = app(); + let _ = app.dispatch_command("DIMANGULAR"); + let _ = app.feed_command(StepInput::Point(DVec3::ZERO)); + let _ = app.feed_command(StepInput::Point(DVec3::new(100.0, 0.0, 0.0))); + assert!(app + .dimension_preview_entities(i, DVec3::new(0.0, 100.0, 0.0)) + .is_none()); + let _ = app.feed_command(StepInput::Point(DVec3::new(0.0, 100.0, 0.0))); + let entity = commit_and_compare(&mut app, i, DVec3::new(70.0, 70.0, 0.0)); + assert_eq!(text(&app, &entity), "90°"); +} + +fn place_linear(app: &mut OpenCADStudio, first: DVec3, second: DVec3, line: DVec3) -> Handle { + let _ = app.dispatch_command("DIMLINEAR"); + let _ = app.feed_command(StepInput::Point(first)); + let _ = app.feed_command(StepInput::Point(second)); + let _ = app.feed_command(StepInput::Point(line)); + committed(app).last().unwrap().common().handle +} + +#[test] +fn continue_and_baseline_previews_follow_style_inheritance() { + let (mut app, i) = app(); + let default_style = app.tabs[i] + .scene + .document + .header + .current_dimstyle_name + .clone(); + let mut style = acadrust::tables::DimStyle::new("Inherit"); + style.dimlfac = 2.0; + app.tabs[i].scene.document.dim_styles.add(style).unwrap(); + app.tabs[i].scene.document.header.current_dimstyle_name = "Inherit".into(); + let base = place_linear( + &mut app, + DVec3::ZERO, + DVec3::new(100.0, 0.0, 0.0), + DVec3::new(50.0, -20.0, 0.0), + ); + assert_eq!(text(&app, committed(&app).last().unwrap()), "200"); + app.tabs[i].scene.document.header.current_dimstyle_name = default_style; + + // DIMCONTINUEMODE=1 keeps the base dimension's style in the preview. + app.dimension_continue_mode = 1; + app.tabs[i].scene.last_created_dimension = Some(base); + let _ = app.dispatch_command("DIMCONTINUE"); + let inherited = commit_and_compare(&mut app, i, DVec3::new(250.0, 0.0, 0.0)); + assert_eq!(style_name(&inherited), "Inherit"); + assert_eq!(text(&app, &inherited), "300"); + + // DIMCONTINUEMODE=0 runs the current creation defaults over the built + // dimension instead; preview and commit must still agree. + app.dimension_continue_mode = 0; + app.tabs[i].scene.last_created_dimension = Some(inherited.common().handle); + let _ = app.dispatch_command("DIMCONTINUE"); + commit_and_compare(&mut app, i, DVec3::new(350.0, 0.0, 0.0)); + + app.dimension_continue_mode = 1; + app.tabs[i].scene.last_created_dimension = Some(base); + let _ = app.dispatch_command("DIMBASELINE"); + let baseline = commit_and_compare(&mut app, i, DVec3::new(300.0, 0.0, 0.0)); + assert_eq!(style_name(&baseline), "Inherit"); + assert_eq!(text(&app, &baseline), "600"); +} + +#[test] +fn qdim_preview_matches_the_committed_batch() { + let (mut app, i) = app(); + let segments = [(0.0, 40.0), (40.0, 100.0), (100.0, 130.0)]; + for (start, end) in segments { + let line = Line::from_points(Vector3::new(start, 0.0, 0.0), Vector3::new(end, 0.0, 0.0)); + let handle = app.tabs[i].scene.add_entity(EntityType::Line(line)); + app.tabs[i].scene.select_entity(handle, false); + } + let _ = app.dispatch_command("QDIM"); + let cursor = DVec3::new(60.0, 40.0, 0.0); + let previewed = preview(&app, i, cursor); + assert_eq!(previewed.len(), segments.len()); + let mut preview_texts: Vec = previewed.iter().map(|e| text(&app, e)).collect(); + let _ = app.feed_command(StepInput::Point(cursor)); + let mut committed_texts: Vec = committed(&app).iter().map(|e| text(&app, e)).collect(); + preview_texts.sort(); + committed_texts.sort(); + assert_eq!(committed_texts, preview_texts); + assert_eq!(committed_texts, ["30", "40", "60"]); +} + +#[test] +fn degenerate_linear_preview_defers_to_plain_rubber_band() { + let (mut app, i) = app(); + let _ = app.dispatch_command("DIMLINEAR"); + assert!(app.dimension_preview_entities(i, DVec3::ZERO).is_none()); + let _ = app.feed_command(StepInput::Point(DVec3::ZERO)); + assert!(app + .dimension_preview_entities(i, DVec3::ZERO) + .is_some_and(|entities| entities.is_empty())); + assert!(app.dimension_preview_wires(i, DVec3::ZERO).is_none()); + assert!(committed(&app).is_empty()); + let cursor = DVec3::new(80.0, 0.0, 0.0); + let styled = preview(&app, i, cursor); + assert_eq!(text(&app, &styled[0]), "80"); + assert!(committed(&app).is_empty(), "hover must not commit"); +} diff --git a/src/app/mod.rs b/src/app/mod.rs index c1a8594b6..fab5a9ecb 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -13,6 +13,8 @@ pub(crate) mod commands; pub(crate) mod dim_viewport; #[cfg(test)] mod viewport_dimension_tests; +#[cfg(test)] +mod dimension_preview_tests; mod document; mod drafting_settings; pub(crate) mod expr_eval; diff --git a/src/app/update/viewport.rs b/src/app/update/viewport.rs index 8eaa7cf08..8478a94b9 100644 --- a/src/app/update/viewport.rs +++ b/src/app/update/viewport.rs @@ -2547,6 +2547,8 @@ impl OpenCADStudio { } } p + } else if let Some(wires) = self.dimension_preview_wires(i, effective) { + wires } else { self.tabs[i] .active_cmd diff --git a/src/app/viewport_dimension_tests.rs b/src/app/viewport_dimension_tests.rs index 3d0371604..a10b4fa45 100644 --- a/src/app/viewport_dimension_tests.rs +++ b/src/app/viewport_dimension_tests.rs @@ -87,6 +87,14 @@ fn displayed(doc: &CadDocument) -> f64 { dim.measurement() * MeasurementScale::user_lfac_for_space(factor, true) } +pub(super) fn preview_text(app: &OpenCADStudio, entity: &EntityType) -> String { + let EntityType::Dimension(dimension) = entity else { panic!("expected dimension") }; + let doc = &app.tabs[app.active_tab].scene.document; + let source = doc.dim_styles.get(&dimension.base().style_name).unwrap(); + let style = crate::entities::dimension::resolved_dimension_style(source, dimension, doc); + crate::entities::dimension::dimension_text_value(dimension, Some(&style)).unwrap() +} + fn finish_aligned(app: &mut OpenCADStudio, line: Handle, frame: ViewportFrame) { let _ = app.dispatch_command("DIMALIGNED"); point(app, frame, line, DVec3::ZERO); @@ -300,7 +308,13 @@ fn viewport_dimension_linear_aligned_and_radial_object_picks() { if radial { 1 } else { 2 }, "{name}" ); - let _ = app.feed_command(StepInput::Point(DVec3::new(55.0, 80.0, 0.0))); + let place = DVec3::new(55.0, 80.0, 0.0); + let preview = app.dimension_preview_entities(i, place).unwrap(); + assert_eq!(preview.len(), 1, "{name}"); + let value = preview_text(&app, &preview[0]); + assert!(!app.dimension_preview_wires(i, place).unwrap().is_empty(), "{name}"); + let _ = app.feed_command(StepInput::Point(place)); + assert_eq!(preview_text(&app, &EntityType::Dimension(dimension(&app.tabs[i].scene.document).clone())), value, "{name}"); assert!( (displayed(&app.tabs[i].scene.document) - expected).abs() < 1e-4, "{name}: {}", @@ -326,12 +340,45 @@ fn viewport_dimension_angular_preserves_angle_without_length_factor() { point(&mut app, frame, line, DVec3::ZERO); point(&mut app, frame, line, DVec3::X * 100.0); point(&mut app, frame, vertical, DVec3::Y * 100.0); - let _ = app.feed_command(StepInput::Point(DVec3::new(70.0, 70.0, 0.0))); + let place = DVec3::new(70.0, 70.0, 0.0); + let preview = app.dimension_preview_entities(i, place).unwrap(); + assert_eq!(preview.len(), 1); + assert!(MeasurementScale::read(&preview[0].common().extended_data).is_none()); + let value = preview_text(&app, &preview[0]); + let _ = app.feed_command(StepInput::Point(place)); let doc = &app.tabs[app.active_tab].scene.document; + assert_eq!(preview_text(&app, &EntityType::Dimension(dimension(doc).clone())), value); assert!((dimension(doc).measurement() - 90.0).abs() < 1e-5); assert!(MeasurementScale::read(&dimension(doc).base().common.extended_data).is_none()); } +#[test] +fn viewport_dimension_preview_uses_candidate_scale_only_before_second_pick() { + for command in ["DIMLINEAR", "DIMALIGNED"] { + let (mut app, model, frame) = fixture(); + let i = app.active_tab; + let mut style = acadrust::tables::DimStyle::new("PreviewUnits"); + style.dimlfac = 25.4; + app.tabs[i].scene.document.dim_styles.add(style).unwrap(); + app.tabs[i].scene.document.header.current_dimstyle_name = "PreviewUnits".into(); + let _ = app.dispatch_command(command); + let _ = app.feed_command(StepInput::Point(DVec3::new(55.0, 50.0, 0.0))); + let target = hit(frame, model, DVec3::X * 100.0); + let sheet = app.dimension_preview_entities(i, target.world).unwrap(); + assert_eq!(preview_text(&app, &sheet[0]), "127"); + app.vp_snap_frame = Some(frame); + app.tabs[i].snap_result = Some(target); + let model_preview = app.dimension_preview_entities(i, target.world).unwrap(); + assert_eq!(preview_text(&app, &model_preview[0]), "1270"); + point(&mut app, frame, model, DVec3::X * 100.0); + let other = ViewportFrame { viewport: Handle::new(0xFFFF), scale: frame.scale * 2.0, ..frame }; + app.vp_snap_frame = Some(other); + app.tabs[i].snap_result = Some(hit(other, model, DVec3::X * 100.0)); + let placement = app.dimension_preview_entities(i, DVec3::new(60.0, 80.0, 0.0)).unwrap(); + assert_eq!(preview_text(&app, &placement[0]), "1270"); + } +} + #[test] fn viewport_dimension_snap_query_matches_pan_scale_and_twist() { for (height, twist, target) in [ diff --git a/src/command.rs b/src/command.rs index 4a8f64344..a39f6a171 100644 --- a/src/command.rs +++ b/src/command.rs @@ -1345,6 +1345,23 @@ pub enum DimensionBreakOperation { Remove, } +/// A transient dimension built for the placement preview. +pub struct DimensionPreview { + pub entity: EntityType, + /// Keep the base dimension's style instead of the current one + /// (`DIMCONTINUE` / `DIMBASELINE` with `DIMCONTINUEMODE=1`). + pub preserve_base_style: bool, +} + +impl DimensionPreview { + pub fn current_style(entity: EntityType) -> Self { + Self { + entity, + preserve_base_style: false, + } + } +} + /// Returned by every `CadCommand` method to tell main.rs what to do. #[allow(dead_code)] pub enum CmdResult { @@ -2279,6 +2296,13 @@ pub trait CadCommand: Send { false } + /// Dimensions the command would commit if the cursor were clicked now. + /// `None` means this stage has no committable dimension; an empty list + /// means the geometry is degenerate and nothing styled should be shown. + fn dimension_preview(&self, _cursor: DVec3) -> Option> { + None + } + /// Needs entity hit-testing instead of point input. fn needs_entity_pick(&self) -> bool { false diff --git a/src/modules/annotate/aligned_dim.rs b/src/modules/annotate/aligned_dim.rs index ed7940633..36013041b 100644 --- a/src/modules/annotate/aligned_dim.rs +++ b/src/modules/annotate/aligned_dim.rs @@ -6,7 +6,8 @@ use acadrust::EntityType; use glam::DVec3; use crate::command::{ - CadCommand, CmdOption, CmdResult, DimensionAssociationInput, InputKind, WorkingPlane, + CadCommand, CmdOption, CmdResult, DimensionAssociationInput, DimensionPreview, InputKind, + WorkingPlane, }; use crate::modules::{IconKind, ModuleEvent, ToolDef}; use crate::scene::model::wire_model::WireModel; @@ -61,6 +62,29 @@ impl AlignedDimensionCommand { mtext_override: false, } } + + fn build_dimension(&self, first: DVec3, second: DVec3, point: DVec3) -> EntityType { + let first = self.plane.to_local(first); + let second = self.plane.to_local(second); + let point = self.plane.to_local(point); + let mut dim = DimensionAligned::new(v3(first), v3(second)); + // The placement point is stored so commit matches the preview. + dim.definition_point = v3(point); + dim.base.definition_point = v3(point); + let (d1, d2) = dim_line_endpoints(first, second, point); + dim.base.text_middle_point = v3((d1 + d2) * 0.5); + dim.base.insertion_point = dim.base.text_middle_point; + dim.base.actual_measurement = dim.measurement(); + crate::entities::dimension::set_dimension_text_override( + &mut dim.base, + self.text_override.clone(), + ); + // An explicit text angle overrides the default rotation. + if let Some(angle) = self.text_angle { + dim.base.text_rotation = angle; + } + self.plane.place_entity(EntityType::Dimension(Dimension::Aligned(dim))) + } } impl CadCommand for AlignedDimensionCommand { @@ -115,27 +139,8 @@ impl CadCommand for AlignedDimensionCommand { CmdResult::NeedPoint } Step::DimLine { p1, p2 } => { - let p1 = self.plane.to_local(p1); - let p2 = self.plane.to_local(p2); - let pt = self.plane.to_local(pt); - let mut dim = DimensionAligned::new(v3(p1), v3(p2)); - // Store the cursor position so commit matches the preview. - dim.definition_point = v3(pt); - dim.base.definition_point = v3(pt); - let (d1, d2) = dim_line_endpoints(p1, p2, pt); - dim.base.text_middle_point = v3((d1 + d2) * 0.5); - dim.base.insertion_point = dim.base.text_middle_point; - dim.base.actual_measurement = dim.measurement(); - crate::entities::dimension::set_dimension_text_override( - &mut dim.base, - self.text_override.clone(), - ); - // An explicit text angle overrides the default rotation. - if let Some(a) = self.text_angle { - dim.base.text_rotation = a; - } CmdResult::CommitDimension { - entity: self.plane.place_entity(EntityType::Dimension(Dimension::Aligned(dim))), + entity: self.build_dimension(p1, p2, pt), association: DimensionAssociationInput::Infer(self.source_handle), preserve_base_style: false, continue_command: false, @@ -363,6 +368,23 @@ impl CadCommand for AlignedDimensionCommand { ref_point: None, }) } + + fn dimension_preview(&self, cursor: DVec3) -> Option> { + if self.selecting_object { + return None; + } + let (first, second) = match self.step { + Step::First => return None, + Step::Second(first) => (first, cursor), + Step::DimLine { p1, p2 } => (p1, p2), + }; + if first.distance_squared(second) <= 1e-24 { + return Some(Vec::new()); + } + Some(vec![DimensionPreview::current_style( + self.build_dimension(first, second, cursor), + )]) + } } fn v3(p: DVec3) -> Vector3 { diff --git a/src/modules/annotate/angular_dim.rs b/src/modules/annotate/angular_dim.rs index 66fb49596..3df4b2d3d 100644 --- a/src/modules/annotate/angular_dim.rs +++ b/src/modules/annotate/angular_dim.rs @@ -7,8 +7,8 @@ use cadkernel::geom2d::{ }; use crate::command::{ - CadCommand, CmdOption, CmdResult, DimensionAssociationInput, - DimensionAssociationSource, InputKind, WorkingPlane, + CadCommand, CmdOption, CmdResult, DimensionAssociationInput, DimensionAssociationSource, + DimensionPreview, InputKind, WorkingPlane, }; use crate::modules::{IconKind, ModuleEvent, ToolDef}; use crate::scene::model::wire_model::WireModel; @@ -807,6 +807,32 @@ impl CadCommand for AngularDimensionCommand { }; Some(preview_wire(points)) } + + fn dimension_preview(&self, cursor: DVec3) -> Option> { + if self.selecting_object { + return None; + } + let result = match self.step { + Step::ArcPoint3 { + vertex, + first, + second, + } => self.finish_three_point(vertex, first, second, cursor), + Step::ArcPoint2 { + first_start, + first_end, + second_start, + second_end, + } => self.finish_two_line(first_start, first_end, second_start, second_end, cursor), + _ => return None, + }; + match result { + CmdResult::CommitDimension { entity, .. } => { + Some(vec![DimensionPreview::current_style(entity)]) + } + _ => Some(Vec::new()), + } + } } fn picked_curve(entity: &EntityType, click: DVec3) -> Option { diff --git a/src/modules/annotate/arc_length_dim.rs b/src/modules/annotate/arc_length_dim.rs index 4797c70c9..74ad33677 100644 --- a/src/modules/annotate/arc_length_dim.rs +++ b/src/modules/annotate/arc_length_dim.rs @@ -5,8 +5,8 @@ use cadkernel::geom2d::tessellate::{arc, DEFAULT_SEGMENTS_PER_RADIAN}; use glam::DVec3; use crate::command::{ - CadCommand, CmdOption, CmdResult, DimensionAssociationInput, - DimensionAssociationSource, InputKind, WorkingPlane, + CadCommand, CmdOption, CmdResult, DimensionAssociationInput, DimensionAssociationSource, + DimensionPreview, InputKind, WorkingPlane, }; use crate::modules::{IconKind, ModuleEvent, ToolDef}; use crate::scene::dimension_assoc::{ @@ -516,6 +516,18 @@ impl CadCommand for ArcLengthDimensionCommand { } } } + + fn dimension_preview(&self, cursor: DVec3) -> Option> { + let Step::DimLine(selection) = self.step else { + return None; + }; + match self.commit_dimension(selection, cursor) { + CmdResult::CommitDimension { entity, .. } => { + Some(vec![DimensionPreview::current_style(entity)]) + } + _ => Some(Vec::new()), + } + } } fn positive_sweep(start: f64, end: f64) -> f64 { diff --git a/src/modules/annotate/diameter_dim.rs b/src/modules/annotate/diameter_dim.rs index b41fa2839..ef9255aae 100644 --- a/src/modules/annotate/diameter_dim.rs +++ b/src/modules/annotate/diameter_dim.rs @@ -4,7 +4,8 @@ use acadrust::EntityType; use glam::{DVec3, Vec3}; use crate::command::{ - CadCommand, CmdOption, CmdResult, DimensionAssociationInput, InputKind, WorkingPlane, + CadCommand, CmdOption, CmdResult, DimensionAssociationInput, DimensionPreview, InputKind, + WorkingPlane, }; use crate::modules::{IconKind, ModuleEvent, ToolDef}; use crate::scene::model::wire_model::WireModel; @@ -55,6 +56,35 @@ impl DiameterDimensionCommand { Step::DimLine(source) => dvec(source.point_at_angle(source.start_angle)), } } + + fn build_dimension( + &self, + source: crate::scene::dimension_assoc::RadialSourceGeometry, + world: DVec3, + ) -> EntityType { + let plane = WorkingPlane::new( + DVec3::from_array(source.plane.origin), + DVec3::from_array(source.plane.x_axis), + DVec3::from_array(source.plane.y_axis), + ); + let chord = plane.to_local(dvec(source.chord_at(world.to_array()))); + let far_chord = plane.to_local(dvec(source.opposite_chord_at(world.to_array()))); + let point = plane.to_local(world); + let mut dim = DimensionDiameter::new(v3(chord), v3(far_chord)); + dim.base.text_middle_point = v3(point); + dim.base.insertion_point = v3(point); + dim.base.text_user_positioned = true; + dim.leader_length = chord.distance(point); + dim.base.actual_measurement = dim.measurement(); + crate::entities::dimension::set_dimension_text_override( + &mut dim.base, + self.text_override.clone(), + ); + if let Some(angle) = self.text_angle { + dim.base.text_rotation = angle; + } + plane.place_entity(EntityType::Dimension(Dimension::Diameter(dim))) + } } impl CadCommand for DiameterDimensionCommand { @@ -88,30 +118,8 @@ impl CadCommand for DiameterDimensionCommand { match self.step { Step::SelectObject => CmdResult::NeedPoint, Step::DimLine(source) => { - let source_plane = WorkingPlane::new( - DVec3::from_array(source.plane.origin), - DVec3::from_array(source.plane.x_axis), - DVec3::from_array(source.plane.y_axis), - ); - let chord = source_plane.to_local(dvec(source.chord_at(pt.to_array()))); - let far_chord = source_plane.to_local(dvec(source.opposite_chord_at(pt.to_array()))); - let pt = source_plane.to_local(pt); - let mut dim = DimensionDiameter::new(v3(chord), v3(far_chord)); - dim.base.text_middle_point = v3(pt); - dim.base.insertion_point = v3(pt); - dim.base.text_user_positioned = true; - dim.leader_length = chord.distance(pt); - dim.base.actual_measurement = dim.measurement(); - crate::entities::dimension::set_dimension_text_override( - &mut dim.base, - self.text_override.clone(), - ); - if let Some(a) = self.text_angle { - dim.base.text_rotation = a; - } CmdResult::CommitDimension { - entity: source_plane - .place_entity(EntityType::Dimension(Dimension::Diameter(dim))), + entity: self.build_dimension(source, pt), association: DimensionAssociationInput::Infer(self.source_handle), preserve_base_style: false, continue_command: false, @@ -278,6 +286,13 @@ impl CadCommand for DiameterDimensionCommand { } } } + + fn dimension_preview(&self, cursor: DVec3) -> Option> { + let Step::DimLine(source) = self.step else { + return None; + }; + Some(vec![DimensionPreview::current_style(self.build_dimension(source, cursor))]) + } } fn v3(p: DVec3) -> Vector3 { diff --git a/src/modules/annotate/dim_baseline.rs b/src/modules/annotate/dim_baseline.rs index 82e1c51b6..28be7350a 100644 --- a/src/modules/annotate/dim_baseline.rs +++ b/src/modules/annotate/dim_baseline.rs @@ -13,7 +13,9 @@ use cadkernel::geom2d::{ use cadkernel::space::Plane; use glam::DVec3; -use crate::command::{CadCommand, CmdOption, CmdResult, DimensionAssociationInput}; +use crate::command::{ + CadCommand, CmdOption, CmdResult, DimensionAssociationInput, DimensionPreview, +}; use crate::modules::{IconKind, ModuleEvent, ToolDef}; use crate::scene::model::wire_model::WireModel; @@ -482,6 +484,16 @@ impl CadCommand for DimBaselineCommand { self.build_dimension(point) .map(|dimension| preview_for_dimension(&dimension)) } + + fn dimension_preview(&self, cursor: DVec3) -> Option> { + self.build_dimension(cursor) + .map(|dimension| { + vec![DimensionPreview { + entity: EntityType::Dimension(dimension), + preserve_base_style: self.preserve_base_style, + }] + }) + } } fn linear_base( diff --git a/src/modules/annotate/dim_continue.rs b/src/modules/annotate/dim_continue.rs index f4651a2af..06bbcb419 100644 --- a/src/modules/annotate/dim_continue.rs +++ b/src/modules/annotate/dim_continue.rs @@ -11,7 +11,9 @@ use cadkernel::geom2d::{ use cadkernel::space::Plane; use glam::DVec3; -use crate::command::{CadCommand, CmdOption, CmdResult, DimensionAssociationInput}; +use crate::command::{ + CadCommand, CmdOption, CmdResult, DimensionAssociationInput, DimensionPreview, +}; use crate::modules::{IconKind, ModuleEvent, ToolDef}; use crate::scene::model::wire_model::WireModel; @@ -423,6 +425,16 @@ impl CadCommand for DimContinueCommand { self.build_dimension(point) .map(|dimension| preview_for_dimension(&dimension)) } + + fn dimension_preview(&self, cursor: DVec3) -> Option> { + self.build_dimension(cursor) + .map(|dimension| { + vec![DimensionPreview { + entity: EntityType::Dimension(dimension), + preserve_base_style: self.preserve_base_style, + }] + }) + } } fn build_linear( diff --git a/src/modules/annotate/jogged_radius_dim.rs b/src/modules/annotate/jogged_radius_dim.rs index b88766a2a..405685658 100644 --- a/src/modules/annotate/jogged_radius_dim.rs +++ b/src/modules/annotate/jogged_radius_dim.rs @@ -5,7 +5,7 @@ use glam::{DVec3, Vec3}; use crate::command::{ CadCommand, CmdOption, CmdResult, DimensionAssociationInput, DimensionAssociationSource, - InputKind, WorkingPlane, + DimensionPreview, InputKind, WorkingPlane, }; use crate::modules::{IconKind, ModuleEvent, ToolDef}; use crate::scene::creation_style::DimensionCreationDefaults; @@ -390,6 +390,35 @@ impl CadCommand for JoggedRadiusDimensionCommand { } } } + + fn dimension_preview(&self, cursor: DVec3) -> Option> { + let result = match self.step { + Step::DimLine { + source, + override_center, + } => { + // The jog is not chosen yet; preview it midway along the + // dimension line so the styled value is visible while the + // text position is placed. + let chord = dvec(source.chord_at(cursor.to_array())); + let jog = (override_center + chord) * 0.5; + self.commit_dimension(source, override_center, chord, cursor, jog) + } + Step::Jog { + source, + override_center, + chord, + text_position, + } => self.commit_dimension(source, override_center, chord, text_position, cursor), + _ => return None, + }; + match result { + CmdResult::CommitDimension { entity, .. } => { + Some(vec![DimensionPreview::current_style(entity)]) + } + _ => Some(Vec::new()), + } + } } fn source_plane(source: RadialSourceGeometry) -> WorkingPlane { diff --git a/src/modules/annotate/linear_dim.rs b/src/modules/annotate/linear_dim.rs index 0e48e1915..5e9b00c94 100644 --- a/src/modules/annotate/linear_dim.rs +++ b/src/modules/annotate/linear_dim.rs @@ -6,7 +6,8 @@ use cadkernel::geom2d::{ }; use crate::command::{ - CadCommand, CmdOption, CmdResult, DimensionAssociationInput, InputKind, WorkingPlane, + CadCommand, CmdOption, CmdResult, DimensionAssociationInput, DimensionPreview, InputKind, + WorkingPlane, }; use crate::modules::{IconKind, ModuleEvent, ToolDef}; use crate::scene::model::wire_model::WireModel; @@ -107,6 +108,29 @@ impl LinearDimensionCommand { mtext_override: false, } } + + fn build_dimension(&self, first: DVec3, second: DVec3, point: DVec3) -> EntityType { + let first = self.plane.to_local(first); + let second = self.plane.to_local(second); + let point = self.plane.to_local(point); + let mut dim = DimensionLinear::new(v3(first), v3(second)); + let axis = self.axis_mode.axis(first, second, point); + dim.rotation = axis.y.atan2(axis.x); + dim.set_offset(dimension_line_offset(second, point, axis)); + dim.base.definition_point = dim.definition_point; + dim.base.text_middle_point = v3(linear_text_pos(first, second, point, axis)); + dim.base.insertion_point = dim.base.text_middle_point; + dim.base.actual_measurement = dim.measurement(); + crate::entities::dimension::set_dimension_text_override( + &mut dim.base, + self.text_override.clone(), + ); + // An explicit text angle overrides the UCS-derived rotation. + if let Some(angle) = self.text_angle { + dim.base.text_rotation = angle; + } + self.plane.place_entity(EntityType::Dimension(Dimension::Linear(dim))) + } } impl CadCommand for LinearDimensionCommand { @@ -164,28 +188,7 @@ impl CadCommand for LinearDimensionCommand { CmdResult::NeedPoint } Step::DimensionLine { first, second } => { - let first = self.plane.to_local(first); - let second = self.plane.to_local(second); - let pt = self.plane.to_local(pt); - let mut dim = DimensionLinear::new(v3(first), v3(second)); - let axis = self.axis_mode.axis(first, second, pt); - dim.rotation = axis.y.atan2(axis.x); - dim.set_offset(dimension_line_offset(second, pt, axis)); - dim.base.definition_point = dim.definition_point; - dim.base.text_middle_point = v3(linear_text_pos(first, second, pt, axis)); - dim.base.insertion_point = dim.base.text_middle_point; - dim.base.actual_measurement = dim.measurement(); - crate::entities::dimension::set_dimension_text_override( - &mut dim.base, - self.text_override.clone(), - ); - // An explicit text angle overrides the UCS-derived rotation. - if let Some(a) = self.text_angle { - dim.base.text_rotation = a; - } - let entity = self.plane.place_entity(EntityType::Dimension( - Dimension::Linear(dim), - )); + let entity = self.build_dimension(first, second, pt); CmdResult::CommitDimension { entity, association: DimensionAssociationInput::Infer(self.source_handle), @@ -402,6 +405,23 @@ impl CadCommand for LinearDimensionCommand { ref_point: None, }) } + + fn dimension_preview(&self, cursor: DVec3) -> Option> { + if self.selecting_object { + return None; + } + let (first, second) = match self.step { + Step::FirstPoint => return None, + Step::SecondPoint(first) => (first, cursor), + Step::DimensionLine { first, second } => (first, second), + }; + if first.distance_squared(second) <= 1e-24 { + return Some(Vec::new()); + } + Some(vec![DimensionPreview::current_style( + self.build_dimension(first, second, cursor), + )]) + } } fn v3(pt: DVec3) -> Vector3 { diff --git a/src/modules/annotate/ordinate_dim.rs b/src/modules/annotate/ordinate_dim.rs index 9611e8aa9..febbf62bd 100644 --- a/src/modules/annotate/ordinate_dim.rs +++ b/src/modules/annotate/ordinate_dim.rs @@ -4,7 +4,8 @@ use acadrust::EntityType; use glam::{DVec3, Vec3}; use crate::command::{ - CadCommand, CmdOption, CmdResult, DimensionAssociationInput, InputKind, WorkingPlane, + CadCommand, CmdOption, CmdResult, DimensionAssociationInput, DimensionPreview, InputKind, + WorkingPlane, }; use crate::modules::{IconKind, ModuleEvent, ToolDef}; use crate::scene::model::wire_model::WireModel; @@ -60,6 +61,26 @@ impl OrdinateDimCommand { Step::LeaderEndpoint { feature } => feature, } } + + fn build_dimension(&self, feature: DVec3, point: DVec3) -> EntityType { + let feature = self.plane.to_local(feature); + let point = self.plane.to_local(point); + let is_x = match self.datum_mode { + DatumMode::Automatic => is_x_type(feature, point), + DatumMode::X => true, + DatumMode::Y => false, + }; + let mut dim = DimensionOrdinate::new(v3(feature), v3(point), is_x); + crate::entities::dimension::set_dimension_text_override( + &mut dim.base, + self.text_override.clone(), + ); + if let Some(angle) = self.text_angle { + dim.base.text_rotation = angle; + } + dim.refresh_measurement(); + self.plane.place_entity(EntityType::Dimension(Dimension::Ordinate(dim))) + } } impl CadCommand for OrdinateDimCommand { @@ -95,26 +116,8 @@ impl CadCommand for OrdinateDimCommand { CmdResult::NeedPoint } Step::LeaderEndpoint { feature } => { - let feature = self.plane.to_local(feature); - let pt = self.plane.to_local(pt); - let is_x = match self.datum_mode { - DatumMode::Automatic => is_x_type(feature, pt), - DatumMode::X => true, - DatumMode::Y => false, - }; - let mut dim = DimensionOrdinate::new(v3(feature), v3(pt), is_x); - crate::entities::dimension::set_dimension_text_override( - &mut dim.base, - self.text_override.clone(), - ); - if let Some(angle) = self.text_angle { - dim.base.text_rotation = angle; - } - dim.refresh_measurement(); CmdResult::CommitDimension { - entity: self.plane.place_entity(EntityType::Dimension( - Dimension::Ordinate(dim), - )), + entity: self.build_dimension(feature, pt), association: DimensionAssociationInput::Infer(None), preserve_base_style: false, continue_command: false, @@ -258,6 +261,13 @@ impl CadCommand for OrdinateDimCommand { .collect(), )) } + + fn dimension_preview(&self, cursor: DVec3) -> Option> { + let Step::LeaderEndpoint { feature } = self.step else { + return None; + }; + Some(vec![DimensionPreview::current_style(self.build_dimension(feature, cursor))]) + } } fn v3(p: DVec3) -> Vector3 { diff --git a/src/modules/annotate/qdim.rs b/src/modules/annotate/qdim.rs index ab10ea0b7..8dbae29c9 100644 --- a/src/modules/annotate/qdim.rs +++ b/src/modules/annotate/qdim.rs @@ -8,7 +8,7 @@ use glam::DVec3; use crate::command::{ CadCommand, CmdOption, CmdResult, DimensionAssociationInput, DimensionAssociationSource, - EntityTransform, SelectionEntity, WorkingPlane, + DimensionPreview, EntityTransform, SelectionEntity, WorkingPlane, }; use crate::modules::{IconKind, ModuleEvent, ToolDef}; use crate::scene::model::wire_model::WireModel; @@ -364,6 +364,15 @@ impl CadCommand for QdimCommand { } Vec::new() } + + fn dimension_preview(&self, cursor: DVec3) -> Option> { + (self.step == Step::Place).then(|| { + self.build_dimensions(cursor) + .into_iter() + .map(|(entity, _)| DimensionPreview::current_style(entity)) + .collect() + }) + } } fn v3(point: DVec3) -> Vector3 { Vector3::new(point.x, point.y, point.z) } diff --git a/src/modules/annotate/radius_dim.rs b/src/modules/annotate/radius_dim.rs index 898ba52ca..e595aef72 100644 --- a/src/modules/annotate/radius_dim.rs +++ b/src/modules/annotate/radius_dim.rs @@ -3,7 +3,8 @@ use acadrust::types::{Handle, Vector3}; use acadrust::EntityType; use crate::command::{ - CadCommand, CmdOption, CmdResult, DimensionAssociationInput, InputKind, WorkingPlane, + CadCommand, CmdOption, CmdResult, DimensionAssociationInput, DimensionPreview, InputKind, + WorkingPlane, }; use crate::modules::{IconKind, ModuleEvent, ToolDef}; use crate::scene::model::wire_model::WireModel; @@ -55,6 +56,36 @@ impl RadiusDimensionCommand { Step::DimLine(source) => dvec(source.point_at_angle(source.start_angle)), } } + + fn build_dimension( + &self, + source: crate::scene::dimension_assoc::RadialSourceGeometry, + world: DVec3, + ) -> EntityType { + let plane = WorkingPlane::new( + DVec3::from_array(source.plane.origin), + DVec3::from_array(source.plane.x_axis), + DVec3::from_array(source.plane.y_axis), + ); + let center = plane.to_local(dvec(source.center_world())); + let chord = plane.to_local(dvec(source.chord_at(world.to_array()))); + let point = plane.to_local(world); + let mut dim = DimensionRadius::new(v3(center), v3(chord)); + dim.base.definition_point = v3(chord); + dim.base.text_middle_point = v3(point); + dim.base.insertion_point = v3(point); + dim.base.text_user_positioned = true; + dim.leader_length = chord.distance(point); + dim.base.actual_measurement = dim.measurement(); + crate::entities::dimension::set_dimension_text_override( + &mut dim.base, + self.text_override.clone(), + ); + if let Some(angle) = self.text_angle { + dim.base.text_rotation = angle; + } + plane.place_entity(EntityType::Dimension(Dimension::Radius(dim))) + } } impl CadCommand for RadiusDimensionCommand { @@ -84,32 +115,8 @@ impl CadCommand for RadiusDimensionCommand { match self.step { Step::SelectObject => CmdResult::NeedPoint, Step::DimLine(source) => { - let source_plane = WorkingPlane::new( - DVec3::from_array(source.plane.origin), - DVec3::from_array(source.plane.x_axis), - DVec3::from_array(source.plane.y_axis), - ); - let center_world = dvec(source.center_world()); - let point_world = dvec(source.chord_at(pt.to_array())); - let center = source_plane.to_local(center_world); - let point = source_plane.to_local(point_world); - let pt = source_plane.to_local(pt); - let mut dim = DimensionRadius::new(v3(center), v3(point)); - dim.base.definition_point = v3(point); - dim.base.text_middle_point = v3(pt); - dim.base.insertion_point = v3(pt); - dim.base.text_user_positioned = true; - dim.leader_length = point.distance(pt); - dim.base.actual_measurement = dim.measurement(); - crate::entities::dimension::set_dimension_text_override( - &mut dim.base, - self.text_override.clone(), - ); - if let Some(a) = self.text_angle { - dim.base.text_rotation = a; - } CmdResult::CommitDimension { - entity: source_plane.place_entity(EntityType::Dimension(Dimension::Radius(dim))), + entity: self.build_dimension(source, pt), association: DimensionAssociationInput::Infer(self.source_handle), preserve_base_style: false, continue_command: false, @@ -284,6 +291,13 @@ impl CadCommand for RadiusDimensionCommand { } } } + + fn dimension_preview(&self, cursor: DVec3) -> Option> { + let Step::DimLine(source) = self.step else { + return None; + }; + Some(vec![DimensionPreview::current_style(self.build_dimension(source, cursor))]) + } } fn v3(pt: DVec3) -> Vector3 {