forked from 1b-agent/opencadstudio-upstream
fix(command): support direct distance entry when dynamic input is disabled
This commit is contained in:
1 parent
960d51d955
commit
33dfbf9230
3 files changed
+149
-19
No files matched your search
@@ -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
@@ -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).
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user