From 368dd0d7a5d00e59d66184b8f460187f0f466bf8 Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Thu, 15 Oct 2026 01:25:35 +0300 Subject: [PATCH] TRIM / EXTEND: Standard mode and TRIMEXTENDMODE (#1733) Both commands gain a mOde option [Quick/Standard]. Quick (the default) starts ready to pick, every object a cutting or boundary edge; Standard starts by selecting those edges, Enter with none taking every object. The choice is TRIMEXTENDMODE (1 Quick, 0 Standard), kept in the profile and settable with SETVAR. Co-Authored-By: Claude Opus 5.5 --- src/app/commands/mod.rs | 1 + src/app/commands/styleprops.rs | 28 +++++++++ src/app/settings.rs | 3 + src/app/update/file.rs | 2 + src/modules/draw/modify/trim.rs | 105 ++++++++++++++++++++++++++++++-- 5 files changed, 135 insertions(+), 4 deletions(-) diff --git a/src/app/commands/mod.rs b/src/app/commands/mod.rs index 82415f89e..807e33528 100644 --- a/src/app/commands/mod.rs +++ b/src/app/commands/mod.rs @@ -756,6 +756,7 @@ inventory::submit!(crate::command::CommandRegistration { "UOSNAP", "FIELDDISPLAY", "FILEDIA", + "TRIMEXTENDMODE", "SURFACEASSOCIATIVITY", "LAYOUTCREATEVIEWPORT", "FIELDEVAL", diff --git a/src/app/commands/styleprops.rs b/src/app/commands/styleprops.rs index 8063e12f5..77683445d 100644 --- a/src/app/commands/styleprops.rs +++ b/src/app/commands/styleprops.rs @@ -1214,6 +1214,7 @@ impl OpenCADStudio { | "UOSNAP" | "FIELDDISPLAY" | "FILEDIA" + | "TRIMEXTENDMODE" | "SURFACEASSOCIATIVITY" | "UCSORTHO" | "UCSVIEW" @@ -1603,6 +1604,33 @@ impl OpenCADStudio { } return Some(self.finish_dispatch(cmd)); } + if name == "TRIMEXTENDMODE" { + use crate::modules::draw::modify::trim; + let current = i16::from(trim::quick_mode()); + let prompt = format!("Enter new value for TRIMEXTENDMODE <{current}>:"); + match value + .as_deref() + .map(|v| v.trim().parse::().ok().filter(|v| (0..=1).contains(v))) + { + Some(Some(mode)) => { + if current != mode { + trim::set_quick_mode(mode == 1); + self.save_config(); + } + } + Some(None) => { + self.command_line + .push_error(crate::t!("Requires 0 or 1 only.").as_ref()); + self.command_line.push_output(&prompt); + self.pending_setvar = Some(name.clone()); + } + None => { + self.command_line.push_output(&prompt); + self.pending_setvar = Some(name.clone()); + } + } + return Some(self.finish_dispatch(cmd)); + } if name == "FILEDIA" { let current = i16::from(crate::sys::file_dia()); match value diff --git a/src/app/settings.rs b/src/app/settings.rs index d60a06421..eac5429e5 100644 --- a/src/app/settings.rs +++ b/src/app/settings.rs @@ -441,6 +441,8 @@ pub struct UserSettings { pub xref_fade: i32, /// FILEDIA: file dialogs of commands that have a command-line form. pub file_dia: bool, + /// TRIMEXTENDMODE: TRIM and EXTEND start in Quick mode (else Standard). + pub trim_extend_quick: bool, /// The file type ATTACH's picker was last left on (1-based). pub attach_filter: u32, /// SURFACEASSOCIATIVITY: new surfaces stay associative to their sources. @@ -915,6 +917,7 @@ impl Default for UserSettings { field_display: true, xref_fade: 50, file_dia: true, + trim_extend_quick: true, attach_filter: 7, surface_associativity: true, ucs_ortho: true, diff --git a/src/app/update/file.rs b/src/app/update/file.rs index 7356ee7c1..45274cb65 100644 --- a/src/app/update/file.rs +++ b/src/app/update/file.rs @@ -810,6 +810,7 @@ impl OpenCADStudio { field_display: crate::entities::field::display(), xref_fade: crate::scene::cache::block_cache::xref_fade_ctl(), file_dia: crate::sys::file_dia(), + trim_extend_quick: crate::modules::draw::modify::trim::quick_mode(), attach_filter: crate::sys::attach_filter(), surface_associativity: crate::scene::model::sweep_model::surface_associativity(), ucs_ortho: crate::modules::view::view_cmd::ucs_ortho(), @@ -946,6 +947,7 @@ impl OpenCADStudio { crate::scene::cache::block_cache::set_xref_fade_ctl(s.xref_fade); self.ribbon.xref_fade = crate::scene::cache::block_cache::xref_fade_ctl(); crate::sys::set_file_dia(s.file_dia); + crate::modules::draw::modify::trim::set_quick_mode(s.trim_extend_quick); crate::sys::set_attach_filter(s.attach_filter); crate::scene::model::sweep_model::set_surface_associativity(s.surface_associativity); crate::modules::view::view_cmd::set_ucs_ortho(s.ucs_ortho); diff --git a/src/modules/draw/modify/trim.rs b/src/modules/draw/modify/trim.rs index 8c9faaeba..d7423baa5 100644 --- a/src/modules/draw/modify/trim.rs +++ b/src/modules/draw/modify/trim.rs @@ -1755,6 +1755,73 @@ enum TrimMode { CrossSecond([f64; 2]), /// Erase mode: each pick deletes the object; Enter returns to Pick. Erase, + /// mOde: choosing between Quick and Standard (TRIMEXTENDMODE). + ChooseMode, +} + +/// TRIMEXTENDMODE: 1 (Quick) starts TRIM and EXTEND ready to pick, every +/// object a cutting or boundary edge; 0 (Standard) starts them selecting +/// those edges first. +static QUICK_MODE: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(true); + +pub fn quick_mode() -> bool { + QUICK_MODE.load(std::sync::atomic::Ordering::Relaxed) +} + +pub fn set_quick_mode(quick: bool) { + QUICK_MODE.store(quick, std::sync::atomic::Ordering::Relaxed); +} + +/// The mOde prompt, offering the current mode as the default. +fn mode_prompt(command: &str) -> String { + let current = if quick_mode() { "Quick" } else { "Standard" }; + crate::tf!("{command} Enter a trim mode option [Quick/Standard] <{current}>:").into_owned() +} + +fn mode_options() -> Vec { + use crate::command::CmdOption; + vec![CmdOption::new("Quick", "Q"), CmdOption::new("Standard", "S")] +} + +/// The mOde option and its answer, shared by TRIM and EXTEND. Returns the +/// step's result and whether the edges must be rebuilt, or `None` for input +/// that is not about the mode. +fn choose_mode( + mode: &mut TrimMode, + edge_set: &mut Vec, + text: &str, +) -> Option<(CmdResult, bool)> { + let word = text.trim().to_uppercase(); + match (&*mode, word.as_str()) { + (TrimMode::Pick, "O" | "MODE") => *mode = TrimMode::ChooseMode, + (TrimMode::ChooseMode, "Q" | "QUICK") => { + set_quick_mode(true); + edge_set.clear(); + *mode = TrimMode::Pick; + return Some((CmdResult::NeedPoint, true)); + } + (TrimMode::ChooseMode, "S" | "STANDARD") => { + set_quick_mode(false); + edge_set.clear(); + *mode = TrimMode::SelectEdges; + } + (TrimMode::ChooseMode, _) => { + let message = crate::t!("Invalid option keyword.").into_owned(); + return Some((CmdResult::ReportError(message), false)); + } + _ => return None, + } + Some((CmdResult::NeedPoint, false)) +} + +/// The first step: picking in Quick mode or when edges were preselected, +/// selecting edges in Standard mode. +fn starting_mode(initial_edges: &[Handle]) -> TrimMode { + if quick_mode() || !initial_edges.is_empty() { + TrimMode::Pick + } else { + TrimMode::SelectEdges + } } #[derive(Clone, Copy)] @@ -2622,6 +2689,14 @@ pub struct TrimCommand { } impl TrimCommand { + fn choose_mode(&mut self, text: &str) -> Option { + let (result, rebuild) = choose_mode(&mut self.mode, &mut self.edge_set, text)?; + if rebuild { + self.rebuild_geos(); + } + Some(result) + } + pub fn new(all_entities: Vec) -> Self { Self::with_cutting_edges(all_entities, Vec::new()) } @@ -2638,7 +2713,7 @@ impl TrimCommand { all_entities, entity_index, geos: Vec::new(), - mode: TrimMode::Pick, + mode: starting_mode(&initial_edges), edge_set: initial_edges, implied_edges: false, shift: false, @@ -2745,6 +2820,7 @@ impl CadCommand for TrimCommand { TrimMode::Erase => { t!("TRIM Erase: click objects to delete [Enter = done]:").into_owned() } + TrimMode::ChooseMode => mode_prompt("TRIM"), } } @@ -2764,8 +2840,10 @@ impl CadCommand for TrimCommand { "E", ), CmdOption::new("Erase", "R"), + CmdOption::new("mOde", "O"), CmdOption::enter("Done"), ], + TrimMode::ChooseMode => mode_options(), _ => vec![CmdOption::enter("Done")], } } @@ -2777,6 +2855,9 @@ impl CadCommand for TrimCommand { fn on_text_input(&mut self, text: &str) -> Option { // Consumed inputs return Some(NeedPoint) — None would be offered to // the command a second time by the driver. + if let Some(result) = self.choose_mode(text) { + return Some(result); + } match text.trim().to_uppercase().as_str() { "T" | "CUTTING" | "B" | "BOUNDARY" => { self.edge_set.clear(); @@ -3325,7 +3406,8 @@ impl CadCommand for TrimCommand { TrimMode::Fence(_) | TrimMode::CrossFirst | TrimMode::CrossSecond(_) - | TrimMode::Erase => CmdResult::NeedPoint, + | TrimMode::Erase + | TrimMode::ChooseMode => CmdResult::NeedPoint, TrimMode::Pick => CmdResult::Cancel, } } @@ -3357,6 +3439,14 @@ pub struct ExtendCommand { } impl ExtendCommand { + fn choose_mode(&mut self, text: &str) -> Option { + let (result, rebuild) = choose_mode(&mut self.mode, &mut self.edge_set, text)?; + if rebuild { + self.rebuild_geos(); + } + Some(result) + } + pub fn new(all_entities: Vec) -> Self { Self::with_boundary_edges(all_entities, Vec::new()) } @@ -3373,7 +3463,7 @@ impl ExtendCommand { all_entities, entity_index, geos: Vec::new(), - mode: TrimMode::Pick, + mode: starting_mode(&initial_edges), edge_set: initial_edges, implied_edges: false, shift: false, @@ -3443,6 +3533,7 @@ impl CadCommand for ExtendCommand { crate::tf!("EXTEND{edge} Crossing: opposite corner:").into_owned() } TrimMode::Erase => t!("EXTEND [Enter = done]:").into_owned(), + TrimMode::ChooseMode => mode_prompt("EXTEND"), } } @@ -3461,8 +3552,10 @@ impl CadCommand for ExtendCommand { }, "E", ), + CmdOption::new("mOde", "O"), CmdOption::enter("Done"), ], + TrimMode::ChooseMode => mode_options(), _ => vec![CmdOption::enter("Done")], } } @@ -3472,6 +3565,9 @@ impl CadCommand for ExtendCommand { } fn on_text_input(&mut self, text: &str) -> Option { + if let Some(result) = self.choose_mode(text) { + return Some(result); + } match text.trim().to_uppercase().as_str() { "B" | "BOUNDARY" | "T" | "CUTTING" => { self.edge_set.clear(); @@ -3788,7 +3884,8 @@ impl CadCommand for ExtendCommand { TrimMode::Fence(_) | TrimMode::CrossFirst | TrimMode::CrossSecond(_) - | TrimMode::Erase => CmdResult::NeedPoint, + | TrimMode::Erase + | TrimMode::ChooseMode => CmdResult::NeedPoint, TrimMode::Pick => CmdResult::Cancel, } }