fix(command): support direct distance entry when dynamic input is disabled

This commit is contained in:
yztink committed 2026-09-21 23:00:04 -07:00
1 parent 960d51d955
commit 33dfbf9230
3 files changed
+149 -19

No files matched your search

+10
View File
@@ -929,6 +929,16 @@ impl OpenCADStudio {
self.push_ucs_to_cmd(i);
return self.feed_command(StepInput::Point(wcs));
} else {
let consumed = self.tabs[i]
.active_cmd
.as_mut()
.and_then(|c| c.on_text_input(token));
if let Some(r) = consumed {
return self.apply_cmd_result(r);
}
if let Some(task) = self.try_direct_distance_entry(token) {
return task;
}
return self.feed_command(StepInput::Text(token.to_string()));
}
}
+70 -19
View File
@@ -778,25 +778,9 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
// OTRACK and Extension both allow a bare scalar to act as a
// distance measured along the active reference ray.
if let Some((base, dir)) = self.active_distance_ray(i) {
if let Some(dist) = crate::app::expr_eval::eval_number(text.trim()) {
let pt = base + dir * dist;
if !self.command_point_allowed(i, pt) {
return Task::none();
}
self.last_point = Some(pt);
self.dyn_user_reshaped = false;
self.dyn_coord_absolute = false;
self.sync_dyn_fields();
self.reset_tracking_after_point();
self.push_ucs_to_cmd(i);
let result = self.tabs[i].active_cmd.as_mut().map(|c| c.on_point(pt));
if let Some(r) = result {
let task = self.apply_cmd_result(r);
self.refresh_active_cmd_preview(i);
return task;
}
return Task::none();
if self.active_distance_ray(i).is_some() {
if let Some(task) = self.try_direct_distance_entry(&text) {
return task;
}
}
@@ -861,6 +845,13 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
return self.apply_cmd_result(result);
}
// Direct distance entry: if an anchor exists and a scalar distance was entered,
// project along the active reference ray (if any) or the current cursor direction
// in the active UCS plane.
if let Some(task) = self.try_direct_distance_entry(&text) {
return task;
}
self.command_line.push_error(crate::tf!(
"Expected Cartesian, polar, cylindrical or spherical coordinates, or a number; got: \"{text}\""
).as_ref());
@@ -879,6 +870,66 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
Task::none()
}
/// Attempt direct distance entry: if an active command is expecting a point,
/// has an established anchor, and `text` parses as a scalar distance,
/// project along the active reference ray (if any) or the current cursor
/// direction in the active UCS plane.
pub(in crate::app) fn try_direct_distance_entry(&mut self, text: &str) -> Option<Task<Message>> {
let i = self.active_tab;
let not_entity_pick = !self.tabs[i]
.active_cmd
.as_ref()
.map(|c| c.needs_entity_pick())
.unwrap_or(false);
if !not_entity_pick {
return None;
}
let anchor = self.tabs[i]
.active_cmd
.as_ref()
.and_then(|c| c.resolved_anchor())
.or(self.tabs[i].dyn_anchor)
.or(self.last_point)?;
let dist = crate::entities::common::parse_length(text.trim())
.or_else(|| crate::app::expr_eval::eval_number(text.trim()))?;
let pt = if let Some((base, dir)) = self.active_distance_ray(i) {
base + dir * dist
} else {
let w = self.tabs[i].last_cursor_world;
let xf = self.tabs[i].ucs_xform();
let d_ucs = xf.vec_to_ucs(w - anchor);
let dx = d_ucs.x;
let dy = d_ucs.y;
let dir_ucs = if (dx * dx + dy * dy) > 1e-12 {
glam::DVec3::new(dx, dy, 0.0).normalize()
} else if d_ucs.length_squared() > 1e-12 {
d_ucs.normalize()
} else {
glam::DVec3::X
};
anchor + xf.vec_to_wcs(dir_ucs * dist)
};
if !self.command_point_allowed(i, pt) {
return Some(Task::none());
}
self.last_point = Some(pt);
self.dyn_user_reshaped = false;
self.dyn_coord_absolute = false;
self.sync_dyn_fields();
self.reset_tracking_after_point();
self.push_ucs_to_cmd(i);
let result = self.tabs[i].active_cmd.as_mut().map(|c| c.on_point(pt))?;
let task = self.apply_cmd_result(result);
self.refresh_active_cmd_preview(i);
Some(task)
}
/// The active command's current step collects free-form prose from the
/// command line. Single decision point for the routes that type into
/// the buffer (CommandInput, Shift+Enter, the view's on_submit wiring).
+69
View File
@@ -940,4 +940,73 @@ mod transparent_tests {
assert!(app.snapper.snap_enabled);
});
}
#[test]
fn test_direct_distance_entry_dyn_off() {
with_stack(|| {
let mut app = line_app();
app.dyn_input = false;
assert_eq!(active(&app), Some("LINE"));
// Point cursor in +X direction from origin
app.tabs[0].last_cursor_world = glam::DVec3::new(10.0, 0.0, 0.0);
// Type "50" into command line and submit
app.command_line.input = "50".to_string();
let _ = app.on_command_submit();
// Line segment should be created from (0,0) to (50,0)
let lines: Vec<_> = app.tabs[0]
.scene
.document
.entities()
.filter_map(|e| match e {
acadrust::EntityType::Line(l) => Some(l.clone()),
_ => None,
})
.collect();
assert_eq!(lines.len(), 1);
assert!((lines[0].start.x - 0.0).abs() < 1e-6 && (lines[0].start.y - 0.0).abs() < 1e-6);
assert!((lines[0].end.x - 50.0).abs() < 1e-6 && (lines[0].end.y - 0.0).abs() < 1e-6);
// Command should still be active for the next line segment
assert_eq!(active(&app), Some("LINE"));
// Point cursor in +Y direction from (50, 0)
app.tabs[0].last_cursor_world = glam::DVec3::new(50.0, 20.0, 0.0);
// Type "25" and submit
app.command_line.input = "25".to_string();
let _ = app.on_command_submit();
let lines: Vec<_> = app.tabs[0]
.scene
.document
.entities()
.filter_map(|e| match e {
acadrust::EntityType::Line(l) => Some(l.clone()),
_ => None,
})
.collect();
assert_eq!(lines.len(), 2);
assert!((lines[1].start.x - 50.0).abs() < 1e-6 && (lines[1].start.y - 0.0).abs() < 1e-6);
assert!((lines[1].end.x - 50.0).abs() < 1e-6 && (lines[1].end.y - 25.0).abs() < 1e-6);
// Point cursor in -X direction from (50, 25) using feed_active_cmd
app.tabs[0].last_cursor_world = glam::DVec3::new(0.0, 25.0, 0.0);
let _ = app.feed_active_cmd("50");
let lines: Vec<_> = app.tabs[0]
.scene
.document
.entities()
.filter_map(|e| match e {
acadrust::EntityType::Line(l) => Some(l.clone()),
_ => None,
})
.collect();
assert_eq!(lines.len(), 3);
assert!((lines[2].start.x - 50.0).abs() < 1e-6 && (lines[2].start.y - 25.0).abs() < 1e-6);
assert!((lines[2].end.x - 0.0).abs() < 1e-6 && (lines[2].end.y - 25.0).abs() < 1e-6);
});
}
}