forked from 1b-agent/opencadstudio-upstream
feat(command): add MTP/M2P midpoint-between-two-points modifier
Add MTP (Midpoint Between Two Points) as a keyword that can be typed during any point-picking command (e.g. LINE) to suspend the current command, prompt for two points, and return the midpoint to the parent. - New Mid2PointCommand registered under both "MTP" and "M2P" aliases - ReturnPoint CmdResult variant feeds the resolved midpoint back to the suspended parent command - MTP/M2P typed during a point step suspends the active command and starts the midpoint modifier; Escape restores the parent command - Preview wires show a rubber-band line with a triangle marker at the midpoint - Character-by-character typing is routed correctly even with dynamic input active (digits no longer leak into dyn fields) - Three automation tests cover the full workflow, ESC restoration, and dynamic-input character routing
This commit is contained in:
1 parent
f4eeba75df
commit
f77c337af4
4 files changed
+330
-9
No files matched your search
@@ -1513,4 +1513,101 @@ mod tests {
|
|||||||
// Finish
|
// Finish
|
||||||
let _ = app.update(Message::CommandOptionPick(String::new()));
|
let _ = app.update(Message::CommandOptionPick(String::new()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_mtp_in_line_command() {
|
||||||
|
use crate::app::Message;
|
||||||
|
let mut app = OpenCADStudio::new_for_test();
|
||||||
|
app.automation_op(r#"{"op":"new"}"#);
|
||||||
|
{
|
||||||
|
app.tabs[0].scene.selection.borrow_mut().vp_size = (1920.0, 1080.0);
|
||||||
|
app.tabs[0].scene.sync_tiles_from_panes(1920.0, 1080.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start LINE
|
||||||
|
let _ = app.update(Message::CommandInput("LINE".to_string()));
|
||||||
|
let _ = app.update(Message::CommandSubmit);
|
||||||
|
assert_eq!(app.tabs[0].active_cmd.as_ref().map(|c| c.name()), Some("LINE"));
|
||||||
|
|
||||||
|
// Type M2P
|
||||||
|
let _ = app.update(Message::CommandInput("M2P".to_string()));
|
||||||
|
let _ = app.update(Message::CommandSubmit);
|
||||||
|
assert_eq!(app.tabs[0].active_cmd.as_ref().map(|c| c.name()), Some("MTP"));
|
||||||
|
assert!(app.tabs[0].suspended_cmd.is_some());
|
||||||
|
assert_eq!(app.tabs[0].suspended_cmd.as_ref().map(|c| c.name()), Some("LINE"));
|
||||||
|
|
||||||
|
// Point 1: 0,0
|
||||||
|
let _ = app.update(Message::CommandInput("0,0".to_string()));
|
||||||
|
let _ = app.update(Message::CommandSubmit);
|
||||||
|
assert_eq!(app.tabs[0].active_cmd.as_ref().map(|c| c.name()), Some("MTP"));
|
||||||
|
|
||||||
|
// Point 2: 10,20
|
||||||
|
let _ = app.update(Message::CommandInput("10,20".to_string()));
|
||||||
|
let _ = app.update(Message::CommandSubmit);
|
||||||
|
|
||||||
|
// MTP should have finished and restored LINE, with midpoint (5, 10, 0)
|
||||||
|
assert_eq!(app.tabs[0].active_cmd.as_ref().map(|c| c.name()), Some("LINE"));
|
||||||
|
assert!(app.tabs[0].suspended_cmd.is_none());
|
||||||
|
assert_eq!(app.last_point, Some(glam::DVec3::new(5.0, 10.0, 0.0)));
|
||||||
|
|
||||||
|
// Cancel LINE
|
||||||
|
let _ = app.update(Message::CommandEscape);
|
||||||
|
assert!(app.tabs[0].active_cmd.is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_mtp_escape_restores_parent() {
|
||||||
|
use crate::app::Message;
|
||||||
|
let mut app = OpenCADStudio::new_for_test();
|
||||||
|
app.automation_op(r#"{"op":"new"}"#);
|
||||||
|
|
||||||
|
// Start LINE
|
||||||
|
let _ = app.update(Message::CommandInput("LINE".to_string()));
|
||||||
|
let _ = app.update(Message::CommandSubmit);
|
||||||
|
assert_eq!(app.tabs[0].active_cmd.as_ref().map(|c| c.name()), Some("LINE"));
|
||||||
|
|
||||||
|
// Type MTP
|
||||||
|
let _ = app.update(Message::CommandInput("MTP".to_string()));
|
||||||
|
let _ = app.update(Message::CommandSubmit);
|
||||||
|
assert_eq!(app.tabs[0].active_cmd.as_ref().map(|c| c.name()), Some("MTP"));
|
||||||
|
|
||||||
|
// Escape during MTP
|
||||||
|
let _ = app.update(Message::CommandEscape);
|
||||||
|
// Parent LINE must be restored, not cancelled!
|
||||||
|
assert_eq!(app.tabs[0].active_cmd.as_ref().map(|c| c.name()), Some("LINE"));
|
||||||
|
assert!(app.tabs[0].suspended_cmd.is_none());
|
||||||
|
|
||||||
|
// Escape again cancels LINE
|
||||||
|
let _ = app.update(Message::CommandEscape);
|
||||||
|
assert!(app.tabs[0].active_cmd.is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_mtp_typing_routing_with_dyn_input() {
|
||||||
|
use crate::app::Message;
|
||||||
|
let mut app = OpenCADStudio::new_for_test();
|
||||||
|
app.automation_op(r#"{"op":"new"}"#);
|
||||||
|
app.dyn_input = true;
|
||||||
|
|
||||||
|
// Start LINE
|
||||||
|
let _ = app.update(Message::CommandInput("LINE".to_string()));
|
||||||
|
let _ = app.update(Message::CommandSubmit);
|
||||||
|
assert_eq!(app.tabs[0].active_cmd.as_ref().map(|c| c.name()), Some("LINE"));
|
||||||
|
|
||||||
|
// Simulate typing 'm', '2', 'p' character by character
|
||||||
|
let _ = app.update(Message::CommandAppendChar("m".to_string()));
|
||||||
|
assert_eq!(app.command_line.input, "M");
|
||||||
|
|
||||||
|
// The digit '2' must stay in command_line.input instead of routing to dyn fields!
|
||||||
|
let _ = app.update(Message::CommandAppendChar("2".to_string()));
|
||||||
|
assert_eq!(app.command_line.input, "M2");
|
||||||
|
|
||||||
|
let _ = app.update(Message::CommandAppendChar("p".to_string()));
|
||||||
|
assert_eq!(app.command_line.input, "M2P");
|
||||||
|
|
||||||
|
// Submit triggers MTP
|
||||||
|
let _ = app.update(Message::CommandSubmit);
|
||||||
|
assert_eq!(app.tabs[0].active_cmd.as_ref().map(|c| c.name()), Some("MTP"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -671,6 +671,29 @@ impl OpenCADStudio {
|
|||||||
}
|
}
|
||||||
return Task::none();
|
return Task::none();
|
||||||
}
|
}
|
||||||
|
let is_mtp = token.trim_start_matches('_').eq_ignore_ascii_case("MTP")
|
||||||
|
|| token.trim_start_matches('_').eq_ignore_ascii_case("M2P");
|
||||||
|
if is_mtp {
|
||||||
|
let is_point_step = !self.tabs[i]
|
||||||
|
.active_cmd
|
||||||
|
.as_ref()
|
||||||
|
.map(|c| c.input_kind().wants_text())
|
||||||
|
.unwrap_or(true)
|
||||||
|
|| self.tabs[i]
|
||||||
|
.active_cmd
|
||||||
|
.as_ref()
|
||||||
|
.map(|c| c.point_step_accepts_keywords())
|
||||||
|
.unwrap_or(false);
|
||||||
|
let not_entity_pick = !self.tabs[i]
|
||||||
|
.active_cmd
|
||||||
|
.as_ref()
|
||||||
|
.map(|c| c.needs_entity_pick())
|
||||||
|
.unwrap_or(false);
|
||||||
|
if is_point_step && not_entity_pick {
|
||||||
|
self.start_mtp_modifier(i);
|
||||||
|
return Task::none();
|
||||||
|
}
|
||||||
|
}
|
||||||
if let Some((coord, kind)) = super::helpers::parse_coord(token) {
|
if let Some((coord, kind)) = super::helpers::parse_coord(token) {
|
||||||
// Match the GUI command line: typed coordinates are in the active
|
// Match the GUI command line: typed coordinates are in the active
|
||||||
// UCS (relative offsets are rotated by the UCS axes), so a multi-
|
// UCS (relative offsets are rotated by the UCS axes), so a multi-
|
||||||
@@ -712,7 +735,22 @@ impl OpenCADStudio {
|
|||||||
/// pick-first selector relaunching MOVE on the picked set works this way).
|
/// pick-first selector relaunching MOVE on the picked set works this way).
|
||||||
/// Pure-selection commands (SELECTALL, QSELECT, …) run without an active
|
/// Pure-selection commands (SELECTALL, QSELECT, …) run without an active
|
||||||
/// command, so `was_active` is false and their selection is preserved.
|
/// command, so `was_active` is false and their selection is preserved.
|
||||||
pub(super) fn apply_cmd_result(&mut self, result: CmdResult) -> Task<Message> {
|
pub(super) fn apply_cmd_result(&mut self, mut result: CmdResult) -> Task<Message> {
|
||||||
|
let i = self.active_tab;
|
||||||
|
if let CmdResult::ReturnPoint(pt) = result {
|
||||||
|
self.tabs[i].scene.clear_preview_wire();
|
||||||
|
if let Some(mut suspended) = self.tabs[i].suspended_cmd.take() {
|
||||||
|
self.last_point = Some(pt);
|
||||||
|
let next_res = suspended.on_point(pt);
|
||||||
|
self.tabs[i].active_cmd = Some(suspended);
|
||||||
|
result = next_res;
|
||||||
|
} else {
|
||||||
|
self.tabs[i].active_cmd = None;
|
||||||
|
self.tabs[i].snap_result = None;
|
||||||
|
self.restore_pre_cmd_tangent();
|
||||||
|
return Task::none();
|
||||||
|
}
|
||||||
|
}
|
||||||
let settings = self.tabs[self.active_tab]
|
let settings = self.tabs[self.active_tab]
|
||||||
.active_cmd
|
.active_cmd
|
||||||
.as_ref()
|
.as_ref()
|
||||||
@@ -877,6 +915,18 @@ impl OpenCADStudio {
|
|||||||
TableCellEditStart::Started
|
TableCellEditStart::Started
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(in crate::app) fn start_mtp_modifier(&mut self, i: usize) {
|
||||||
|
let parent = self.tabs[i].active_cmd.take();
|
||||||
|
self.tabs[i].suspended_cmd = parent;
|
||||||
|
self.tabs[i].scene.clear_preview_wire();
|
||||||
|
let cmd = crate::command::Mid2PointCommand::new();
|
||||||
|
let prompt = crate::command::CadCommand::prompt(&cmd);
|
||||||
|
self.tabs[i].active_cmd = Some(Box::new(cmd));
|
||||||
|
self.command_line.push_info(&prompt);
|
||||||
|
self.command_line.set_step_options(Vec::new());
|
||||||
|
self.refresh_active_cmd_preview(i);
|
||||||
|
}
|
||||||
|
|
||||||
fn apply_cmd_result_inner(&mut self, result: CmdResult) -> Task<Message> {
|
fn apply_cmd_result_inner(&mut self, result: CmdResult) -> Task<Message> {
|
||||||
let i = self.active_tab;
|
let i = self.active_tab;
|
||||||
let preserve_commit_layer = self.tabs[i]
|
let preserve_commit_layer = self.tabs[i]
|
||||||
@@ -2439,14 +2489,30 @@ impl OpenCADStudio {
|
|||||||
cancel @ (CmdResult::Cancel | CmdResult::CancelForSpaceChange) => {
|
cancel @ (CmdResult::Cancel | CmdResult::CancelForSpaceChange) => {
|
||||||
let space_changed = matches!(cancel, CmdResult::CancelForSpaceChange);
|
let space_changed = matches!(cancel, CmdResult::CancelForSpaceChange);
|
||||||
self.tabs[i].scene.clear_preview_wire();
|
self.tabs[i].scene.clear_preview_wire();
|
||||||
self.tabs[i].active_cmd = None;
|
if !space_changed && self.tabs[i].suspended_cmd.is_some() {
|
||||||
self.tabs[i].snap_result = None;
|
self.tabs[i].active_cmd = self.tabs[i].suspended_cmd.take();
|
||||||
self.restore_pre_cmd_tangent();
|
let prompt = self.tabs[i].active_cmd.as_ref().map(|c| c.prompt());
|
||||||
self.command_line.push_info(crate::t!(if space_changed {
|
if let Some(p) = prompt {
|
||||||
"Command cancelled because the active drawing space changed."
|
self.command_line.push_info(&p);
|
||||||
|
}
|
||||||
|
let opts = self.tabs[i]
|
||||||
|
.active_cmd
|
||||||
|
.as_ref()
|
||||||
|
.map(|c| c.options())
|
||||||
|
.unwrap_or_default();
|
||||||
|
self.command_line.set_step_options(opts);
|
||||||
|
self.refresh_active_cmd_preview(i);
|
||||||
} else {
|
} else {
|
||||||
"Command cancelled."
|
self.tabs[i].suspended_cmd = None;
|
||||||
}).as_ref());
|
self.tabs[i].active_cmd = None;
|
||||||
|
self.tabs[i].snap_result = None;
|
||||||
|
self.restore_pre_cmd_tangent();
|
||||||
|
self.command_line.push_info(crate::t!(if space_changed {
|
||||||
|
"Command cancelled because the active drawing space changed."
|
||||||
|
} else {
|
||||||
|
"Command cancelled."
|
||||||
|
}).as_ref());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
CmdResult::SelectByPath {
|
CmdResult::SelectByPath {
|
||||||
path,
|
path,
|
||||||
@@ -4751,6 +4817,12 @@ impl OpenCADStudio {
|
|||||||
self.tabs[i].scene.clear_preview_wire();
|
self.tabs[i].scene.clear_preview_wire();
|
||||||
self.restore_pre_cmd_tangent();
|
self.restore_pre_cmd_tangent();
|
||||||
}
|
}
|
||||||
|
CmdResult::ReturnPoint(_) => {
|
||||||
|
self.tabs[i].active_cmd = None;
|
||||||
|
self.tabs[i].snap_result = None;
|
||||||
|
self.tabs[i].scene.clear_preview_wire();
|
||||||
|
self.restore_pre_cmd_tangent();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// When no command is running the ribbon tool button still has to
|
// When no command is running the ribbon tool button still has to
|
||||||
// visually deactivate. Keyboard focus is assigned below to whichever
|
// visually deactivate. Keyboard focus is assigned below to whichever
|
||||||
|
|||||||
@@ -191,7 +191,11 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
|||||||
c.is_ascii_digit()
|
c.is_ascii_digit()
|
||||||
|| matches!(c, '.' | '-' | '+' | '*' | '/' | '^' | '%' | '(' | ')')
|
|| matches!(c, '.' | '-' | '+' | '*' | '/' | '^' | '%' | '(' | ')')
|
||||||
});
|
});
|
||||||
if dyn_field_char && self.dyn_input && !self.tabs[i].dyn_fields.is_empty() {
|
if dyn_field_char
|
||||||
|
&& self.command_line.input.is_empty()
|
||||||
|
&& self.dyn_input
|
||||||
|
&& !self.tabs[i].dyn_fields.is_empty()
|
||||||
|
{
|
||||||
let a = self.tabs[i]
|
let a = self.tabs[i]
|
||||||
.dyn_active
|
.dyn_active
|
||||||
.min(self.tabs[i].dyn_fields.len() - 1);
|
.min(self.tabs[i].dyn_fields.len() - 1);
|
||||||
@@ -634,6 +638,30 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
|||||||
.map(|c| c.is_free_text_step())
|
.map(|c| c.is_free_text_step())
|
||||||
.unwrap_or(false);
|
.unwrap_or(false);
|
||||||
let raw = self.command_line.input.trim().to_string();
|
let raw = self.command_line.input.trim().to_string();
|
||||||
|
let is_mtp = raw.trim_start_matches('_').eq_ignore_ascii_case("MTP")
|
||||||
|
|| raw.trim_start_matches('_').eq_ignore_ascii_case("M2P");
|
||||||
|
if is_mtp {
|
||||||
|
let is_point_step = !self.tabs[i]
|
||||||
|
.active_cmd
|
||||||
|
.as_ref()
|
||||||
|
.map(|c| c.input_kind().wants_text())
|
||||||
|
.unwrap_or(true)
|
||||||
|
|| self.tabs[i]
|
||||||
|
.active_cmd
|
||||||
|
.as_ref()
|
||||||
|
.map(|c| c.point_step_accepts_keywords())
|
||||||
|
.unwrap_or(false);
|
||||||
|
let not_entity_pick = !self.tabs[i]
|
||||||
|
.active_cmd
|
||||||
|
.as_ref()
|
||||||
|
.map(|c| c.needs_entity_pick())
|
||||||
|
.unwrap_or(false);
|
||||||
|
if is_point_step && not_entity_pick {
|
||||||
|
self.command_line.input.clear();
|
||||||
|
self.start_mtp_modifier(i);
|
||||||
|
return self.focus_cmd_input();
|
||||||
|
}
|
||||||
|
}
|
||||||
let text = if free_text {
|
let text = if free_text {
|
||||||
raw
|
raw
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+124
@@ -744,6 +744,88 @@ impl CadCommand for TwoValuePromptCommand {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Mid between 2 points (MTP / M2P) ────────────────────────────────────────
|
||||||
|
|
||||||
|
/// Point-entry modifier that prompts for two points, draws a preview connecting line
|
||||||
|
/// with a midpoint marker, and returns the midpoint to the caller.
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct Mid2PointCommand {
|
||||||
|
pub first_point: Option<DVec3>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Mid2PointCommand {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self { first_point: None }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CadCommand for Mid2PointCommand {
|
||||||
|
fn name(&self) -> &'static str {
|
||||||
|
"MTP"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prompt(&self) -> String {
|
||||||
|
if self.first_point.is_none() {
|
||||||
|
crate::t!("_mtp Specify first point of mid:").into_owned()
|
||||||
|
} else {
|
||||||
|
crate::t!("_mtp Specify second point of mid:").into_owned()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_point(&mut self, pt: DVec3) -> CmdResult {
|
||||||
|
if let Some(first) = self.first_point {
|
||||||
|
let mid = (first + pt) * 0.5;
|
||||||
|
CmdResult::ReturnPoint(mid)
|
||||||
|
} else {
|
||||||
|
self.first_point = Some(pt);
|
||||||
|
CmdResult::NeedPoint
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_enter(&mut self) -> CmdResult {
|
||||||
|
CmdResult::Cancel
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_escape(&mut self) -> CmdResult {
|
||||||
|
CmdResult::Cancel
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_preview_wires(&mut self, pt: DVec3) -> Vec<WireModel> {
|
||||||
|
let mut wires = Vec::new();
|
||||||
|
if let Some(first) = self.first_point {
|
||||||
|
// Rubber-band line connecting the first point to the cursor
|
||||||
|
wires.push(WireModel::solid_f64(
|
||||||
|
"mtp_rubber_band".to_string(),
|
||||||
|
vec![[first.x, first.y, first.z], [pt.x, pt.y, pt.z]],
|
||||||
|
WireModel::CYAN,
|
||||||
|
false,
|
||||||
|
));
|
||||||
|
// Midpoint marker (AutoCAD style triangle glyph)
|
||||||
|
let mid = (first + pt) * 0.5;
|
||||||
|
let dist = (pt - first).length();
|
||||||
|
let s = (dist * 0.02).clamp(0.5, 10.0);
|
||||||
|
let h = s * 1.5;
|
||||||
|
let w = s * 1.0;
|
||||||
|
wires.push(WireModel::solid_f64(
|
||||||
|
"mtp_mid_triangle".to_string(),
|
||||||
|
vec![
|
||||||
|
[mid.x, mid.y + h * (2.0 / 3.0), mid.z],
|
||||||
|
[mid.x - w, mid.y - h * (1.0 / 3.0), mid.z],
|
||||||
|
[mid.x + w, mid.y - h * (1.0 / 3.0), mid.z],
|
||||||
|
],
|
||||||
|
WireModel::CYAN,
|
||||||
|
true,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
wires
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inventory::submit!(CommandRegistration {
|
||||||
|
names: &["MTP", "M2P"],
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
/// Generic interactive front-end for a keyword command that operates on the
|
/// Generic interactive front-end for a keyword command that operates on the
|
||||||
/// current selection (CHPROP, ADJUST, XDATA, UNDERLAY, DRAWORDER…). If nothing
|
/// current selection (CHPROP, ADJUST, XDATA, UNDERLAY, DRAWORDER…). If nothing
|
||||||
/// is selected when it starts it first gathers a selection (Enter confirms),
|
/// is selected when it starts it first gathers a selection (Enter confirms),
|
||||||
@@ -1629,6 +1711,8 @@ pub enum CmdResult {
|
|||||||
UndoDocument,
|
UndoDocument,
|
||||||
/// Sets the TEXTEDITMODE system variable and ends the command.
|
/// Sets the TEXTEDITMODE system variable and ends the command.
|
||||||
SetTexteditMode(bool),
|
SetTexteditMode(bool),
|
||||||
|
/// Return a resolved point back to a suspended parent command (e.g. MTP / M2P).
|
||||||
|
ReturnPoint(DVec3),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// What kind of value the active command is currently asking for. Drives
|
/// What kind of value the active command is currently asking for. Drives
|
||||||
@@ -2385,3 +2469,43 @@ pub fn all_registered_command_names() -> Vec<&'static str> {
|
|||||||
.flat_map(|r| r.names.iter().copied())
|
.flat_map(|r| r.names.iter().copied())
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_mid2point_command() {
|
||||||
|
let mut cmd = Mid2PointCommand::new();
|
||||||
|
assert_eq!(cmd.name(), "MTP");
|
||||||
|
assert_eq!(cmd.prompt(), "_mtp Specify first point of mid:");
|
||||||
|
|
||||||
|
// First point
|
||||||
|
let res = cmd.on_point(DVec3::new(10.0, 20.0, 0.0));
|
||||||
|
assert!(matches!(res, CmdResult::NeedPoint));
|
||||||
|
assert_eq!(cmd.prompt(), "_mtp Specify second point of mid:");
|
||||||
|
|
||||||
|
// Preview wires
|
||||||
|
let wires = cmd.on_preview_wires(DVec3::new(30.0, 40.0, 0.0));
|
||||||
|
assert!(!wires.is_empty());
|
||||||
|
|
||||||
|
// Second point
|
||||||
|
let res = cmd.on_point(DVec3::new(30.0, 40.0, 0.0));
|
||||||
|
assert!(matches!(res, CmdResult::ReturnPoint(mid) if mid == DVec3::new(20.0, 30.0, 0.0)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_mid2point_cancel() {
|
||||||
|
let mut cmd = Mid2PointCommand::new();
|
||||||
|
assert!(matches!(cmd.on_escape(), CmdResult::Cancel));
|
||||||
|
assert!(matches!(cmd.on_enter(), CmdResult::Cancel));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_mtp_registered() {
|
||||||
|
let names = all_registered_command_names();
|
||||||
|
assert!(names.contains(&"MTP"));
|
||||||
|
assert!(names.contains(&"M2P"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Reference in new issue
Block a user