Complete rectangle command behavior
This commit is contained in:
parent
cfbf3233be
commit
be40b76922
2 changed files with 532 additions and 59 deletions
|
|
@ -19,6 +19,13 @@ thread_local! {
|
||||||
static ARRAY_P_ANGLE: Cell<f64> = Cell::new(360.0); // degrees
|
static ARRAY_P_ANGLE: Cell<f64> = Cell::new(360.0); // degrees
|
||||||
static ARRAY_PATH_COUNT: Cell<f64> = Cell::new(6.0);
|
static ARRAY_PATH_COUNT: Cell<f64> = Cell::new(6.0);
|
||||||
static POLYGON_SIDES: Cell<f64> = Cell::new(6.0);
|
static POLYGON_SIDES: Cell<f64> = Cell::new(6.0);
|
||||||
|
static RECT_ELEVATION: Cell<f64> = Cell::new(0.0);
|
||||||
|
static RECT_THICKNESS: Cell<f64> = Cell::new(0.0);
|
||||||
|
static RECT_WIDTH: Cell<f64> = Cell::new(0.0);
|
||||||
|
static RECT_ROTATION: Cell<f64> = Cell::new(0.0); // degrees
|
||||||
|
static RECT_CHAMFER1: Cell<f64> = Cell::new(0.0);
|
||||||
|
static RECT_CHAMFER2: Cell<f64> = Cell::new(0.0);
|
||||||
|
static RECT_FILLET: Cell<f64> = Cell::new(0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! accessors {
|
macro_rules! accessors {
|
||||||
|
|
@ -54,3 +61,10 @@ accessors!(get_array_p_count, set_array_p_count, ARRAY_P_COUNT);
|
||||||
accessors!(get_array_p_angle, set_array_p_angle, ARRAY_P_ANGLE);
|
accessors!(get_array_p_angle, set_array_p_angle, ARRAY_P_ANGLE);
|
||||||
accessors!(get_array_path_count, set_array_path_count, ARRAY_PATH_COUNT);
|
accessors!(get_array_path_count, set_array_path_count, ARRAY_PATH_COUNT);
|
||||||
accessors!(get_polygon_sides, set_polygon_sides, POLYGON_SIDES);
|
accessors!(get_polygon_sides, set_polygon_sides, POLYGON_SIDES);
|
||||||
|
accessors!(get_rect_elevation, set_rect_elevation, RECT_ELEVATION);
|
||||||
|
accessors!(get_rect_thickness, set_rect_thickness, RECT_THICKNESS);
|
||||||
|
accessors!(get_rect_width, set_rect_width, RECT_WIDTH);
|
||||||
|
accessors!(get_rect_rotation, set_rect_rotation, RECT_ROTATION);
|
||||||
|
accessors!(get_rect_chamfer1, set_rect_chamfer1, RECT_CHAMFER1);
|
||||||
|
accessors!(get_rect_chamfer2, set_rect_chamfer2, RECT_CHAMFER2);
|
||||||
|
accessors!(get_rect_fillet, set_rect_fillet, RECT_FILLET);
|
||||||
|
|
|
||||||
|
|
@ -21,21 +21,6 @@ use crate::modules::IconKind;
|
||||||
use crate::scene::model::wire_model::WireModel;
|
use crate::scene::model::wire_model::WireModel;
|
||||||
use glam::DVec3;
|
use glam::DVec3;
|
||||||
|
|
||||||
/// Build the four corners of an axis-aligned box between opposite corners `a`
|
|
||||||
/// and `b`, axis-aligned in the active UCS (`ucs` = UCS→wire affine, identity =
|
|
||||||
/// world). The two given corners stay put; the other two are placed square to
|
|
||||||
/// the UCS axes instead of the world axes.
|
|
||||||
fn ucs_box_corners(a: DVec3, b: DVec3, plane: WorkingPlane) -> [DVec3; 4] {
|
|
||||||
let au = plane.to_local(a);
|
|
||||||
let bu = plane.to_local(b);
|
|
||||||
[
|
|
||||||
plane.to_world(DVec3::new(au.x, au.y, au.z)),
|
|
||||||
plane.to_world(DVec3::new(bu.x, au.y, au.z)),
|
|
||||||
plane.to_world(DVec3::new(bu.x, bu.y, au.z)),
|
|
||||||
plane.to_world(DVec3::new(au.x, bu.y, au.z)),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Four corners of a box centred at `c` with half-extents taken from `corner`,
|
/// Four corners of a box centred at `c` with half-extents taken from `corner`,
|
||||||
/// axis-aligned in the active UCS (`ucs` = UCS→wire affine, identity = world).
|
/// axis-aligned in the active UCS (`ucs` = UCS→wire affine, identity = world).
|
||||||
fn ucs_box_around_center(c: DVec3, corner: DVec3, plane: WorkingPlane) -> [DVec3; 4] {
|
fn ucs_box_around_center(c: DVec3, corner: DVec3, plane: WorkingPlane) -> [DVec3; 4] {
|
||||||
|
|
@ -96,6 +81,146 @@ fn make_pline(points: &[DVec3], plane: WorkingPlane) -> EntityType {
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
struct RectStyle {
|
||||||
|
chamfer_first: f64,
|
||||||
|
chamfer_second: f64,
|
||||||
|
fillet_radius: f64,
|
||||||
|
width: f64,
|
||||||
|
thickness: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rectangle_corners(
|
||||||
|
first: DVec3,
|
||||||
|
cursor: DVec3,
|
||||||
|
plane: WorkingPlane,
|
||||||
|
rotation_deg: f64,
|
||||||
|
fixed_dimensions: Option<(f64, f64)>,
|
||||||
|
) -> Option<[DVec3; 4]> {
|
||||||
|
let first_local = plane.to_local(first);
|
||||||
|
let cursor_local = plane.to_local(cursor);
|
||||||
|
let angle = rotation_deg.to_radians();
|
||||||
|
let axis_x = DVec3::new(angle.cos(), angle.sin(), 0.0);
|
||||||
|
let axis_y = DVec3::new(-angle.sin(), angle.cos(), 0.0);
|
||||||
|
let delta = cursor_local - first_local;
|
||||||
|
let raw_width = delta.dot(axis_x);
|
||||||
|
let raw_height = delta.dot(axis_y);
|
||||||
|
let (width, height) = fixed_dimensions.map_or((raw_width, raw_height), |(w, h)| {
|
||||||
|
(w.copysign(raw_width), h.copysign(raw_height))
|
||||||
|
});
|
||||||
|
if width.abs() <= 1.0e-9 || height.abs() <= 1.0e-9 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let local = [
|
||||||
|
first_local,
|
||||||
|
first_local + axis_x * width,
|
||||||
|
first_local + axis_x * width + axis_y * height,
|
||||||
|
first_local + axis_y * height,
|
||||||
|
];
|
||||||
|
Some(local.map(|point| plane.to_world(point)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn trimmed_rectangle_vertices(
|
||||||
|
corners: [DVec3; 4],
|
||||||
|
plane: WorkingPlane,
|
||||||
|
style: RectStyle,
|
||||||
|
) -> Vec<(DVec3, f64)> {
|
||||||
|
let use_fillet = style.fillet_radius > 1.0e-9;
|
||||||
|
let use_chamfer = !use_fillet
|
||||||
|
&& (style.chamfer_first > 1.0e-9 || style.chamfer_second > 1.0e-9);
|
||||||
|
if !use_fillet && !use_chamfer {
|
||||||
|
return corners.into_iter().map(|point| (point, 0.0)).collect();
|
||||||
|
}
|
||||||
|
|
||||||
|
let local = corners.map(|point| plane.to_local(point));
|
||||||
|
let area_twice: f64 = (0..4)
|
||||||
|
.map(|index| {
|
||||||
|
let a = local[index];
|
||||||
|
let b = local[(index + 1) % 4];
|
||||||
|
a.x * b.y - b.x * a.y
|
||||||
|
})
|
||||||
|
.sum();
|
||||||
|
let bulge = area_twice.signum() * (std::f64::consts::PI / 8.0).tan();
|
||||||
|
let mut out = Vec::with_capacity(8);
|
||||||
|
for index in 0..4 {
|
||||||
|
let corner = corners[index];
|
||||||
|
let previous = corners[(index + 3) % 4];
|
||||||
|
let next = corners[(index + 1) % 4];
|
||||||
|
let incoming_length = (previous - corner).length();
|
||||||
|
let outgoing_length = (next - corner).length();
|
||||||
|
let max_trim = incoming_length.min(outgoing_length) * 0.499_999;
|
||||||
|
let (incoming_trim, outgoing_trim, arc_bulge) = if use_fillet {
|
||||||
|
let trim = style.fillet_radius.min(max_trim);
|
||||||
|
(trim, trim, bulge)
|
||||||
|
} else {
|
||||||
|
(
|
||||||
|
style.chamfer_first.min(max_trim),
|
||||||
|
style.chamfer_second.min(max_trim),
|
||||||
|
0.0,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
let incoming = corner + (previous - corner).normalize() * incoming_trim;
|
||||||
|
let outgoing = corner + (next - corner).normalize() * outgoing_trim;
|
||||||
|
out.push((incoming, arc_bulge));
|
||||||
|
out.push((outgoing, 0.0));
|
||||||
|
}
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_rect_pline(corners: [DVec3; 4], plane: WorkingPlane, style: RectStyle) -> EntityType {
|
||||||
|
let points = trimmed_rectangle_vertices(corners, plane, style);
|
||||||
|
let local: Vec<(DVec3, f64)> = points
|
||||||
|
.into_iter()
|
||||||
|
.map(|(point, bulge)| (plane.to_local(point), bulge))
|
||||||
|
.collect();
|
||||||
|
let elevation = local.first().map_or(0.0, |(point, _)| point.z);
|
||||||
|
let mut polyline = LwPolyline {
|
||||||
|
vertices: local
|
||||||
|
.iter()
|
||||||
|
.map(|(point, bulge)| {
|
||||||
|
let mut vertex = LwVertex::new(Vector2::new(point.x, point.y));
|
||||||
|
vertex.bulge = *bulge;
|
||||||
|
vertex
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
elevation,
|
||||||
|
is_closed: true,
|
||||||
|
constant_width: style.width,
|
||||||
|
thickness: style.thickness,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let mut marker = acadrust::xdata::ExtendedDataRecord::new("OCS_RECTANGLE");
|
||||||
|
marker.add_value(acadrust::xdata::XDataValue::Integer16(1));
|
||||||
|
polyline.common.extended_data.add_record(marker);
|
||||||
|
plane.place_entity(EntityType::LwPolyline(polyline))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rectangle_wire(corners: [DVec3; 4], plane: WorkingPlane, style: RectStyle) -> WireModel {
|
||||||
|
let vertices = trimmed_rectangle_vertices(corners, plane, style);
|
||||||
|
let mut points = Vec::new();
|
||||||
|
for index in 0..vertices.len() {
|
||||||
|
let (start, bulge) = vertices[index];
|
||||||
|
let end = vertices[(index + 1) % vertices.len()].0;
|
||||||
|
points.push([start.x, start.y, start.z]);
|
||||||
|
if bulge.abs() > 1.0e-9 {
|
||||||
|
let a = plane.to_local(start);
|
||||||
|
let b = plane.to_local(end);
|
||||||
|
if let Some(arc) = crate::entities::common::BulgeArc::from_bulge(
|
||||||
|
[a.x, a.y],
|
||||||
|
[b.x, b.y],
|
||||||
|
bulge,
|
||||||
|
) {
|
||||||
|
for step in 1..8 {
|
||||||
|
let point = arc.sample(step as f64 / 8.0);
|
||||||
|
let world = plane.to_world(DVec3::new(point[0], point[1], a.z));
|
||||||
|
points.push([world.x, world.y, world.z]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wire_loop(points)
|
||||||
|
}
|
||||||
|
|
||||||
fn wire_loop(pts: Vec<[f64; 3]>) -> WireModel {
|
fn wire_loop(pts: Vec<[f64; 3]>) -> WireModel {
|
||||||
let mut p = pts;
|
let mut p = pts;
|
||||||
if let Some(&first) = p.first() {
|
if let Some(&first) = p.first() {
|
||||||
|
|
@ -155,18 +280,79 @@ fn plane_distance(from: DVec3, to: DVec3, plane: WorkingPlane) -> f64 {
|
||||||
|
|
||||||
// ── Command: Rectangle — Two Corners (RECT) ──────────────────────────────
|
// ── Command: Rectangle — Two Corners (RECT) ──────────────────────────────
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
enum RectStep {
|
||||||
|
FirstCorner,
|
||||||
|
Opposite,
|
||||||
|
ChamferFirst,
|
||||||
|
ChamferSecond,
|
||||||
|
Elevation,
|
||||||
|
Fillet,
|
||||||
|
Thickness,
|
||||||
|
Width,
|
||||||
|
Rotation,
|
||||||
|
AreaValue,
|
||||||
|
AreaBasis(f64),
|
||||||
|
AreaDimension { area: f64, by_length: bool },
|
||||||
|
DimensionsLength,
|
||||||
|
DimensionsWidth(f64),
|
||||||
|
PlaceSized { width: f64, height: f64 },
|
||||||
|
}
|
||||||
|
|
||||||
pub struct RectCommand {
|
pub struct RectCommand {
|
||||||
a: Option<DVec3>,
|
step: RectStep,
|
||||||
|
first: Option<DVec3>,
|
||||||
plane: WorkingPlane,
|
plane: WorkingPlane,
|
||||||
|
chamfer_first: f64,
|
||||||
|
chamfer_second: f64,
|
||||||
|
fillet_radius: f64,
|
||||||
|
elevation: f64,
|
||||||
|
thickness: f64,
|
||||||
|
width: f64,
|
||||||
|
rotation_deg: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RectCommand {
|
impl RectCommand {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
a: None,
|
step: RectStep::FirstCorner,
|
||||||
|
first: None,
|
||||||
plane: WorkingPlane::default(),
|
plane: WorkingPlane::default(),
|
||||||
|
chamfer_first: defaults::get_rect_chamfer1().max(0.0),
|
||||||
|
chamfer_second: defaults::get_rect_chamfer2().max(0.0),
|
||||||
|
fillet_radius: defaults::get_rect_fillet().max(0.0),
|
||||||
|
elevation: defaults::get_rect_elevation(),
|
||||||
|
thickness: defaults::get_rect_thickness(),
|
||||||
|
width: defaults::get_rect_width().max(0.0),
|
||||||
|
rotation_deg: defaults::get_rect_rotation(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn style(&self) -> RectStyle {
|
||||||
|
RectStyle {
|
||||||
|
chamfer_first: self.chamfer_first,
|
||||||
|
chamfer_second: self.chamfer_second,
|
||||||
|
fillet_radius: self.fillet_radius,
|
||||||
|
width: self.width,
|
||||||
|
thickness: self.thickness,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn finish(&self, cursor: DVec3, fixed_dimensions: Option<(f64, f64)>) -> CmdResult {
|
||||||
|
let Some(first) = self.first else {
|
||||||
|
return CmdResult::NeedPoint;
|
||||||
|
};
|
||||||
|
let Some(corners) = rectangle_corners(
|
||||||
|
first,
|
||||||
|
cursor,
|
||||||
|
self.plane,
|
||||||
|
self.rotation_deg,
|
||||||
|
fixed_dimensions,
|
||||||
|
) else {
|
||||||
|
return CmdResult::NeedPoint;
|
||||||
|
};
|
||||||
|
CmdResult::CommitAndExit(make_rect_pline(corners, self.plane, self.style()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CadCommand for RectCommand {
|
impl CadCommand for RectCommand {
|
||||||
|
|
@ -177,78 +363,341 @@ impl CadCommand for RectCommand {
|
||||||
self.plane = plane;
|
self.plane = plane;
|
||||||
}
|
}
|
||||||
fn prompt(&self) -> String {
|
fn prompt(&self) -> String {
|
||||||
if self.a.is_none() {
|
match self.step {
|
||||||
crate::t!("RECT Specify first corner:").into_owned()
|
RectStep::FirstCorner => crate::t!("RECT Specify first corner:").into_owned(),
|
||||||
} else {
|
RectStep::Opposite => crate::t!("RECT Specify opposite corner:").into_owned(),
|
||||||
crate::t!("RECT Specify opposite corner:").into_owned()
|
RectStep::ChamferFirst => format!(
|
||||||
|
"RECT Specify first chamfer distance <{}>:",
|
||||||
|
crate::entities::common::format_length(self.chamfer_first)
|
||||||
|
),
|
||||||
|
RectStep::ChamferSecond => format!(
|
||||||
|
"RECT Specify second chamfer distance <{}>:",
|
||||||
|
crate::entities::common::format_length(self.chamfer_second)
|
||||||
|
),
|
||||||
|
RectStep::Elevation => {
|
||||||
|
format!(
|
||||||
|
"RECT Specify elevation <{}>:",
|
||||||
|
crate::entities::common::format_length(self.elevation)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
RectStep::Fillet => {
|
||||||
|
format!(
|
||||||
|
"RECT Specify fillet radius <{}>:",
|
||||||
|
crate::entities::common::format_length(self.fillet_radius)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
RectStep::Thickness => {
|
||||||
|
format!(
|
||||||
|
"RECT Specify thickness <{}>:",
|
||||||
|
crate::entities::common::format_length(self.thickness)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
RectStep::Width => format!(
|
||||||
|
"RECT Specify width <{}>:",
|
||||||
|
crate::entities::common::format_length(self.width)
|
||||||
|
),
|
||||||
|
RectStep::Rotation => {
|
||||||
|
format!(
|
||||||
|
"RECT Specify rotation angle <{}>:",
|
||||||
|
crate::entities::common::format_angle(self.rotation_deg.to_radians())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
RectStep::AreaValue => "RECT Specify rectangle area:".to_string(),
|
||||||
|
RectStep::AreaBasis(_) => {
|
||||||
|
"RECT Calculate dimensions based on [Length / Width] <Length>:".to_string()
|
||||||
|
}
|
||||||
|
RectStep::AreaDimension { by_length: true, .. } => {
|
||||||
|
"RECT Specify rectangle length:".to_string()
|
||||||
|
}
|
||||||
|
RectStep::AreaDimension { by_length: false, .. } => {
|
||||||
|
"RECT Specify rectangle width:".to_string()
|
||||||
|
}
|
||||||
|
RectStep::DimensionsLength => "RECT Specify rectangle length:".to_string(),
|
||||||
|
RectStep::DimensionsWidth(_) => "RECT Specify rectangle width:".to_string(),
|
||||||
|
RectStep::PlaceSized { .. } => {
|
||||||
|
"RECT Specify orientation from the first corner:".to_string()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn options(&self) -> Vec<crate::command::CmdOption> {
|
fn options(&self) -> Vec<crate::command::CmdOption> {
|
||||||
use crate::command::CmdOption;
|
use crate::command::CmdOption;
|
||||||
// First corner also offers the alternate rectangle methods; later steps
|
match self.step {
|
||||||
// are plain point picks. (#304)
|
RectStep::FirstCorner => vec![
|
||||||
if self.a.is_none() {
|
CmdOption::new("Chamfer", "CHAMFER"),
|
||||||
vec![
|
CmdOption::new("Elevation", "ELEVATION"),
|
||||||
|
CmdOption::new("Fillet", "FILLET"),
|
||||||
|
CmdOption::new("Thickness", "THICKNESS"),
|
||||||
|
CmdOption::new("Width", "WIDTH"),
|
||||||
|
],
|
||||||
|
RectStep::Opposite => vec![
|
||||||
|
CmdOption::new("Area", "AREA"),
|
||||||
|
CmdOption::new("Dimensions", "DIMENSIONS"),
|
||||||
CmdOption::new("Rotation", "ROTATION"),
|
CmdOption::new("Rotation", "ROTATION"),
|
||||||
CmdOption::new("Center", "CENTER"),
|
],
|
||||||
]
|
RectStep::AreaBasis(_) => vec![
|
||||||
} else {
|
CmdOption::new("Length", "LENGTH"),
|
||||||
vec![]
|
CmdOption::new("Width", "WIDTH"),
|
||||||
|
],
|
||||||
|
_ => vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn point_step_accepts_keywords(&self) -> bool {
|
fn point_step_accepts_keywords(&self) -> bool {
|
||||||
self.a.is_none()
|
matches!(self.step, RectStep::FirstCorner | RectStep::Opposite)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wants_text_input(&self) -> bool {
|
||||||
|
!matches!(self.step, RectStep::PlaceSized { .. })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dyn_field(&self) -> crate::command::DynField {
|
||||||
|
match self.step {
|
||||||
|
RectStep::Rotation => crate::command::DynField::Angle,
|
||||||
|
RectStep::ChamferFirst
|
||||||
|
| RectStep::ChamferSecond
|
||||||
|
| RectStep::Elevation
|
||||||
|
| RectStep::Fillet
|
||||||
|
| RectStep::Thickness
|
||||||
|
| RectStep::Width
|
||||||
|
| RectStep::AreaValue
|
||||||
|
| RectStep::AreaBasis(_)
|
||||||
|
| RectStep::AreaDimension { .. }
|
||||||
|
| RectStep::DimensionsLength
|
||||||
|
| RectStep::DimensionsWidth(_) => crate::command::DynField::Scalar,
|
||||||
|
_ => crate::command::DynField::Point,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_text_input(&mut self, text: &str) -> Option<CmdResult> {
|
fn on_text_input(&mut self, text: &str) -> Option<CmdResult> {
|
||||||
// At the first corner, keyword options hand off to the dedicated
|
let upper = text.trim().to_uppercase();
|
||||||
// variant command. (#304)
|
match self.step {
|
||||||
if self.a.is_none() {
|
RectStep::FirstCorner => {
|
||||||
return match text.trim().to_uppercase().as_str() {
|
return match upper.as_str() {
|
||||||
"R" | "ROTATION" => Some(CmdResult::Dispatch("RECT_ROT".into())),
|
"C" | "CHAMFER" => {
|
||||||
"C" | "CENTER" => Some(CmdResult::Dispatch("RECT_CEN".into())),
|
self.step = RectStep::ChamferFirst;
|
||||||
_ => None,
|
Some(CmdResult::NeedPoint)
|
||||||
};
|
}
|
||||||
|
"E" | "ELEVATION" => {
|
||||||
|
self.step = RectStep::Elevation;
|
||||||
|
Some(CmdResult::NeedPoint)
|
||||||
|
}
|
||||||
|
"F" | "FILLET" => {
|
||||||
|
self.step = RectStep::Fillet;
|
||||||
|
Some(CmdResult::NeedPoint)
|
||||||
|
}
|
||||||
|
"T" | "THICKNESS" => {
|
||||||
|
self.step = RectStep::Thickness;
|
||||||
|
Some(CmdResult::NeedPoint)
|
||||||
|
}
|
||||||
|
"W" | "WIDTH" => {
|
||||||
|
self.step = RectStep::Width;
|
||||||
|
Some(CmdResult::NeedPoint)
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
RectStep::Opposite => {
|
||||||
|
return match upper.as_str() {
|
||||||
|
"A" | "AREA" => {
|
||||||
|
self.step = RectStep::AreaValue;
|
||||||
|
Some(CmdResult::NeedPoint)
|
||||||
|
}
|
||||||
|
"D" | "DIMENSIONS" => {
|
||||||
|
self.step = RectStep::DimensionsLength;
|
||||||
|
Some(CmdResult::NeedPoint)
|
||||||
|
}
|
||||||
|
"R" | "ROTATION" => {
|
||||||
|
self.step = RectStep::Rotation;
|
||||||
|
Some(CmdResult::NeedPoint)
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
RectStep::AreaBasis(area) => {
|
||||||
|
return match upper.as_str() {
|
||||||
|
"L" | "LENGTH" => {
|
||||||
|
self.step = RectStep::AreaDimension {
|
||||||
|
area,
|
||||||
|
by_length: true,
|
||||||
|
};
|
||||||
|
Some(CmdResult::NeedPoint)
|
||||||
|
}
|
||||||
|
"W" | "WIDTH" => {
|
||||||
|
self.step = RectStep::AreaDimension {
|
||||||
|
area,
|
||||||
|
by_length: false,
|
||||||
|
};
|
||||||
|
Some(CmdResult::NeedPoint)
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
None
|
|
||||||
|
if matches!(self.step, RectStep::Rotation) {
|
||||||
|
let angle = crate::entities::common::parse_typed_angle(text)?;
|
||||||
|
self.rotation_deg = angle.to_degrees();
|
||||||
|
defaults::set_rect_rotation(self.rotation_deg);
|
||||||
|
self.step = RectStep::Opposite;
|
||||||
|
return Some(CmdResult::NeedPoint);
|
||||||
|
}
|
||||||
|
let value = if matches!(self.step, RectStep::AreaValue) {
|
||||||
|
text.trim().replace(',', ".").parse().ok()?
|
||||||
|
} else {
|
||||||
|
crate::entities::common::parse_typed_length(text)?
|
||||||
|
};
|
||||||
|
match self.step {
|
||||||
|
RectStep::ChamferFirst if value >= 0.0 => {
|
||||||
|
self.chamfer_first = value;
|
||||||
|
defaults::set_rect_chamfer1(value);
|
||||||
|
self.step = RectStep::ChamferSecond;
|
||||||
|
}
|
||||||
|
RectStep::ChamferSecond if value >= 0.0 => {
|
||||||
|
self.chamfer_second = value;
|
||||||
|
self.fillet_radius = 0.0;
|
||||||
|
defaults::set_rect_chamfer2(value);
|
||||||
|
defaults::set_rect_fillet(0.0);
|
||||||
|
self.step = RectStep::FirstCorner;
|
||||||
|
}
|
||||||
|
RectStep::Elevation => {
|
||||||
|
self.elevation = value;
|
||||||
|
defaults::set_rect_elevation(value);
|
||||||
|
self.step = RectStep::FirstCorner;
|
||||||
|
}
|
||||||
|
RectStep::Fillet if value >= 0.0 => {
|
||||||
|
self.fillet_radius = value;
|
||||||
|
self.chamfer_first = 0.0;
|
||||||
|
self.chamfer_second = 0.0;
|
||||||
|
defaults::set_rect_fillet(value);
|
||||||
|
defaults::set_rect_chamfer1(0.0);
|
||||||
|
defaults::set_rect_chamfer2(0.0);
|
||||||
|
self.step = RectStep::FirstCorner;
|
||||||
|
}
|
||||||
|
RectStep::Thickness => {
|
||||||
|
self.thickness = value;
|
||||||
|
defaults::set_rect_thickness(value);
|
||||||
|
self.step = RectStep::FirstCorner;
|
||||||
|
}
|
||||||
|
RectStep::Width if value >= 0.0 => {
|
||||||
|
self.width = value;
|
||||||
|
defaults::set_rect_width(value);
|
||||||
|
self.step = RectStep::FirstCorner;
|
||||||
|
}
|
||||||
|
RectStep::AreaValue if value > 0.0 => {
|
||||||
|
self.step = RectStep::AreaBasis(value);
|
||||||
|
}
|
||||||
|
RectStep::AreaDimension { area, by_length } if value > 0.0 => {
|
||||||
|
let (width, height) = if by_length {
|
||||||
|
(value, area / value)
|
||||||
|
} else {
|
||||||
|
(area / value, value)
|
||||||
|
};
|
||||||
|
self.step = RectStep::PlaceSized { width, height };
|
||||||
|
}
|
||||||
|
RectStep::DimensionsLength if value > 0.0 => {
|
||||||
|
self.step = RectStep::DimensionsWidth(value);
|
||||||
|
}
|
||||||
|
RectStep::DimensionsWidth(length) if value > 0.0 => {
|
||||||
|
self.step = RectStep::PlaceSized {
|
||||||
|
width: length,
|
||||||
|
height: value,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
_ => return None,
|
||||||
|
}
|
||||||
|
Some(CmdResult::NeedPoint)
|
||||||
}
|
}
|
||||||
fn on_point(&mut self, pt: DVec3) -> CmdResult {
|
fn on_point(&mut self, pt: DVec3) -> CmdResult {
|
||||||
match self.a {
|
match self.step {
|
||||||
None => {
|
RectStep::FirstCorner => {
|
||||||
self.a = Some(pt);
|
let mut local = self.plane.to_local(pt);
|
||||||
|
local.z = self.elevation;
|
||||||
|
self.first = Some(self.plane.to_world(local));
|
||||||
|
self.step = RectStep::Opposite;
|
||||||
CmdResult::NeedPoint
|
CmdResult::NeedPoint
|
||||||
}
|
}
|
||||||
Some(a) => {
|
RectStep::Opposite => self.finish(pt, None),
|
||||||
let c = ucs_box_corners(a, pt, self.plane);
|
RectStep::PlaceSized { width, height } => {
|
||||||
CmdResult::CommitAndExit(make_pline(&c, self.plane))
|
self.finish(pt, Some((width, height)))
|
||||||
}
|
}
|
||||||
|
RectStep::Rotation => {
|
||||||
|
let Some(first) = self.first else {
|
||||||
|
return CmdResult::NeedPoint;
|
||||||
|
};
|
||||||
|
let delta = self.plane.vector_to_local(pt - first);
|
||||||
|
if delta.x.hypot(delta.y) <= 1.0e-9 {
|
||||||
|
return CmdResult::NeedPoint;
|
||||||
|
}
|
||||||
|
self.rotation_deg = delta.y.atan2(delta.x).to_degrees();
|
||||||
|
defaults::set_rect_rotation(self.rotation_deg);
|
||||||
|
self.step = RectStep::Opposite;
|
||||||
|
CmdResult::NeedPoint
|
||||||
|
}
|
||||||
|
_ => CmdResult::NeedPoint,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn on_enter(&mut self) -> CmdResult {
|
fn on_enter(&mut self) -> CmdResult {
|
||||||
CmdResult::Cancel
|
match self.step {
|
||||||
|
RectStep::FirstCorner => CmdResult::Cancel,
|
||||||
|
RectStep::ChamferFirst => {
|
||||||
|
self.step = RectStep::ChamferSecond;
|
||||||
|
CmdResult::NeedPoint
|
||||||
|
}
|
||||||
|
RectStep::ChamferSecond => {
|
||||||
|
self.fillet_radius = 0.0;
|
||||||
|
defaults::set_rect_fillet(0.0);
|
||||||
|
self.step = RectStep::FirstCorner;
|
||||||
|
CmdResult::NeedPoint
|
||||||
|
}
|
||||||
|
RectStep::Elevation
|
||||||
|
| RectStep::Fillet
|
||||||
|
| RectStep::Thickness
|
||||||
|
| RectStep::Width => {
|
||||||
|
self.step = RectStep::FirstCorner;
|
||||||
|
CmdResult::NeedPoint
|
||||||
|
}
|
||||||
|
RectStep::Rotation => {
|
||||||
|
self.step = RectStep::Opposite;
|
||||||
|
CmdResult::NeedPoint
|
||||||
|
}
|
||||||
|
RectStep::AreaBasis(area) => {
|
||||||
|
self.step = RectStep::AreaDimension {
|
||||||
|
area,
|
||||||
|
by_length: true,
|
||||||
|
};
|
||||||
|
CmdResult::NeedPoint
|
||||||
|
}
|
||||||
|
_ => CmdResult::NeedPoint,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
fn on_escape(&mut self) -> CmdResult {
|
fn on_escape(&mut self) -> CmdResult {
|
||||||
CmdResult::Cancel
|
CmdResult::Cancel
|
||||||
}
|
}
|
||||||
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> {
|
fn on_mouse_move(&mut self, pt: DVec3) -> Option<WireModel> {
|
||||||
let a = self.a?;
|
let first = self.first?;
|
||||||
let c = ucs_box_corners(a, pt, self.plane);
|
let fixed_dimensions = match self.step {
|
||||||
Some(wire_loop(vec![
|
RectStep::PlaceSized { width, height } => Some((width, height)),
|
||||||
[c[0].x, c[0].y, c[0].z],
|
RectStep::Opposite => None,
|
||||||
[c[1].x, c[1].y, c[1].z],
|
_ => return None,
|
||||||
[c[2].x, c[2].y, c[2].z],
|
};
|
||||||
[c[3].x, c[3].y, c[3].z],
|
let corners = rectangle_corners(
|
||||||
]))
|
first,
|
||||||
|
pt,
|
||||||
|
self.plane,
|
||||||
|
self.rotation_deg,
|
||||||
|
fixed_dimensions,
|
||||||
|
)?;
|
||||||
|
Some(rectangle_wire(corners, self.plane, self.style()))
|
||||||
}
|
}
|
||||||
fn dyn_spec(&self) -> Option<crate::command::DynSpec> {
|
fn dyn_spec(&self) -> Option<crate::command::DynSpec> {
|
||||||
use crate::command::{DynAnchor, DynFieldSpec, DynGuide, DynRole, DynSpec};
|
use crate::command::{DynAnchor, DynFieldSpec, DynGuide, DynRole, DynSpec};
|
||||||
// Opposite corner: enter width and height (signed deltas from the first
|
if !matches!(self.step, RectStep::Opposite) || self.rotation_deg.abs() > 1.0e-9 {
|
||||||
// corner), with the rectangle drawn as the guide. First corner is a
|
return None;
|
||||||
// normal point pick.
|
}
|
||||||
self.a.map(|a| DynSpec {
|
self.first.map(|first| DynSpec {
|
||||||
anchor: DynAnchor::Point(a),
|
anchor: DynAnchor::Point(first),
|
||||||
fields: vec![
|
fields: vec![
|
||||||
DynFieldSpec::new(DynRole::Width),
|
DynFieldSpec::new(DynRole::Width),
|
||||||
DynFieldSpec::new(DynRole::Height),
|
DynFieldSpec::new(DynRole::Height),
|
||||||
|
|
@ -305,6 +754,9 @@ impl CadCommand for RectRotCommand {
|
||||||
CmdResult::NeedPoint
|
CmdResult::NeedPoint
|
||||||
}
|
}
|
||||||
1 => {
|
1 => {
|
||||||
|
if plane_distance(self.a, pt, self.plane) <= 1.0e-9 {
|
||||||
|
return CmdResult::NeedPoint;
|
||||||
|
}
|
||||||
self.b = pt;
|
self.b = pt;
|
||||||
self.step = 2;
|
self.step = 2;
|
||||||
CmdResult::NeedPoint
|
CmdResult::NeedPoint
|
||||||
|
|
@ -318,6 +770,9 @@ impl CadCommand for RectRotCommand {
|
||||||
let dir = (b - a).normalize_or_zero();
|
let dir = (b - a).normalize_or_zero();
|
||||||
let perp = DVec3::new(-dir.y, dir.x, 0.0);
|
let perp = DVec3::new(-dir.y, dir.x, 0.0);
|
||||||
let h = (pt - b).dot(perp); // signed height
|
let h = (pt - b).dot(perp); // signed height
|
||||||
|
if h.abs() <= 1.0e-9 {
|
||||||
|
return CmdResult::NeedPoint;
|
||||||
|
}
|
||||||
let c = b + perp * h;
|
let c = b + perp * h;
|
||||||
let d = a + perp * h;
|
let d = a + perp * h;
|
||||||
let corners = [a, b, c, d].map(|point| self.plane.to_world(point));
|
let corners = [a, b, c, d].map(|point| self.plane.to_world(point));
|
||||||
|
|
@ -420,6 +875,10 @@ impl CadCommand for RectCenCommand {
|
||||||
CmdResult::NeedPoint
|
CmdResult::NeedPoint
|
||||||
}
|
}
|
||||||
Some(c) => {
|
Some(c) => {
|
||||||
|
let delta = self.plane.vector_to_local(pt - c);
|
||||||
|
if delta.x.abs() <= 1.0e-9 || delta.y.abs() <= 1.0e-9 {
|
||||||
|
return CmdResult::NeedPoint;
|
||||||
|
}
|
||||||
let q = ucs_box_around_center(c, pt, self.plane);
|
let q = ucs_box_around_center(c, pt, self.plane);
|
||||||
CmdResult::CommitAndExit(make_pline(&q, self.plane))
|
CmdResult::CommitAndExit(make_pline(&q, self.plane))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue