forked from 1b-agent/opencadstudio-upstream
DCDIAMETER/DCRADIUS: the reference flow with a real radial dimension
The commands ask for the circle or arc themselves, report the measured size, take a dimension line location and then the value or name and value, creating a radius or diameter dimension as the constraint's dynamic dimension with a radN/diaN parameter, refusing a second radial constraint on the same object and any non-positive value. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
1 parent
64c020293a
commit
19204d1b2c
8 files changed
+531
-24
No files matched your search
@@ -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,
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<String>,
|
||||
) -> Option<EntityType> {
|
||||
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) {
|
||||
}
|
||||
|
||||
@@ -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<String>,
|
||||
) -> Option<EntityType> {
|
||||
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<DVec3> {
|
||||
let delta = location - center;
|
||||
(delta.length() > 1.0e-9).then(|| delta.normalize())
|
||||
}
|
||||
|
||||
impl CadCommand for RadiusDimensionCommand {
|
||||
fn set_working_plane(&mut self, _plane: WorkingPlane) {
|
||||
}
|
||||
|
||||
@@ -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 <dia1=50>:`
|
||||
RadialValue {
|
||||
target: RadialTarget,
|
||||
location: DVec3,
|
||||
measured: f64,
|
||||
},
|
||||
/// `Enter value or name and value <d1=100>:`
|
||||
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<RadialTarget> {
|
||||
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<CmdResult> {
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Vec<Vector3>>,
|
||||
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(),
|
||||
|
||||
Reference in new issue
Block a user