forked from 1b-agent/opencadstudio-upstream
DCRADIUS: reference radial dimensional constraint with a dynamic radial dimension
Select arc or circle / Dimension text / dimension line location / value or name prompts with a radN parameter, invalid-selection, miss, duplicate and invalid-expression messages, negative values as magnitudes, a dynamic radial dimension on the constraints layer drawn as the reference does (mid-radius text on the center-to-rim line, no hook, no center mark, one text grip), the stored text position following the radius, center-anchored resizing, the Properties title and Constraint section, DIMCONSTRAINT's Radius default, the small-button Dimensional ribbon grid and tooltip descriptions. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
64c020293a
commit
aab42e2aa9
11 files changed
+516
-87
No files matched your search
@@ -62,32 +62,40 @@ impl RadiusDimensionCommand {
|
||||
source: crate::scene::dimension_assoc::RadialSourceGeometry,
|
||||
world: DVec3,
|
||||
) -> EntityType {
|
||||
let plane = WorkingPlane::new(
|
||||
DVec3::from_array(source.plane.origin),
|
||||
DVec3::from_array(source.plane.x_axis),
|
||||
DVec3::from_array(source.plane.y_axis),
|
||||
);
|
||||
let center = plane.to_local(dvec(source.center_world()));
|
||||
let chord = plane.to_local(dvec(source.chord_at(world.to_array())));
|
||||
let point = plane.to_local(world);
|
||||
let mut dim = DimensionRadius::new(v3(center), v3(chord));
|
||||
dim.base.definition_point = v3(chord);
|
||||
dim.base.text_middle_point = v3(point);
|
||||
dim.base.insertion_point = v3(point);
|
||||
dim.base.text_user_positioned = true;
|
||||
dim.leader_length = chord.distance(point);
|
||||
dim.base.actual_measurement = dim.measurement();
|
||||
crate::entities::dimension::set_dimension_text_override(
|
||||
&mut dim.base,
|
||||
self.text_override.clone(),
|
||||
);
|
||||
if let Some(angle) = self.text_angle {
|
||||
dim.base.text_rotation = angle;
|
||||
}
|
||||
plane.place_entity(EntityType::Dimension(Dimension::Radius(dim)))
|
||||
radial_dimension_entity(source, world, self.text_override.clone(), self.text_angle)
|
||||
}
|
||||
}
|
||||
|
||||
/// A radius dimension of `source` with its text at `world`: the dimension
|
||||
/// line runs from the center through the text (DIMUPT).
|
||||
pub(crate) fn radial_dimension_entity(
|
||||
source: crate::scene::dimension_assoc::RadialSourceGeometry,
|
||||
world: DVec3,
|
||||
text_override: Option<String>,
|
||||
text_angle: Option<f64>,
|
||||
) -> EntityType {
|
||||
let plane = WorkingPlane::new(
|
||||
DVec3::from_array(source.plane.origin),
|
||||
DVec3::from_array(source.plane.x_axis),
|
||||
DVec3::from_array(source.plane.y_axis),
|
||||
);
|
||||
let center = plane.to_local(dvec(source.center_world()));
|
||||
let chord = plane.to_local(dvec(source.chord_at(world.to_array())));
|
||||
let point = plane.to_local(world);
|
||||
let mut dim = DimensionRadius::new(v3(center), v3(chord));
|
||||
dim.base.definition_point = v3(chord);
|
||||
dim.base.text_middle_point = v3(point);
|
||||
dim.base.insertion_point = v3(point);
|
||||
dim.base.text_user_positioned = true;
|
||||
dim.leader_length = chord.distance(point);
|
||||
dim.base.actual_measurement = dim.measurement();
|
||||
crate::entities::dimension::set_dimension_text_override(&mut dim.base, text_override);
|
||||
if let Some(angle) = text_angle {
|
||||
dim.base.text_rotation = angle;
|
||||
}
|
||||
plane.place_entity(EntityType::Dimension(Dimension::Radius(dim)))
|
||||
}
|
||||
|
||||
impl CadCommand for RadiusDimensionCommand {
|
||||
fn set_working_plane(&mut self, _plane: WorkingPlane) {
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@ pub enum DimConstraintAxis {
|
||||
Aligned,
|
||||
/// The angle between two lines, or at a vertex between two points.
|
||||
Angular,
|
||||
/// The radius of a circle or arc.
|
||||
Radius,
|
||||
}
|
||||
|
||||
/// A picked line (a line entity or one polyline segment) an Aligned
|
||||
@@ -106,6 +108,16 @@ enum Step {
|
||||
sector: u8,
|
||||
measured: f64,
|
||||
},
|
||||
/// Radius: `Select arc or circle:`
|
||||
RadialSelect,
|
||||
/// `Specify dimension line location:` of a radius.
|
||||
RadialLocation { circle: ParametricRef, radius: f64 },
|
||||
/// `Enter value or name and value <rad1=30>:`
|
||||
RadialValue {
|
||||
circle: ParametricRef,
|
||||
radius: f64,
|
||||
location: DVec3,
|
||||
},
|
||||
/// `Enter value or name and value <d1=100>:`
|
||||
Value {
|
||||
first: ParametricRef,
|
||||
@@ -155,14 +167,16 @@ impl DimConstraintCommand {
|
||||
"The object or point is already selected. Select a different object or constraint point.";
|
||||
pub const INVALID_LINE: &'static str = "Invalid selection for Aligned. Select a line segment, polyline segment, text, MText, major or minor axis of ellipse or elliptical arc.";
|
||||
pub const PARALLEL_LINES: &'static str = "Lines are parallel.";
|
||||
pub const INVALID_RADIAL: &'static str =
|
||||
"Invalid selection for Radial. Select a circle or arc.";
|
||||
|
||||
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 => Step::RadialSelect,
|
||||
_ => Step::First,
|
||||
},
|
||||
picked_entity: None,
|
||||
default_name,
|
||||
@@ -304,6 +318,25 @@ impl DimConstraintCommand {
|
||||
})
|
||||
}
|
||||
|
||||
fn build_radial(&self, name: String, expression: String) -> Option<CmdResult> {
|
||||
let Step::RadialValue {
|
||||
circle, location, ..
|
||||
} = self.step
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
if expression.trim().is_empty() {
|
||||
return None;
|
||||
}
|
||||
Some(CmdResult::AddRadialConstraint {
|
||||
circle,
|
||||
location,
|
||||
renamed: name != self.default_name,
|
||||
name,
|
||||
expression,
|
||||
})
|
||||
}
|
||||
|
||||
fn command_name(&self) -> &'static str {
|
||||
match self.axis {
|
||||
DimConstraintAxis::Linear => "DCLINEAR",
|
||||
@@ -311,6 +344,7 @@ impl DimConstraintCommand {
|
||||
DimConstraintAxis::Vertical => "DCVERTICAL",
|
||||
DimConstraintAxis::Aligned => "DCALIGNED",
|
||||
DimConstraintAxis::Angular => "DCANGULAR",
|
||||
DimConstraintAxis::Radius => "DCRADIUS",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -321,6 +355,7 @@ impl DimConstraintCommand {
|
||||
DimConstraintAxis::Vertical => "Vertical",
|
||||
DimConstraintAxis::Aligned => "Aligned",
|
||||
DimConstraintAxis::Angular => "Angular",
|
||||
DimConstraintAxis::Radius => "Radial",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -474,8 +509,8 @@ 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 => (
|
||||
// Angular and Radius never measure here; their own steps decide.
|
||||
DimConstraintAxis::Aligned | DimConstraintAxis::Angular | DimConstraintAxis::Radius => (
|
||||
ConstraintKind::Distance,
|
||||
(second - first).normalize_or(DVec3::X),
|
||||
),
|
||||
@@ -525,7 +560,9 @@ 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 => {
|
||||
"Aligned constraint"
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -567,14 +604,17 @@ impl CadCommand for DimConstraintCommand {
|
||||
Step::AngularPoint2 { .. } => {
|
||||
format!("{name} Specify second angle constraint point:")
|
||||
}
|
||||
Step::AngularLocation { .. } | Step::Location { .. } => {
|
||||
Step::RadialSelect => format!("{name} Select arc or circle:"),
|
||||
Step::AngularLocation { .. } | Step::Location { .. } | Step::RadialLocation { .. } => {
|
||||
format!("{name} Specify dimension line location:")
|
||||
}
|
||||
Step::AngularValue { measured, .. } => format!(
|
||||
"{name} Enter value or name and value <{}={}>:",
|
||||
self.default_name,
|
||||
measured_expression(measured)
|
||||
),
|
||||
Step::AngularValue { measured, .. } | Step::RadialValue { radius: measured, .. } => {
|
||||
format!(
|
||||
"{name} Enter value or name and value <{}={}>:",
|
||||
self.default_name,
|
||||
measured_expression(measured)
|
||||
)
|
||||
}
|
||||
Step::Value { measured, .. } => format!(
|
||||
"{name} Enter value or name and value <{}={}>:",
|
||||
self.default_name,
|
||||
@@ -609,6 +649,7 @@ impl CadCommand for DimConstraintCommand {
|
||||
| Step::Value { .. }
|
||||
| Step::AngularFirst
|
||||
| Step::AngularValue { .. }
|
||||
| Step::RadialValue { .. }
|
||||
)
|
||||
}
|
||||
|
||||
@@ -643,16 +684,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 +717,7 @@ impl CadCommand for DimConstraintCommand {
|
||||
| Step::AngularVertex
|
||||
| Step::AngularPoint1 { .. }
|
||||
| Step::AngularPoint2 { .. }
|
||||
| Step::RadialSelect
|
||||
)
|
||||
}
|
||||
|
||||
@@ -867,10 +909,35 @@ impl CadCommand for DimConstraintCommand {
|
||||
whole_curve: false,
|
||||
})
|
||||
}
|
||||
Step::RadialSelect => {
|
||||
if handle.is_null() {
|
||||
return Self::report(Self::NO_OBJECT);
|
||||
}
|
||||
let Some(entity) = self.picked_entity.take() else {
|
||||
return CmdResult::NeedPoint;
|
||||
};
|
||||
let radius = match &entity {
|
||||
EntityType::Circle(circle) => circle.radius,
|
||||
EntityType::Arc(arc) => arc.radius,
|
||||
_ => return Self::report(Self::INVALID_RADIAL),
|
||||
};
|
||||
self.step = Step::RadialLocation {
|
||||
circle: ParametricRef::whole(handle),
|
||||
radius,
|
||||
};
|
||||
// The reference reports the radius before asking where the
|
||||
// dimension goes.
|
||||
CmdResult::ReportMeasurement(format!(
|
||||
"Dimension text = {}",
|
||||
measured_expression(radius)
|
||||
))
|
||||
}
|
||||
Step::Location { .. }
|
||||
| Step::Value { .. }
|
||||
| Step::AngularLocation { .. }
|
||||
| Step::AngularValue { .. } => self.on_point(point),
|
||||
| Step::AngularValue { .. }
|
||||
| Step::RadialLocation { .. }
|
||||
| Step::RadialValue { .. } => self.on_point(point),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -998,7 +1065,16 @@ impl CadCommand for DimConstraintCommand {
|
||||
| Step::TwoLinesFirst
|
||||
| Step::TwoLinesSecond { .. }
|
||||
| Step::AngularFirst
|
||||
| Step::AngularSecond { .. } => Self::report(Self::NO_OBJECT),
|
||||
| Step::AngularSecond { .. }
|
||||
| Step::RadialSelect => Self::report(Self::NO_OBJECT),
|
||||
Step::RadialLocation { circle, radius } => {
|
||||
self.step = Step::RadialValue {
|
||||
circle,
|
||||
radius,
|
||||
location: point,
|
||||
};
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
Step::AngularLocation { data } => {
|
||||
let frame = match data {
|
||||
AngularData::Lines { first, second } => Self::angle_frame(
|
||||
@@ -1050,9 +1126,10 @@ impl CadCommand for DimConstraintCommand {
|
||||
};
|
||||
CmdResult::ReportMeasurement(format!("Dimension text = {measured:.4}"))
|
||||
}
|
||||
Step::TwoLinesParallel { .. } | Step::Value { .. } | Step::AngularValue { .. } => {
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
Step::TwoLinesParallel { .. }
|
||||
| Step::Value { .. }
|
||||
| Step::AngularValue { .. }
|
||||
| Step::RadialValue { .. } => CmdResult::NeedPoint,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1070,6 +1147,9 @@ impl CadCommand for DimConstraintCommand {
|
||||
Step::AngularValue { measured, .. } => self
|
||||
.build_angular(self.default_name.clone(), measured_expression(measured))
|
||||
.unwrap_or(CmdResult::Cancel),
|
||||
Step::RadialValue { radius, .. } => self
|
||||
.build_radial(self.default_name.clone(), measured_expression(radius))
|
||||
.unwrap_or(CmdResult::Cancel),
|
||||
Step::PointLinePoint => {
|
||||
self.step = Step::LineFirst;
|
||||
CmdResult::NeedPoint
|
||||
|
||||
@@ -123,10 +123,14 @@ impl CadModule for ParametricModule {
|
||||
default: "DCLINEAR",
|
||||
},
|
||||
RibbonItem::LargeTool(dimensional_tools::aligned()),
|
||||
RibbonItem::LargeTool(dimensional_tools::radius()),
|
||||
RibbonItem::LargeTool(dimensional_tools::diameter()),
|
||||
RibbonItem::LargeTool(dimensional_tools::angular()),
|
||||
RibbonItem::LargeTool(dimensional_tools::convert()),
|
||||
// Two rows of small icon-only buttons, as in the
|
||||
// reference: Radius, Diameter / Angular, Convert.
|
||||
RibbonItem::ToolGrid {
|
||||
columns: vec![
|
||||
vec![dimensional_tools::radius(), dimensional_tools::angular()],
|
||||
vec![dimensional_tools::diameter(), dimensional_tools::convert()],
|
||||
],
|
||||
},
|
||||
RibbonItem::LabeledDropdown {
|
||||
id: "DCVISIBILITY", label: "Show/Hide",
|
||||
icon: IconKind::Svg(include_bytes!("../../../assets/icons/constrain/show.svg")),
|
||||
@@ -200,10 +204,17 @@ mod tests {
|
||||
);
|
||||
assert_eq!(
|
||||
groups[1].tools.iter().map(item_id).collect::<Vec<_>>(),
|
||||
[
|
||||
"DC_LINEAR_MENU", "DCALIGNED", "DCRADIUS", "DCDIAMETER",
|
||||
"DCANGULAR", "DCCONVERT", "DCVISIBILITY", "DCSHOWALL", "DCHIDEALL",
|
||||
]
|
||||
["DC_LINEAR_MENU", "DCALIGNED", "GRID", "DCVISIBILITY", "DCSHOWALL", "DCHIDEALL"]
|
||||
);
|
||||
let RibbonItem::ToolGrid { columns } = &groups[1].tools[2] else {
|
||||
panic!("radius, diameter, angular and convert must be a small-button grid");
|
||||
};
|
||||
assert_eq!(
|
||||
columns
|
||||
.iter()
|
||||
.map(|column| column.iter().map(|tool| tool.id).collect::<Vec<_>>())
|
||||
.collect::<Vec<_>>(),
|
||||
[["DCRADIUS", "DCANGULAR"], ["DCDIAMETER", "DCCONVERT"]]
|
||||
);
|
||||
assert_eq!(groups[2].tools.iter().map(item_id).collect::<Vec<_>>(), ["DELCONSTRAINT", "PARAMETERS"]);
|
||||
|
||||
@@ -218,8 +229,6 @@ mod tests {
|
||||
assert!(groups[0].tools[1..13]
|
||||
.iter()
|
||||
.all(|item| matches!(item, RibbonItem::LargeTool(_))));
|
||||
assert!(groups[1].tools[2..6]
|
||||
.iter()
|
||||
.all(|item| matches!(item, RibbonItem::LargeTool(_))));
|
||||
assert!(matches!(groups[1].tools[1], RibbonItem::LargeTool(_)));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user