From 488081f1c722f31c3a2db0480c0839efa758a736 Mon Sep 17 00:00:00 2001 From: ramox81 <184937705+ramox81@users.noreply.github.com> Date: Tue, 25 Aug 2026 15:39:38 +0300 Subject: [PATCH 1/3] Use corrected diameter dimension model --- Cargo.lock | 2 +- Cargo.toml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 808da0125..7629df017 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -72,7 +72,7 @@ checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618" [[package]] name = "acadrust" version = "0.4.1" -source = "git+https://git@github.com/HakanSeven12/cadcodec.git?rev=5b2ae66#5b2ae66d0bd7b0da2d13392d3c3332f8a39caa1f" +source = "git+https://git@github.com/HakanSeven12/cadcodec.git?rev=7e9a63b#7e9a63b84763fab82cbeb3cbe5721ed2a1c8efc3" dependencies = [ "ahash 0.8.12", "anyhow", diff --git a/Cargo.toml b/Cargo.toml index dc49bfb1c..a29e71798 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,7 +27,7 @@ glam = { version = "0.33", features = ["bytemuck"] } rfd = "0.17" clap = { version = "4", features = ["derive"] } env_logger = "0.11" -acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "5b2ae66", features = ["serde"] } +acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "7e9a63b", features = ["serde"] } cadkernel = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "b2b1d4b", features = ["acis", "offset"] } dwg-thumbnailer = { path = "crates/dwg-thumbnailer" } flate2 = "1" @@ -61,7 +61,7 @@ iced_core = { git = "https://github.com/iced-rs/iced.git", rev = "23604ff22ab0aa iced_widget = { git = "https://github.com/iced-rs/iced.git", rev = "23604ff22ab0aad9e00b9327cb7b8546ed84db39" } [patch."https://github.com/HakanSeven12/cadcodec.git"] -acadrust = { git = "https://git@github.com/HakanSeven12/cadcodec.git", rev = "5b2ae66" } +acadrust = { git = "https://git@github.com/HakanSeven12/cadcodec.git", rev = "7e9a63b" } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] ocs_plugin_api = { path = "crates/ocs_plugin_api", features = ["host"] } From 0ba33a55ff287b46be4880d473d263341b7294bb Mon Sep 17 00:00:00 2001 From: ramox81 <184937705+ramox81@users.noreply.github.com> Date: Tue, 25 Aug 2026 15:39:44 +0300 Subject: [PATCH 2/3] Add associative diameter dimension workflow --- src/command.rs | 2 +- src/modules/annotate/diameter_dim.rs | 155 ++++++++---- src/scene/dimension_assoc.rs | 353 ++++++++++++++++++++++++++- 3 files changed, 463 insertions(+), 47 deletions(-) diff --git a/src/command.rs b/src/command.rs index 797b27b4b..a79db230a 100644 --- a/src/command.rs +++ b/src/command.rs @@ -1212,7 +1212,7 @@ pub enum CmdResult { CommitEntitiesAndExit(Vec), /// Commit an acadrust entity to the document and end the command. CommitAndExit(EntityType), - /// Commit a linear/aligned dimension using the drawing's association mode. + /// Commit a dimension using the drawing's association mode. /// Object-selection workflows may retain their source. CommitDimension { entity: EntityType, diff --git a/src/modules/annotate/diameter_dim.rs b/src/modules/annotate/diameter_dim.rs index 1fe7ccfa5..3bbc1c237 100644 --- a/src/modules/annotate/diameter_dim.rs +++ b/src/modules/annotate/diameter_dim.rs @@ -1,7 +1,7 @@ // DIMDIAMETER command — diameter dimension for circles and arcs. use acadrust::entities::{Dimension, DimensionDiameter}; -use acadrust::types::Vector3; +use acadrust::types::{Handle, Vector3}; use acadrust::EntityType; use glam::{DVec3, Vec3}; @@ -22,14 +22,12 @@ pub fn tool() -> ToolDef { } enum Step { - CenterPoint, - ArcPoint(DVec3), - TextPoint { center: DVec3, arc_pt: DVec3 }, + SelectObject, + DimLine(crate::scene::dimension_assoc::RadialSourceGeometry), } pub struct DiameterDimensionCommand { step: Step, - plane: WorkingPlane, /// Optional text that replaces the measured value (None = measurement). text_override: Option, /// True while the next typed line is captured as the text override. @@ -38,24 +36,28 @@ pub struct DiameterDimensionCommand { text_angle: Option, /// True while the next typed value is captured as the text angle. awaiting_angle: bool, + picked_entity: Option, + source_handle: Option, + mtext_override: bool, } impl DiameterDimensionCommand { pub fn new() -> Self { Self { - step: Step::CenterPoint, - plane: WorkingPlane::default(), + step: Step::SelectObject, text_override: None, awaiting_text: false, text_angle: None, awaiting_angle: false, + picked_entity: None, + source_handle: None, + mtext_override: false, } } } impl CadCommand for DiameterDimensionCommand { - fn set_working_plane(&mut self, plane: WorkingPlane) { - self.plane = plane; + fn set_working_plane(&mut self, _plane: WorkingPlane) { } fn name(&self) -> &'static str { @@ -64,48 +66,58 @@ impl CadCommand for DiameterDimensionCommand { fn prompt(&self) -> String { if self.awaiting_text { - return t!("DIMDIAMETER Enter dimension text (blank = measured value):").into_owned(); + return if self.mtext_override { + t!("DIMDIAMETER Enter formatted dimension text (blank = measured value):") + .into_owned() + } else { + t!("DIMDIAMETER Enter dimension text (blank = measured value):").into_owned() + }; } if self.awaiting_angle { return t!("DIMDIAMETER Specify text angle (degrees):").into_owned(); } match self.step { - Step::CenterPoint => t!("DIMDIAMETER Specify center point:").into_owned(), - Step::ArcPoint(_) => t!("DIMDIAMETER Specify point on circle:").into_owned(), - Step::TextPoint { .. } => { - t!("DIMDIAMETER Specify dimension line location [Text/Angle]:").into_owned() + Step::SelectObject => { + t!("DIMDIAMETER Select arc, circle, or polyline arc:").into_owned() + } + Step::DimLine(_) => { + t!("DIMDIAMETER Specify dimension line location [Mtext/Text/Angle]:") + .into_owned() } } } fn on_point(&mut self, pt: DVec3) -> CmdResult { match self.step { - Step::CenterPoint => { - self.step = Step::ArcPoint(pt); - CmdResult::NeedPoint - } - Step::ArcPoint(center) => { - self.step = Step::TextPoint { center, arc_pt: pt }; - CmdResult::NeedPoint - } - Step::TextPoint { center, arc_pt } => { - let center = self.plane.to_local(center); - let arc_pt = self.plane.to_local(arc_pt); - let pt = self.plane.to_local(pt); - let mut dim = DimensionDiameter::new(v3(center), v3(arc_pt)); - dim.base.definition_point = v3(arc_pt); + 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.leader_length = arc_pt.distance(pt); + dim.base.text_user_positioned = true; + dim.leader_length = chord.distance(pt); dim.base.actual_measurement = dim.measurement(); - dim.base.user_text = self.text_override.clone(); + 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::CommitAndExit(self.plane.place_entity(EntityType::Dimension( - Dimension::Diameter(dim), - ))) + CmdResult::CommitDimension { + entity: source_plane + .place_entity(EntityType::Dimension(Dimension::Diameter(dim))), + source: self.source_handle, + } } } } @@ -160,8 +172,17 @@ impl CadCommand for DiameterDimensionCommand { self.awaiting_angle = false; return Some(CmdResult::NeedPoint); } + if !matches!(self.step, Step::DimLine(_)) { + return None; + } match text.trim().to_uppercase().as_str() { - "T" | "TEXT" | "M" | "MTEXT" => { + "T" | "TEXT" => { + self.mtext_override = false; + self.awaiting_text = true; + Some(CmdResult::NeedPoint) + } + "M" | "MTEXT" => { + self.mtext_override = true; self.awaiting_text = true; Some(CmdResult::NeedPoint) } @@ -173,14 +194,47 @@ impl CadCommand for DiameterDimensionCommand { } } + fn needs_entity_pick(&self) -> bool { + matches!(self.step, Step::SelectObject) + } + + fn entity_pick_highlights_hover(&self) -> bool { + true + } + + fn inject_before_entity_pick(&self) -> bool { + true + } + + fn inject_picked_entity(&mut self, entity: EntityType) { + self.picked_entity = Some(entity); + } + + fn on_entity_pick(&mut self, handle: Handle, point: DVec3) -> CmdResult { + let Some(entity) = self.picked_entity.take() else { + return CmdResult::NeedPoint; + }; + let Some(source) = crate::scene::dimension_assoc::radial_source_at( + &entity, + Vector3::new(point.x, point.y, point.z), + ) else { + return CmdResult::NeedPoint; + }; + if !source.radius.is_finite() || source.radius <= 1e-12 { + return CmdResult::NeedPoint; + } + self.source_handle = Some(handle); + self.step = Step::DimLine(source); + CmdResult::NeedPoint + } + fn on_mouse_move(&mut self, pt: DVec3) -> Option { - let pt = pt.as_vec3(); match self.step { - Step::CenterPoint => None, - Step::ArcPoint(center) => Some(preview_line(center.as_vec3(), pt)), - Step::TextPoint { center, arc_pt } => { - let far = center + (center - arc_pt); // opposite point on circle - Some(preview_line(far.as_vec3(), pt)) + Step::SelectObject => None, + Step::DimLine(source) => { + let chord = dvec(source.chord_at(pt.to_array())).as_vec3(); + let far_chord = dvec(source.opposite_chord_at(pt.to_array())).as_vec3(); + Some(preview_line(far_chord, chord, pt.as_vec3())) } } } @@ -190,7 +244,12 @@ fn v3(p: DVec3) -> Vector3 { Vector3::new(p.x, p.y, p.z) } -fn preview_line(a: Vec3, b: Vec3) -> WireModel { +fn dvec(point: Vector3) -> DVec3 { + DVec3::new(point.x, point.y, point.z) +} + +fn preview_line(far_chord: Vec3, chord: Vec3, text: Vec3) -> WireModel { + let separator = [f32::NAN; 3]; WireModel { point_marker: None, taper_widths: Vec::new(), @@ -204,11 +263,17 @@ fn preview_line(a: Vec3, b: Vec3) -> WireModel { render_instance: None, pick_tris: Vec::new(), pick_tris_low: Vec::new(), - dash_from_start: false, - dash_align_end: None, - text_verts: Vec::new(), + dash_from_start: false, + dash_align_end: None, + text_verts: Vec::new(), name: "dimdia_preview".into(), - points: vec![[a.x, a.y, a.z], [b.x, b.y, b.z]], + points: vec![ + far_chord.to_array(), + chord.to_array(), + separator, + chord.to_array(), + text.to_array(), + ], points_low: Vec::new(), color: WireModel::CYAN, selected: false, diff --git a/src/scene/dimension_assoc.rs b/src/scene/dimension_assoc.rs index 2814695ea..5989179ae 100644 --- a/src/scene/dimension_assoc.rs +++ b/src/scene/dimension_assoc.rs @@ -8,10 +8,198 @@ use acadrust::EntityType; use cadkernel::geom2d::{ closest_point, Circle as KernelCircle, Curve as KernelCurve, }; +use cadkernel::space::Plane; use std::f64::consts::TAU; use super::{ChangeKind, Scene}; +#[derive(Clone, Copy, Debug)] +pub(crate) struct RadialSourceGeometry { + pub plane: Plane, + pub center: [f64; 2], + pub radius: f64, + pub start_angle: f64, + pub sweep: f64, + pub limited: bool, + pub marker: i32, +} + +impl RadialSourceGeometry { + pub(crate) fn center_world(self) -> Vector3 { + vector3(self.plane.point_at(self.center)) + } + + pub(crate) fn point_at_angle(self, angle: f64) -> Vector3 { + vector3(self.plane.point_at([ + self.center[0] + self.radius * angle.cos(), + self.center[1] + self.radius * angle.sin(), + ])) + } + + pub(crate) fn angle_at(self, point: DPoint) -> f64 { + let projected = self.plane.project(point).unwrap_or(self.center); + (projected[1] - self.center[1]).atan2(projected[0] - self.center[0]) + } + + pub(crate) fn chord_at(self, point: DPoint) -> Vector3 { + self.point_at_angle(self.angle_at(point)) + } + + pub(crate) fn opposite_chord_at(self, point: DPoint) -> Vector3 { + self.point_at_angle(self.angle_at(point) + std::f64::consts::PI) + } + + fn contains_angle(self, angle: f64) -> bool { + if !self.limited { + return true; + } + signed_progress(self.start_angle, angle, self.sweep) + .is_some_and(|progress| progress <= self.sweep.abs() + 1e-10) + } + + fn distance_squared_to(self, point: DPoint) -> f64 { + let Some(projected) = self.plane.project(point) else { + return f64::INFINITY; + }; + let angle = (projected[1] - self.center[1]).atan2(projected[0] - self.center[0]); + let radial = ((projected[0] - self.center[0]).hypot(projected[1] - self.center[1]) + - self.radius) + .abs(); + let candidate = if self.contains_angle(angle) { + [ + self.center[0] + self.radius * angle.cos(), + self.center[1] + self.radius * angle.sin(), + ] + } else { + let start = [ + self.center[0] + self.radius * self.start_angle.cos(), + self.center[1] + self.radius * self.start_angle.sin(), + ]; + let end_angle = self.start_angle + self.sweep; + let end = [ + self.center[0] + self.radius * end_angle.cos(), + self.center[1] + self.radius * end_angle.sin(), + ]; + if squared_2d(start, projected) <= squared_2d(end, projected) { + start + } else { + end + } + }; + let planar = if self.contains_angle(angle) { + radial * radial + } else { + squared_2d(candidate, projected) + }; + let world = self.plane.point_at(projected); + let dx = world[0] - point[0]; + let dy = world[1] - point[1]; + let dz = world[2] - point[2]; + planar + dx * dx + dy * dy + dz * dz + } +} + +type DPoint = [f64; 3]; + +fn vector3(point: DPoint) -> Vector3 { + Vector3::new(point[0], point[1], point[2]) +} + +fn dpoint(point: Vector3) -> DPoint { + [point.x, point.y, point.z] +} + +fn squared_2d(first: [f64; 2], second: [f64; 2]) -> f64 { + let dx = first[0] - second[0]; + let dy = first[1] - second[1]; + dx * dx + dy * dy +} + +fn signed_progress(start: f64, angle: f64, sweep: f64) -> Option { + if !start.is_finite() || !angle.is_finite() || !sweep.is_finite() || sweep.abs() <= 1e-12 { + return None; + } + Some(if sweep > 0.0 { + (angle - start).rem_euclid(TAU) + } else { + (start - angle).rem_euclid(TAU) + }) +} + +fn radial_candidates(entity: &EntityType) -> Vec { + let Some(planar) = crate::entities::curve::entity_curve(entity) else { + return Vec::new(); + }; + match planar.curve { + KernelCurve::Circle(circle) => vec![RadialSourceGeometry { + plane: planar.plane, + center: circle.centre, + radius: circle.radius, + start_angle: 0.0, + sweep: TAU, + limited: false, + marker: 0, + }], + KernelCurve::Arc(arc) => vec![RadialSourceGeometry { + plane: planar.plane, + center: arc.centre, + radius: arc.radius, + start_angle: arc.start_angle, + sweep: arc.sweep(), + limited: true, + marker: 0, + }], + KernelCurve::Polyline(polyline) => (0..polyline.vertices.len()) + .filter_map(|index| { + let arc = polyline.segment_arc(index)?; + Some(RadialSourceGeometry { + plane: planar.plane, + center: arc.center, + radius: arc.radius, + start_angle: arc.start_angle, + sweep: arc.sweep, + limited: true, + marker: index as i32, + }) + }) + .collect(), + _ => Vec::new(), + } +} + +pub(crate) fn radial_source_at( + entity: &EntityType, + point: Vector3, +) -> Option { + radial_candidates(entity).into_iter().min_by(|first, second| { + first + .distance_squared_to(dpoint(point)) + .total_cmp(&second.distance_squared_to(dpoint(point))) + }) +} + +fn radial_source_for_marker(entity: &EntityType, marker: i32) -> Option { + radial_candidates(entity) + .into_iter() + .find(|candidate| candidate.marker == marker) +} + +fn radial_source_matching( + entity: &EntityType, + center: Vector3, + radius: f64, + chord: Vector3, +) -> Option { + radial_candidates(entity).into_iter().min_by(|first, second| { + let score = |candidate: &RadialSourceGeometry| { + point_distance_squared(candidate.center_world(), center) + + (candidate.radius - radius).powi(2) + + candidate.distance_squared_to(dpoint(chord)) * 1e-6 + }; + score(first).total_cmp(&score(second)) + }) +} + fn point_distance_squared(first: Vector3, second: Vector3) -> f64 { let dx = first.x - second.x; let dy = first.y - second.y; @@ -172,6 +360,71 @@ pub(crate) fn dimension_is_associative( }) } +pub(crate) fn radial_extension_points( + document: &acadrust::CadDocument, + dimension: Handle, + gap: f64, + extension: f64, +) -> Option> { + let EntityType::Dimension(Dimension::Diameter(diameter)) = document.get_entity(dimension)? + else { + return None; + }; + let association = document.objects.values().find_map(|object| { + let ObjectType::Associative(object) = object else { + return None; + }; + let AssociativeData::DimensionAssociation(association) = &object.data else { + return None; + }; + (association.dimension == dimension).then_some(association) + })?; + let reference = association.references[0].first()?; + let source = document.get_entity(*reference.xrefs.first()?)?; + let radial = radial_source_for_marker(source, reference.main_gs_marker)?; + if !radial.limited || radial.radius <= 1e-12 { + return None; + } + let target = radial.angle_at(dpoint(diameter.angle_vertex)); + if radial.contains_angle(target) { + return None; + } + + let end = radial.start_angle + radial.sweep; + let direction = radial.sweep.signum(); + let from_start = if direction > 0.0 { + (radial.start_angle - target).rem_euclid(TAU) + } else { + (target - radial.start_angle).rem_euclid(TAU) + }; + let from_end = if direction > 0.0 { + (target - end).rem_euclid(TAU) + } else { + (end - target).rem_euclid(TAU) + }; + let (origin, extension_direction, span) = if from_start <= from_end { + (radial.start_angle, -direction, from_start) + } else { + (end, direction, from_end) + }; + let gap_angle = gap.max(0.0) / radial.radius; + if span <= gap_angle + 1e-10 { + return None; + } + let extension_angle = extension.max(0.0) / radial.radius; + let visible_span = span - gap_angle + extension_angle; + let first_angle = origin + extension_direction * gap_angle; + let segments = (visible_span / (5.0_f64.to_radians())).ceil().max(1.0) as usize; + Some( + (0..=segments) + .map(|index| { + let fraction = index as f64 / segments as f64; + radial.point_at_angle(first_angle + extension_direction * visible_span * fraction) + }) + .collect(), + ) +} + impl Scene { pub(crate) fn attach_dimension_association( &mut self, @@ -182,6 +435,42 @@ impl Scene { else { return; }; + if let Dimension::Diameter(diameter) = entity { + let Some(source) = sources.into_iter().flatten().next() else { + return; + }; + let Some(source_entity) = self.document.get_entity(source) else { + return; + }; + let center = diameter.center(); + let radius = diameter.measurement() * 0.5; + let Some(radial) = radial_source_matching( + source_entity, + center, + radius, + diameter.angle_vertex, + ) else { + return; + }; + let angle = radial.angle_at(dpoint(diameter.angle_vertex)); + let reference = AssocDimensionReference { + class_name: "AcDbOsnapPointRef".to_string(), + osnap_type: 10, + xrefs: vec![source], + main_subent_type: 1, + main_gs_marker: radial.marker, + osnap_distance: angle, + osnap_point: diameter.angle_vertex, + ..AssocDimensionReference::default() + }; + self.store_dimension_association( + dimension, + [vec![reference], Vec::new(), Vec::new(), Vec::new()], + 1, + [Some(source), None], + ); + return; + } let Some(source_data) = dimension_points(entity) else { return; }; @@ -235,6 +524,16 @@ impl Scene { } } + self.store_dimension_association(dimension, references, associativity, sources); + } + + fn store_dimension_association( + &mut self, + dimension: Handle, + references: [Vec; 4], + associativity: i32, + sources: [Option; 2], + ) { let association_handle = self.document.allocate_handle(); let mut object = AssociativeObject::new("DIMASSOC", "AcDbDimAssoc"); object.handle = association_handle; @@ -270,6 +569,33 @@ impl Scene { else { return [None, None]; }; + if let Dimension::Diameter(diameter) = entity { + let measurement = diameter.measurement(); + let radius = measurement * 0.5; + let center = diameter.center(); + let tolerance = measurement.abs().max(1.0) * 1e-9; + let source = self + .document + .entities() + .filter(|candidate| candidate.common().handle != dimension) + .filter_map(|candidate| { + let radial = radial_source_matching( + candidate, + center, + radius, + diameter.angle_vertex, + )?; + let center_error = point_distance_squared(radial.center_world(), center); + let radius_error = (radial.radius - radius).powi(2); + (center_error + radius_error <= tolerance * tolerance).then_some(( + center_error + radius_error, + candidate.common().handle, + )) + }) + .min_by(|first, second| first.0.total_cmp(&second.0)) + .map(|(_, handle)| handle); + return [source, None]; + } let Some(points) = dimension_points(entity) else { return [None, None]; }; @@ -318,13 +644,19 @@ impl Scene { let mut refreshed = Vec::new(); for association in associations { + let radial_source = association.references[0].first().and_then(|reference| { + let source = *reference.xrefs.first()?; + let entity = self.document.get_entity(source)?; + let radial = radial_source_for_marker(entity, reference.main_gs_marker)?; + Some((radial, reference.osnap_distance)) + }); let first = association.references[0] .first() .and_then(|reference| resolve_reference(self, reference)); let second = association.references[1] .first() .and_then(|reference| resolve_reference(self, reference)); - if first.is_none() && second.is_none() { + if first.is_none() && second.is_none() && radial_source.is_none() { continue; } let Some(EntityType::Dimension(dimension)) = @@ -353,6 +685,25 @@ impl Scene { aligned.base.actual_measurement = aligned.measurement(); aligned.base.definition_point = aligned.definition_point; } + Dimension::Diameter(diameter) => { + let Some((radial, angle)) = radial_source else { + continue; + }; + let old_chord = diameter.angle_vertex; + let new_chord = radial.point_at_angle(angle); + let new_far_chord = radial.point_at_angle(angle + std::f64::consts::PI); + let delta = Vector3::new( + new_chord.x - old_chord.x, + new_chord.y - old_chord.y, + new_chord.z - old_chord.z, + ); + diameter.angle_vertex = new_chord; + diameter.definition_point = new_far_chord; + diameter.base.definition_point = new_far_chord; + diameter.base.text_middle_point = diameter.base.text_middle_point + delta; + diameter.base.insertion_point = diameter.base.insertion_point + delta; + diameter.base.actual_measurement = diameter.measurement(); + } _ => continue, } refreshed.push((association.dimension, ChangeKind::Modified)); From 75f042607ec027d60a5703b2a81fa04ca8b442fa Mon Sep 17 00:00:00 2001 From: ramox81 <184937705+ramox81@users.noreply.github.com> Date: Tue, 25 Aug 2026 15:39:48 +0300 Subject: [PATCH 3/3] Align diameter properties and graphics --- src/entities/dim_override.rs | 25 +++ src/entities/dimension.rs | 338 ++++++++++++++++++++++++++--- src/modules/draw/modify/explode.rs | 179 +++++++++++++-- 3 files changed, 492 insertions(+), 50 deletions(-) diff --git a/src/entities/dim_override.rs b/src/entities/dim_override.rs index 13ce51b8a..868bbab64 100644 --- a/src/entities/dim_override.rs +++ b/src/entities/dim_override.rs @@ -352,6 +352,7 @@ fn inherited_real(doc: &CadDocument, handle: Handle, code: i16) -> f64 { }; match code { DIMGAP => style.dimgap, + DIMCEN => style.dimcen, DIMTP => style.dimtp, DIMTM => style.dimtm, _ => 0.0, @@ -390,6 +391,30 @@ pub fn set_property( value: &str, ) -> bool { let trimmed = value.trim(); + if field == "dim_center_type" { + let current = inherited_real(doc, handle, DIMCEN); + let size = current.abs().max(0.01); + let value = match trimmed.to_ascii_lowercase().as_str() { + "none" => 0.0, + "mark" => size, + "lines" => -size, + _ => return false, + }; + set(doc, handle, DIMCEN, Some(XDataValue::Real(value))); + return true; + } + if field == "dim_center_size" { + let Ok(size) = trimmed.parse::() else { + return false; + }; + if !size.is_finite() || size < 0.0 { + return false; + } + let current = inherited_real(doc, handle, DIMCEN); + let value = if current < 0.0 { -size } else { size }; + set(doc, handle, DIMCEN, Some(XDataValue::Real(value))); + return true; + } let handle_field = match field { "dim_arrowhead_1" => Some(DIMBLK1), "dim_arrowhead_2" => Some(DIMBLK2), diff --git a/src/entities/dimension.rs b/src/entities/dimension.rs index ae3032661..c633e27b6 100644 --- a/src/entities/dimension.rs +++ b/src/entities/dimension.rs @@ -86,6 +86,23 @@ fn base_props(base: &DimensionBase) -> Vec Vec { + if let Dimension::Diameter(diameter) = dim { + return vec![PropSection { + title: t!("Misc").into_owned(), + props: vec![ + Property { + label: t!("Dimension style").into_owned(), + field: "style_name", + value: PropValue::PlainText(diameter.base.style_name.clone()), + }, + edit( + t!("Leader Length").as_ref(), + "leader_length", + diameter.leader_length, + ), + ], + }]; + } let compact_linear = match dim { Dimension::Linear(d) => Some(( &d.base, @@ -1268,11 +1285,29 @@ impl Grippable for Dimension { 1 => apply_to_v3(&mut d.definition_point, &apply), _ => {} }, - Dimension::Diameter(d) => match grip_id { - 0 => apply_to_v3(&mut d.angle_vertex, &apply), - 1 => apply_to_v3(&mut d.definition_point, &apply), - _ => {} - }, + Dimension::Diameter(d) => { + let center = d.center(); + let radius = d.measurement() * 0.5; + let mut target = match grip_id { + 0 => d.angle_vertex, + 1 => d.definition_point, + _ => center, + }; + if grip_id <= 1 { + apply_to_v3(&mut target, &apply); + let offset = target - center; + if offset.length_squared() > 1e-24 && radius > 1e-12 { + let radial = offset.normalize() * radius; + if grip_id == 0 { + d.angle_vertex = center + radial; + d.definition_point = center - radial; + } else { + d.definition_point = center + radial; + d.angle_vertex = center - radial; + } + } + } + } Dimension::Angular2Ln(d) => match grip_id { 0 => apply_to_v3(&mut d.angle_vertex, &apply), 1 => apply_to_v3(&mut d.first_point, &apply), @@ -1636,8 +1671,15 @@ pub fn style_sections( let linetype = |code, inherited| { linetype_name(document, ov::handle(data, code).unwrap_or(inherited)) }; - let arrow_1 = arrow_name(ov::DIMBLK1, s.dimblk1, &s.dimblk1_name); - let arrow_2 = arrow_name(ov::DIMBLK2, s.dimblk2, &s.dimblk2_name); + let diameter_arrow = |code: i16, handle: acadrust::types::Handle, name: &str| { + if matches!(dimension, Dimension::Diameter(_)) && !s.dimsah && handle.is_null() { + arrow_name(code, s.dimblk, &s.dimblk_name) + } else { + arrow_name(code, handle, name) + } + }; + let arrow_1 = diameter_arrow(ov::DIMBLK1, s.dimblk1, &s.dimblk1_name); + let arrow_2 = diameter_arrow(ov::DIMBLK2, s.dimblk2, &s.dimblk2_name); for current in [&arrow_1, &arrow_2] { if !arrow_options.contains(current) { arrow_options.push(current.clone()); @@ -1682,7 +1724,7 @@ pub fn style_sections( _ => choice_value("None", &["None", "Background", "Color"]), }; - vec![ + let mut sections = vec![ PropSection { title: t!("Lines & Arrows").into_owned(), props: vec![ @@ -2312,7 +2354,68 @@ pub fn style_sections( ), ], }, - ] + ]; + + if matches!(dimension, Dimension::Diameter(_)) { + let dimcen = real(ov::DIMCEN, s.dimcen); + let center_type = if dimcen > 1e-12 { + "Mark" + } else if dimcen < -1e-12 { + "Lines" + } else { + "None" + }; + if let Some(lines) = sections + .iter_mut() + .find(|section| section.title == t!("Lines & Arrows").as_ref()) + { + const DIAMETER_LINE_FIELDS: &[&str] = &[ + "dim_arrowhead_1", + "dim_arrowhead_2", + "dim_arrow_size", + "dim_line_lineweight", + "dim_ext_line_lineweight", + "dim_line_1", + "dim_line_2", + "dim_line_color", + "dim_linetype", + "dim_ext_linetype_1", + "dim_ext_line_1", + "dim_ext_line_color", + "dim_ext_line_ext", + "dim_ext_line_offset", + ]; + lines + .props + .retain(|property| DIAMETER_LINE_FIELDS.contains(&property.field)); + lines.props.insert(3, choice( + t!("Center mark").as_ref(), + "dim_center_type", + center_type, + &["None", "Mark", "Lines"], + true, + )); + lines.props.insert(4, number( + t!("Center size").as_ref(), + "dim_center_size", + dimcen.abs(), + center_type != "None", + )); + } + if let Some(primary_units) = sections + .iter_mut() + .find(|section| section.title == t!("Primary Units").as_ref()) + { + primary_units.props.retain(|property| { + !matches!( + property.field, + "dim_sub_units_suffix" | "dim_sub_units_scale" + ) + }); + } + } + + sections } fn property(label: &str, field: &'static str, value: PropValue) -> Property { @@ -2835,7 +2938,33 @@ fn tessellate_dimension_inner( }; (t.clone(), t) } else if let Some(s) = style { - if dimsah { + if matches!(dim, Dimension::Diameter(_)) { + let data = &dim.base().common.extended_data; + let first = crate::entities::dim_override::handle( + data, + crate::entities::dim_override::DIMBLK1, + ) + .unwrap_or(if dimsah { s.dimblk1 } else { s.dimblk }); + let second = crate::entities::dim_override::handle( + data, + crate::entities::dim_override::DIMBLK2, + ) + .unwrap_or(if dimsah { s.dimblk2 } else { s.dimblk }); + ( + arrow_from_block_with_deferred_hatch( + document, + first, + dimasz, + defer_arrow_hatches, + ), + arrow_from_block_with_deferred_hatch( + document, + second, + dimasz, + defer_arrow_hatches, + ), + ) + } else if dimsah { ( arrow_from_block_with_deferred_hatch( document, @@ -2894,6 +3023,7 @@ fn tessellate_dimension_inner( None } }; + let text_position = vec3_local(dimension_text_pos_f64(dim, style, dim_txt, dim_scale)); let mut geom = dimension_geometry( dim, @@ -2912,6 +3042,8 @@ fn tessellate_dimension_inner( text_width, dimatfit: style.map(|s| s.dimatfit).unwrap_or(3), dimtofl: style.map(|s| s.dimtofl).unwrap_or(false), + text_position, + text_movement: style.map(|s| s.dimtmove).unwrap_or(0), text_break, }, SuppressFlags { @@ -2922,6 +3054,18 @@ fn tessellate_dimension_inner( }, ); + if !dimse1 { + if let Some(points) = crate::scene::dimension_assoc::radial_extension_points( + document, + handle, + dimexo as f64, + dimexe as f64, + ) { + let points: Vec = points.into_iter().map(vec3_local).collect(); + add_polyline(&mut geom.ext_lines, &points); + } + } + // DIMTMOVE = 1: when the saved text_middle_point sits far from the // dim-line anchor, draw a short leader connecting them. (=0 anchors text // to the dim line — no leader; =2 frees text without a leader.) @@ -3409,7 +3553,15 @@ fn dimtmove_leader_endpoints(dim: &Dimension) -> Option<(Vec3, Vec3)> { (first + perp * off1 + second + perp * off2) * 0.5 } Dimension::Radius(d) => lv(d.definition_point), - Dimension::Diameter(d) => (lv(d.angle_vertex) + lv(d.definition_point)) * 0.5, + Dimension::Diameter(d) => { + let chord = lv(d.angle_vertex); + let far_chord = lv(d.definition_point); + if chord.distance_squared(lv(txt)) <= far_chord.distance_squared(lv(txt)) { + chord + } else { + far_chord + } + } _ => return None, }; Some((anchor, lv(txt))) @@ -3479,6 +3631,8 @@ struct DimLineParams { text_width: f32, dimatfit: i16, dimtofl: bool, + text_position: Vec3, + text_movement: i16, /// Text box (local centre, half-width, half-height) used to break the /// dimension line where the text sits on it, so a DIMTFILL background reads /// over the line. None when the text doesn't overlap the line. @@ -3576,26 +3730,21 @@ fn dimension_geometry( append_center_mark(&mut g, center, params.dimcen, radius); } Dimension::Diameter(d) => { - // angle_vertex is the circle centre and definition_point a point on - // the circle. The diameter line runs edge-to-edge THROUGH the centre - // (far edge → near edge), with arrows pointing inward at each edge. - let center = lv(d.angle_vertex); - let edge = lv(d.definition_point); - let far = center * 2.0 - edge; - add_segment(&mut g.dim_lines, far, edge); - append_arrow(&mut g, edge, normalized_or(far - edge, Vec3::X), arrow1); - append_arrow(&mut g, far, normalized_or(edge - far, Vec3::X), arrow2); - // Diameter leader: continue past the near edge toward the text. - if d.leader_length.abs() > 1e-9 { - let text = dimension_text_position(dim); - let leader_dir = normalized_or(text - edge, edge - far); - add_segment( - &mut g.dim_lines, - edge, - edge + leader_dir * (d.leader_length as f32), - ); - } - let radius = (edge - center).length(); + // Diametric data stores the two opposite chord points directly. + let chord = lv(d.angle_vertex); + let far_chord = lv(d.definition_point); + append_diameter_dimension( + &mut g, + chord, + far_chord, + arrow1, + arrow2, + d.leader_length as f32, + params, + suppress, + ); + let center = (chord + far_chord) * 0.5; + let radius = chord.distance(far_chord) * 0.5; append_center_mark(&mut g, center, params.dimcen, radius); } Dimension::Angular2Ln(d) => { @@ -3862,6 +4011,107 @@ fn append_linear_dimension( } } +fn append_diameter_dimension( + g: &mut DimGeom, + chord: Vec3, + far_chord: Vec3, + arrow1: &ArrowKind, + arrow2: &ArrowKind, + leader_length: f32, + params: DimLineParams, + suppress: SuppressFlags, +) { + let axis = normalized_or(far_chord - chord, Vec3::X); + let diameter = chord.distance(far_chord); + if diameter <= 1e-6 { + return; + } + + let arrows_outside = if params.ticks || params.arrow_len <= 1e-6 { + false + } else if diameter < 2.0 * params.arrow_len { + true + } else if diameter < params.text_width + 2.0 * params.arrow_len { + match params.dimatfit { + 0 | 1 => true, + 2 => false, + _ => params.text_width <= diameter, + } + } else { + false + }; + + let extension = if params.ticks { params.dimdle } else { 0.0 }; + let first = chord - axis * extension; + let second = far_chord + axis * extension; + let line_length = first.distance(second); + let mut left_end = line_length * 0.5; + let mut right_start = left_end; + if let Some((text_center, half_width, half_height)) = params.text_break { + let along = (text_center - first).dot(axis); + if along > 0.0 && along < line_length { + left_end = along; + right_start = along; + let perpendicular = (text_center - (first + axis * along)).length(); + if perpendicular < half_height + && along - half_width > 0.0 + && along + half_width < line_length + { + left_end = along - half_width; + right_start = along + half_width; + } + } + } + + let draw_inside_line = !arrows_outside || params.dimtofl; + if draw_inside_line && !suppress.dim1 && left_end > 1e-6 { + add_segment(&mut g.dim_lines, first, first + axis * left_end); + } + if draw_inside_line && !suppress.dim2 && line_length - right_start > 1e-6 { + add_segment( + &mut g.dim_lines, + first + axis * right_start, + second, + ); + } + if arrows_outside && !params.dimsoxd { + let stub = params.arrow_len * 2.0; + if !suppress.dim1 { + add_segment(&mut g.dim_lines, chord - axis * stub, chord); + } + if !suppress.dim2 { + add_segment(&mut g.dim_lines, far_chord, far_chord + axis * stub); + } + } + + if arrows_outside { + append_arrow(g, chord, -axis, arrow1); + append_arrow(g, far_chord, axis, arrow2); + } else { + append_arrow(g, chord, axis, arrow1); + append_arrow(g, far_chord, -axis, arrow2); + } + + let text_along = (params.text_position - chord).dot(axis); + if params.text_movement == 0 { + let (tip, suppressed) = if params.text_position.distance_squared(chord) + <= params.text_position.distance_squared(far_chord) + { + (chord, suppress.dim1) + } else { + (far_chord, suppress.dim2) + }; + if !suppressed && leader_length.abs() > 1e-6 { + let direction = normalized_or(params.text_position - tip, axis); + add_segment(&mut g.dim_lines, tip, tip + direction * leader_length.abs()); + } else if text_along < 0.0 && !suppress.dim1 { + add_segment(&mut g.dim_lines, chord, params.text_position); + } else if text_along > diameter && !suppress.dim2 { + add_segment(&mut g.dim_lines, far_chord, params.text_position); + } + } +} + /// Draw a center mark for radius/diameter dimensions. /// DIMCEN > 0 → small "+" of half-length |DIMCEN| at the centre. /// DIMCEN < 0 → small "+" *plus* four line segments extending from the @@ -4299,6 +4549,11 @@ fn dimension_text_is_outside(dim: &Dimension, style: Option<&DimStyle>) -> bool let length = (delta.x * delta.x + delta.y * delta.y).sqrt().max(1e-12); (d.first_point, d.second_point, delta / length) } + Dimension::Diameter(d) => { + let delta = d.definition_point - d.angle_vertex; + let length = (delta.x * delta.x + delta.y * delta.y).sqrt().max(1e-12); + (d.angle_vertex, d.definition_point, delta / length) + } _ => return false, }; let first_axis = first.x * axis.x + first.y * axis.y; @@ -4338,6 +4593,8 @@ fn dimension_text_natural_rotation(dim: &Dimension) -> f64 { let dy = d.second_point.y - d.first_point.y; dy.atan2(dx) } + Dimension::Diameter(d) => (d.definition_point.y - d.angle_vertex.y) + .atan2(d.definition_point.x - d.angle_vertex.x), _ => 0.0, }; // Clamp to (-π/2, π/2] so text never appears upside-down. @@ -5119,6 +5376,25 @@ fn dimension_text_pos_f64( dimtad, ) } + Dimension::Diameter(d) => { + let dx = d.definition_point.x - d.angle_vertex.x; + let dy = d.definition_point.y - d.angle_vertex.y; + let len = (dx * dx + dy * dy).sqrt().max(1e-12); + text_on_dim_line( + d.angle_vertex, + d.definition_point, + d.angle_vertex, + dx / len, + dy / len, + dimjust, + perp_off, + text_w, + arrow, + dimtix, + dimatfit, + dimtad, + ) + } _ => { // Non-linear (radius / diameter / angular / ordinate): lift the // natural mid point straight up by the style offset. A user-dragged diff --git a/src/modules/draw/modify/explode.rs b/src/modules/draw/modify/explode.rs index 302033f9e..f4a3e4831 100644 --- a/src/modules/draw/modify/explode.rs +++ b/src/modules/draw/modify/explode.rs @@ -612,10 +612,66 @@ struct DimMetrics { /// suppression), DIMCLRD/E/T (colours) and DIMLWD/E (lineweights). fn dim_metrics(dim: &Dimension, doc: &CadDocument) -> DimMetrics { let name = dim.base().style_name.as_str(); - let style = doc.dim_styles.iter().find(|s| { + let mut effective_style = doc.dim_styles.iter().find(|s| { s.name.eq_ignore_ascii_case(name) || (name.trim().is_empty() && s.name.eq_ignore_ascii_case("Standard")) - }); + }).cloned(); + if let Some(style) = &mut effective_style { + use crate::entities::dim_override as ov; + let data = &dim.base().common.extended_data; + macro_rules! real { + ($field:ident, $code:ident) => { + if let Some(value) = ov::real(data, ov::$code) { + style.$field = value; + } + }; + } + macro_rules! int { + ($field:ident, $code:ident) => { + if let Some(value) = ov::int(data, ov::$code) { + style.$field = value; + } + }; + } + macro_rules! flag { + ($field:ident, $code:ident) => { + if let Some(value) = ov::int(data, ov::$code) { + style.$field = value != 0; + } + }; + } + macro_rules! handle { + ($field:ident, $code:ident) => { + if let Some(value) = ov::handle(data, ov::$code) { + style.$field = value; + } + }; + } + real!(dimscale, DIMSCALE); + real!(dimasz, DIMASZ); + real!(dimcen, DIMCEN); + real!(dimexo, DIMEXO); + real!(dimexe, DIMEXE); + real!(dimtsz, DIMTSZ); + real!(dimdle, DIMDLE); + real!(dimfxl, DIMFXL); + flag!(dimfxlon, DIMFXLON); + flag!(dimse1, DIMSE1); + flag!(dimse2, DIMSE2); + flag!(dimsd1, DIMSD1); + flag!(dimsd2, DIMSD2); + flag!(dimsoxd, DIMSOXD); + flag!(dimsah, DIMSAH); + int!(dimclrd, DIMCLRD); + int!(dimclre, DIMCLRE); + int!(dimclrt, DIMCLRT); + int!(dimlwd, DIMLWD); + int!(dimlwe, DIMLWE); + handle!(dimblk, DIMBLK); + handle!(dimblk1, DIMBLK1); + handle!(dimblk2, DIMBLK2); + } + let style = effective_style.as_ref(); let scale = style .map(|s| if s.dimscale > 1e-6 { s.dimscale } else { 1.0 }) .unwrap_or(1.0); @@ -630,7 +686,15 @@ fn dim_metrics(dim: &Dimension, doc: &CadDocument) -> DimMetrics { let t = ArrowKind::Tick { size: dimtsz as f32 }; (t.clone(), t) } else if let Some(s) = style { - if s.dimsah { + if matches!(dim, Dimension::Diameter(_)) { + use crate::entities::dim_override as ov; + let data = &dim.base().common.extended_data; + let first = ov::handle(data, ov::DIMBLK1) + .unwrap_or(if s.dimsah { s.dimblk1 } else { s.dimblk }); + let second = ov::handle(data, ov::DIMBLK2) + .unwrap_or(if s.dimsah { s.dimblk2 } else { s.dimblk }); + (arrow_from_block(doc, first, asz), arrow_from_block(doc, second, asz)) + } else if s.dimsah { (arrow_from_block(doc, s.dimblk1, asz), arrow_from_block(doc, s.dimblk2, asz)) } else { let a = arrow_from_block(doc, s.dimblk, asz); @@ -993,27 +1057,103 @@ fn explode_dimension(dim: &Dimension, doc: &CadDocument) -> Vec { result.extend(dim_center_mark(center, met.dimcen, len, &dim_c)); } Dimension::Diameter(d) => { - // Full diameter through the centre (far edge -> near edge), inward - // terminators at both edges, plus the centre mark. - let (center, edge) = (d.angle_vertex, d.definition_point); - let far = v3(2.0 * center.x - edge.x, 2.0 * center.y - edge.y, edge.z); - result.push(make_seg(&far, &edge, &dim_c)); + // The stored points are the two opposite chord points. Their + // midpoint is the measured circle centre. + let (edge, far) = (d.angle_vertex, d.definition_point); + let center = v3( + (edge.x + far.x) * 0.5, + (edge.y + far.y) * 0.5, + (edge.z + far.z) * 0.5, + ); let len = ((edge.x - far.x).powi(2) + (edge.y - far.y).powi(2)) .sqrt() .max(1e-12); let (ux, uy) = ((edge.x - far.x) / len, (edge.y - far.y) / len); - result.extend(dim_terminator(edge, -ux, -uy, &met.arrow1, &dim_c)); - result.extend(dim_terminator(far, ux, uy, &met.arrow2, &dim_c)); - // Optional leader past the near edge toward the text. DIM-DIA-LEADER. - if d.leader_length.abs() > 1e-9 { - let anchor = dim_text_anchor(base, center, edge); - let ld = norm2(anchor.x - edge.x, anchor.y - edge.y, ux, uy); + let ticks = met.dimtsz > 1e-9; + let extension = if ticks { met.dimdle } else { 0.0 }; + let edge_outer = v3( + edge.x + ux * extension, + edge.y + uy * extension, + edge.z, + ); + let far_outer = v3( + far.x - ux * extension, + far.y - uy * extension, + far.z, + ); + if !met.dimsd1 { + result.push(make_seg(&edge_outer, ¢er, &dim_c)); + } + if !met.dimsd2 { + result.push(make_seg(¢er, &far_outer, &dim_c)); + } + let outside = !ticks && met.dimasz > 1e-6 && len < 2.0 * met.dimasz; + if outside { + result.extend(dim_terminator(edge, ux, uy, &met.arrow1, &dim_c)); + result.extend(dim_terminator(far, -ux, -uy, &met.arrow2, &dim_c)); + if !met.dimsoxd { + let stub = 2.0 * met.dimasz; + if !met.dimsd1 { + result.push(make_seg( + &edge, + &v3(edge.x + ux * stub, edge.y + uy * stub, edge.z), + &dim_c, + )); + } + if !met.dimsd2 { + result.push(make_seg( + &far, + &v3(far.x - ux * stub, far.y - uy * stub, far.z), + &dim_c, + )); + } + } + } else { + result.extend(dim_terminator(edge, -ux, -uy, &met.arrow1, &dim_c)); + result.extend(dim_terminator(far, ux, uy, &met.arrow2, &dim_c)); + } + let anchor = dim_text_anchor(base, center, edge); + let distance_squared = |first: Vector3, second: Vector3| { + (first.x - second.x).powi(2) + + (first.y - second.y).powi(2) + + (first.z - second.z).powi(2) + }; + let (leader_tip, suppressed, fallback) = if distance_squared(anchor, edge) + <= distance_squared(anchor, far) + { + (edge, met.dimsd1, (ux, uy)) + } else { + (far, met.dimsd2, (-ux, -uy)) + }; + if !suppressed && d.leader_length.abs() > 1e-9 { + let ld = norm2( + anchor.x - leader_tip.x, + anchor.y - leader_tip.y, + fallback.0, + fallback.1, + ); result.push(make_seg( - &edge, - &v3(edge.x + ld.0 * d.leader_length, edge.y + ld.1 * d.leader_length, edge.z), + &leader_tip, + &v3( + leader_tip.x + ld.0 * d.leader_length.abs(), + leader_tip.y + ld.1 * d.leader_length.abs(), + leader_tip.z, + ), &dim_c, )); } + if !met.dimse1 { + if let Some(points) = crate::scene::dimension_assoc::radial_extension_points( + doc, + base.common.handle, + met.dimexo, + met.dimexe, + ) { + for pair in points.windows(2) { + result.push(make_seg(&pair[0], &pair[1], &ext_c)); + } + } + } result.extend(dim_center_mark(center, met.dimcen, len * 0.5, &dim_c)); } Dimension::Angular2Ln(d) => { @@ -1567,15 +1707,16 @@ mod tests { } // A diameter dimension bakes a line edge-to-edge THROUGH the centre, not a - // radius-length line. The two extreme endpoints must be equidistant from the - // centre (angle_vertex) and the centre must lie between them. + // radius-length line. The two stored extreme endpoints must be equidistant + // from their midpoint and that midpoint must be the circle centre. #[test] fn diameter_dim_bakes_through_center() { use acadrust::entities::DimensionDiameter; let mut doc = CadDocument::new(); let center = Vector3::new(3.0, 4.0, 0.0); let edge = Vector3::new(8.0, 4.0, 0.0); // radius 5 along +x - let mut d = DimensionDiameter::new(center, edge); + let far = Vector3::new(-2.0, 4.0, 0.0); + let mut d = DimensionDiameter::new(edge, far); d.base.text_middle_point = Vector3::new(3.0, 9.0, 0.0); let handle = doc .add_entity(EntityType::Dimension(Dimension::Diameter(d)))