forked from 1b-agent/opencadstudio-upstream
feat(draw): continue paths on initial Enter
Start LINE, PLINE, ARC, and SPLINE from the latest path endpoint when Enter accepts the first point. Fall back to the active UCS origin.\n\nRefs #610
This commit is contained in:
1 parent
796d7d91d0
commit
dccaf95f49
10 files changed
+175
-12
No files matched your search
@@ -4,6 +4,68 @@ use acadrust::Handle;
|
||||
use iced::Task;
|
||||
|
||||
impl OpenCADStudio {
|
||||
/// Point supplied by a bare Enter before LINE/PLINE's first click. Prefer
|
||||
/// the current endpoint of the most recently created path drawable in the
|
||||
/// active space. A loaded drawing has no runtime anchor, so recover its
|
||||
/// newest line/arc/polyline endpoint. An empty space starts at the active
|
||||
/// UCS origin (0,0,0 in user coordinates).
|
||||
fn default_draw_start(&self, i: usize) -> glam::DVec3 {
|
||||
let tab = &self.tabs[i];
|
||||
let endpoint = |entity: &acadrust::EntityType| {
|
||||
let last_grip = match entity {
|
||||
acadrust::EntityType::Line(line) => {
|
||||
return Some(glam::DVec3::new(
|
||||
line.end.x,
|
||||
line.end.y,
|
||||
line.end.z,
|
||||
));
|
||||
}
|
||||
acadrust::EntityType::Arc(_) => Some(2),
|
||||
acadrust::EntityType::LwPolyline(polyline) => {
|
||||
polyline.vertices.len().checked_sub(1)
|
||||
}
|
||||
acadrust::EntityType::Polyline(polyline) => {
|
||||
polyline.vertices.len().checked_sub(1)
|
||||
}
|
||||
acadrust::EntityType::Polyline2D(polyline) => {
|
||||
polyline.vertices.len().checked_sub(1)
|
||||
}
|
||||
acadrust::EntityType::Polyline3D(polyline) => {
|
||||
polyline.vertices.len().checked_sub(1)
|
||||
}
|
||||
_ => None,
|
||||
}?;
|
||||
crate::scene::view::dispatch::grips(entity)
|
||||
.into_iter()
|
||||
.find(|grip| grip.id == last_grip)
|
||||
.map(|grip| grip.world)
|
||||
};
|
||||
|
||||
if let Some(handle) = tab.last_draw_anchor {
|
||||
if tab.scene.entity_belongs_to_active_space(handle) {
|
||||
if let Some(point) = tab.scene.document.get_entity(handle).and_then(endpoint) {
|
||||
return point;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let recovered = tab
|
||||
.scene
|
||||
.document
|
||||
.entities()
|
||||
.filter_map(|entity| {
|
||||
let handle = entity.common().handle;
|
||||
tab.scene
|
||||
.entity_belongs_to_active_space(handle)
|
||||
.then(|| endpoint(entity).map(|point| (handle, point)))
|
||||
.flatten()
|
||||
})
|
||||
.max_by_key(|(handle, _)| handle.value())
|
||||
.map(|(_, point)| point);
|
||||
|
||||
recovered.unwrap_or_else(|| tab.ucs_origin_world())
|
||||
}
|
||||
|
||||
/// Drop cursor-relative state that was computed in the drawing space being
|
||||
/// left. This is also used by MVIEW, whose command object survives its
|
||||
/// intentional paper/model round-trip while its old-space overlays cannot.
|
||||
@@ -169,11 +231,31 @@ impl OpenCADStudio {
|
||||
}
|
||||
}
|
||||
let i = self.active_tab;
|
||||
let default_start = matches!(&input, StepInput::Enter)
|
||||
&& self.tabs[i]
|
||||
.active_cmd
|
||||
.as_ref()
|
||||
.is_some_and(|command| command.enter_accepts_default_start());
|
||||
let input = if default_start {
|
||||
StepInput::Point(self.default_draw_start(i))
|
||||
} else {
|
||||
input
|
||||
};
|
||||
if let StepInput::Point(point) = &input {
|
||||
if !self.command_point_allowed(i, *point) {
|
||||
return Task::none();
|
||||
}
|
||||
}
|
||||
if default_start {
|
||||
let StepInput::Point(point) = &input else {
|
||||
unreachable!("default command start must be a point");
|
||||
};
|
||||
self.last_point = Some(*point);
|
||||
self.dyn_user_reshaped = false;
|
||||
self.sync_dyn_fields();
|
||||
self.reset_tracking_after_point();
|
||||
self.push_ucs_to_cmd(i);
|
||||
}
|
||||
let ctrl = self.ctrl_down;
|
||||
let shift = self.shift_down;
|
||||
let result: Option<CmdResult> = {
|
||||
@@ -1245,6 +1327,15 @@ impl OpenCADStudio {
|
||||
entity,
|
||||
finish,
|
||||
} => {
|
||||
let tracks_draw_anchor = matches!(
|
||||
&entity,
|
||||
acadrust::EntityType::Line(_)
|
||||
| acadrust::EntityType::Arc(_)
|
||||
| acadrust::EntityType::LwPolyline(_)
|
||||
| acadrust::EntityType::Polyline(_)
|
||||
| acadrust::EntityType::Polyline2D(_)
|
||||
| acadrust::EntityType::Polyline3D(_)
|
||||
);
|
||||
// Replace the live entity's geometry in place, preserving its
|
||||
// handle and layer (the fresh entity from the command carries
|
||||
// defaults — a NULL handle would desync it from the document
|
||||
@@ -1263,6 +1354,9 @@ impl OpenCADStudio {
|
||||
.bump_entities(&[(handle, crate::scene::ChangeKind::Modified)]);
|
||||
self.tabs[i].dirty = true;
|
||||
}
|
||||
if tracks_draw_anchor {
|
||||
self.tabs[i].last_draw_anchor = Some(handle);
|
||||
}
|
||||
if finish {
|
||||
self.finish_live_entity_history(i, handle);
|
||||
self.tabs[i].scene.clear_preview_wire();
|
||||
@@ -1292,6 +1386,12 @@ impl OpenCADStudio {
|
||||
// document, drop its provisional history entry and keep
|
||||
// prompting. A later second point creates one fresh entry.
|
||||
self.tabs[i].scene.erase_entities(&[handle]);
|
||||
if self.tabs[i]
|
||||
.last_draw_anchor
|
||||
.is_some_and(|anchor_handle| anchor_handle == handle)
|
||||
{
|
||||
self.tabs[i].last_draw_anchor = None;
|
||||
}
|
||||
self.discard_last_undo_entry(i);
|
||||
self.tabs[i].dirty = true;
|
||||
let prompt = self.tabs[i].active_cmd.as_ref().map(|c| c.prompt());
|
||||
|
||||
@@ -132,6 +132,9 @@ pub(super) struct DocumentTab {
|
||||
/// "Previous" keyword at any Select objects prompt (#426).
|
||||
pub(super) prev_selection: Vec<acadrust::Handle>,
|
||||
pub(super) last_cmd: Option<String>,
|
||||
/// Most recently created path drawable. A fresh LINE/PLINE can accept its
|
||||
/// current final endpoint with Enter before the first click.
|
||||
pub(super) last_draw_anchor: Option<Handle>,
|
||||
pub(super) snap_result: Option<SnapResult>,
|
||||
pub(super) active_grip: Option<GripEdit>,
|
||||
pub(super) selected_grips: Vec<GripDef>,
|
||||
@@ -437,6 +440,7 @@ impl DocumentTab {
|
||||
layers: LayerPanel::default(),
|
||||
active_cmd: None,
|
||||
last_cmd: None,
|
||||
last_draw_anchor: None,
|
||||
snap_result: None,
|
||||
active_grip: None,
|
||||
selected_grips: vec![],
|
||||
|
||||
+18
-1
@@ -1775,6 +1775,16 @@ impl OpenCADStudio {
|
||||
mut entity: acadrust::EntityType,
|
||||
) -> Option<Handle> {
|
||||
let i = self.active_tab;
|
||||
let tracks_draw_anchor = self.tabs[i].active_cmd.is_some()
|
||||
&& matches!(
|
||||
&entity,
|
||||
acadrust::EntityType::Line(_)
|
||||
| acadrust::EntityType::Arc(_)
|
||||
| acadrust::EntityType::LwPolyline(_)
|
||||
| acadrust::EntityType::Polyline(_)
|
||||
| acadrust::EntityType::Polyline2D(_)
|
||||
| acadrust::EntityType::Polyline3D(_)
|
||||
);
|
||||
let layer = &self.tabs[i].active_layer;
|
||||
if layer != "0" || entity.as_entity().layer().is_empty() {
|
||||
entity.as_entity_mut().set_layer(layer.clone());
|
||||
@@ -1946,7 +1956,7 @@ impl OpenCADStudio {
|
||||
}
|
||||
|
||||
|
||||
if matches!(&entity, acadrust::EntityType::Viewport(_))
|
||||
let new_handle = if matches!(&entity, acadrust::EntityType::Viewport(_))
|
||||
&& self.tabs[i].scene.current_layout != "Model"
|
||||
{
|
||||
// Assign a unique viewport ID (max existing id + 1, min 2).
|
||||
@@ -1999,7 +2009,14 @@ impl OpenCADStudio {
|
||||
}
|
||||
} else {
|
||||
Some(self.tabs[i].scene.add_entity(entity))
|
||||
};
|
||||
|
||||
if tracks_draw_anchor {
|
||||
if let Some(handle) = new_handle {
|
||||
self.tabs[i].last_draw_anchor = Some(handle);
|
||||
}
|
||||
}
|
||||
new_handle
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -356,11 +356,7 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
||||
}
|
||||
|
||||
if text.is_empty() {
|
||||
let result = self.tabs[i].active_cmd.as_mut().map(|c| c.on_enter());
|
||||
if let Some(r) = result {
|
||||
return self.apply_cmd_result(r);
|
||||
}
|
||||
return Task::none();
|
||||
return self.feed_command(crate::command::StepInput::Enter);
|
||||
}
|
||||
|
||||
// OTRACK: while aligned to a tracking ray, a bare distance
|
||||
@@ -494,11 +490,7 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
||||
}
|
||||
let i = self.active_tab;
|
||||
if self.tabs[i].active_cmd.is_some() {
|
||||
let result = self.tabs[i].active_cmd.as_mut().map(|c| c.on_enter());
|
||||
if let Some(r) = result {
|
||||
return self.apply_cmd_result(r);
|
||||
}
|
||||
Task::none()
|
||||
self.feed_command(crate::command::StepInput::Enter)
|
||||
} else if let Some(cmd) = self.tabs[i].last_cmd.clone() {
|
||||
self.dispatch_command(&cmd)
|
||||
} else {
|
||||
|
||||
@@ -247,7 +247,7 @@ impl OpenCADStudio {
|
||||
/// point has been committed to the active command. Temporary tracking
|
||||
/// points are reset on every input so they don't pile up across a
|
||||
/// multi-point command and overwhelm the next pick (issue #85).
|
||||
fn reset_tracking_after_point(&mut self) {
|
||||
pub(in crate::app) fn reset_tracking_after_point(&mut self) {
|
||||
self.snapper.clear_tracking();
|
||||
self.otrack_active = None;
|
||||
}
|
||||
|
||||
@@ -1279,6 +1279,14 @@ pub trait CadCommand: Send {
|
||||
/// Called when the user presses Enter (finalize / next option).
|
||||
fn on_enter(&mut self) -> CmdResult;
|
||||
|
||||
/// Whether a bare Enter should supply the drawing's continuation point as
|
||||
/// this command's first point instead of calling [`Self::on_enter`]. Draw
|
||||
/// commands opt in only while their first point is still unset; later
|
||||
/// Enter presses retain their normal finish/cancel meaning.
|
||||
fn enter_accepts_default_start(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Called when the user presses Escape (cancel).
|
||||
#[allow(dead_code)]
|
||||
fn on_escape(&mut self) -> CmdResult {
|
||||
|
||||
@@ -369,6 +369,9 @@ impl CadCommand for ArcCommand {
|
||||
fn on_enter(&mut self) -> CmdResult {
|
||||
CmdResult::Cancel
|
||||
}
|
||||
fn enter_accepts_default_start(&self) -> bool {
|
||||
self.step == 0
|
||||
}
|
||||
fn on_escape(&mut self) -> CmdResult {
|
||||
CmdResult::Cancel
|
||||
}
|
||||
@@ -467,6 +470,9 @@ impl CadCommand for Arc3PCommand {
|
||||
fn on_enter(&mut self) -> CmdResult {
|
||||
CmdResult::Cancel
|
||||
}
|
||||
fn enter_accepts_default_start(&self) -> bool {
|
||||
self.pts.is_empty()
|
||||
}
|
||||
fn on_escape(&mut self) -> CmdResult {
|
||||
CmdResult::Cancel
|
||||
}
|
||||
@@ -564,6 +570,9 @@ impl CadCommand for ArcSCECommand {
|
||||
fn on_enter(&mut self) -> CmdResult {
|
||||
CmdResult::Cancel
|
||||
}
|
||||
fn enter_accepts_default_start(&self) -> bool {
|
||||
self.step == 0
|
||||
}
|
||||
fn on_escape(&mut self) -> CmdResult {
|
||||
CmdResult::Cancel
|
||||
}
|
||||
@@ -664,6 +673,9 @@ impl CadCommand for ArcSCACommand {
|
||||
fn on_enter(&mut self) -> CmdResult {
|
||||
CmdResult::Cancel
|
||||
}
|
||||
fn enter_accepts_default_start(&self) -> bool {
|
||||
self.step == 0
|
||||
}
|
||||
fn on_escape(&mut self) -> CmdResult {
|
||||
CmdResult::Cancel
|
||||
}
|
||||
@@ -787,6 +799,9 @@ impl CadCommand for ArcSCLCommand {
|
||||
fn on_enter(&mut self) -> CmdResult {
|
||||
CmdResult::Cancel
|
||||
}
|
||||
fn enter_accepts_default_start(&self) -> bool {
|
||||
self.step == 0
|
||||
}
|
||||
fn on_escape(&mut self) -> CmdResult {
|
||||
CmdResult::Cancel
|
||||
}
|
||||
@@ -886,6 +901,9 @@ impl CadCommand for ArcSEACommand {
|
||||
fn on_enter(&mut self) -> CmdResult {
|
||||
CmdResult::Cancel
|
||||
}
|
||||
fn enter_accepts_default_start(&self) -> bool {
|
||||
self.step == 0
|
||||
}
|
||||
fn on_escape(&mut self) -> CmdResult {
|
||||
CmdResult::Cancel
|
||||
}
|
||||
@@ -961,6 +979,9 @@ impl CadCommand for ArcSERCommand {
|
||||
fn on_enter(&mut self) -> CmdResult {
|
||||
CmdResult::Cancel
|
||||
}
|
||||
fn enter_accepts_default_start(&self) -> bool {
|
||||
self.step == 0
|
||||
}
|
||||
fn on_escape(&mut self) -> CmdResult {
|
||||
CmdResult::Cancel
|
||||
}
|
||||
@@ -1074,6 +1095,9 @@ impl CadCommand for ArcSEDCommand {
|
||||
fn on_enter(&mut self) -> CmdResult {
|
||||
CmdResult::Cancel
|
||||
}
|
||||
fn enter_accepts_default_start(&self) -> bool {
|
||||
self.step == 0
|
||||
}
|
||||
fn on_escape(&mut self) -> CmdResult {
|
||||
CmdResult::Cancel
|
||||
}
|
||||
@@ -1160,6 +1184,9 @@ impl CadCommand for ArcCSACommand {
|
||||
fn on_enter(&mut self) -> CmdResult {
|
||||
CmdResult::Cancel
|
||||
}
|
||||
fn enter_accepts_default_start(&self) -> bool {
|
||||
self.step == 0
|
||||
}
|
||||
fn on_escape(&mut self) -> CmdResult {
|
||||
CmdResult::Cancel
|
||||
}
|
||||
@@ -1279,6 +1306,9 @@ impl CadCommand for ArcCSLCommand {
|
||||
fn on_enter(&mut self) -> CmdResult {
|
||||
CmdResult::Cancel
|
||||
}
|
||||
fn enter_accepts_default_start(&self) -> bool {
|
||||
self.step == 0
|
||||
}
|
||||
fn on_escape(&mut self) -> CmdResult {
|
||||
CmdResult::Cancel
|
||||
}
|
||||
|
||||
@@ -150,6 +150,10 @@ impl CadCommand for LineCommand {
|
||||
CmdResult::Cancel
|
||||
}
|
||||
|
||||
fn enter_accepts_default_start(&self) -> bool {
|
||||
self.points.is_empty()
|
||||
}
|
||||
|
||||
fn on_escape(&mut self) -> CmdResult {
|
||||
CmdResult::Cancel
|
||||
}
|
||||
|
||||
@@ -355,6 +355,10 @@ impl CadCommand for PlineCommand {
|
||||
}
|
||||
}
|
||||
|
||||
fn enter_accepts_default_start(&self) -> bool {
|
||||
self.vertices.is_empty()
|
||||
}
|
||||
|
||||
fn on_escape(&mut self) -> CmdResult {
|
||||
match self.live_handle {
|
||||
Some(handle) => CmdResult::FinalizeLiveEntity(handle),
|
||||
|
||||
@@ -143,6 +143,10 @@ impl CadCommand for SplineCommand {
|
||||
}
|
||||
}
|
||||
|
||||
fn enter_accepts_default_start(&self) -> bool {
|
||||
self.pts.is_empty()
|
||||
}
|
||||
|
||||
fn on_escape(&mut self) -> CmdResult {
|
||||
match self.build(false) {
|
||||
Some(e) => CmdResult::CommitAndExit(e),
|
||||
|
||||
Reference in new issue
Block a user