forked from 1b-agent/opencadstudio-upstream
Angular dimensional constraint: reference flow, dynamic angular dimension, angN parameters
DCANGULAR picks two lines, an arc or three points, takes the dimension line location to choose the sector and the value or name, and creates the dynamic two-line or three-point angular dimension driven by an angN parameter. Reference messages for invalid, repeated and parallel sides, existing names and invalid names; angle values display within one turn at the angular precision; the first side (or vertex and first point) stays when the value changes; the ribbon Dimensional panel follows the reference order. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
960d51d955
commit
4ad4041858
8 files changed
+928
-162
No files matched your search
@@ -14,6 +14,8 @@ pub enum DimConstraintAxis {
|
||||
Horizontal,
|
||||
Vertical,
|
||||
Aligned,
|
||||
/// The angle between two lines, or at a vertex between two points.
|
||||
Angular,
|
||||
}
|
||||
|
||||
/// A picked line (a line entity or one polyline segment) an Aligned
|
||||
@@ -82,6 +84,28 @@ enum Step {
|
||||
/// The line the distance is measured perpendicular to.
|
||||
direction: Option<LineTarget>,
|
||||
},
|
||||
/// Angular: `Select first line or arc or [3Point] <3Point>:`
|
||||
AngularFirst,
|
||||
/// `Select second line:`
|
||||
AngularSecond { first: LineTarget },
|
||||
/// 3Point: `Specify angle vertex:`
|
||||
AngularVertex,
|
||||
/// `Specify first angle constraint point:`
|
||||
AngularPoint1 { vertex: (ParametricRef, DVec3) },
|
||||
/// `Specify second angle constraint point:`
|
||||
AngularPoint2 {
|
||||
vertex: (ParametricRef, DVec3),
|
||||
first: (ParametricRef, DVec3),
|
||||
},
|
||||
/// `Specify dimension line location:` of an angle.
|
||||
AngularLocation { data: AngularData },
|
||||
/// `Enter value or name and value <ang1=27>:`
|
||||
AngularValue {
|
||||
data: AngularData,
|
||||
location: DVec3,
|
||||
sector: u8,
|
||||
measured: f64,
|
||||
},
|
||||
/// `Enter value or name and value <d1=100>:`
|
||||
Value {
|
||||
first: ParametricRef,
|
||||
@@ -96,6 +120,21 @@ enum Step {
|
||||
},
|
||||
}
|
||||
|
||||
/// Angular's picks: two lines, or a vertex and two points (an arc gives
|
||||
/// its center and its ends).
|
||||
#[derive(Clone, Copy)]
|
||||
enum AngularData {
|
||||
Lines {
|
||||
first: LineTarget,
|
||||
second: LineTarget,
|
||||
},
|
||||
Points {
|
||||
vertex: (ParametricRef, DVec3),
|
||||
first: (ParametricRef, DVec3),
|
||||
second: (ParametricRef, DVec3),
|
||||
},
|
||||
}
|
||||
|
||||
/// 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.
|
||||
@@ -105,6 +144,8 @@ pub struct DimConstraintCommand {
|
||||
picked_entity: Option<EntityType>,
|
||||
/// The next free `dN` the host reserved for this constraint.
|
||||
default_name: String,
|
||||
/// Angular precision (DIMADEC) for `Dimension text = 27`.
|
||||
angle_decimals: usize,
|
||||
}
|
||||
|
||||
impl DimConstraintCommand {
|
||||
@@ -113,22 +154,163 @@ impl DimConstraintCommand {
|
||||
pub const SAME_POINT: &'static str =
|
||||
"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 fn new(axis: DimConstraintAxis, default_name: String) -> Self {
|
||||
Self {
|
||||
axis,
|
||||
step: Step::First,
|
||||
step: if axis == DimConstraintAxis::Angular {
|
||||
Step::AngularFirst
|
||||
} else {
|
||||
Step::First
|
||||
},
|
||||
picked_entity: None,
|
||||
default_name,
|
||||
angle_decimals: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// The angular precision the measured angle is reported with.
|
||||
pub fn with_angle_decimals(mut self, decimals: usize) -> Self {
|
||||
self.angle_decimals = decimals;
|
||||
self
|
||||
}
|
||||
|
||||
fn angle_display(&self, degrees: f64) -> String {
|
||||
let decimals = self.angle_decimals;
|
||||
format!(
|
||||
"{:.decimals$}",
|
||||
crate::scene::parametric_constraints::normalize_angle_display(degrees)
|
||||
)
|
||||
}
|
||||
|
||||
/// An Angular pick: a line or a polyline segment (text baselines and
|
||||
/// ellipse axes are not angle sides).
|
||||
fn angular_line(entity: &EntityType, handle: Handle, point: DVec3) -> Option<LineTarget> {
|
||||
matches!(
|
||||
entity,
|
||||
EntityType::Line(_) | EntityType::LwPolyline(_) | EntityType::Polyline2D(_)
|
||||
)
|
||||
.then(|| Self::line_target(entity, handle, point))
|
||||
.flatten()
|
||||
}
|
||||
|
||||
/// The angle (degrees) the dimension line location picks between the
|
||||
/// directed sides `p1a→p1b` and `p2a→p2b`, and which of the four
|
||||
/// sectors it is in the solver's terms (which signed sides bound it).
|
||||
fn angle_frame(
|
||||
p1a: DVec3,
|
||||
p1b: DVec3,
|
||||
p2a: DVec3,
|
||||
p2b: DVec3,
|
||||
location: DVec3,
|
||||
) -> Option<(f64, u8)> {
|
||||
use crate::scene::parametric_constraints::angle_sector;
|
||||
let tau = std::f64::consts::TAU;
|
||||
let (_, start, end) =
|
||||
crate::modules::annotate::angular_dim::two_line_frame(p1a, p1b, p2a, p2b, location)?;
|
||||
let sweep = (end - start).rem_euclid(tau);
|
||||
let same = |a: f64, b: f64| {
|
||||
let d = (a - b).rem_euclid(tau);
|
||||
d < 1.0e-6 || d > tau - 1.0e-6
|
||||
};
|
||||
let d1 = p1b - p1a;
|
||||
let d2 = p2b - p2a;
|
||||
let a1 = d1.y.atan2(d1.x);
|
||||
let a2 = d2.y.atan2(d2.x);
|
||||
let pi = std::f64::consts::PI;
|
||||
let sector = if same(start, a1) || same(start, a1 + pi) {
|
||||
// Swept from a side of the first line to a side of the second.
|
||||
if same(start, a1) == same(end, a2) {
|
||||
angle_sector::PARALLEL_COUNTERCLOCKWISE
|
||||
} else {
|
||||
angle_sector::ANTIPARALLEL_COUNTERCLOCKWISE
|
||||
}
|
||||
} else if same(start, a2) == same(end, a1) {
|
||||
angle_sector::PARALLEL_CLOCKWISE
|
||||
} else {
|
||||
angle_sector::ANTIPARALLEL_CLOCKWISE
|
||||
};
|
||||
Some((sweep.to_degrees(), sector))
|
||||
}
|
||||
|
||||
/// The angle (degrees) the dimension line location picks at `vertex`
|
||||
/// between the rays to `first` and `second`: the counterclockwise sweep
|
||||
/// from the first ray when the location lies in it, else the other way
|
||||
/// round (a semicircle measures 180, a reflex angle is allowed).
|
||||
fn ray_frame(vertex: DVec3, first: DVec3, second: DVec3, location: DVec3) -> Option<(f64, u8)> {
|
||||
use crate::scene::parametric_constraints::angle_sector;
|
||||
let tau = std::f64::consts::TAU;
|
||||
let r1 = first - vertex;
|
||||
let r2 = second - vertex;
|
||||
let at = location - vertex;
|
||||
if r1.length_squared() < 1.0e-18 || r2.length_squared() < 1.0e-18 || at.length_squared() < 1.0e-18 {
|
||||
return None;
|
||||
}
|
||||
let a1 = r1.y.atan2(r1.x);
|
||||
let a2 = r2.y.atan2(r2.x);
|
||||
let t = at.y.atan2(at.x);
|
||||
let sweep = (a2 - a1).rem_euclid(tau);
|
||||
if sweep < 1.0e-9 {
|
||||
return None;
|
||||
}
|
||||
if (t - a1).rem_euclid(tau) <= sweep + 1.0e-9 {
|
||||
Some((sweep.to_degrees(), angle_sector::PARALLEL_COUNTERCLOCKWISE))
|
||||
} else {
|
||||
Some(((tau - sweep).to_degrees(), angle_sector::PARALLEL_CLOCKWISE))
|
||||
}
|
||||
}
|
||||
|
||||
fn build_angular(&self, name: String, expression: String) -> Option<CmdResult> {
|
||||
let Step::AngularValue {
|
||||
data,
|
||||
location,
|
||||
sector,
|
||||
..
|
||||
} = self.step
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
if expression.trim().is_empty() {
|
||||
return None;
|
||||
}
|
||||
let (refs, points) = match data {
|
||||
AngularData::Lines { first, second } => (
|
||||
vec![first.line, second.line],
|
||||
vec![
|
||||
first.ends[0].1,
|
||||
first.ends[1].1,
|
||||
second.ends[0].1,
|
||||
second.ends[1].1,
|
||||
],
|
||||
),
|
||||
AngularData::Points {
|
||||
vertex,
|
||||
first,
|
||||
second,
|
||||
} => (
|
||||
vec![first.0, vertex.0, second.0],
|
||||
vec![vertex.1, first.1, second.1],
|
||||
),
|
||||
};
|
||||
Some(CmdResult::AddAngularConstraint {
|
||||
refs,
|
||||
points,
|
||||
location,
|
||||
sector,
|
||||
renamed: name != self.default_name,
|
||||
name,
|
||||
expression,
|
||||
})
|
||||
}
|
||||
|
||||
fn command_name(&self) -> &'static str {
|
||||
match self.axis {
|
||||
DimConstraintAxis::Linear => "DCLINEAR",
|
||||
DimConstraintAxis::Horizontal => "DCHORIZONTAL",
|
||||
DimConstraintAxis::Vertical => "DCVERTICAL",
|
||||
DimConstraintAxis::Aligned => "DCALIGNED",
|
||||
DimConstraintAxis::Angular => "DCANGULAR",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,6 +320,7 @@ impl DimConstraintCommand {
|
||||
DimConstraintAxis::Horizontal => "Horizontal",
|
||||
DimConstraintAxis::Vertical => "Vertical",
|
||||
DimConstraintAxis::Aligned => "Aligned",
|
||||
DimConstraintAxis::Angular => "Angular",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -291,7 +474,8 @@ impl DimConstraintCommand {
|
||||
}
|
||||
DimConstraintAxis::Horizontal => (ConstraintKind::DistanceX, DVec3::X),
|
||||
DimConstraintAxis::Vertical => (ConstraintKind::DistanceY, DVec3::Y),
|
||||
DimConstraintAxis::Aligned => (
|
||||
// Angular never measures here; its own steps decide the sector.
|
||||
DimConstraintAxis::Aligned | DimConstraintAxis::Angular => (
|
||||
ConstraintKind::Distance,
|
||||
(second - first).normalize_or(DVec3::X),
|
||||
),
|
||||
@@ -334,13 +518,14 @@ impl DimConstraintCommand {
|
||||
location,
|
||||
axis,
|
||||
direction: direction.map(|line| line.line),
|
||||
renamed: name != self.default_name,
|
||||
name,
|
||||
expression,
|
||||
label: match self.axis {
|
||||
DimConstraintAxis::Linear => "Linear constraint",
|
||||
DimConstraintAxis::Horizontal => "Horizontal distance constraint",
|
||||
DimConstraintAxis::Vertical => "Vertical distance constraint",
|
||||
DimConstraintAxis::Aligned => "Aligned constraint",
|
||||
DimConstraintAxis::Aligned | DimConstraintAxis::Angular => "Aligned constraint",
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -371,7 +556,25 @@ impl CadCommand for DimConstraintCommand {
|
||||
Step::TwoLinesSecond { .. } | Step::TwoLinesParallel { .. } => {
|
||||
format!("{name} Select second line to make parallel:")
|
||||
}
|
||||
Step::Location { .. } => format!("{name} Specify dimension line location:"),
|
||||
Step::AngularFirst => {
|
||||
format!("{name} Select first line or arc or [3Point] <3Point>:")
|
||||
}
|
||||
Step::AngularSecond { .. } => format!("{name} Select second line:"),
|
||||
Step::AngularVertex => format!("{name} Specify angle vertex:"),
|
||||
Step::AngularPoint1 { .. } => {
|
||||
format!("{name} Specify first angle constraint point:")
|
||||
}
|
||||
Step::AngularPoint2 { .. } => {
|
||||
format!("{name} Specify second angle constraint point:")
|
||||
}
|
||||
Step::AngularLocation { .. } | Step::Location { .. } => {
|
||||
format!("{name} Specify dimension line location:")
|
||||
}
|
||||
Step::AngularValue { 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,
|
||||
@@ -388,6 +591,7 @@ impl CadCommand for DimConstraintCommand {
|
||||
CmdOption::new("2Lines", "2L"),
|
||||
],
|
||||
Step::First => vec![CmdOption::new("Object", "O")],
|
||||
Step::AngularFirst => vec![CmdOption::new("3Point", "3P")],
|
||||
Step::PointLinePoint => vec![CmdOption::new("Line", "L")],
|
||||
_ => Vec::new(),
|
||||
}
|
||||
@@ -400,7 +604,11 @@ impl CadCommand for DimConstraintCommand {
|
||||
fn point_step_accepts_keywords(&self) -> bool {
|
||||
matches!(
|
||||
self.step,
|
||||
Step::First | Step::PointLinePoint | Step::Value { .. }
|
||||
Step::First
|
||||
| Step::PointLinePoint
|
||||
| Step::Value { .. }
|
||||
| Step::AngularFirst
|
||||
| Step::AngularValue { .. }
|
||||
)
|
||||
}
|
||||
|
||||
@@ -427,13 +635,25 @@ impl CadCommand for DimConstraintCommand {
|
||||
}
|
||||
None
|
||||
}
|
||||
Step::Value { .. } => {
|
||||
Step::AngularFirst => {
|
||||
let keyword = text.trim().trim_start_matches('_').to_ascii_uppercase();
|
||||
if matches!(keyword.as_str(), "3P" | "3POINT") {
|
||||
self.step = Step::AngularVertex;
|
||||
return Some(CmdResult::NeedPoint);
|
||||
}
|
||||
None
|
||||
}
|
||||
Step::Value { .. } | Step::AngularValue { .. } => {
|
||||
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()),
|
||||
};
|
||||
self.build(name, expression)
|
||||
if matches!(self.step, Step::AngularValue { .. }) {
|
||||
self.build_angular(name, expression)
|
||||
} else {
|
||||
self.build(name, expression)
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
@@ -451,6 +671,11 @@ impl CadCommand for DimConstraintCommand {
|
||||
| Step::LinePoint { .. }
|
||||
| Step::TwoLinesFirst
|
||||
| Step::TwoLinesSecond { .. }
|
||||
| Step::AngularFirst
|
||||
| Step::AngularSecond { .. }
|
||||
| Step::AngularVertex
|
||||
| Step::AngularPoint1 { .. }
|
||||
| Step::AngularPoint2 { .. }
|
||||
)
|
||||
}
|
||||
|
||||
@@ -571,7 +796,81 @@ impl CadCommand for DimConstraintCommand {
|
||||
};
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
Step::Location { .. } | Step::Value { .. } => self.on_point(point),
|
||||
Step::AngularFirst => {
|
||||
if handle.is_null() {
|
||||
return Self::report(Self::NO_OBJECT);
|
||||
}
|
||||
let Some(entity) = self.picked_entity.take() else {
|
||||
return CmdResult::NeedPoint;
|
||||
};
|
||||
if matches!(entity, EntityType::Arc(_)) {
|
||||
// An arc's included angle: its center and its ends.
|
||||
let center = ParametricRef::center(handle);
|
||||
let start = ParametricRef::point(handle, 0);
|
||||
let end = ParametricRef::point(handle, 1);
|
||||
let (Some(c), Some(s), Some(e)) = (
|
||||
Self::world(&entity, center),
|
||||
Self::world(&entity, start),
|
||||
Self::world(&entity, end),
|
||||
) else {
|
||||
return Self::report(Self::NO_POINT);
|
||||
};
|
||||
self.step = Step::AngularLocation {
|
||||
data: AngularData::Points {
|
||||
vertex: (center, c),
|
||||
first: (start, s),
|
||||
second: (end, e),
|
||||
},
|
||||
};
|
||||
return CmdResult::NeedPoint;
|
||||
}
|
||||
match Self::angular_line(&entity, handle, point) {
|
||||
Some(line) => {
|
||||
self.step = Step::AngularSecond { first: line };
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
None => Self::report(&format!(
|
||||
"Invalid selection for {}. Select a line, polyline segment or arc.",
|
||||
self.noun()
|
||||
)),
|
||||
}
|
||||
}
|
||||
Step::AngularSecond { first } => {
|
||||
if handle.is_null() {
|
||||
return Self::report(Self::NO_OBJECT);
|
||||
}
|
||||
let Some(entity) = self.picked_entity.take() else {
|
||||
return CmdResult::NeedPoint;
|
||||
};
|
||||
let Some(second) = Self::angular_line(&entity, handle, point) else {
|
||||
return Self::report(&format!(
|
||||
"Invalid selection for {}. Select a line, polyline segment or arc.",
|
||||
self.noun()
|
||||
));
|
||||
};
|
||||
if second.line == first.line {
|
||||
return Self::report(Self::SAME_POINT);
|
||||
}
|
||||
let cross = first.dir.x * second.dir.y - first.dir.y * second.dir.x;
|
||||
if cross.abs() < 1.0e-9 {
|
||||
return CmdResult::CancelWithMessage(Self::PARALLEL_LINES.to_string());
|
||||
}
|
||||
self.step = Step::AngularLocation {
|
||||
data: AngularData::Lines { first, second },
|
||||
};
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
Step::AngularVertex | Step::AngularPoint1 { .. } | Step::AngularPoint2 { .. } => {
|
||||
CmdResult::CheckConstraintPoint(CoincidentPick {
|
||||
handle: (!handle.is_null()).then_some(handle),
|
||||
point,
|
||||
whole_curve: false,
|
||||
})
|
||||
}
|
||||
Step::Location { .. }
|
||||
| Step::Value { .. }
|
||||
| Step::AngularLocation { .. }
|
||||
| Step::AngularValue { .. } => self.on_point(point),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -644,6 +943,35 @@ impl CadCommand for DimConstraintCommand {
|
||||
};
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
Step::AngularVertex => {
|
||||
self.step = Step::AngularPoint1 {
|
||||
vertex: (reference, point),
|
||||
};
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
Step::AngularPoint1 { vertex } => {
|
||||
if reference == vertex.0 {
|
||||
return Self::report(Self::SAME_POINT);
|
||||
}
|
||||
self.step = Step::AngularPoint2 {
|
||||
vertex,
|
||||
first: (reference, point),
|
||||
};
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
Step::AngularPoint2 { vertex, first } => {
|
||||
if reference == vertex.0 || reference == first.0 {
|
||||
return Self::report(Self::SAME_POINT);
|
||||
}
|
||||
self.step = Step::AngularLocation {
|
||||
data: AngularData::Points {
|
||||
vertex,
|
||||
first,
|
||||
second: (reference, point),
|
||||
},
|
||||
};
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
_ => CmdResult::NeedPoint,
|
||||
}
|
||||
}
|
||||
@@ -657,11 +985,49 @@ impl CadCommand for DimConstraintCommand {
|
||||
whole_curve: false,
|
||||
})
|
||||
}
|
||||
Step::AngularVertex | Step::AngularPoint1 { .. } | Step::AngularPoint2 { .. } => {
|
||||
CmdResult::CheckConstraintPoint(CoincidentPick {
|
||||
handle: None,
|
||||
point,
|
||||
whole_curve: false,
|
||||
})
|
||||
}
|
||||
Step::Object
|
||||
| Step::PointLineLine { .. }
|
||||
| Step::LineFirst
|
||||
| Step::TwoLinesFirst
|
||||
| Step::TwoLinesSecond { .. } => Self::report(Self::NO_OBJECT),
|
||||
| Step::TwoLinesSecond { .. }
|
||||
| Step::AngularFirst
|
||||
| Step::AngularSecond { .. } => Self::report(Self::NO_OBJECT),
|
||||
Step::AngularLocation { data } => {
|
||||
let frame = match data {
|
||||
AngularData::Lines { first, second } => Self::angle_frame(
|
||||
first.ends[0].1,
|
||||
first.ends[1].1,
|
||||
second.ends[0].1,
|
||||
second.ends[1].1,
|
||||
point,
|
||||
),
|
||||
AngularData::Points {
|
||||
vertex,
|
||||
first,
|
||||
second,
|
||||
} => Self::ray_frame(vertex.1, first.1, second.1, point),
|
||||
};
|
||||
let Some((measured, sector)) = frame else {
|
||||
return Self::report(Self::NO_POINT);
|
||||
};
|
||||
self.step = Step::AngularValue {
|
||||
data,
|
||||
location: point,
|
||||
sector,
|
||||
measured,
|
||||
};
|
||||
CmdResult::ReportMeasurement(format!(
|
||||
"Dimension text = {}",
|
||||
self.angle_display(measured)
|
||||
))
|
||||
}
|
||||
Step::Location {
|
||||
first,
|
||||
second,
|
||||
@@ -684,7 +1050,9 @@ impl CadCommand for DimConstraintCommand {
|
||||
};
|
||||
CmdResult::ReportMeasurement(format!("Dimension text = {measured:.4}"))
|
||||
}
|
||||
Step::TwoLinesParallel { .. } | Step::Value { .. } => CmdResult::NeedPoint,
|
||||
Step::TwoLinesParallel { .. } | Step::Value { .. } | Step::AngularValue { .. } => {
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -694,6 +1062,14 @@ impl CadCommand for DimConstraintCommand {
|
||||
self.step = Step::Object;
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
// Enter takes the 3Point default.
|
||||
Step::AngularFirst => {
|
||||
self.step = Step::AngularVertex;
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
Step::AngularValue { measured, .. } => self
|
||||
.build_angular(self.default_name.clone(), measured_expression(measured))
|
||||
.unwrap_or(CmdResult::Cancel),
|
||||
Step::PointLinePoint => {
|
||||
self.step = Step::LineFirst;
|
||||
CmdResult::NeedPoint
|
||||
|
||||
@@ -50,7 +50,7 @@ pub use tools::{
|
||||
perpendicular, symmetric, tangent, vertical,
|
||||
};
|
||||
pub use value::{
|
||||
angle_tool, dimensional_tools, distance_tool, AngleConstraintCommand,
|
||||
angle_tool, dimensional_tools, distance_tool,
|
||||
DistanceConstraintCommand, DistanceMode,
|
||||
};
|
||||
|
||||
@@ -123,9 +123,9 @@ impl CadModule for ParametricModule {
|
||||
default: "DCLINEAR",
|
||||
},
|
||||
RibbonItem::LargeTool(dimensional_tools::aligned()),
|
||||
RibbonItem::LargeTool(dimensional_tools::angular()),
|
||||
RibbonItem::LargeTool(dimensional_tools::diameter()),
|
||||
RibbonItem::LargeTool(dimensional_tools::radius()),
|
||||
RibbonItem::LargeTool(dimensional_tools::diameter()),
|
||||
RibbonItem::LargeTool(dimensional_tools::angular()),
|
||||
RibbonItem::LargeTool(dimensional_tools::convert()),
|
||||
RibbonItem::LabeledDropdown {
|
||||
id: "DCVISIBILITY", label: "Show/Hide",
|
||||
@@ -201,8 +201,8 @@ mod tests {
|
||||
assert_eq!(
|
||||
groups[1].tools.iter().map(item_id).collect::<Vec<_>>(),
|
||||
[
|
||||
"DC_LINEAR_MENU", "DCALIGNED", "DCANGULAR", "DCDIAMETER",
|
||||
"DCRADIUS", "DCCONVERT", "DCVISIBILITY", "DCSHOWALL", "DCHIDEALL",
|
||||
"DC_LINEAR_MENU", "DCALIGNED", "DCRADIUS", "DCDIAMETER",
|
||||
"DCANGULAR", "DCCONVERT", "DCVISIBILITY", "DCSHOWALL", "DCHIDEALL",
|
||||
]
|
||||
);
|
||||
assert_eq!(groups[2].tools.iter().map(item_id).collect::<Vec<_>>(), ["DELCONSTRAINT", "PARAMETERS"]);
|
||||
|
||||
@@ -372,99 +372,6 @@ impl CadCommand for DistanceConstraintCommand {
|
||||
}
|
||||
}
|
||||
|
||||
/// Constrains the angle (in degrees) from `fixed`'s direction to `moving`'s.
|
||||
pub struct AngleConstraintCommand {
|
||||
command_name: &'static str,
|
||||
fixed_handle: Handle,
|
||||
moving_handle: Handle,
|
||||
default_value: f64,
|
||||
/// Snapshot of `Scene::named_parameters`' names at construction time —
|
||||
/// see `parse_driving_value`'s doc comment for why a snapshot.
|
||||
known_param_names: Vec<String>,
|
||||
}
|
||||
|
||||
impl AngleConstraintCommand {
|
||||
/// `None` unless both `fixed` and `moving` are lines.
|
||||
pub fn new(scene: &Scene, fixed: Handle, moving: Handle) -> Option<Self> {
|
||||
Self::with_name(scene, fixed, moving, "ACONSTRAINT")
|
||||
}
|
||||
|
||||
pub fn with_name(
|
||||
scene: &Scene,
|
||||
fixed: Handle,
|
||||
moving: Handle,
|
||||
command_name: &'static str,
|
||||
) -> Option<Self> {
|
||||
let fixed_entity = scene.document.get_entity(fixed)?;
|
||||
let moving_entity = scene.document.get_entity(moving)?;
|
||||
let (EntityType::Line(f), EntityType::Line(m)) = (fixed_entity, moving_entity) else {
|
||||
return None;
|
||||
};
|
||||
let a1 = (f.end.y - f.start.y).atan2(f.end.x - f.start.x);
|
||||
let a2 = (m.end.y - m.start.y).atan2(m.end.x - m.start.x);
|
||||
let default_value = (a2 - a1).to_degrees();
|
||||
let known_param_names = scene
|
||||
.named_parameters()
|
||||
.iter()
|
||||
.map(|p| p.name.clone())
|
||||
.collect();
|
||||
Some(Self {
|
||||
command_name,
|
||||
fixed_handle: fixed,
|
||||
moving_handle: moving,
|
||||
default_value,
|
||||
known_param_names,
|
||||
})
|
||||
}
|
||||
|
||||
fn build(&self, target: DrivingValue) -> Option<CmdResult> {
|
||||
if matches!(&target, DrivingValue::Literal(value) if !value.is_finite()) {
|
||||
return None;
|
||||
}
|
||||
Some(CmdResult::AddParametricConstraint {
|
||||
kind: ConstraintKind::Angle,
|
||||
refs: vec![
|
||||
ParametricRef::whole(self.fixed_handle),
|
||||
ParametricRef::whole(self.moving_handle),
|
||||
],
|
||||
driving_param: Some(target),
|
||||
label: "Angle constraint",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl CadCommand for AngleConstraintCommand {
|
||||
fn name(&self) -> &'static str {
|
||||
self.command_name
|
||||
}
|
||||
|
||||
fn prompt(&self) -> String {
|
||||
format!("Specify angle in degrees <{:.4}>: ", self.default_value)
|
||||
}
|
||||
|
||||
fn input_kind(&self) -> InputKind {
|
||||
InputKind::SingleToken
|
||||
}
|
||||
|
||||
fn on_point(&mut self, _pt: DVec3) -> CmdResult {
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
|
||||
fn on_enter(&mut self) -> CmdResult {
|
||||
self.build(DrivingValue::Literal(self.default_value))
|
||||
.unwrap_or(CmdResult::Cancel)
|
||||
}
|
||||
|
||||
fn on_text_input(&mut self, text: &str) -> Option<CmdResult> {
|
||||
let value = parse_driving_value(text, &self.known_param_names)?;
|
||||
self.build(value)
|
||||
}
|
||||
|
||||
fn on_escape(&mut self) -> CmdResult {
|
||||
CmdResult::Cancel
|
||||
}
|
||||
}
|
||||
|
||||
// ── Autocomplete registry ─────────────────────────────────
|
||||
inventory::submit!(crate::command::CommandRegistration {
|
||||
names: &[
|
||||
|
||||
Reference in new issue
Block a user