From 1d58082e0ac2a6cb1305875ca362726bcb9b6b5b Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Fri, 28 Aug 2026 08:01:44 +0300 Subject: [PATCH] fix(dimension): repair baseline workflows --- src/app/command_driver.rs | 21 +- src/app/commands/dim.rs | 87 +--- src/command.rs | 2 + src/modules/annotate/aligned_dim.rs | 1 + src/modules/annotate/angular_dim.rs | 1 + src/modules/annotate/arc_length_dim.rs | 1 + src/modules/annotate/diameter_dim.rs | 1 + src/modules/annotate/dim_baseline.rs | 591 ++++++++++++++-------- src/modules/annotate/dim_continue.rs | 308 ++++++----- src/modules/annotate/jogged_radius_dim.rs | 1 + src/modules/annotate/linear_dim.rs | 1 + src/modules/annotate/ordinate_dim.rs | 1 + src/modules/annotate/radius_dim.rs | 1 + src/scene/dimension_assoc.rs | 73 ++- 14 files changed, 640 insertions(+), 450 deletions(-) diff --git a/src/app/command_driver.rs b/src/app/command_driver.rs index d33b27233..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,6 +1288,7 @@ impl OpenCADStudio { mut entity, association, preserve_base_style, + continue_command, } => { let label = self.history_label_from_active_cmd(i, "DIMENSION"); let association_mode = self.tabs[i] @@ -1378,8 +1379,6 @@ impl OpenCADStudio { self.tabs[i] .scene .infer_dimension_sources(handle) - .into_iter() - .collect() }, |source| { if single_source_dimension { @@ -1414,12 +1413,22 @@ impl OpenCADStudio { }; self.tabs[i].dirty = true; self.tabs[i].scene.clear_preview_wire(); - self.tabs[i].active_cmd = None; self.tabs[i].snap_result = None; - self.restore_pre_cmd_tangent(); 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(); + } } CmdResult::CommitSolid { entity, diff --git a/src/app/commands/dim.rs b/src/app/commands/dim.rs index 9d84edcb6..9c16bf312 100644 --- a/src/app/commands/dim.rs +++ b/src/app/commands/dim.rs @@ -646,13 +646,19 @@ impl OpenCADStudio { "DIMCONTINUE" => { use crate::modules::annotate::dim_continue::DimContinueCommand; - let cmd = if let Some((p1, p2, dp, rot, trot)) = - find_last_linear_dim(&self.tabs[i].scene) - { - DimContinueCommand::from_base(p1, p2, dp, rot, trot) - } else { - DimContinueCommand::new() - }; + let scene = &self.tabs[i].scene; + let preserve_base_style = self.dimension_continue_mode == 1; + let cmd = scene + .last_created_dimension + .filter(|handle| scene.entity_belongs_to_active_space(*handle)) + .and_then(|handle| scene.document.get_entity(handle)) + .and_then(|entity| match entity { + acadrust::EntityType::Dimension(dimension) => Some( + DimContinueCommand::from_dimension(dimension, preserve_base_style), + ), + _ => None, + }) + .unwrap_or_else(|| DimContinueCommand::new(preserve_base_style)); self.command_line.push_info(&cmd.prompt()); self.tabs[i].active_cmd = Some(Box::new(cmd)); } @@ -662,6 +668,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 dimdli_by_style = scene @@ -1383,72 +1390,6 @@ impl OpenCADStudio { } } -/// Find the last placed linear or aligned dimension in the document. -/// Returns `(first_point, second_point, definition_point, rotation_rad)` in world-space. -fn find_last_linear_dim( - scene: &crate::scene::Scene, -) -> Option<(glam::Vec3, glam::Vec3, glam::Vec3, f64, f64)> { - use acadrust::entities::Dimension; - let mut best_handle: u64 = 0; - let mut result: Option<(glam::Vec3, glam::Vec3, glam::Vec3, f64, f64)> = None; - - for entity in scene.document.entities() { - if let acadrust::EntityType::Dimension(dim) = entity { - let h = entity.common().handle.value(); - if h <= best_handle { - continue; - } - let item = match dim { - Dimension::Linear(d) => { - let p1 = glam::Vec3::new( - d.first_point.x as f32, - d.first_point.y as f32, - d.first_point.z as f32, - ); - let p2 = glam::Vec3::new( - d.second_point.x as f32, - d.second_point.y as f32, - d.second_point.z as f32, - ); - let dp = glam::Vec3::new( - d.base.definition_point.x as f32, - d.base.definition_point.y as f32, - d.base.definition_point.z as f32, - ); - Some((p1, p2, dp, d.rotation, d.base.text_rotation)) - } - Dimension::Aligned(d) => { - let p1 = glam::Vec3::new( - d.first_point.x as f32, - d.first_point.y as f32, - d.first_point.z as f32, - ); - let p2 = glam::Vec3::new( - d.second_point.x as f32, - d.second_point.y as f32, - d.second_point.z as f32, - ); - let dp = glam::Vec3::new( - d.base.definition_point.x as f32, - d.base.definition_point.y as f32, - d.base.definition_point.z as f32, - ); - let dx = (d.second_point.x - d.first_point.x) as f32; - let dy = (d.second_point.y - d.first_point.y) as f32; - let rot = dy.atan2(dx) as f64; - Some((p1, p2, dp, rot, d.base.text_rotation)) - } - _ => None, - }; - if let Some(data) = item { - best_handle = h; - result = Some(data); - } - } - } - result -} - /// Collect candidate dimension endpoints from an entity for QDIM — line ends, /// polyline vertices, arc endpoints — in world space. fn qdim_collect_points(e: &acadrust::EntityType, out: &mut Vec) { diff --git a/src/command.rs b/src/command.rs index e8bf675e8..cd7544513 100644 --- a/src/command.rs +++ b/src/command.rs @@ -1251,6 +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 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 dafc2183b..9ecfa7c70 100644 --- a/src/modules/annotate/aligned_dim.rs +++ b/src/modules/annotate/aligned_dim.rs @@ -136,6 +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, + continue_command: false, } } } diff --git a/src/modules/annotate/angular_dim.rs b/src/modules/annotate/angular_dim.rs index f1c00059f..f23e3b555 100644 --- a/src/modules/annotate/angular_dim.rs +++ b/src/modules/annotate/angular_dim.rs @@ -235,6 +235,7 @@ impl AngularDimensionCommand { entity, association: DimensionAssociationInput::Explicit(self.source_refs.clone()), preserve_base_style: false, + continue_command: false, } } diff --git a/src/modules/annotate/arc_length_dim.rs b/src/modules/annotate/arc_length_dim.rs index 434eabbe6..bbdfaf412 100644 --- a/src/modules/annotate/arc_length_dim.rs +++ b/src/modules/annotate/arc_length_dim.rs @@ -266,6 +266,7 @@ impl ArcLengthDimensionCommand { entity: plane.place_entity(EntityType::Dimension(Dimension::Arc(dimension))), association, preserve_base_style: false, + continue_command: false, } } } diff --git a/src/modules/annotate/diameter_dim.rs b/src/modules/annotate/diameter_dim.rs index 946cde255..19dfcb1ca 100644 --- a/src/modules/annotate/diameter_dim.rs +++ b/src/modules/annotate/diameter_dim.rs @@ -114,6 +114,7 @@ impl CadCommand for DiameterDimensionCommand { .place_entity(EntityType::Dimension(Dimension::Diameter(dim))), association: DimensionAssociationInput::Infer(self.source_handle), preserve_base_style: false, + continue_command: false, } } } diff --git a/src/modules/annotate/dim_baseline.rs b/src/modules/annotate/dim_baseline.rs index 1656432d8..834222a34 100644 --- a/src/modules/annotate/dim_baseline.rs +++ b/src/modules/annotate/dim_baseline.rs @@ -1,6 +1,5 @@ -//! Repeating baseline dimensions from a session base or an explicitly picked base. - use std::collections::HashMap; +use std::f64::consts::PI; use acadrust::entities::{ Dimension, DimensionAligned, DimensionAngular2Ln, DimensionAngular3Pt, DimensionBase, @@ -8,6 +7,10 @@ use acadrust::entities::{ }; use acadrust::types::Vector3; use acadrust::{EntityType, Handle}; +use cadkernel::geom2d::{ + arc_span, intersect, nearest_of, Arc, Curve, Line, Tolerance, Transform, Vec2, XLine, +}; +use cadkernel::space::Plane; use glam::DVec3; use crate::command::{CadCommand, CmdOption, CmdResult, DimensionAssociationInput}; @@ -56,35 +59,39 @@ impl SourceStyle { enum BaselineKind { Linear { - fixed: DVec3, + fixed: Vec2, rotation: f64, aligned: bool, - perpendicular: DVec3, + perpendicular: Vec2, next_offset: f64, increment: f64, }, Angular2Ln { - source: DimensionAngular2Ln, - vertex: DVec3, - fixed_ray: DVec3, + fixed_line: [Vec2; 2], + fixed_is_first: bool, + vertex: Vec2, + fixed_ray: Vec2, next_radius: f64, increment: f64, }, Angular3Pt { - source: DimensionAngular3Pt, - vertex: DVec3, - fixed_ray: DVec3, + vertex: Vec2, + fixed_point: Vec2, + fixed_ray: Vec2, next_radius: f64, increment: f64, }, Ordinate { - source: DimensionOrdinate, + definition: Vec2, + leader: Vec2, + is_x: bool, }, } struct BaselineState { kind: BaselineKind, style: SourceStyle, + plane: Plane, } pub struct DimBaselineCommand { @@ -140,35 +147,59 @@ impl DimBaselineCommand { }; let style = SourceStyle::from_base(dimension.base()); let increment = self.effective_increment(&style.style_name); + let plane = plane_from_normal(dimension.base().definition_point, style.normal); + let pick = pick.and_then(|point| plane.project(point.to_array()).map(Vec2::from)); let kind = match dimension { Dimension::Linear(source) => { - let (fixed, _) = nearest_end(source.first_point, source.second_point, pick); + let first = project(&plane, source.first_point); + let second = project(&plane, source.second_point); + let (fixed, _) = nearest_end(first, second, pick); linear_base( fixed, source.rotation, false, - dv(source.base.definition_point), + project(&plane, source.base.definition_point), increment, ) } Dimension::Aligned(source) => { - let (fixed, other) = nearest_end(source.first_point, source.second_point, pick); + let first = project(&plane, source.first_point); + let second = project(&plane, source.second_point); + let (fixed, other) = nearest_end(first, second, pick); linear_base( fixed, - (other - fixed).y.atan2((other - fixed).x), + (other - fixed).angle(), true, - dv(source.base.definition_point), + project(&plane, source.base.definition_point), increment, ) } Dimension::Angular2Ln(source) => { - let vertex = angular2_vertex(source); - let first = dv(source.second_point) - vertex; - let second = dv(source.definition_point) - vertex; - let fixed_ray = nearest_direction(first, second, vertex, pick); - let radius = (dv(source.dimension_arc) - vertex).length().max(1.0e-9); + 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]); + let fixed_index = nearest_line(lines, pick); + let fixed_line = lines[fixed_index]; + let fixed_end = if fixed_index == 0 { + lines[0][1] + } else { + lines[1][1] + }; + let fixed_ray = fixed_end - vertex; + let radius = project(&plane, source.dimension_arc) + .distance(vertex) + .max(1.0e-9); BaselineKind::Angular2Ln { - source: source.clone(), + fixed_line, + fixed_is_first: fixed_index == 0, vertex, fixed_ray, next_radius: radius + increment, @@ -176,31 +207,42 @@ impl DimBaselineCommand { } } Dimension::Angular3Pt(source) => { - let vertex = dv(source.angle_vertex); - let first = dv(source.first_point) - vertex; - let second = dv(source.second_point) - vertex; - let fixed_ray = nearest_direction(first, second, vertex, pick); - let radius = (dv(source.definition_point) - vertex).length().max(1.0e-9); + let vertex = project(&plane, source.angle_vertex); + let points = [ + project(&plane, source.first_point), + project(&plane, source.second_point), + ]; + let fixed_index = nearest_line( + [[vertex, points[0]], [vertex, points[1]]], + pick, + ); + let fixed_point = points[fixed_index]; + let radius = project(&plane, source.definition_point) + .distance(vertex) + .max(1.0e-9); BaselineKind::Angular3Pt { - source: source.clone(), vertex, - fixed_ray, + fixed_point, + fixed_ray: fixed_point - vertex, next_radius: radius + increment, increment, } } Dimension::Ordinate(source) => BaselineKind::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(BaselineState { kind, style }); + self.base = Some(BaselineState { kind, style, plane }); self.committed = 0; 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 { BaselineKind::Linear { fixed, @@ -210,82 +252,93 @@ impl DimBaselineCommand { next_offset, .. } => build_linear( + state.plane, *fixed, point, *rotation, *aligned, *perpendicular, *next_offset, - ), + )?, BaselineKind::Angular2Ln { - source, + fixed_line, + fixed_is_first, vertex, fixed_ray, next_radius, .. } => { let moving = point - *vertex; - if moving.length_squared() <= 1.0e-18 { - return None; - } - let mut result = source.clone(); - result.first_point = v3(*vertex); - result.second_point = v3(*vertex + normalized_or(*fixed_ray, DVec3::X)); - result.angle_vertex = v3(*vertex); - result.definition_point = v3(point); let (definition, text) = - angular_definition_and_text(*vertex, *fixed_ray, moving, *next_radius); - result.dimension_arc = v3(definition); + angular_definition_and_text(*vertex, *fixed_ray, moving, *next_radius)?; + let mut result = DimensionAngular2Ln::default(); + if *fixed_is_first { + result.first_point = world(state.plane, fixed_line[0]); + result.second_point = world(state.plane, fixed_line[1]); + result.angle_vertex = world(state.plane, *vertex); + result.definition_point = world(state.plane, point); + } else { + result.first_point = world(state.plane, *vertex); + result.second_point = world(state.plane, point); + result.angle_vertex = world(state.plane, fixed_line[0]); + result.definition_point = world(state.plane, fixed_line[1]); + } + 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) } BaselineKind::Angular3Pt { - source, vertex, + fixed_point, fixed_ray, next_radius, .. } => { let moving = point - *vertex; - if moving.length_squared() <= 1.0e-18 { - return None; - } - let mut result = source.clone(); - result.angle_vertex = v3(*vertex); - result.first_point = v3(*vertex + normalized_or(*fixed_ray, DVec3::X)); - result.second_point = v3(point); let (definition, text) = - angular_definition_and_text(*vertex, *fixed_ray, moving, *next_radius); - result.definition_point = v3(definition); + angular_definition_and_text(*vertex, *fixed_ray, moving, *next_radius)?; + let mut result = DimensionAngular3Pt::new( + world(state.plane, *vertex), + world(state.plane, *fixed_point), + 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) } - BaselineKind::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) + BaselineKind::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 leader = Vec2::from(from_dimension.apply_point(leader.into())); let mut result = DimensionOrdinate::new( - v3(point), - v3(leader), - source.is_ordinate_type_x, + world(state.plane, point), + world(state.plane, leader), + *is_x, ); - result.definition_point = source.definition_point; - result.base.definition_point = source.base.definition_point; - result.base.text_middle_point = v3(leader); + 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; 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) @@ -389,6 +442,7 @@ impl CadCommand for DimBaselineCommand { entity: EntityType::Dimension(dimension), association: DimensionAssociationInput::Infer(None), preserve_base_style: self.preserve_base_style, + continue_command: true, } } @@ -426,18 +480,19 @@ impl CadCommand for DimBaselineCommand { } fn on_mouse_move(&mut self, point: DVec3) -> Option { - self.build_dimension(point).map(|dimension| preview_for_dimension(&dimension)) + self.build_dimension(point) + .map(|dimension| preview_for_dimension(&dimension)) } } fn linear_base( - fixed: DVec3, + fixed: Vec2, rotation: f64, aligned: bool, - definition: DVec3, + definition: Vec2, increment: f64, ) -> BaselineKind { - let perpendicular = DVec3::new(-rotation.sin(), rotation.cos(), 0.0); + let perpendicular = Vec2::new(-rotation.sin(), rotation.cos()); let offset = (definition - fixed).dot(perpendicular); let increment = increment * if offset >= 0.0 { 1.0 } else { -1.0 }; BaselineKind::Linear { @@ -451,39 +506,48 @@ fn linear_base( } fn build_linear( - fixed: DVec3, - point: DVec3, + plane: Plane, + fixed: Vec2, + point: Vec2, rotation: f64, aligned: bool, - perpendicular: DVec3, + source_perpendicular: Vec2, offset: f64, -) -> Dimension { - let target = fixed.dot(perpendicular) + offset; - let first_line = fixed + perpendicular * (target - fixed.dot(perpendicular)); - let second_line = point + perpendicular * (target - point.dot(perpendicular)); - if aligned { - let mut result = DimensionAligned::new(v3(fixed), v3(point)); - 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::Aligned(result) +) -> Option { + let (first_line, second_line) = if aligned { + let mut perpendicular = (point - fixed).normalize()?.perpendicular(); + let source_side = source_perpendicular * offset.signum(); + if perpendicular.dot(source_side) < 0.0 { + perpendicular = -perpendicular; + } + let distance = offset.abs(); + (fixed + perpendicular * distance, point + perpendicular * distance) } else { - let mut result = DimensionLinear::new(v3(fixed), v3(point)); - 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); + let target = fixed.dot(source_perpendicular) + offset; + ( + project_to_line(fixed, source_perpendicular, target), + project_to_line(point, source_perpendicular, target), + ) + }; + if aligned { + let mut result = DimensionAligned::new(world(plane, fixed), world(plane, point)); + 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; - result.base.actual_measurement = result.measurement(); - Dimension::Linear(result) + Some(Dimension::Aligned(result)) + } else { + let mut result = DimensionLinear::new(world(plane, fixed), world(plane, point)); + 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; + Some(Dimension::Linear(result)) } } -fn nearest_end(first: Vector3, second: Vector3, pick: Option) -> (DVec3, DVec3) { - let first = dv(first); - let second = dv(second); +fn nearest_end(first: Vec2, second: Vec2, pick: Option) -> (Vec2, Vec2) { if pick.is_some_and(|point| point.distance_squared(second) < point.distance_squared(first)) { (second, first) } else { @@ -491,104 +555,156 @@ fn nearest_end(first: Vector3, second: Vector3, pick: Option) -> (DVec3, } } -fn nearest_direction(first: DVec3, second: DVec3, vertex: DVec3, pick: Option) -> DVec3 { - if let Some(point) = pick { - let direction = normalized_or(point - vertex, DVec3::X); - if normalized_or(second, DVec3::Y).dot(direction) - > normalized_or(first, DVec3::X).dot(direction) - { - return second; - } - } - first +fn nearest_line(lines: [[Vec2; 2]; 2], pick: Option) -> usize { + let Some(pick) = pick else { + return 0; + }; + let curves = lines.map(|line| { + Curve::Line(Line { + start: line[0].into(), + end: line[1].into(), + }) + }); + nearest_of(curves.iter(), pick.into()) + .map(|(index, _)| index) + .unwrap_or(0) } -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 + .into_iter() + .flatten() + .map(Vec2::length) + .fold(1.0, f64::max); + intersect(&first, &second, Tolerance::new(scale * 1.0e-9)) + .first() + .map(|crossing| Vec2::from(crossing.point)) } fn angular_definition_and_text( - vertex: DVec3, - first: DVec3, - second: DVec3, + vertex: Vec2, + first: Vec2, + second: Vec2, radius: f64, -) -> (DVec3, DVec3) { - 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; - } - while sweep > std::f64::consts::PI { - sweep -= std::f64::consts::TAU; - } - 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)) +) -> Option<(Vec2, Vec2)> { + let curve = angular_arc(vertex, first, second, radius)?; + Some(( + Vec2::from(curve.point_at(2.0 / 3.0)), + Vec2::from(curve.point_at(0.5)), + )) } -fn normalized_or(value: DVec3, fallback: DVec3) -> DVec3 { - if value.length_squared() > 1.0e-18 { - value.normalize() +fn angular_arc(vertex: Vec2, first: Vec2, second: Vec2, radius: f64) -> Option { + let first = first.normalize()?; + let second = second.normalize()?; + let first_angle = first.angle(); + let second_angle = second.angle(); + let (start_angle, end_angle) = if arc_span(first_angle, second_angle) <= PI { + (first_angle, second_angle) } else { - fallback + (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::Aligned(value) => value.base.actual_measurement = value.measurement(), + 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(), + _ => {} } } fn preview_for_dimension(dimension: &Dimension) -> WireModel { let mut points = Vec::<[f32; 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), - ), - Dimension::Aligned(dimension) => { - let first = dv(dimension.first_point); - let second = dv(dimension.second_point); - let delta = second - first; + Dimension::Linear(value) => { + let plane = plane_from_normal(value.first_point, value.base.normal); linear_preview( &mut points, - first, - second, - delta.y.atan2(delta.x), - dv(dimension.definition_point), + plane, + project(&plane, value.first_point), + project(&plane, value.second_point), + value.rotation, + project(&plane, value.definition_point), ); } - Dimension::Angular2Ln(dimension) => angular_preview( + Dimension::Aligned(value) => { + let plane = plane_from_normal(value.first_point, value.base.normal); + let first = project(&plane, value.first_point); + let second = project(&plane, value.second_point); + linear_preview( + &mut points, + plane, + first, + second, + (second - first).angle(), + project(&plane, value.definition_point), + ); + } + 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, + lines[0][1] - vertex, + lines[1][1] - vertex, + project(&plane, value.dimension_arc), + ); + } + 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), + ); + } + Dimension::Ordinate(value) => segment_world( &mut points, - angular2_vertex(dimension), - dv(dimension.second_point) - angular2_vertex(dimension), - dv(dimension.definition_point) - angular2_vertex(dimension), - dv(dimension.dimension_arc), - ), - 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), - ), - Dimension::Ordinate(dimension) => segment( - &mut points, - dv(dimension.feature_location), - dv(dimension.leader_endpoint), + point(value.feature_location), + point(value.leader_endpoint), ), _ => {} } @@ -629,72 +745,103 @@ fn preview_for_dimension(dimension: &Dimension) -> WireModel { fn linear_preview( points: &mut Vec<[f32; 3]>, - first: DVec3, - second: DVec3, + plane: Plane, + first: Vec2, + second: Vec2, rotation: f64, - definition: DVec3, + definition: Vec2, ) { - let perpendicular = DVec3::new(-rotation.sin(), rotation.cos(), 0.0); + let perpendicular = Vec2::new(-rotation.sin(), rotation.cos()); let target = definition.dot(perpendicular); - let first_line = first + perpendicular * (target - first.dot(perpendicular)); - let second_line = second + perpendicular * (target - second.dot(perpendicular)); - segment(points, first, first_line); - segment(points, second, second_line); - segment(points, first_line, second_line); - append_arrows(points, first_line, second_line); + let first_line = project_to_line(first, perpendicular, target); + let second_line = project_to_line(second, perpendicular, target); + segment_local(points, plane, first, first_line); + segment_local(points, plane, second, second_line); + segment_local(points, plane, first_line, second_line); + append_arrows(points, plane, first_line, second_line); } -fn segment(points: &mut Vec<[f32; 3]>, first: DVec3, second: DVec3) { - points.push(first.as_vec3().to_array()); - points.push(second.as_vec3().to_array()); - points.push([f32::NAN, 0.0, 0.0]); -} - -fn append_arrows(points: &mut Vec<[f32; 3]>, first: DVec3, second: DVec3) { - let axis = normalized_or(second - first, DVec3::X); - let normal = DVec3::new(-axis.y, axis.x, 0.0); +fn append_arrows(points: &mut Vec<[f32; 3]>, plane: Plane, first: Vec2, second: Vec2) { + let Some(axis) = (second - first).normalize() else { + return; + }; + let normal = axis.perpendicular(); let size = first.distance(second).clamp(1.0, 100.0) * 0.035; for (tip, inward) in [(first, axis), (second, -axis)] { let back = tip + inward * size; - segment(points, tip, back + normal * size * 0.4); - segment(points, tip, back - normal * size * 0.4); + segment_local(points, plane, tip, back + normal * size * 0.4); + segment_local(points, plane, tip, back - normal * size * 0.4); } } fn angular_preview( points: &mut Vec<[f32; 3]>, - vertex: DVec3, - first: DVec3, - second: DVec3, - arc_point: DVec3, + plane: Plane, + vertex: Vec2, + first: Vec2, + second: Vec2, + arc_point: Vec2, ) { - 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; - } - while sweep > std::f64::consts::PI { - sweep -= std::f64::consts::TAU; - } - segment(points, vertex, vertex + normalized_or(first, DVec3::X) * radius); - segment(points, vertex, vertex + normalized_or(second, DVec3::Y) * radius); + 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_local(points, plane, vertex, vertex + first_direction * radius); + segment_local(points, plane, vertex, vertex + second_direction * radius); for index in 0..=24 { - let angle = start + sweep * index as f64 / 24.0; - points.push( - (vertex + DVec3::new(angle.cos(), angle.sin(), 0.0) * radius) - .as_vec3() - .to_array(), - ); + push_local(points, plane, Vec2::from(arc.point_at(index as f64 / 24.0))); } } -fn dv(point: Vector3) -> DVec3 { - DVec3::new(point.x, point.y, point.z) +fn project_to_line(point: Vec2, perpendicular: Vec2, offset: f64) -> Vec2 { + point + perpendicular * (offset - point.dot(perpendicular)) } -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_local(points: &mut Vec<[f32; 3]>, plane: Plane, first: Vec2, second: Vec2) { + segment_world(points, plane.point_at(first.into()), plane.point_at(second.into())); +} + +fn segment_world(points: &mut Vec<[f32; 3]>, first: [f64; 3], second: [f64; 3]) { + points.push(float3(first)); + points.push(float3(second)); + points.push([f32::NAN, 0.0, 0.0]); +} + +fn push_local(points: &mut Vec<[f32; 3]>, plane: Plane, point: Vec2) { + points.push(float3(plane.point_at(point.into()))); +} + +fn float3(value: [f64; 3]) -> [f32; 3] { + [value[0] as f32, value[1] as f32, value[2] as f32] } inventory::submit!(crate::command::CommandRegistration { names: &["DIMBASELINE"] }); diff --git a/src/modules/annotate/dim_continue.rs b/src/modules/annotate/dim_continue.rs index b7eb336b7..fe5a255d9 100644 --- a/src/modules/annotate/dim_continue.rs +++ b/src/modules/annotate/dim_continue.rs @@ -1,17 +1,10 @@ -// DIMCONTINUE command — chain linear/aligned dimensions end-to-end. -// -// Each new point becomes the second extension line origin of a new dimension, -// whose first extension line origin is the second extension line of the previous dim. -// The dimension line stays at the same perpendicular offset as the base dimension. -// -// Constructed from commands.rs after finding the last placed linear/aligned dimension. - -use acadrust::entities::{Dimension, DimensionLinear}; +use acadrust::entities::{Dimension, DimensionBase, DimensionLinear}; use acadrust::types::Vector3; use acadrust::EntityType; -use glam::{DVec3, Vec3}; +use cadkernel::space::Plane; +use glam::DVec3; -use crate::command::{CadCommand, CmdResult}; +use crate::command::{CadCommand, CmdResult, DimensionAssociationInput}; use crate::modules::{IconKind, ModuleEvent, ToolDef}; use crate::scene::model::wire_model::WireModel; use crate::t; @@ -27,75 +20,112 @@ pub fn tool() -> ToolDef { } } -pub struct DimContinueCommand { - /// Fixed first-extension-line origin for the current step (moves each iteration). - chain_p1: DVec3, - /// Direction along the dimension axis (0.0 = horizontal, PI/2 = vertical). - rotation: f64, - /// Text reading rotation inherited from the base dim so a UCS-aligned chain - /// keeps its text consistent with the originating DIMLINEAR. 0 = natural. +#[derive(Clone)] +struct SourceStyle { + layer: String, + style_name: String, + normal: Vector3, text_rotation: f64, - /// Absolute perpendicular coordinate (dot with `perp`) of the base - /// dimension line. Each continued dim line is projected onto this exact - /// coordinate so the whole chain stays collinear — even when the extension - /// origins sit at different perpendicular positions (extension lines of - /// different lengths). (#151) - dim_line_perp: f64, - /// Direction of "up" perpendicular to the dim axis (points toward the dim line). - perp: DVec3, - /// True once we have a base dimension loaded. - ready: bool, + horizontal_direction: f64, } -impl DimContinueCommand { - /// No base dim found — will show an error prompt and cancel immediately. - pub fn new() -> Self { +impl SourceStyle { + fn from_base(base: &DimensionBase) -> Self { Self { - chain_p1: DVec3::ZERO, - rotation: 0.0, - text_rotation: 0.0, - dim_line_perp: 0.0, - perp: DVec3::Y, - ready: false, + layer: base.common.layer.clone(), + style_name: base.style_name.clone(), + normal: base.normal, + text_rotation: base.text_rotation, + horizontal_direction: base.horizontal_direction, } } - /// Build from the last placed dimension. - /// - /// `p2` — second extension line origin of the base dim (the chain starts - /// here). `p1` is unused — the dim line position comes from - /// `definition_point` alone — but kept for signature parity with the - /// shared `find_last_linear_dim` tuple (DIMBASELINE uses p1). - /// `definition_point` — where the dim line was placed; its perpendicular - /// coordinate is the line the whole chain stays collinear with. - /// `rotation` — 0.0 = horizontal dim, PI/2 = vertical dim. - pub fn from_base( - _p1: Vec3, - p2: Vec3, - definition_point: Vec3, - rotation: f64, - text_rotation: f64, - ) -> Self { - // Widen the base-dim points to f64 so the committed chain math runs in - // full precision. (The base points arrive as f32 from the entity, but - // every derived committed coordinate must stay f64 from here on.) - let p2 = p2.as_dvec3(); - let definition_point = definition_point.as_dvec3(); - // Axis unit vector along the measurement direction — the base dim's - // rotation angle (any angle, incl. a UCS-aligned one), not a world H/V. - let axis = DVec3::new(rotation.cos(), rotation.sin(), 0.0); - // Perpendicular unit vector toward the dim line. - let perp = DVec3::new(-axis.y, axis.x, 0.0); - let dim_line_perp = definition_point.dot(perp); + fn apply(&self, base: &mut DimensionBase) { + base.common.layer = self.layer.clone(); + base.style_name = self.style_name.clone(); + base.normal = self.normal; + base.text_rotation = self.text_rotation; + base.horizontal_direction = self.horizontal_direction; + } +} + +struct ContinueState { + chain_p1: [f64; 2], + rotation: f64, + dim_line_perp: f64, + perpendicular: [f64; 2], + plane: Plane, + style: SourceStyle, +} + +pub struct DimContinueCommand { + state: Option, + preserve_base_style: bool, +} + +impl DimContinueCommand { + pub fn new(preserve_base_style: bool) -> Self { Self { - chain_p1: p2, - rotation, - text_rotation, - dim_line_perp, - perp, - ready: true, + state: None, + preserve_base_style, } } + + pub fn from_dimension(dimension: &Dimension, preserve_base_style: bool) -> Self { + let (first, second, definition, rotation) = match dimension { + Dimension::Linear(source) => ( + source.first_point, + source.second_point, + source.base.definition_point, + source.rotation, + ), + Dimension::Aligned(source) => { + let plane = plane_from_normal(source.first_point, source.base.normal); + let first = plane.project(point(source.first_point)).unwrap_or([0.0; 2]); + let second = plane.project(point(source.second_point)).unwrap_or(first); + let delta = [second[0] - first[0], second[1] - first[1]]; + ( + source.first_point, + source.second_point, + source.base.definition_point, + delta[1].atan2(delta[0]), + ) + } + _ => return Self::new(preserve_base_style), + }; + let plane = plane_from_normal(first, dimension.base().normal); + let second = plane.project(point(second)).unwrap_or([0.0; 2]); + let definition = plane.project(point(definition)).unwrap_or(second); + let perpendicular = [-rotation.sin(), rotation.cos()]; + let dim_line_perp = dot(definition, perpendicular); + Self { + state: Some(ContinueState { + chain_p1: second, + rotation, + dim_line_perp, + perpendicular, + plane, + style: SourceStyle::from_base(dimension.base()), + }), + preserve_base_style, + } + } + + fn placement(&self, point_world: DVec3) -> Option<([f64; 2], [f64; 2], [f64; 2])> { + let state = self.state.as_ref()?; + let second = state.plane.project(point_world.to_array())?; + let first_line = project_to_line( + state.chain_p1, + state.perpendicular, + state.dim_line_perp, + ); + let second_line = project_to_line( + second, + state.perpendicular, + state.dim_line_perp, + ); + Some((second, first_line, second_line)) + } } impl CadCommand for DimContinueCommand { @@ -104,61 +134,61 @@ impl CadCommand for DimContinueCommand { } fn prompt(&self) -> String { - if !self.ready { + if self.state.is_none() { t!("DIMCONTINUE No base dimension found. Place a dimension first.").into_owned() } else { t!("DIMCONTINUE Specify a second extension line origin (Enter to exit):").into_owned() } } - fn on_point(&mut self, pt: DVec3) -> CmdResult { - if !self.ready { + fn on_point(&mut self, point_world: DVec3) -> CmdResult { + let Some((second, first_line, second_line)) = self.placement(point_world) else { return CmdResult::Cancel; + }; + let state = self.state.as_mut().expect("continue state"); + let mut dimension = DimensionLinear::new( + vector3(state.plane.point_at(state.chain_p1)), + vector3(state.plane.point_at(second)), + ); + dimension.rotation = state.rotation; + state.style.apply(&mut dimension.base); + dimension.definition_point = vector3(state.plane.point_at(first_line)); + dimension.base.definition_point = dimension.definition_point; + dimension.base.text_middle_point = vector3(state.plane.point_at([ + (first_line[0] + second_line[0]) * 0.5, + (first_line[1] + second_line[1]) * 0.5, + ])); + dimension.base.insertion_point = dimension.base.text_middle_point; + let axis = [state.rotation.cos(), state.rotation.sin()]; + dimension.base.actual_measurement = dot( + [ + second[0] - state.chain_p1[0], + second[1] - state.chain_p1[1], + ], + axis, + ) + .abs(); + state.chain_p1 = second; + + CmdResult::CommitDimension { + entity: EntityType::Dimension(Dimension::Linear(dimension)), + association: DimensionAssociationInput::Infer(None), + preserve_base_style: self.preserve_base_style, + continue_command: true, } - let p1 = self.chain_p1; - let p2 = pt; - - // Build a new linear dimension. - let mut dim = DimensionLinear::new(v3(p1), v3(p2)); - dim.rotation = self.rotation; - if self.text_rotation.abs() > 1e-9 { - dim.base.text_rotation = self.text_rotation; - } - - // Dim line stays collinear with the base: project both extension - // origins onto the base dim line's absolute perpendicular coordinate. - // (A fixed offset from p1 drifts off the base line whenever p1 and p2 - // sit at different perpendicular positions.) (#151) - let d1 = p1 + self.perp * (self.dim_line_perp - p1.dot(self.perp)); - let d2 = p2 + self.perp * (self.dim_line_perp - p2.dot(self.perp)); - dim.definition_point = v3(d1); - dim.base.definition_point = v3(d1); - 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(); - - // Advance chain: next dim's P1 = this dim's P2. - self.chain_p1 = p2; - - CmdResult::CommitEntity(EntityType::Dimension(Dimension::Linear(dim))) } fn on_enter(&mut self) -> CmdResult { CmdResult::Cancel } - fn on_mouse_move(&mut self, pt: DVec3) -> Option { - if !self.ready { - return None; - } - // Preview / rubber-band geometry feeds an f32 GPU buffer, so drop to f32 - // at this screen-only boundary. - let pt = pt.as_vec3(); - let p1 = self.chain_p1.as_vec3(); - let perp = self.perp.as_vec3(); - let dim_line_perp = self.dim_line_perp as f32; - let dim_line_pt = p1 + perp * (dim_line_perp - p1.dot(perp)); - let dim_line_pt2 = pt + perp * (dim_line_perp - pt.dot(perp)); + fn on_mouse_move(&mut self, point_world: DVec3) -> Option { + let (second, first_line, second_line) = self.placement(point_world)?; + let state = self.state.as_ref()?; + let first = state.plane.point_at(state.chain_p1); + let second = state.plane.point_at(second); + let first_line = state.plane.point_at(first_line); + let second_line = state.plane.point_at(second_line); Some(WireModel { point_marker: None, taper_widths: Vec::new(), @@ -177,14 +207,14 @@ impl CadCommand for DimContinueCommand { text_verts: Vec::new(), name: "dimcont_preview".into(), points: vec![ - [p1.x, p1.y, p1.z], - [dim_line_pt.x, dim_line_pt.y, dim_line_pt.z], + float3(first), + float3(first_line), [f32::NAN, 0.0, 0.0], - [pt.x, pt.y, pt.z], - [dim_line_pt2.x, dim_line_pt2.y, dim_line_pt2.z], + float3(second), + float3(second_line), [f32::NAN, 0.0, 0.0], - [dim_line_pt.x, dim_line_pt.y, dim_line_pt.z], - [dim_line_pt2.x, dim_line_pt2.y, dim_line_pt2.z], + float3(first_line), + float3(second_line), ], points_low: Vec::new(), color: WireModel::CYAN, @@ -192,22 +222,50 @@ impl CadCommand for DimContinueCommand { pattern_length: 0.0, pattern: [0.0; 8], line_weight_px: 1.0, - snap_pts: vec![], - tangent_geoms: vec![], + snap_pts: Vec::new(), + tangent_geoms: Vec::new(), aci: 0, - key_vertices: vec![], + key_vertices: Vec::new(), aabb: WireModel::UNBOUNDED_AABB, plinegen: true, - fill_tris: vec![], + fill_tris: Vec::new(), fill_tris_low: Vec::new(), }) } } -fn v3(p: DVec3) -> Vector3 { - Vector3::new(p.x, p.y, p.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_to_line(point: [f64; 2], perpendicular: [f64; 2], offset: f64) -> [f64; 2] { + let distance = offset - dot(point, perpendicular); + [ + point[0] + perpendicular[0] * distance, + point[1] + perpendicular[1] * distance, + ] +} -// ── Autocomplete registry ───────────────────────────────── -inventory::submit!(crate::command::CommandRegistration { names: &["DIMCONTINUE"] }); // DimContinueCommand +fn dot(first: [f64; 2], second: [f64; 2]) -> f64 { + first[0] * second[0] + first[1] * second[1] +} + +fn point(value: Vector3) -> [f64; 3] { + [value.x, value.y, value.z] +} + +fn vector3(value: [f64; 3]) -> Vector3 { + Vector3::new(value[0], value[1], value[2]) +} + +fn float3(value: [f64; 3]) -> [f32; 3] { + [value[0] as f32, value[1] as f32, value[2] as f32] +} + +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 2363082b1..9a517c968 100644 --- a/src/modules/annotate/jogged_radius_dim.rs +++ b/src/modules/annotate/jogged_radius_dim.rs @@ -140,6 +140,7 @@ impl JoggedRadiusDimensionCommand { ))), association, preserve_base_style: false, + continue_command: false, } } } diff --git a/src/modules/annotate/linear_dim.rs b/src/modules/annotate/linear_dim.rs index fd6d6d6c5..53d09b08e 100644 --- a/src/modules/annotate/linear_dim.rs +++ b/src/modules/annotate/linear_dim.rs @@ -188,6 +188,7 @@ impl CadCommand for LinearDimensionCommand { entity, association: DimensionAssociationInput::Infer(self.source_handle), preserve_base_style: false, + continue_command: false, } } } diff --git a/src/modules/annotate/ordinate_dim.rs b/src/modules/annotate/ordinate_dim.rs index 221523c7f..1437ba953 100644 --- a/src/modules/annotate/ordinate_dim.rs +++ b/src/modules/annotate/ordinate_dim.rs @@ -117,6 +117,7 @@ impl CadCommand for OrdinateDimCommand { )), association: DimensionAssociationInput::Infer(None), preserve_base_style: false, + continue_command: false, } } } diff --git a/src/modules/annotate/radius_dim.rs b/src/modules/annotate/radius_dim.rs index 10681bad8..4cb6acc9d 100644 --- a/src/modules/annotate/radius_dim.rs +++ b/src/modules/annotate/radius_dim.rs @@ -112,6 +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, + 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() +}