From 38b02164e8b2327f9f05feceda21966cf341cef0 Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Fri, 28 Aug 2026 18:52:57 +0300 Subject: [PATCH] fix(dimension): repair continued workflows --- src/app/command_driver.rs | 23 +- src/app/commands/dim.rs | 1 + src/command.rs | 4 +- src/modules/annotate/aligned_dim.rs | 2 +- src/modules/annotate/angular_dim.rs | 2 +- src/modules/annotate/arc_length_dim.rs | 2 +- src/modules/annotate/diameter_dim.rs | 2 +- src/modules/annotate/dim_continue.rs | 695 ++++++++++++++-------- src/modules/annotate/jogged_radius_dim.rs | 2 +- src/modules/annotate/linear_dim.rs | 2 +- src/modules/annotate/ordinate_dim.rs | 2 +- src/modules/annotate/radius_dim.rs | 2 +- src/scene/dimension_assoc.rs | 73 ++- 13 files changed, 510 insertions(+), 302 deletions(-) diff --git a/src/app/command_driver.rs b/src/app/command_driver.rs index 3e7e4bafb..cf4a960a3 100644 --- a/src/app/command_driver.rs +++ b/src/app/command_driver.rs @@ -856,7 +856,7 @@ impl OpenCADStudio { .infer_dimension_sources(handle); self.tabs[i] .scene - .attach_dimension_association(handle, sources.to_vec()); + .attach_dimension_association(handle, sources); } } self.tabs[i].dirty = true; @@ -1269,7 +1269,7 @@ impl OpenCADStudio { .infer_dimension_sources(handle); self.tabs[i] .scene - .attach_dimension_association(handle, sources.to_vec()); + .attach_dimension_association(handle, sources); } } self.tabs[i].dirty = true; @@ -1288,7 +1288,7 @@ impl OpenCADStudio { mut entity, association, preserve_base_style, - keep_active, + continue_command, } => { let label = self.history_label_from_active_cmd(i, "DIMENSION"); let association_mode = self.tabs[i] @@ -1379,8 +1379,6 @@ impl OpenCADStudio { self.tabs[i] .scene .infer_dimension_sources(handle) - .into_iter() - .collect() }, |source| { if single_source_dimension { @@ -1416,18 +1414,21 @@ impl OpenCADStudio { self.tabs[i].dirty = true; self.tabs[i].scene.clear_preview_wire(); self.tabs[i].snap_result = None; - if keep_active { - let prompt = self.tabs[i].active_cmd.as_ref().map(|command| command.prompt()); - if let Some(prompt) = prompt { + if let Some(pd) = pending { + self.commit_undo_delta(i, pd); + } + if continue_command { + if let Some(prompt) = self.tabs[i] + .active_cmd + .as_ref() + .map(|cmd| cmd.prompt()) + { self.command_line.push_info(&prompt); } } else { self.tabs[i].active_cmd = None; self.restore_pre_cmd_tangent(); } - if let Some(pd) = pending { - self.commit_undo_delta(i, pd); - } } CmdResult::CommitSolid { entity, diff --git a/src/app/commands/dim.rs b/src/app/commands/dim.rs index 90da46a80..042140ed5 100644 --- a/src/app/commands/dim.rs +++ b/src/app/commands/dim.rs @@ -649,6 +649,7 @@ impl OpenCADStudio { let scene = &self.tabs[i].scene; let recent = scene .last_created_dimension + .filter(|handle| scene.entity_belongs_to_active_space(*handle)) .and_then(|handle| scene.document.get_entity(handle)) .cloned(); let cmd = DimContinueCommand::new( diff --git a/src/command.rs b/src/command.rs index 968c9d29e..cd7544513 100644 --- a/src/command.rs +++ b/src/command.rs @@ -1251,8 +1251,8 @@ pub enum CmdResult { /// Retain the source dimension's layer and style instead of replacing /// them with the current creation defaults (DIMCONTINUEMODE=1). preserve_base_style: bool, - /// Keep the interactive command active after this dimension is committed. - keep_active: bool, + /// Keep collecting points after the dimension is committed. + continue_command: bool, }, /// Commit a Model-tab 3D solid: the acadrust entity (for selection / /// persistence) plus its B-rep (cached for boolean ops + shaded diff --git a/src/modules/annotate/aligned_dim.rs b/src/modules/annotate/aligned_dim.rs index 5765e4759..9ecfa7c70 100644 --- a/src/modules/annotate/aligned_dim.rs +++ b/src/modules/annotate/aligned_dim.rs @@ -136,7 +136,7 @@ impl CadCommand for AlignedDimensionCommand { entity: self.plane.place_entity(EntityType::Dimension(Dimension::Aligned(dim))), association: DimensionAssociationInput::Infer(self.source_handle), preserve_base_style: false, - keep_active: false, + continue_command: false, } } } diff --git a/src/modules/annotate/angular_dim.rs b/src/modules/annotate/angular_dim.rs index 9e7547b96..f23e3b555 100644 --- a/src/modules/annotate/angular_dim.rs +++ b/src/modules/annotate/angular_dim.rs @@ -235,7 +235,7 @@ impl AngularDimensionCommand { entity, association: DimensionAssociationInput::Explicit(self.source_refs.clone()), preserve_base_style: false, - keep_active: false, + continue_command: false, } } diff --git a/src/modules/annotate/arc_length_dim.rs b/src/modules/annotate/arc_length_dim.rs index 4ae6ea24d..bbdfaf412 100644 --- a/src/modules/annotate/arc_length_dim.rs +++ b/src/modules/annotate/arc_length_dim.rs @@ -266,7 +266,7 @@ impl ArcLengthDimensionCommand { entity: plane.place_entity(EntityType::Dimension(Dimension::Arc(dimension))), association, preserve_base_style: false, - keep_active: false, + continue_command: false, } } } diff --git a/src/modules/annotate/diameter_dim.rs b/src/modules/annotate/diameter_dim.rs index 36286d340..19dfcb1ca 100644 --- a/src/modules/annotate/diameter_dim.rs +++ b/src/modules/annotate/diameter_dim.rs @@ -114,7 +114,7 @@ impl CadCommand for DiameterDimensionCommand { .place_entity(EntityType::Dimension(Dimension::Diameter(dim))), association: DimensionAssociationInput::Infer(self.source_handle), preserve_base_style: false, - keep_active: false, + continue_command: false, } } } diff --git a/src/modules/annotate/dim_continue.rs b/src/modules/annotate/dim_continue.rs index 4d4f99b7b..45d69520c 100644 --- a/src/modules/annotate/dim_continue.rs +++ b/src/modules/annotate/dim_continue.rs @@ -1,11 +1,14 @@ -//! Repeating continued dimensions from the latest session dimension or a picked base. - use acadrust::entities::{ Dimension, DimensionAngular2Ln, DimensionAngular3Pt, DimensionBase, DimensionLinear, DimensionOrdinate, }; use acadrust::types::Vector3; use acadrust::{EntityType, Handle}; +use cadkernel::geom2d::{ + arc_span, closest_point, intersect, nearest_of, Arc, Curve, Ray, Tolerance, Transform, Vec2, + XLine, +}; +use cadkernel::space::Plane; use glam::DVec3; use crate::command::{CadCommand, CmdOption, CmdResult, DimensionAssociationInput}; @@ -55,25 +58,25 @@ impl SourceStyle { #[derive(Clone)] enum ContinueKind { Linear { - current: DVec3, + current: Vec2, rotation: f64, - perpendicular: DVec3, + perpendicular: Vec2, line_coordinate: f64, }, Angular2Ln { - source: DimensionAngular2Ln, - vertex: DVec3, - current: DVec3, + vertex: Vec2, + current: Vec2, radius: f64, }, Angular3Pt { - source: DimensionAngular3Pt, - vertex: DVec3, - current: DVec3, + vertex: Vec2, + current: Vec2, radius: f64, }, Ordinate { - source: DimensionOrdinate, + definition: Vec2, + leader: Vec2, + is_x: bool, }, } @@ -81,6 +84,7 @@ enum ContinueKind { struct ContinueState { kind: ContinueKind, style: SourceStyle, + plane: Plane, } pub struct DimContinueCommand { @@ -104,78 +108,96 @@ impl DimContinueCommand { command } - fn install_base(&mut self, entity: &EntityType, pick: Option) -> bool { + fn install_base(&mut self, entity: &EntityType, pick_world: Option) -> bool { let EntityType::Dimension(dimension) = entity else { return false; }; let style = SourceStyle::from_base(dimension.base()); + let plane = plane_from_normal(dimension.base().definition_point, style.normal); + let pick = pick_world.and_then(|point| plane.project(point.to_array()).map(Vec2::from)); let kind = match dimension { Dimension::Linear(source) => { let rotation = source.rotation; - let perpendicular = DVec3::new(-rotation.sin(), rotation.cos(), 0.0); - let current = selected_linear_end( - dv(source.first_point), - dv(source.second_point), - pick, - ); + let perpendicular = Vec2::new(-rotation.sin(), rotation.cos()); ContinueKind::Linear { - current, + current: selected_linear_end( + project(&plane, source.first_point), + project(&plane, source.second_point), + pick, + ), rotation, perpendicular, - line_coordinate: dv(source.base.definition_point).dot(perpendicular), + line_coordinate: project(&plane, source.base.definition_point) + .dot(perpendicular), } } Dimension::Aligned(source) => { - let first = dv(source.first_point); - let second = dv(source.second_point); - let delta = second - first; - if delta.length_squared() <= 1.0e-18 { + let first = project(&plane, source.first_point); + let second = project(&plane, source.second_point); + let Some(direction) = (second - first).normalize() else { return false; - } - let rotation = delta.y.atan2(delta.x); - let perpendicular = DVec3::new(-rotation.sin(), rotation.cos(), 0.0); + }; + let rotation = direction.angle(); + let perpendicular = direction.perpendicular(); ContinueKind::Linear { current: selected_linear_end(first, second, pick), rotation, perpendicular, - line_coordinate: dv(source.base.definition_point).dot(perpendicular), + line_coordinate: project(&plane, source.base.definition_point) + .dot(perpendicular), } } Dimension::Angular2Ln(source) => { - let vertex = angular2_vertex(source); - let first = dv(source.second_point); - let second = dv(source.definition_point); + let lines = [ + [ + project(&plane, source.first_point), + project(&plane, source.second_point), + ], + [ + project(&plane, source.angle_vertex), + project(&plane, source.definition_point), + ], + ]; + let vertex = angular2_vertex(lines).unwrap_or(lines[1][0]); ContinueKind::Angular2Ln { - source: source.clone(), vertex, - current: selected_angular_end(first, second, pick), - radius: (dv(source.dimension_arc) - vertex).length().max(1.0e-9), + current: selected_angular_end(vertex, [lines[0][1], lines[1][1]], pick), + radius: project(&plane, source.dimension_arc) + .distance(vertex) + .max(1.0e-9), } } Dimension::Angular3Pt(source) => { - let vertex = dv(source.angle_vertex); + let vertex = project(&plane, source.angle_vertex); ContinueKind::Angular3Pt { - source: source.clone(), vertex, current: selected_angular_end( - dv(source.first_point), - dv(source.second_point), + vertex, + [ + project(&plane, source.first_point), + project(&plane, source.second_point), + ], pick, ), - radius: (dv(source.definition_point) - vertex).length().max(1.0e-9), + radius: project(&plane, source.definition_point) + .distance(vertex) + .max(1.0e-9), } } Dimension::Ordinate(source) => ContinueKind::Ordinate { - source: source.clone(), + definition: project(&plane, source.definition_point), + leader: project(&plane, source.leader_endpoint), + is_x: source.is_ordinate_type_x, }, _ => return false, }; - self.base = Some(ContinueState { kind, style }); + self.base = Some(ContinueState { kind, style, plane }); true } - fn build_dimension(&self, point: DVec3) -> Option { + fn build_dimension(&self, point_world: DVec3) -> Option { let state = self.base.as_ref()?; + let point = state.plane.project(point_world.to_array()).map(Vec2::from)?; let mut dimension = match &state.kind { ContinueKind::Linear { current, @@ -183,6 +205,7 @@ impl DimContinueCommand { perpendicular, line_coordinate, } => build_linear( + state.plane, *current, point, *rotation, @@ -190,78 +213,88 @@ impl DimContinueCommand { *line_coordinate, ), ContinueKind::Angular2Ln { - source, vertex, current, radius, } => { - let moving = point - *vertex; let previous = *current - *vertex; - if moving.length_squared() <= 1.0e-18 || previous.length_squared() <= 1.0e-18 { - return None; - } - let mut result = source.clone(); - result.first_point = v3(*vertex); - result.second_point = v3(point); - result.angle_vertex = v3(*vertex); - result.definition_point = v3(*current); + let moving = point - *vertex; let (definition, text) = - angular_definition_and_text(*vertex, previous, moving, *radius); - result.dimension_arc = v3(definition); + angular_definition_and_text(*vertex, previous, moving, *radius)?; + let mut result = DimensionAngular2Ln::default(); + result.first_point = world(state.plane, *vertex); + result.second_point = world(state.plane, *current); + result.angle_vertex = world(state.plane, *vertex); + result.definition_point = world(state.plane, point); + result.dimension_arc = world(state.plane, definition); result.base.definition_point = result.dimension_arc; - result.base.text_middle_point = v3(text); + result.base.text_middle_point = world(state.plane, text); result.base.insertion_point = result.base.text_middle_point; - result.base.actual_measurement = result.measurement_degrees(); Dimension::Angular2Ln(result) } ContinueKind::Angular3Pt { - source, vertex, current, radius, } => { - let moving = point - *vertex; let previous = *current - *vertex; - if moving.length_squared() <= 1.0e-18 || previous.length_squared() <= 1.0e-18 { - return None; - } - let mut result = source.clone(); - result.angle_vertex = v3(*vertex); - result.first_point = v3(point); - result.second_point = v3(*current); + let moving = point - *vertex; let (definition, text) = - angular_definition_and_text(*vertex, previous, moving, *radius); - result.definition_point = v3(definition); + angular_definition_and_text(*vertex, previous, moving, *radius)?; + let mut result = DimensionAngular3Pt::new( + world(state.plane, *vertex), + world(state.plane, *current), + world(state.plane, point), + ); + result.definition_point = world(state.plane, definition); result.base.definition_point = result.definition_point; - result.base.text_middle_point = v3(text); + result.base.text_middle_point = world(state.plane, text); result.base.insertion_point = result.base.text_middle_point; - result.base.actual_measurement = result.measurement_degrees(); Dimension::Angular3Pt(result) } - ContinueKind::Ordinate { source } => { - let source_leader = dv(source.leader_endpoint); - let leader = if source.is_ordinate_type_x { - DVec3::new(point.x, source_leader.y, source_leader.z) + ContinueKind::Ordinate { + definition, + leader, + is_x, + } => { + let to_dimension = Transform::rotation(-state.style.horizontal_direction); + let from_dimension = Transform::rotation(state.style.horizontal_direction); + let feature = Vec2::from(to_dimension.apply_point(point.into())); + let source_leader = Vec2::from(to_dimension.apply_point((*leader).into())); + let leader = if *is_x { + Vec2::new(feature.x, source_leader.y) } else { - DVec3::new(source_leader.x, point.y, source_leader.z) + Vec2::new(source_leader.x, feature.y) }; - let mut result = - DimensionOrdinate::new(v3(point), v3(leader), source.is_ordinate_type_x); - result.definition_point = source.definition_point; - result.base.definition_point = source.base.definition_point; - result.base.text_middle_point = v3(leader); + let leader = Vec2::from(from_dimension.apply_point(leader.into())); + let mut result = DimensionOrdinate::new( + world(state.plane, point), + world(state.plane, leader), + *is_x, + ); + result.definition_point = world(state.plane, *definition); + result.base.definition_point = result.definition_point; + result.base.text_middle_point = result.leader_endpoint; result.base.insertion_point = result.base.text_middle_point; - result.base.actual_measurement = result.measurement(); Dimension::Ordinate(result) } }; state.style.apply(dimension.base_mut()); + refresh_measurement(&mut dimension, state.plane); dimension.base_mut().common.handle = Handle::NULL; dimension.base_mut().block_name.clear(); Some(dimension) } - fn advance(&mut self, point: DVec3) { + fn advance(&mut self, point_world: DVec3) { + let Some(point) = self + .base + .as_ref() + .and_then(|state| state.plane.project(point_world.to_array())) + .map(Vec2::from) + else { + return; + }; let Some(state) = self.base.as_mut() else { return; }; @@ -348,7 +381,7 @@ impl CadCommand for DimContinueCommand { entity: EntityType::Dimension(dimension), association: DimensionAssociationInput::Infer(None), preserve_base_style: self.preserve_base_style, - keep_active: true, + continue_command: true, } } @@ -393,7 +426,26 @@ impl CadCommand for DimContinueCommand { } } -fn selected_linear_end(first: DVec3, second: DVec3, pick: Option) -> DVec3 { +fn build_linear( + plane: Plane, + first: Vec2, + second: Vec2, + rotation: f64, + perpendicular: Vec2, + line_coordinate: f64, +) -> Dimension { + let first_line = project_to_line(first, perpendicular, line_coordinate); + let second_line = project_to_line(second, perpendicular, line_coordinate); + let mut result = DimensionLinear::new(world(plane, first), world(plane, second)); + result.rotation = rotation; + result.definition_point = world(plane, second_line); + result.base.definition_point = result.definition_point; + result.base.text_middle_point = world(plane, first_line.lerp(second_line, 0.5)); + result.base.insertion_point = result.base.text_middle_point; + Dimension::Linear(result) +} + +fn selected_linear_end(first: Vec2, second: Vec2, pick: Option) -> Vec2 { pick.map_or(second, |point| { if point.distance_squared(first) <= point.distance_squared(second) { first @@ -403,112 +455,157 @@ fn selected_linear_end(first: DVec3, second: DVec3, pick: Option) -> DVec }) } -fn selected_angular_end(first: DVec3, second: DVec3, pick: Option) -> DVec3 { - pick.map_or(first, |point| { - if point.distance_squared(first) <= point.distance_squared(second) { - first - } else { - second - } - }) +fn selected_angular_end(vertex: Vec2, ends: [Vec2; 2], pick: Option) -> Vec2 { + let Some(pick) = pick else { + return ends[0]; + }; + let rays = ends.map(|end| { + Curve::Ray(Ray { + origin: vertex.into(), + direction: (end - vertex).into(), + }) + }); + nearest_of(rays.iter(), pick.into()) + .map(|(index, _)| ends[index]) + .unwrap_or(ends[0]) } -fn build_linear( - first: DVec3, - second: DVec3, - rotation: f64, - perpendicular: DVec3, - line_coordinate: f64, -) -> Dimension { - let first_line = first + perpendicular * (line_coordinate - first.dot(perpendicular)); - let second_line = second + perpendicular * (line_coordinate - second.dot(perpendicular)); - let mut result = DimensionLinear::new(v3(first), v3(second)); - result.rotation = rotation; - result.definition_point = v3(second_line); - result.base.definition_point = v3(second_line); - result.base.text_middle_point = v3((first_line + second_line) * 0.5); - result.base.insertion_point = result.base.text_middle_point; - result.base.actual_measurement = result.measurement(); - Dimension::Linear(result) -} - -fn angular2_vertex(source: &DimensionAngular2Ln) -> DVec3 { - let first = dv(source.first_point); - let first_direction = dv(source.second_point) - first; - let second = dv(source.angle_vertex); - let second_direction = dv(source.definition_point) - second; - line_intersection(first, first_direction, second, second_direction).unwrap_or(second) -} - -fn line_intersection(a: DVec3, b: DVec3, c: DVec3, d: DVec3) -> Option { - let cross = b.x * d.y - b.y * d.x; - if cross.abs() <= 1.0e-12 { - return None; - } - let delta = c - a; - Some(a + b * ((delta.x * d.y - delta.y * d.x) / cross)) +fn angular2_vertex(lines: [[Vec2; 2]; 2]) -> Option { + let first = Curve::XLine(XLine { + base: lines[0][0].into(), + direction: (lines[0][1] - lines[0][0]).into(), + }); + let second = Curve::XLine(XLine { + base: lines[1][0].into(), + direction: (lines[1][1] - lines[1][0]).into(), + }); + let scale = (lines[0][1] - lines[0][0]) + .length() + .max((lines[1][1] - lines[1][0]).length()) + .max(1.0); + intersect(&first, &second, Tolerance::new(scale * 1.0e-9)) + .first() + .map(|crossing| Vec2::from(crossing.point)) } fn angular_definition_and_text( - vertex: DVec3, - previous: DVec3, - moving: DVec3, + vertex: Vec2, + previous: Vec2, + moving: Vec2, radius: f64, -) -> (DVec3, DVec3) { - let start = previous.y.atan2(previous.x); - let mut sweep = moving.y.atan2(moving.x) - start; - while sweep <= -std::f64::consts::PI { - sweep += std::f64::consts::TAU; +) -> Option<(Vec2, Vec2)> { + let arc = angular_arc(vertex, previous, moving, radius)?; + Some(( + Vec2::from(arc.point_at(2.0 / 3.0)), + Vec2::from(arc.point_at(0.5)), + )) +} + +fn angular_arc(vertex: Vec2, first: Vec2, second: Vec2, radius: f64) -> Option { + let first = first.normalize()?; + let second = second.normalize()?; + if first.distance_squared(second) <= 1.0e-18 { + return None; } - while sweep > std::f64::consts::PI { - sweep -= std::f64::consts::TAU; + let first_angle = first.angle(); + let second_angle = second.angle(); + let (start_angle, end_angle) = + if arc_span(first_angle, second_angle) <= std::f64::consts::PI { + (first_angle, second_angle) + } else { + (second_angle, first_angle) + }; + Some(Curve::Arc(Arc { + centre: vertex.into(), + radius, + start_angle, + end_angle, + })) +} + +fn refresh_measurement(dimension: &mut Dimension, plane: Plane) { + match dimension { + Dimension::Linear(value) => { + let first = project(&plane, value.first_point); + let second = project(&plane, value.second_point); + let axis = Vec2::new(value.rotation.cos(), value.rotation.sin()); + value.base.actual_measurement = (second - first).dot(axis).abs(); + } + Dimension::Angular2Ln(value) => { + value.base.actual_measurement = value.measurement_degrees() + } + Dimension::Angular3Pt(value) => { + value.base.actual_measurement = value.measurement_degrees() + } + Dimension::Ordinate(value) => value.refresh_measurement(), + _ => {} } - let point_at = |fraction: f64| { - let angle = start + sweep * fraction; - vertex + DVec3::new(angle.cos(), angle.sin(), 0.0) * radius - }; - (point_at(2.0 / 3.0), point_at(0.5)) } fn preview_for_dimension(dimension: &Dimension) -> WireModel { let mut points = Vec::<[f64; 3]>::new(); match dimension { - Dimension::Linear(dimension) => linear_preview( - &mut points, - dv(dimension.first_point), - dv(dimension.second_point), - dimension.rotation, - dv(dimension.definition_point), - dv(dimension.base.text_middle_point), - dimension.base.actual_measurement, - ), - Dimension::Angular2Ln(dimension) => { - let vertex = angular2_vertex(dimension); - angular_preview( + Dimension::Linear(value) => { + let plane = plane_from_normal(value.first_point, value.base.normal); + linear_preview( &mut points, + plane, + project(&plane, value.first_point), + project(&plane, value.second_point), + value.rotation, + project(&plane, value.definition_point), + project(&plane, value.base.text_middle_point), + value.base.actual_measurement, + ); + } + Dimension::Angular2Ln(value) => { + let plane = plane_from_normal(value.first_point, value.base.normal); + let lines = [ + [ + project(&plane, value.first_point), + project(&plane, value.second_point), + ], + [ + project(&plane, value.angle_vertex), + project(&plane, value.definition_point), + ], + ]; + let vertex = angular2_vertex(lines).unwrap_or(lines[1][0]); + angular_preview( + &mut points, + plane, vertex, - dv(dimension.second_point) - vertex, - dv(dimension.definition_point) - vertex, - dv(dimension.dimension_arc), - dv(dimension.base.text_middle_point), - dimension.base.actual_measurement, + lines[0][1] - vertex, + lines[1][1] - vertex, + project(&plane, value.dimension_arc), + project(&plane, value.base.text_middle_point), + value.base.actual_measurement, + ); + } + Dimension::Angular3Pt(value) => { + let plane = plane_from_normal(value.angle_vertex, value.base.normal); + let vertex = project(&plane, value.angle_vertex); + angular_preview( + &mut points, + plane, + vertex, + project(&plane, value.first_point) - vertex, + project(&plane, value.second_point) - vertex, + project(&plane, value.definition_point), + project(&plane, value.base.text_middle_point), + value.base.actual_measurement, + ); + } + Dimension::Ordinate(value) => { + let plane = plane_from_normal(value.definition_point, value.base.normal); + ordinate_preview( + &mut points, + plane, + project(&plane, value.feature_location), + project(&plane, value.leader_endpoint), + value.base.actual_measurement, ); } - Dimension::Angular3Pt(dimension) => angular_preview( - &mut points, - dv(dimension.angle_vertex), - dv(dimension.first_point) - dv(dimension.angle_vertex), - dv(dimension.second_point) - dv(dimension.angle_vertex), - dv(dimension.definition_point), - dv(dimension.base.text_middle_point), - dimension.base.actual_measurement, - ), - Dimension::Ordinate(dimension) => ordinate_preview( - &mut points, - dv(dimension.feature_location), - dv(dimension.leader_endpoint), - dimension.base.actual_measurement, - ), _ => {} } WireModel::solid_f64( @@ -521,103 +618,166 @@ fn preview_for_dimension(dimension: &Dimension) -> WireModel { fn linear_preview( points: &mut Vec<[f64; 3]>, - first: DVec3, - second: DVec3, + plane: Plane, + first: Vec2, + second: Vec2, rotation: f64, - definition: DVec3, - text: DVec3, + definition: Vec2, + text: Vec2, measurement: f64, ) { - let axis = DVec3::new(rotation.cos(), rotation.sin(), 0.0); - let perpendicular = DVec3::new(-axis.y, axis.x, 0.0); + let axis = Vec2::new(rotation.cos(), rotation.sin()); + let perpendicular = axis.perpendicular(); let coordinate = definition.dot(perpendicular); - let first_line = first + perpendicular * (coordinate - first.dot(perpendicular)); - let second_line = second + perpendicular * (coordinate - second.dot(perpendicular)); - segment(points, first, first_line); - segment(points, second, second_line); - segment(points, first_line, second_line); - arrow(points, first_line, axis, perpendicular, first_line.distance(second_line)); - arrow(points, second_line, -axis, perpendicular, first_line.distance(second_line)); - text_box(points, text, axis, perpendicular, measurement); + let first_line = project_to_line(first, perpendicular, coordinate); + let second_line = project_to_line(second, perpendicular, coordinate); + segment(points, plane, first, first_line); + segment(points, plane, second, second_line); + segment(points, plane, first_line, second_line); + arrow( + points, + plane, + first_line, + axis, + perpendicular, + first_line.distance(second_line), + ); + arrow( + points, + plane, + second_line, + -axis, + perpendicular, + first_line.distance(second_line), + ); + text_box(points, plane, text, axis, perpendicular, measurement); } fn angular_preview( points: &mut Vec<[f64; 3]>, - vertex: DVec3, - first: DVec3, - second: DVec3, - arc_point: DVec3, - text: DVec3, + plane: Plane, + vertex: Vec2, + first: Vec2, + second: Vec2, + arc_point: Vec2, + text: Vec2, measurement: f64, ) { - let radius = (arc_point - vertex).length().max(1.0e-9); - let start = first.y.atan2(first.x); - let mut sweep = second.y.atan2(second.x) - start; - while sweep <= -std::f64::consts::PI { - sweep += std::f64::consts::TAU; + let radius = arc_point.distance(vertex).max(1.0e-9); + let Some(first_direction) = first.normalize() else { + return; + }; + let Some(second_direction) = second.normalize() else { + return; + }; + let Some(arc) = angular_arc(vertex, first, second, radius) else { + return; + }; + segment(points, plane, vertex, vertex + first_direction * radius); + segment(points, plane, vertex, vertex + second_direction * radius); + for index in 0..=24 { + push_local(points, plane, Vec2::from(arc.point_at(index as f64 / 24.0))); } - while sweep > std::f64::consts::PI { - sweep -= std::f64::consts::TAU; - } - let first_end = vertex + normalized_or(first, DVec3::X) * radius; - let second_end = vertex + normalized_or(second, DVec3::Y) * radius; - segment(points, vertex, first_end); - segment(points, vertex, second_end); - let arc = cadkernel::geom2d::tessellate::arc( - [vertex.x, vertex.y], - radius, + points.push([f64::NAN; 3]); + let start = Vec2::from(arc.point_at(0.0)); + let start_next = Vec2::from(arc.point_at(1.0 / 1024.0)); + let end = Vec2::from(arc.point_at(1.0)); + let end_previous = Vec2::from(arc.point_at(1.0 - 1.0 / 1024.0)); + arrow( + points, + plane, start, - start + sweep, - vertex.z, - cadkernel::geom2d::tessellate::DEFAULT_SEGMENTS_PER_RADIAN, + (start_next - start).normalize().unwrap_or(first_direction), + first_direction, + radius, + ); + arrow( + points, + plane, + end, + (end_previous - end).normalize().unwrap_or(-second_direction), + second_direction, + radius, + ); + let before_middle = Vec2::from(arc.point_at(0.5 - 1.0 / 1024.0)); + let after_middle = Vec2::from(arc.point_at(0.5 + 1.0 / 1024.0)); + let text_axis = (after_middle - before_middle) + .normalize() + .unwrap_or(Vec2::new(1.0, 0.0)); + text_box( + points, + plane, + text, + text_axis, + text_axis.perpendicular(), + measurement, ); - points.extend(arc); - points.push([f64::NAN, f64::NAN, f64::NAN]); - let sign = sweep.signum(); - let start_tangent = DVec3::new(-start.sin(), start.cos(), 0.0) * sign; - let end_angle = start + sweep; - let end_tangent = DVec3::new(end_angle.sin(), -end_angle.cos(), 0.0) * sign; - arrow(points, first_end, start_tangent, normalized_or(first, DVec3::X), radius); - arrow(points, second_end, end_tangent, normalized_or(second, DVec3::Y), radius); - let middle = start + sweep * 0.5; - let text_axis = normalized_or(DVec3::new(-middle.sin(), middle.cos(), 0.0), DVec3::X); - let text_perpendicular = DVec3::new(-text_axis.y, text_axis.x, 0.0); - text_box(points, text, text_axis, text_perpendicular, measurement); } fn ordinate_preview( points: &mut Vec<[f64; 3]>, - feature: DVec3, - leader: DVec3, + plane: Plane, + feature: Vec2, + leader: Vec2, measurement: f64, ) { - segment(points, feature, leader); - let axis = normalized_or(leader - feature, DVec3::Y); - let perpendicular = DVec3::new(-axis.y, axis.x, 0.0); - arrow(points, feature, axis, perpendicular, feature.distance(leader)); - text_box(points, leader, perpendicular, axis, measurement); + segment(points, plane, feature, leader); + let axis = (leader - feature) + .normalize() + .unwrap_or(Vec2::new(0.0, 1.0)); + let perpendicular = axis.perpendicular(); + arrow( + points, + plane, + feature, + axis, + perpendicular, + feature.distance(leader), + ); + text_box( + points, + plane, + leader, + perpendicular, + axis, + measurement, + ); } fn arrow( points: &mut Vec<[f64; 3]>, - tip: DVec3, - inward: DVec3, - perpendicular: DVec3, + plane: Plane, + tip: Vec2, + inward: Vec2, + perpendicular: Vec2, span: f64, ) { - let inward = normalized_or(inward, DVec3::X); - let perpendicular = normalized_or(perpendicular, DVec3::Y); + let inward = inward.normalize().unwrap_or(Vec2::new(1.0, 0.0)); + let perpendicular = perpendicular + .normalize() + .unwrap_or(Vec2::new(0.0, 1.0)); let size = span.clamp(1.0, 100.0) * 0.035; let back = tip + inward * size; - segment(points, tip, back + perpendicular * size * 0.4); - segment(points, tip, back - perpendicular * size * 0.4); + segment( + points, + plane, + tip, + back + perpendicular * size * 0.4, + ); + segment( + points, + plane, + tip, + back - perpendicular * size * 0.4, + ); } fn text_box( points: &mut Vec<[f64; 3]>, - center: DVec3, - axis: DVec3, - perpendicular: DVec3, + plane: Plane, + center: Vec2, + axis: Vec2, + perpendicular: Vec2, measurement: f64, ) { let digits = if measurement.abs() < 1.0 { @@ -631,30 +791,51 @@ fn text_box( let b = center + axis * half_width - perpendicular * half_height; let c = center + axis * half_width + perpendicular * half_height; let d = center - axis * half_width + perpendicular * half_height; - points.extend([a.to_array(), b.to_array(), c.to_array(), d.to_array(), a.to_array()]); - points.push([f64::NAN, f64::NAN, f64::NAN]); -} - -fn segment(points: &mut Vec<[f64; 3]>, first: DVec3, second: DVec3) { - points.push(first.to_array()); - points.push(second.to_array()); - points.push([f64::NAN, f64::NAN, f64::NAN]); -} - -fn normalized_or(value: DVec3, fallback: DVec3) -> DVec3 { - if value.length_squared() > 1.0e-18 { - value.normalize() - } else { - fallback + for point in [a, b, c, d, a] { + push_local(points, plane, point); } + points.push([f64::NAN; 3]); } -fn dv(point: Vector3) -> DVec3 { - DVec3::new(point.x, point.y, point.z) +fn project_to_line(point: Vec2, perpendicular: Vec2, coordinate: f64) -> Vec2 { + let line = Curve::XLine(XLine { + base: (perpendicular * coordinate).into(), + direction: perpendicular.perpendicular().into(), + }); + Vec2::from(closest_point(&line, point.into()).point) } -fn v3(point: DVec3) -> Vector3 { - Vector3::new(point.x, point.y, point.z) +fn plane_from_normal(origin: Vector3, normal: Vector3) -> Plane { + let (x_axis, y_axis) = + crate::scene::view::transform::ocs_axes((normal.x, normal.y, normal.z)); + Plane::from_axes( + point(origin), + [x_axis.0, x_axis.1, x_axis.2], + [y_axis.0, y_axis.1, y_axis.2], + ) +} + +fn project(plane: &Plane, value: Vector3) -> Vec2 { + Vec2::from(plane.project(point(value)).unwrap_or([0.0; 2])) +} + +fn world(plane: Plane, value: Vec2) -> Vector3 { + let value = plane.point_at(value.into()); + Vector3::new(value[0], value[1], value[2]) +} + +fn point(value: Vector3) -> [f64; 3] { + [value.x, value.y, value.z] +} + +fn segment(points: &mut Vec<[f64; 3]>, plane: Plane, first: Vec2, second: Vec2) { + push_local(points, plane, first); + push_local(points, plane, second); + points.push([f64::NAN; 3]); +} + +fn push_local(points: &mut Vec<[f64; 3]>, plane: Plane, point: Vec2) { + points.push(plane.point_at(point.into())); } inventory::submit!(crate::command::CommandRegistration { names: &["DIMCONTINUE"] }); diff --git a/src/modules/annotate/jogged_radius_dim.rs b/src/modules/annotate/jogged_radius_dim.rs index 4ce9f8d5d..9a517c968 100644 --- a/src/modules/annotate/jogged_radius_dim.rs +++ b/src/modules/annotate/jogged_radius_dim.rs @@ -140,7 +140,7 @@ impl JoggedRadiusDimensionCommand { ))), association, preserve_base_style: false, - keep_active: false, + continue_command: false, } } } diff --git a/src/modules/annotate/linear_dim.rs b/src/modules/annotate/linear_dim.rs index 7036e0950..53d09b08e 100644 --- a/src/modules/annotate/linear_dim.rs +++ b/src/modules/annotate/linear_dim.rs @@ -188,7 +188,7 @@ impl CadCommand for LinearDimensionCommand { entity, association: DimensionAssociationInput::Infer(self.source_handle), preserve_base_style: false, - keep_active: false, + continue_command: false, } } } diff --git a/src/modules/annotate/ordinate_dim.rs b/src/modules/annotate/ordinate_dim.rs index 9343249d9..1437ba953 100644 --- a/src/modules/annotate/ordinate_dim.rs +++ b/src/modules/annotate/ordinate_dim.rs @@ -117,7 +117,7 @@ impl CadCommand for OrdinateDimCommand { )), association: DimensionAssociationInput::Infer(None), preserve_base_style: false, - keep_active: false, + continue_command: false, } } } diff --git a/src/modules/annotate/radius_dim.rs b/src/modules/annotate/radius_dim.rs index 2fa565352..4cb6acc9d 100644 --- a/src/modules/annotate/radius_dim.rs +++ b/src/modules/annotate/radius_dim.rs @@ -112,7 +112,7 @@ impl CadCommand for RadiusDimensionCommand { entity: source_plane.place_entity(EntityType::Dimension(Dimension::Radius(dim))), association: DimensionAssociationInput::Infer(self.source_handle), preserve_base_style: false, - keep_active: false, + continue_command: false, } } } diff --git a/src/scene/dimension_assoc.rs b/src/scene/dimension_assoc.rs index 9e752c68b..82359b26c 100644 --- a/src/scene/dimension_assoc.rs +++ b/src/scene/dimension_assoc.rs @@ -408,12 +408,23 @@ fn resolve_reference(scene: &Scene, reference: &AssocDimensionReference) -> Opti .copied() } -fn dimension_inference_points(dimension: &Dimension) -> Option<[Option; 2]> { +fn dimension_inference_points(dimension: &Dimension) -> Vec> { match dimension { - Dimension::Linear(linear) => Some([Some(linear.first_point), Some(linear.second_point)]), - Dimension::Aligned(aligned) => Some([Some(aligned.first_point), Some(aligned.second_point)]), - Dimension::Ordinate(ordinate) => Some([Some(ordinate.feature_location), None]), - _ => None, + Dimension::Linear(linear) => vec![Some(linear.first_point), Some(linear.second_point)], + Dimension::Aligned(aligned) => vec![Some(aligned.first_point), Some(aligned.second_point)], + Dimension::Angular3Pt(angular) => vec![ + Some(angular.angle_vertex), + Some(angular.first_point), + Some(angular.second_point), + ], + Dimension::Angular2Ln(angular) => vec![ + Some(angular.first_point), + Some(angular.second_point), + Some(angular.angle_vertex), + Some(angular.definition_point), + ], + Dimension::Ordinate(ordinate) => vec![Some(ordinate.feature_location)], + _ => Vec::new(), } } @@ -800,10 +811,10 @@ impl Scene { pub(crate) fn infer_dimension_sources( &self, dimension: Handle, - ) -> [Option; 2] { + ) -> Vec> { let Some(EntityType::Dimension(entity)) = self.document.get_entity(dimension) else { - return [None, None]; + return Vec::new(); }; let radial_data = match entity { Dimension::Radius(radius) => Some(( @@ -845,23 +856,25 @@ impl Scene { }) .min_by(|first, second| first.0.total_cmp(&second.0)) .map(|(_, handle)| handle); - return [source, None]; + return vec![source]; } - let Some(points) = dimension_inference_points(entity) else { - return [None, None]; - }; - points.map(|point| { - point.and_then(|point| self.document - .entities() - .filter(|entity| entity.common().handle != dimension) - .filter_map(|entity| { - source_distance_squared(entity, point) - .map(|distance| (distance, entity.common().handle)) + dimension_inference_points(entity) + .into_iter() + .map(|point| { + point.and_then(|point| { + self.document + .entities() + .filter(|entity| entity.common().handle != dimension) + .filter_map(|entity| { + source_distance_squared(entity, point) + .map(|distance| (distance, entity.common().handle)) + }) + .filter(|(distance, _)| *distance <= 1e-16) + .min_by(|first, second| first.0.total_cmp(&second.0)) + .map(|(_, handle)| handle) }) - .filter(|(distance, _)| *distance <= 1e-16) - .min_by(|first, second| first.0.total_cmp(&second.0)) - .map(|(_, handle)| handle)) - }) + }) + .collect() } pub(crate) fn refresh_associative_dimensions( @@ -970,7 +983,7 @@ impl Scene { if let Some(point) = resolved[3] { angular.definition_point = point; } - angular.base.definition_point = angular.definition_point; + angular.base.definition_point = angular.dimension_arc; } Dimension::Radius(radius) => { let Some((radial, angle)) = radial_source else { @@ -1137,9 +1150,21 @@ impl Scene { arc.base.actual_measurement = arc.measurement(); } } - dimension.base_mut().actual_measurement = dimension.measurement(); + let measurement = match &*dimension { + Dimension::Linear(linear) => linear_measurement(linear), + _ => dimension.measurement(), + }; + dimension.base_mut().actual_measurement = measurement; refreshed.push((association.dimension, ChangeKind::Modified)); } refreshed } } + +fn linear_measurement(linear: &acadrust::entities::DimensionLinear) -> f64 { + let plane = plane_from_normal(linear.first_point, linear.base.normal); + let first = plane.project(dpoint(linear.first_point)).unwrap_or([0.0; 2]); + let second = plane.project(dpoint(linear.second_point)).unwrap_or(first); + let axis = [linear.rotation.cos(), linear.rotation.sin()]; + ((second[0] - first[0]) * axis[0] + (second[1] - first[1]) * axis[1]).abs() +}