diff --git a/src/app/command_driver.rs b/src/app/command_driver.rs index b5bd1a2d0..64e868352 100644 --- a/src/app/command_driver.rs +++ b/src/app/command_driver.rs @@ -4246,6 +4246,177 @@ impl OpenCADStudio { self.command_line.push_error(&message); return self.apply_cmd_result(CmdResult::Cancel); } + CmdResult::AddRadialConstraint { + circle, + center, + radius, + location, + diameter, + name, + expression, + renamed, + } => { + use crate::modules::annotate::{diameter_dim, radius_dim}; + use crate::scene::named_parameters::{is_valid_name, DrivingValue}; + use crate::scene::parametric_constraints::{ + dynamic_dimension_text, ConstraintKind, ParametricRef, + }; + + let scope = self.tabs[i].current_parametric_scope(); + let kind = if diameter { + ConstraintKind::Diameter + } else { + ConstraintKind::Radius + }; + let refs = vec![circle]; + // A circle already carrying a radius or diameter constraint + // is refused after the value, as the reference does. + let duplicate = self.tabs[i] + .scene + .parametric_constraint_set(scope) + .is_some_and(|set| { + set.constraints.iter().any(|c| { + c.enabled + && matches!( + c.kind, + ConstraintKind::Radius | ConstraintKind::Diameter + ) + && c.refs.iter().any(|r| r.entity == circle.entity) + }) + }); + if duplicate { + self.command_line + .push_output("The constraint already exists on the selected objects."); + self.tabs[i].active_cmd = None; + self.tabs[i].snap_result = None; + return Task::none(); + } + if let Err(message) = self.tabs[i].scene.validate_parametric_constraint( + kind, + &refs, + Some(&DrivingValue::Literal(1.0)), + ) { + self.reprompt_active_command(i, message); + return Task::none(); + } + let name = name.trim().to_string(); + if !is_valid_name(&name) { + self.command_line.push_error( + "Only alphanumeric names, starting with alpha characters, are allowed.", + ); + self.reprompt_active_command(i, &format!("Invalid parameter name: {name}.")); + return Task::none(); + } + if renamed && self.tabs[i].scene.named_parameters().contains(&name) { + self.reprompt_active_command(i, "Parameter with this name already exists."); + return Task::none(); + } + let mut table = self.tabs[i].scene.named_parameters().clone(); + if let Err(error) = table.set(&name, &expression) { + self.reprompt_active_command(i, &error.to_string()); + return Task::none(); + } + // A circle has no zero or negative size: the value is + // refused and the prompt comes back. + let value = match table.resolve(&name) { + Ok(value) if value.is_finite() && value > 0.0 => value, + _ => { + self.command_line.push_error("Invalid expression."); + self.reprompt_active_command( + i, + "The parameter is used in an expression which results in an invalid value for a dimensional constraint.", + ); + return Task::none(); + } + }; + let annotational = self.constraint_form_annotational; + let text = dynamic_dimension_text( + &name, + value, + self.tabs[i].scene.constraint_name_format, + false, + Some(&expression), + annotational, + None, + ); + let entity = if diameter { + diameter_dim::diameter_constraint_entity(center, radius, location, Some(text)) + } else { + radius_dim::radius_constraint_entity(center, radius, location, Some(text)) + }; + let Some(mut entity) = entity else { + self.reprompt_active_command(i, "No valid constraint point found."); + return Task::none(); + }; + crate::scene::creation_style::apply_current_creation_styles( + &self.tabs[i].scene.document, + &mut entity, + ); + if annotational { + entity + .as_entity_mut() + .set_layer(self.tabs[i].active_layer.clone()); + } else { + self.tabs[i].scene.ensure_dynamic_dimension_layer(); + entity.as_entity_mut().set_layer( + crate::scene::parametric_constraints::DYNAMIC_DIMENSION_LAYER.to_string(), + ); + entity.common_mut().color = acadrust::types::Color::Rgb { + r: 103, + g: 109, + b: 118, + }; + } + let constraints_before = self.tabs[i] + .scene + .parametric_constraint_set(scope) + .cloned() + .unwrap_or_else(|| { + crate::scene::parametric_constraints::ParametricConstraintSet::new(scope) + }); + // The centre stays where it is; the value moves the rim. + let anchors = vec![ParametricRef::center(circle.entity)]; + let pending = self.begin_undo( + i, + if diameter { + "Diameter constraint" + } else { + "Radius constraint" + }, + 2, + false, + ); + self.tabs[i] + .scene + .record_undo_parametric_constraints_before(scope, constraints_before); + self.tabs[i].scene.record_undo_named_parameters_before(); + self.tabs[i].scene.named_parameters = table; + let dimension = self.tabs[i].scene.add_entity(entity); + let set = self.tabs[i].scene.parametric_constraint_set_mut(scope); + let id = set.add(kind, refs, Some(DrivingValue::Named(name))); + set.dimensions.insert(id, dimension); + self.tabs[i].scene.note_parametric_constraint_applied( + scope, + id, + self.constraint_bar_display, + ); + self.tabs[i] + .scene + .attach_dimension_association(dimension, vec![Some(circle.entity)]); + let changes = vec![(circle.entity, crate::scene::ChangeKind::Modified)]; + self.tabs[i] + .scene + .bump_entities_with_parametric_policy(&changes, &anchors, false); + self.tabs[i].scene.refresh_hidden_dynamic_dimensions(); + self.tabs[i].scene.refresh_dynamic_dimension_scales(true); + self.tabs[i].dirty = true; + self.tabs[i].active_cmd = None; + self.tabs[i].snap_result = None; + self.refresh_properties(); + if let Some(pd) = pending { + self.commit_undo_delta(i, pd); + } + } CmdResult::AddAngularConstraint { refs, points, diff --git a/src/app/commands/draw.rs b/src/app/commands/draw.rs index 26344dbad..a51fdbdfd 100644 --- a/src/app/commands/draw.rs +++ b/src/app/commands/draw.rs @@ -1891,12 +1891,30 @@ impl OpenCADStudio { self.tabs[i].active_cmd = Some(Box::new(command)); } - "DCONSTRAINT" | "DCRADIUS" | "DCDIAMETER" => { - if cmd == "DCRADIUS" { - self.dim_constraint_last = "Radius"; - } else if cmd == "DCDIAMETER" { - self.dim_constraint_last = "Diameter"; - } + "DCRADIUS" | "DCDIAMETER" => { + use crate::modules::parametric::{DimConstraintAxis, DimConstraintCommand}; + use crate::scene::parametric_constraints::next_radial_parameter_name; + let diameter = cmd == "DCDIAMETER"; + self.dim_constraint_last = if diameter { "Diameter" } else { "Radius" }; + // The reference asks for the circle itself, whatever is + // selected, so a pick-first set is left alone. + let name = next_radial_parameter_name( + self.tabs[i].scene.named_parameters(), + diameter, + ); + let command = DimConstraintCommand::new( + if diameter { + DimConstraintAxis::Diameter + } else { + DimConstraintAxis::Radius + }, + name, + ); + self.command_line.push_info(&command.prompt()); + self.tabs[i].active_cmd = Some(Box::new(command)); + } + + "DCONSTRAINT" => { let handles = self.tabs[i].scene.selected_handles_in_order(); if handles.is_empty() { use crate::modules::draw::select::SelectObjectsCommand; @@ -1914,8 +1932,6 @@ impl OpenCADStudio { "DCHORIZONTAL" => ("DCHORIZONTAL", DistanceMode::X), "DCVERTICAL" => ("DCVERTICAL", DistanceMode::Y), "DCALIGNED" => ("DCALIGNED", DistanceMode::Aligned), - "DCRADIUS" => ("DCRADIUS", DistanceMode::Radius), - "DCDIAMETER" => ("DCDIAMETER", DistanceMode::Diameter), _ => ("DCONSTRAINT", DistanceMode::Auto), }; match DistanceConstraintCommand::with_mode( diff --git a/src/app/properties.rs b/src/app/properties.rs index fd29ab28b..6f2938ea7 100644 --- a/src/app/properties.rs +++ b/src/app/properties.rs @@ -2368,6 +2368,12 @@ impl OpenCADStudio { acadrust::EntityType::Dimension( Dimension::Angular2Ln(_) | Dimension::Angular3Pt(_), ) => t!("Angular Dimensional Constraint"), + acadrust::EntityType::Dimension(Dimension::Radius(_)) => { + t!("Radius Dimensional Constraint") + } + acadrust::EntityType::Dimension(Dimension::Diameter(_)) => { + t!("Diameter Dimensional Constraint") + } _ => t!("Linear Dimensional Constraint"), } .into_owned() diff --git a/src/command.rs b/src/command.rs index 199017a5b..bc2cc4d61 100644 --- a/src/command.rs +++ b/src/command.rs @@ -1635,6 +1635,22 @@ pub enum CmdResult { renamed: bool, label: &'static str, }, + /// Adds a radius or diameter constraint with its dynamic radial + /// dimension and the `radN`/`diaN` parameter that drives it. + AddRadialConstraint { + circle: crate::scene::parametric_constraints::ParametricRef, + center: DVec3, + radius: f64, + /// Where the dimension line was picked; its direction from the + /// centre places the dimension. + location: DVec3, + diameter: bool, + name: String, + expression: String, + /// The user named the parameter (`name=expression`); an existing + /// name is then refused. + renamed: bool, + }, /// Adds an angular constraint with its dynamic angular dimension and /// the `angN` parameter that drives it. Two lines: `refs` = /// `[first_line, second_line]`, `points` = both lines' ends; three diff --git a/src/modules/annotate/diameter_dim.rs b/src/modules/annotate/diameter_dim.rs index ef9255aae..6db2470fd 100644 --- a/src/modules/annotate/diameter_dim.rs +++ b/src/modules/annotate/diameter_dim.rs @@ -87,6 +87,25 @@ impl DiameterDimensionCommand { } } +/// A diameter dimension for a dimensional constraint: the dimension line +/// runs through the centre towards `location`, and the style places the +/// text. +pub(crate) fn diameter_constraint_entity( + center: DVec3, + radius: f64, + location: DVec3, + text: Option, +) -> Option { + let direction = super::radius_dim::radial_direction(center, location)?; + let chord = center + direction * radius; + let far_chord = center - direction * radius; + let mut dim = DimensionDiameter::new(v3(chord), v3(far_chord)); + dim.leader_length = chord.distance(location).max(radius * 0.5); + dim.base.actual_measurement = dim.measurement(); + crate::entities::dimension::set_dimension_text_override(&mut dim.base, text); + Some(EntityType::Dimension(Dimension::Diameter(dim))) +} + impl CadCommand for DiameterDimensionCommand { fn set_working_plane(&mut self, _plane: WorkingPlane) { } diff --git a/src/modules/annotate/radius_dim.rs b/src/modules/annotate/radius_dim.rs index e595aef72..8dfbd9fef 100644 --- a/src/modules/annotate/radius_dim.rs +++ b/src/modules/annotate/radius_dim.rs @@ -88,6 +88,31 @@ impl RadiusDimensionCommand { } } +/// A radius dimension for a dimensional constraint: the leader leaves the +/// centre towards `location`, and the style places the text. +pub(crate) fn radius_constraint_entity( + center: DVec3, + radius: f64, + location: DVec3, + text: Option, +) -> Option { + let direction = radial_direction(center, location)?; + let chord = center + direction * radius; + let mut dim = DimensionRadius::new(v3(center), v3(chord)); + dim.base.definition_point = v3(chord); + dim.leader_length = chord.distance(location).max(radius * 0.5); + dim.base.actual_measurement = dim.measurement(); + crate::entities::dimension::set_dimension_text_override(&mut dim.base, text); + Some(EntityType::Dimension(Dimension::Radius(dim))) +} + +/// The unit direction a radial constraint's dimension points in, from the +/// centre to where its dimension line was picked. +pub(crate) fn radial_direction(center: DVec3, location: DVec3) -> Option { + let delta = location - center; + (delta.length() > 1.0e-9).then(|| delta.normalize()) +} + impl CadCommand for RadiusDimensionCommand { fn set_working_plane(&mut self, _plane: WorkingPlane) { } diff --git a/src/modules/parametric/dim_constraint.rs b/src/modules/parametric/dim_constraint.rs index 63fc1df6b..548c4cc96 100644 --- a/src/modules/parametric/dim_constraint.rs +++ b/src/modules/parametric/dim_constraint.rs @@ -16,6 +16,10 @@ pub enum DimConstraintAxis { Aligned, /// The angle between two lines, or at a vertex between two points. Angular, + /// A circle's or arc's radius. + Radius, + /// A circle's or arc's diameter. + Diameter, } /// A picked line (a line entity or one polyline segment) an Aligned @@ -106,6 +110,16 @@ enum Step { sector: u8, measured: f64, }, + /// Radius/Diameter: `Select arc or circle:` + RadialObject, + /// `Specify dimension line location:` of a radius or diameter. + RadialLocation { target: RadialTarget }, + /// `Enter value or name and value :` + RadialValue { + target: RadialTarget, + location: DVec3, + measured: f64, + }, /// `Enter value or name and value :` Value { first: ParametricRef, @@ -135,6 +149,14 @@ enum AngularData { }, } +/// The circle or arc a Radius/Diameter constraint measures. +#[derive(Clone, Copy)] +struct RadialTarget { + circle: ParametricRef, + center: DVec3, + radius: f64, +} + /// The reference's Linear/Horizontal/Vertical/Aligned dimensional constraint: /// two constraint points (or one object's ends), a dimension line location, /// then the parameter name and expression the dynamic dimension carries. @@ -159,10 +181,10 @@ impl DimConstraintCommand { pub fn new(axis: DimConstraintAxis, default_name: String) -> Self { Self { axis, - step: if axis == DimConstraintAxis::Angular { - Step::AngularFirst - } else { - Step::First + step: match axis { + DimConstraintAxis::Angular => Step::AngularFirst, + DimConstraintAxis::Radius | DimConstraintAxis::Diameter => Step::RadialObject, + _ => Step::First, }, picked_entity: None, default_name, @@ -311,6 +333,8 @@ impl DimConstraintCommand { DimConstraintAxis::Vertical => "DCVERTICAL", DimConstraintAxis::Aligned => "DCALIGNED", DimConstraintAxis::Angular => "DCANGULAR", + DimConstraintAxis::Radius => "DCRADIUS", + DimConstraintAxis::Diameter => "DCDIAMETER", } } @@ -321,9 +345,47 @@ impl DimConstraintCommand { DimConstraintAxis::Vertical => "Vertical", DimConstraintAxis::Aligned => "Aligned", DimConstraintAxis::Angular => "Angular", + DimConstraintAxis::Radius => "Radius", + DimConstraintAxis::Diameter => "Diameter", } } + /// The circle or arc a radial pick landed on, with its centre and radius. + fn radial_target(entity: &EntityType, handle: Handle) -> Option { + let (center, radius) = match entity { + EntityType::Circle(circle) => (circle.center, circle.radius), + EntityType::Arc(arc) => (arc.center, arc.radius), + _ => return None, + }; + (radius > 1.0e-12).then_some(RadialTarget { + circle: ParametricRef::whole(handle), + center: DVec3::new(center.x, center.y, center.z), + radius, + }) + } + + fn build_radial(&self, name: String, expression: String) -> Option { + let Step::RadialValue { + target, location, .. + } = self.step + else { + return None; + }; + if expression.trim().is_empty() { + return None; + } + Some(CmdResult::AddRadialConstraint { + circle: target.circle, + center: target.center, + radius: target.radius, + location, + diameter: self.axis == DimConstraintAxis::Diameter, + renamed: name != self.default_name, + name, + expression, + }) + } + fn report(message: &str) -> CmdResult { CmdResult::ReportError(message.to_string()) } @@ -475,7 +537,10 @@ impl DimConstraintCommand { DimConstraintAxis::Horizontal => (ConstraintKind::DistanceX, DVec3::X), DimConstraintAxis::Vertical => (ConstraintKind::DistanceY, DVec3::Y), // Angular never measures here; its own steps decide the sector. - DimConstraintAxis::Aligned | DimConstraintAxis::Angular => ( + DimConstraintAxis::Aligned + | DimConstraintAxis::Angular + | DimConstraintAxis::Radius + | DimConstraintAxis::Diameter => ( ConstraintKind::Distance, (second - first).normalize_or(DVec3::X), ), @@ -525,7 +590,10 @@ impl DimConstraintCommand { DimConstraintAxis::Linear => "Linear constraint", DimConstraintAxis::Horizontal => "Horizontal distance constraint", DimConstraintAxis::Vertical => "Vertical distance constraint", - DimConstraintAxis::Aligned | DimConstraintAxis::Angular => "Aligned constraint", + DimConstraintAxis::Aligned + | DimConstraintAxis::Angular + | DimConstraintAxis::Radius + | DimConstraintAxis::Diameter => "Aligned constraint", }, }) } @@ -567,9 +635,17 @@ impl CadCommand for DimConstraintCommand { Step::AngularPoint2 { .. } => { format!("{name} Specify second angle constraint point:") } - Step::AngularLocation { .. } | Step::Location { .. } => { + Step::RadialObject => format!("{name} Select arc or circle:"), + Step::AngularLocation { .. } + | Step::Location { .. } + | Step::RadialLocation { .. } => { format!("{name} Specify dimension line location:") } + Step::RadialValue { measured, .. } => format!( + "{name} Enter value or name and value <{}={}>:", + self.default_name, + measured_expression(measured) + ), Step::AngularValue { measured, .. } => format!( "{name} Enter value or name and value <{}={}>:", self.default_name, @@ -609,6 +685,7 @@ impl CadCommand for DimConstraintCommand { | Step::Value { .. } | Step::AngularFirst | Step::AngularValue { .. } + | Step::RadialValue { .. } ) } @@ -643,16 +720,16 @@ impl CadCommand for DimConstraintCommand { } None } - Step::Value { .. } | Step::AngularValue { .. } => { + Step::Value { .. } | Step::AngularValue { .. } | Step::RadialValue { .. } => { let text = text.trim(); let (name, expression) = match text.split_once('=') { Some((name, expression)) => (name.trim().to_string(), expression.trim().to_string()), None => (self.default_name.clone(), text.to_string()), }; - if matches!(self.step, Step::AngularValue { .. }) { - self.build_angular(name, expression) - } else { - self.build(name, expression) + match self.step { + Step::AngularValue { .. } => self.build_angular(name, expression), + Step::RadialValue { .. } => self.build_radial(name, expression), + _ => self.build(name, expression), } } _ => None, @@ -676,6 +753,7 @@ impl CadCommand for DimConstraintCommand { | Step::AngularVertex | Step::AngularPoint1 { .. } | Step::AngularPoint2 { .. } + | Step::RadialObject ) } @@ -867,10 +945,38 @@ impl CadCommand for DimConstraintCommand { whole_curve: false, }) } + Step::RadialObject => { + if handle.is_null() { + return Self::report(Self::NO_OBJECT); + } + let Some(entity) = self.picked_entity.take() else { + return CmdResult::NeedPoint; + }; + let Some(target) = Self::radial_target(&entity, handle) else { + return Self::report(&format!( + "Invalid selection for {}. Select a circle or arc.", + self.noun() + )); + }; + let measured = if self.axis == DimConstraintAxis::Diameter { + target.radius * 2.0 + } else { + target.radius + }; + self.step = Step::RadialLocation { target }; + // The measured size is reported before the location, as the + // reference prints it, with trailing zeros suppressed. + CmdResult::ReportMeasurement(format!( + "Dimension text = {}", + measured_expression(measured) + )) + } Step::Location { .. } | Step::Value { .. } | Step::AngularLocation { .. } - | Step::AngularValue { .. } => self.on_point(point), + | Step::AngularValue { .. } + | Step::RadialLocation { .. } + | Step::RadialValue { .. } => self.on_point(point), } } @@ -1050,9 +1156,24 @@ impl CadCommand for DimConstraintCommand { }; CmdResult::ReportMeasurement(format!("Dimension text = {measured:.4}")) } - Step::TwoLinesParallel { .. } | Step::Value { .. } | Step::AngularValue { .. } => { + Step::RadialObject => Self::report(Self::NO_OBJECT), + Step::RadialLocation { target } => { + let measured = if self.axis == DimConstraintAxis::Diameter { + target.radius * 2.0 + } else { + target.radius + }; + self.step = Step::RadialValue { + target, + location: point, + measured, + }; CmdResult::NeedPoint } + Step::TwoLinesParallel { .. } + | Step::Value { .. } + | Step::AngularValue { .. } + | Step::RadialValue { .. } => CmdResult::NeedPoint, } } @@ -1077,6 +1198,9 @@ impl CadCommand for DimConstraintCommand { Step::Value { measured, .. } => self .build(self.default_name.clone(), measured_expression(measured)) .unwrap_or(CmdResult::Cancel), + Step::RadialValue { measured, .. } => self + .build_radial(self.default_name.clone(), measured_expression(measured)) + .unwrap_or(CmdResult::Cancel), _ => CmdResult::Cancel, } } diff --git a/src/scene/parametric_constraints.rs b/src/scene/parametric_constraints.rs index 0b8cbe752..21069764b 100644 --- a/src/scene/parametric_constraints.rs +++ b/src/scene/parametric_constraints.rs @@ -363,6 +363,19 @@ pub(crate) fn next_dimensional_parameter_name( .unwrap_or_else(|| "d1".to_string()) } +/// The first free `rad1`/`dia1`, `rad2`/`dia2`, … name a new radius or +/// diameter constraint takes. +pub(crate) fn next_radial_parameter_name( + table: &super::named_parameters::ParameterTable, + diameter: bool, +) -> String { + let prefix = if diameter { "dia" } else { "rad" }; + (1..) + .map(|n| format!("{prefix}{n}")) + .find(|name| !table.contains(name)) + .unwrap_or_else(|| format!("{prefix}1")) +} + /// The first free `ang1`, `ang2`, … name a new angular constraint takes. pub(crate) fn next_angular_parameter_name( table: &super::named_parameters::ParameterTable, @@ -601,6 +614,99 @@ fn angular_follow_points( } } +/// A radial constraint's circle or arc: its centre and radius now. +fn radial_geometry( + document: &acadrust::CadDocument, + constraint: &ParametricConstraint, +) -> Option<(glam::DVec3, f64)> { + let reference = constraint.refs.first()?; + let (center, radius) = match document.get_entity(reference.entity)? { + acadrust::EntityType::Circle(circle) => (circle.center, circle.radius), + acadrust::EntityType::Arc(arc) => (arc.center, arc.radius), + _ => return None, + }; + Some((glam::DVec3::new(center.x, center.y, center.z), radius)) +} + +/// Rebuilds a dynamic radial dimension on its circle's current centre and +/// radius, keeping the direction its dimension line was placed in; true +/// when anything moved. +fn dynamic_dimension_follow_radial( + dimension: &mut acadrust::entities::Dimension, + center: glam::DVec3, + radius: f64, +) -> bool { + use acadrust::entities::Dimension; + let to_dvec = |p: Vector3| glam::DVec3::new(p.x, p.y, p.z); + let same = |a: Vector3, b: Vector3| (a - b).length_squared() < 1.0e-16; + let text = dimension.base().user_text.clone(); + let (rebuilt, leader) = match &*dimension { + Dimension::Radius(d) => { + let direction = to_dvec(d.definition_point) - to_dvec(d.angle_vertex); + let direction = direction + .try_normalize() + .unwrap_or(glam::DVec3::X); + ( + crate::modules::annotate::radius_dim::radius_constraint_entity( + center, + radius, + center + direction * radius.max(1.0e-9), + text, + ), + d.leader_length, + ) + } + Dimension::Diameter(d) => { + let direction = to_dvec(d.angle_vertex) - to_dvec(d.definition_point); + let direction = direction + .try_normalize() + .unwrap_or(glam::DVec3::X); + ( + crate::modules::annotate::diameter_dim::diameter_constraint_entity( + center, + radius, + center + direction * radius.max(1.0e-9), + text, + ), + d.leader_length, + ) + } + _ => return false, + }; + let Some(acadrust::EntityType::Dimension(mut rebuilt)) = rebuilt else { + return false; + }; + let unchanged = match (&*dimension, &rebuilt) { + (Dimension::Radius(old), Dimension::Radius(new)) => { + same(old.angle_vertex, new.angle_vertex) + && same(old.definition_point, new.definition_point) + } + (Dimension::Diameter(old), Dimension::Diameter(new)) => { + same(old.angle_vertex, new.angle_vertex) + && same(old.definition_point, new.definition_point) + } + _ => false, + }; + if unchanged { + return false; + } + match &mut rebuilt { + Dimension::Radius(new) => new.leader_length = leader, + Dimension::Diameter(new) => new.leader_length = leader, + _ => {} + } + let base = dimension.base(); + let target = rebuilt.base_mut(); + target.common = base.common.clone(); + target.style_name = base.style_name.clone(); + target.text_rotation = base.text_rotation; + target.attachment_point = base.attachment_point; + target.line_spacing_style = base.line_spacing_style; + target.line_spacing_factor = base.line_spacing_factor; + *dimension = rebuilt; + true +} + /// Rebuilds a dynamic angular dimension on its sides' current positions, /// keeping its arc point; true when anything moved. fn dynamic_dimension_follow_angle( @@ -1983,6 +2089,7 @@ impl super::Scene { String, Option<(Vector3, Vector3)>, Option>, + Option<(glam::DVec3, f64)>, )> = Vec::new(); // A reference (driven) constraint's parameter follows the geometry. let mut followed: Vec<(String, f64)> = Vec::new(); @@ -2010,6 +2117,20 @@ impl super::Scene { constraint.kind, ConstraintKind::Angle | ConstraintKind::Angle3Point ); + let radial = matches!( + constraint.kind, + ConstraintKind::Radius | ConstraintKind::Diameter + ); + let radial_now = radial + .then(|| radial_geometry(&self.document, constraint)) + .flatten(); + let radial_measured = radial_now.map(|(_, radius)| { + if constraint.kind == ConstraintKind::Diameter { + radius * 2.0 + } else { + radius + } + }); let measured = points.filter(|_| !angular).map(|(first, second)| { let delta = second - first; let axis = match self.document.get_entity(*dimension) { @@ -2027,6 +2148,7 @@ impl super::Scene { _ => delta.length(), } }); + let measured = radial_measured.or(measured); let (name, value, source) = match &constraint.driving_param { Some(DrivingValue::Named(name)) => { let value = match (reference, measured) { @@ -2068,8 +2190,11 @@ impl super::Scene { annotational, decimals, ), - points, + // A radial dimension follows its own circle, not a pair + // of constraint points. + points.filter(|_| !radial), angle_points, + radial_now, )); } } @@ -2079,7 +2204,7 @@ impl super::Scene { let _ = self.named_parameters.set(&name, &source); } } - for (handle, text, points, angle_points) in updates { + for (handle, text, points, angle_points, radial) in updates { let Some(acadrust::EntityType::Dimension(mut dimension)) = self.document.get_entity(handle).cloned() else { @@ -2096,6 +2221,11 @@ impl super::Scene { changed = true; } } + if let Some((center, radius)) = radial { + if dynamic_dimension_follow_radial(&mut dimension, center, radius) { + changed = true; + } + } if dimension.base().user_text.as_deref() != Some(text.as_str()) { crate::entities::dimension::set_dimension_text_override( dimension.base_mut(),