fix(stretch): let the selection stage behave like every other one

STRETCH ended itself when a crossing window caught nothing, so a missed
aim meant restarting the command to try again. A missed aim is not a
decision to stop: say what happened and ask for the corner afresh, the
way a selection that picks nothing leaves MOVE still asking.

Enter now ends the selection stage too. With objects already picked the
window is only there to narrow them, so there is nothing left to choose
— Enter takes a window containing all of them, which stretches every
vertex, which is the whole selection moving. That is what a
pick-selected stretch means. With nothing picked there is nothing to
end, so Enter still cancels. The prompt says so.

Not changed: with a prior selection, a window that misses every vertex
still goes on to ask for the base point. The command holds wires, not
entities, and the entity rules do not follow the wires — a small window
inside a circle catches its centre, and so the whole circle, while no
tessellated vertex is inside. Guarding on the wires would refuse that
stretch.

Closes #676

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Hakan Seven 2026-08-08 00:42:16 +03:00
commit eb513ac5f8
2 changed files with 66 additions and 12 deletions

View file

@ -2320,21 +2320,25 @@ impl OpenCADStudio {
.filter(|&h| !scene.is_layer_locked(h)),
);
}
if handles.is_empty() {
use crate::command::CadCommand;
use crate::modules::draw::modify::stretch::StretchCommand;
// A window that caught nothing is a missed aim, not a decision
// to stop. Ending the command there made the user restart it to
// try again; instead say so and ask for the corner afresh, the
// way a selection that picks nothing leaves MOVE still asking.
// (#676)
let cmd = if handles.is_empty() {
self.command_line
.push_output(crate::t!("STRETCH: nothing crosses the window.").as_ref());
self.tabs[i].active_cmd = None;
self.tabs[i].snap_result = None;
self.tabs[i].scene.clear_preview_wire();
self.restore_pre_cmd_tangent();
StretchCommand::new(Vec::new(), Vec::new())
} else {
use crate::command::CadCommand;
use crate::modules::draw::modify::stretch::StretchCommand;
let wires = self.tabs[i].scene.wire_models_for(&handles);
let cmd = StretchCommand::with_window(handles, wires, win_min, win_max);
self.command_line.push_info(&CadCommand::prompt(&cmd));
self.tabs[i].active_cmd = Some(Box::new(cmd));
}
StretchCommand::with_window(handles, wires, win_min, win_max)
};
self.tabs[i].snap_result = None;
self.tabs[i].scene.clear_preview_wire();
self.command_line.push_info(&CadCommand::prompt(&cmd));
self.tabs[i].active_cmd = Some(Box::new(cmd));
}
CmdResult::StretchEntities {
handles,