forked from 1b-agent/opencadstudio-upstream
Convert: turn associative dimensions into dimensional constraints
The command deleted the dimension and left a literal constraint behind. The reference keeps the dimension, drives it with a new parameter and gathers only associative dimensions, saying how many objects it dropped. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
133bfddba9
commit
6d36356529
4 files changed
+296
-88
No files matched your search
@@ -432,11 +432,62 @@ impl OpenCADStudio {
|
|||||||
self.feed_command_consumed(input).0
|
self.feed_command_consumed(input).0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Keeps only associative dimensions in a completed selection when the
|
||||||
|
/// active command asks for that, deselecting the rest and reporting how
|
||||||
|
/// many objects were dropped.
|
||||||
|
fn filter_associative_dimension_selection(&mut self, input: StepInput) -> StepInput {
|
||||||
|
let StepInput::SelectionComplete(handles) = input else {
|
||||||
|
return input;
|
||||||
|
};
|
||||||
|
let i = self.active_tab;
|
||||||
|
if !self.tabs[i]
|
||||||
|
.active_cmd
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|command| command.selection_keeps_associative_dimensions())
|
||||||
|
{
|
||||||
|
return StepInput::SelectionComplete(handles);
|
||||||
|
}
|
||||||
|
let (kept, dropped): (Vec<Handle>, Vec<Handle>) = handles.into_iter().partition(|handle| {
|
||||||
|
// A dimension that already drives a constraint is not one to
|
||||||
|
// convert either.
|
||||||
|
crate::scene::parametric_constraints::dynamic_dimension_constraint(
|
||||||
|
&self.tabs[i].scene.parametric_constraints,
|
||||||
|
*handle,
|
||||||
|
)
|
||||||
|
.is_none()
|
||||||
|
&& crate::scene::dimension_assoc::constraint_from_associative_dimension(
|
||||||
|
&self.tabs[i].scene.document,
|
||||||
|
*handle,
|
||||||
|
)
|
||||||
|
.is_some()
|
||||||
|
});
|
||||||
|
for handle in &dropped {
|
||||||
|
self.tabs[i].scene.deselect_entity(*handle);
|
||||||
|
}
|
||||||
|
if !dropped.is_empty() {
|
||||||
|
let count = dropped.len();
|
||||||
|
self.command_line.push_info(
|
||||||
|
if count == 1 {
|
||||||
|
crate::t!("1 was filtered out.")
|
||||||
|
} else {
|
||||||
|
crate::tf!("{count} were filtered out.")
|
||||||
|
}
|
||||||
|
.as_ref(),
|
||||||
|
);
|
||||||
|
self.refresh_properties();
|
||||||
|
}
|
||||||
|
StepInput::SelectionComplete(kept)
|
||||||
|
}
|
||||||
|
|
||||||
/// `feed_command`, also reporting whether the step actually took the input.
|
/// `feed_command`, also reporting whether the step actually took the input.
|
||||||
/// Only a `Text` token can come back unclaimed (`on_text_input` returning
|
/// Only a `Text` token can come back unclaimed (`on_text_input` returning
|
||||||
/// `None`), which is what lets the caller read it as something else — a
|
/// `None`), which is what lets the caller read it as something else — a
|
||||||
/// typed distance, say — instead of guessing beforehand.
|
/// typed distance, say — instead of guessing beforehand.
|
||||||
pub(super) fn feed_command_consumed(&mut self, input: StepInput) -> (Task<Message>, bool) {
|
pub(super) fn feed_command_consumed(&mut self, input: StepInput) -> (Task<Message>, bool) {
|
||||||
|
// A command that takes associative dimensions only keeps just those:
|
||||||
|
// the rest leave the selection and the count of what went is reported,
|
||||||
|
// as the reference does while gathering.
|
||||||
|
let input = self.filter_associative_dimension_selection(input);
|
||||||
// Selection keywords (P / PREVIOUS, L / LAST) consume the token
|
// Selection keywords (P / PREVIOUS, L / LAST) consume the token
|
||||||
// before it reaches the command (#426).
|
// before it reaches the command (#426).
|
||||||
if let StepInput::Text(s) = &input {
|
if let StepInput::Text(s) = &input {
|
||||||
@@ -6179,15 +6230,18 @@ impl OpenCADStudio {
|
|||||||
CmdResult::Dispatch(cmd) => {
|
CmdResult::Dispatch(cmd) => {
|
||||||
// End this interactive front-end, then run the assembled command
|
// End this interactive front-end, then run the assembled command
|
||||||
// through the normal dispatcher. Selection is left untouched.
|
// through the normal dispatcher. Selection is left untouched.
|
||||||
// DIMCONSTRAINT's Radius option becomes its next default; the
|
// The option picked in DIMCONSTRAINT becomes its next default;
|
||||||
// DCRADIUS command run on its own leaves the default alone.
|
// the same command run on its own leaves the default alone.
|
||||||
if cmd == "DCRADIUS"
|
let from_menu = self.tabs[i]
|
||||||
&& self.tabs[i]
|
.active_cmd
|
||||||
.active_cmd
|
.as_ref()
|
||||||
.as_ref()
|
.is_some_and(|command| command.name() == "DIMCONSTRAINT");
|
||||||
.is_some_and(|command| command.name() == "DIMCONSTRAINT")
|
if from_menu {
|
||||||
{
|
match cmd.as_str() {
|
||||||
self.dim_constraint_last = "Radius";
|
"DCRADIUS" => self.dim_constraint_last = "Radius",
|
||||||
|
"DCCONVERT" => self.dim_constraint_last = "Convert",
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
self.tabs[i].active_cmd = None;
|
self.tabs[i].active_cmd = None;
|
||||||
self.tabs[i].snap_result = None;
|
self.tabs[i].snap_result = None;
|
||||||
|
|||||||
+191
-79
@@ -1310,92 +1310,204 @@ impl OpenCADStudio {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A pre-selection is not used: the reference always asks, and it
|
||||||
|
// gathers associative dimensions only.
|
||||||
"DCCONVERT" => {
|
"DCCONVERT" => {
|
||||||
self.dim_constraint_last = "Convert";
|
use crate::modules::draw::select::SelectObjectsCommand;
|
||||||
|
self.tabs[i].scene.deselect_all();
|
||||||
|
let sel = SelectObjectsCommand::associative_dimensions(
|
||||||
|
cmd,
|
||||||
|
"DCCONVERTAPPLY",
|
||||||
|
"Select associative dimensions to convert:",
|
||||||
|
);
|
||||||
|
self.command_line.push_info(&sel.prompt());
|
||||||
|
self.tabs[i].active_cmd = Some(Box::new(sel));
|
||||||
|
}
|
||||||
|
|
||||||
|
// The gathered dimensions become dimensional constraints: each one
|
||||||
|
// keeps its dimension, which turns into the constraint's dynamic
|
||||||
|
// (or annotational) dimension driven by a new parameter.
|
||||||
|
"DCCONVERTAPPLY" => {
|
||||||
|
use crate::scene::named_parameters::DrivingValue;
|
||||||
|
use crate::scene::parametric_constraints::{
|
||||||
|
dynamic_dimension_text, next_angular_parameter_name,
|
||||||
|
next_dimensional_parameter_name, next_radial_parameter_name,
|
||||||
|
ConstraintKind,
|
||||||
|
};
|
||||||
|
|
||||||
|
let scope = self.tabs[i].current_parametric_scope();
|
||||||
let handles = self.tabs[i].scene.selected_handles_in_order();
|
let handles = self.tabs[i].scene.selected_handles_in_order();
|
||||||
if handles.is_empty() {
|
let annotational = self.constraint_form_annotational;
|
||||||
use crate::modules::draw::select::SelectObjectsCommand;
|
let mut table = self.tabs[i].scene.named_parameters().clone();
|
||||||
let sel = SelectObjectsCommand::new(cmd);
|
let mut conversions = Vec::new();
|
||||||
self.command_line.push_info(&sel.prompt());
|
let mut refused = 0usize;
|
||||||
self.tabs[i].active_cmd = Some(Box::new(sel));
|
for handle in handles {
|
||||||
} else {
|
if self.tabs[i].scene.is_layer_locked(handle) {
|
||||||
let scope = self.tabs[i].current_parametric_scope();
|
refused += 1;
|
||||||
let conversions: Vec<_> = handles
|
continue;
|
||||||
.iter()
|
|
||||||
.copied()
|
|
||||||
.filter(|handle| !self.tabs[i].scene.is_layer_locked(*handle))
|
|
||||||
.filter_map(|handle| {
|
|
||||||
let (kind, refs, value) =
|
|
||||||
crate::scene::dimension_assoc::constraint_from_associative_dimension(
|
|
||||||
&self.tabs[i].scene.document,
|
|
||||||
handle,
|
|
||||||
)?;
|
|
||||||
self.tabs[i]
|
|
||||||
.scene
|
|
||||||
.validate_parametric_constraint(kind, &refs, Some(&value))
|
|
||||||
.ok()?;
|
|
||||||
Some((handle, kind, refs, value))
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
if conversions.is_empty() {
|
|
||||||
self.command_line
|
|
||||||
.push_output("No supported associative dimensions were selected.");
|
|
||||||
return None;
|
|
||||||
}
|
}
|
||||||
let before = self.tabs[i]
|
// A dimension already driving a constraint is not converted
|
||||||
.scene
|
// a second time.
|
||||||
.parametric_constraint_set(scope)
|
if crate::scene::parametric_constraints::dynamic_dimension_constraint(
|
||||||
.cloned()
|
&self.tabs[i].scene.parametric_constraints,
|
||||||
.unwrap_or_else(|| {
|
handle,
|
||||||
crate::scene::parametric_constraints::ParametricConstraintSet::new(
|
)
|
||||||
scope,
|
.is_some()
|
||||||
)
|
{
|
||||||
});
|
refused += 1;
|
||||||
let mut touched: Vec<_> = conversions
|
continue;
|
||||||
.iter()
|
|
||||||
.flat_map(|(_, _, refs, _)| refs.iter().map(|reference| reference.entity))
|
|
||||||
.collect();
|
|
||||||
touched.sort_unstable();
|
|
||||||
touched.dedup();
|
|
||||||
let dimensions: Vec<_> = conversions
|
|
||||||
.iter()
|
|
||||||
.map(|(handle, _, _, _)| *handle)
|
|
||||||
.collect();
|
|
||||||
let pending = self.begin_undo(
|
|
||||||
i,
|
|
||||||
"Convert dimensions to constraints",
|
|
||||||
touched.len() + dimensions.len(),
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
self.tabs[i]
|
|
||||||
.scene
|
|
||||||
.record_undo_parametric_constraints_before(scope, before);
|
|
||||||
for (_, kind, refs, value) in conversions {
|
|
||||||
let id = self.tabs[i].scene.parametric_constraint_set_mut(scope).add(
|
|
||||||
kind,
|
|
||||||
refs,
|
|
||||||
Some(value),
|
|
||||||
);
|
|
||||||
self.tabs[i].scene.note_parametric_constraint_applied(
|
|
||||||
scope,
|
|
||||||
id,
|
|
||||||
self.constraint_bar_display,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
self.tabs[i].scene.erase_entities(&dimensions);
|
let Some((kind, refs, value)) =
|
||||||
let changes: Vec<_> = touched
|
crate::scene::dimension_assoc::constraint_from_associative_dimension(
|
||||||
.into_iter()
|
&self.tabs[i].scene.document,
|
||||||
.map(|handle| (handle, crate::scene::ChangeKind::Modified))
|
handle,
|
||||||
.collect();
|
)
|
||||||
self.tabs[i].scene.bump_entities(&changes);
|
else {
|
||||||
self.tabs[i].dirty = true;
|
refused += 1;
|
||||||
self.refresh_properties();
|
continue;
|
||||||
|
};
|
||||||
|
let DrivingValue::Literal(measured) = value else {
|
||||||
|
refused += 1;
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if self.tabs[i]
|
||||||
|
.scene
|
||||||
|
.validate_parametric_constraint(kind, &refs, Some(&DrivingValue::Literal(measured)))
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
refused += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// The parameter is named after the constraint it drives,
|
||||||
|
// as a constraint made by hand would be.
|
||||||
|
let name = match kind {
|
||||||
|
ConstraintKind::Radius => next_radial_parameter_name(&table, false),
|
||||||
|
ConstraintKind::Diameter => next_radial_parameter_name(&table, true),
|
||||||
|
ConstraintKind::Angle | ConstraintKind::Angle3Point => {
|
||||||
|
next_angular_parameter_name(&table)
|
||||||
|
}
|
||||||
|
_ => next_dimensional_parameter_name(&table),
|
||||||
|
};
|
||||||
|
let expression =
|
||||||
|
crate::scene::parametric_constraints::measured_expression(measured);
|
||||||
|
if table.set(&name, &expression).is_err() {
|
||||||
|
refused += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
conversions.push((handle, kind, refs, name, expression, measured));
|
||||||
|
}
|
||||||
|
if conversions.is_empty() {
|
||||||
|
self.command_line
|
||||||
|
.push_output("0 associative dimensions converted");
|
||||||
self.command_line.push_output(
|
self.command_line.push_output(
|
||||||
format!("{} dimension(s) converted.", dimensions.len()).as_str(),
|
format!("{refused} associative dimension(s) could not be converted")
|
||||||
|
.as_str(),
|
||||||
);
|
);
|
||||||
if let Some(pd) = pending {
|
self.tabs[i].scene.deselect_all();
|
||||||
self.commit_undo_delta(i, pd);
|
self.refresh_properties();
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let constraints_before = self.tabs[i]
|
||||||
|
.scene
|
||||||
|
.parametric_constraint_set(scope)
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
crate::scene::parametric_constraints::ParametricConstraintSet::new(scope)
|
||||||
|
});
|
||||||
|
let mut touched: Vec<_> = conversions
|
||||||
|
.iter()
|
||||||
|
.flat_map(|(_, _, refs, _, _, _)| refs.iter().map(|r| r.entity))
|
||||||
|
.collect();
|
||||||
|
touched.sort_unstable();
|
||||||
|
touched.dedup();
|
||||||
|
// The constraints layer is made before the recording opens: a
|
||||||
|
// new layer inside it would cost the delta its entity undo.
|
||||||
|
if !annotational {
|
||||||
|
self.tabs[i].scene.ensure_dynamic_dimension_layer();
|
||||||
|
}
|
||||||
|
let pending = self.begin_undo(
|
||||||
|
i,
|
||||||
|
"Convert dimensions to constraints",
|
||||||
|
touched.len() + conversions.len(),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
self.tabs[i]
|
||||||
|
.scene
|
||||||
|
.record_undo_parametric_constraints_before(scope, constraints_before);
|
||||||
|
self.tabs[i].scene.record_undo_named_parameters_before();
|
||||||
|
self.tabs[i].scene.named_parameters = table;
|
||||||
|
let format = self.tabs[i].scene.constraint_name_format;
|
||||||
|
let converted = conversions.len();
|
||||||
|
for (handle, kind, refs, name, expression, measured) in conversions {
|
||||||
|
let angular =
|
||||||
|
matches!(kind, ConstraintKind::Angle | ConstraintKind::Angle3Point);
|
||||||
|
let decimals = angular.then(|| {
|
||||||
|
crate::scene::parametric_constraints::angle_decimals(
|
||||||
|
&self.tabs[i].scene.document,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
let text = dynamic_dimension_text(
|
||||||
|
&name,
|
||||||
|
measured,
|
||||||
|
format,
|
||||||
|
false,
|
||||||
|
Some(&expression),
|
||||||
|
annotational,
|
||||||
|
decimals,
|
||||||
|
);
|
||||||
|
if let Some(acadrust::EntityType::Dimension(mut dimension)) =
|
||||||
|
self.tabs[i].scene.document.get_entity(handle).cloned()
|
||||||
|
{
|
||||||
|
let before = self.tabs[i].scene.document.get_entity_arc(handle);
|
||||||
|
self.tabs[i].scene.record_undo_before(handle, before);
|
||||||
|
crate::entities::dimension::set_dimension_text_override(
|
||||||
|
dimension.base_mut(),
|
||||||
|
Some(text),
|
||||||
|
);
|
||||||
|
// The dynamic form lives on the reference's hidden
|
||||||
|
// constraints layer in the constraint grey; the
|
||||||
|
// annotational form stays where the dimension is.
|
||||||
|
if !annotational {
|
||||||
|
dimension.base_mut().common.layer =
|
||||||
|
crate::scene::parametric_constraints::DYNAMIC_DIMENSION_LAYER
|
||||||
|
.to_string();
|
||||||
|
dimension.base_mut().common.color = acadrust::types::Color::Rgb {
|
||||||
|
r: 103,
|
||||||
|
g: 109,
|
||||||
|
b: 118,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
self.tabs[i]
|
||||||
|
.scene
|
||||||
|
.update_entity(acadrust::EntityType::Dimension(dimension));
|
||||||
}
|
}
|
||||||
|
let set = self.tabs[i].scene.parametric_constraint_set_mut(scope);
|
||||||
|
let id = set.add(kind, refs, Some(DrivingValue::Named(name)));
|
||||||
|
set.dimensions.insert(id, handle);
|
||||||
|
self.tabs[i].scene.note_parametric_constraint_applied(
|
||||||
|
scope,
|
||||||
|
id,
|
||||||
|
self.constraint_bar_display,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let changes: Vec<_> = touched
|
||||||
|
.into_iter()
|
||||||
|
.map(|handle| (handle, crate::scene::ChangeKind::Modified))
|
||||||
|
.collect();
|
||||||
|
self.tabs[i].scene.bump_entities(&changes);
|
||||||
|
self.tabs[i].scene.refresh_hidden_dynamic_dimensions();
|
||||||
|
self.tabs[i].scene.refresh_dynamic_dimension_scales(true);
|
||||||
|
self.tabs[i].scene.deselect_all();
|
||||||
|
self.tabs[i].dirty = true;
|
||||||
|
self.refresh_properties();
|
||||||
|
self.command_line
|
||||||
|
.push_output(format!("{converted} associative dimensions converted").as_str());
|
||||||
|
self.command_line.push_output(
|
||||||
|
format!("{refused} associative dimension(s) could not be converted").as_str(),
|
||||||
|
);
|
||||||
|
if let Some(pd) = pending {
|
||||||
|
self.commit_undo_delta(i, pd);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2782,6 +2782,13 @@ pub trait CadCommand: Send {
|
|||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The command takes associative dimensions only. The host drops every
|
||||||
|
/// other object from a completed selection, says how many it dropped, and
|
||||||
|
/// hands the command what is left.
|
||||||
|
fn selection_keeps_associative_dimensions(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
/// Called after a selection action completes while `is_selection_gathering` is true.
|
/// Called after a selection action completes while `is_selection_gathering` is true.
|
||||||
/// `handles` is the full set of currently selected entities.
|
/// `handles` is the full set of currently selected entities.
|
||||||
/// Return `Relaunch` to fire the pending command, or `NeedPoint` to keep gathering.
|
/// Return `Relaunch` to fire the pending command, or `NeedPoint` to keep gathering.
|
||||||
|
|||||||
@@ -36,6 +36,11 @@ pub struct SelectObjectsCommand {
|
|||||||
pick_crossing: bool,
|
pick_crossing: bool,
|
||||||
/// Add the command-specific Settings keyword used by Auto Constrain.
|
/// Add the command-specific Settings keyword used by Auto Constrain.
|
||||||
auto_constrain_settings: bool,
|
auto_constrain_settings: bool,
|
||||||
|
/// A command-specific prompt replacing the generic "Select objects:".
|
||||||
|
prompt_text: Option<&'static str>,
|
||||||
|
/// The command works on associative dimensions only; the host drops
|
||||||
|
/// everything else from each completed selection.
|
||||||
|
associative_dimensions_only: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SelectObjectsCommand {
|
impl SelectObjectsCommand {
|
||||||
@@ -50,6 +55,8 @@ impl SelectObjectsCommand {
|
|||||||
pick: None,
|
pick: None,
|
||||||
pick_crossing: true,
|
pick_crossing: true,
|
||||||
auto_constrain_settings: false,
|
auto_constrain_settings: false,
|
||||||
|
prompt_text: None,
|
||||||
|
associative_dimensions_only: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,6 +72,8 @@ impl SelectObjectsCommand {
|
|||||||
pick: None,
|
pick: None,
|
||||||
pick_crossing: true,
|
pick_crossing: true,
|
||||||
auto_constrain_settings: false,
|
auto_constrain_settings: false,
|
||||||
|
prompt_text: None,
|
||||||
|
associative_dimensions_only: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,6 +89,8 @@ impl SelectObjectsCommand {
|
|||||||
pick: None,
|
pick: None,
|
||||||
pick_crossing: true,
|
pick_crossing: true,
|
||||||
auto_constrain_settings: false,
|
auto_constrain_settings: false,
|
||||||
|
prompt_text: None,
|
||||||
|
associative_dimensions_only: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,9 +106,24 @@ impl SelectObjectsCommand {
|
|||||||
pick: None,
|
pick: None,
|
||||||
pick_crossing: true,
|
pick_crossing: true,
|
||||||
auto_constrain_settings: false,
|
auto_constrain_settings: false,
|
||||||
|
prompt_text: None,
|
||||||
|
associative_dimensions_only: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gather associative dimensions with the command's own wording; the host
|
||||||
|
/// drops every other object from each completed selection.
|
||||||
|
pub fn associative_dimensions(
|
||||||
|
prompt_cmd: &str,
|
||||||
|
pending_cmd: &str,
|
||||||
|
prompt_text: &'static str,
|
||||||
|
) -> Self {
|
||||||
|
let mut command = Self::plain(prompt_cmd, pending_cmd);
|
||||||
|
command.prompt_text = Some(prompt_text);
|
||||||
|
command.associative_dimensions_only = true;
|
||||||
|
command
|
||||||
|
}
|
||||||
|
|
||||||
pub fn auto_constrain(pending_cmd: &str) -> Self {
|
pub fn auto_constrain(pending_cmd: &str) -> Self {
|
||||||
let mut command = Self::new(pending_cmd);
|
let mut command = Self::new(pending_cmd);
|
||||||
command.auto_constrain_settings = true;
|
command.auto_constrain_settings = true;
|
||||||
@@ -111,6 +137,11 @@ impl CadCommand for SelectObjectsCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn prompt(&self) -> String {
|
fn prompt(&self) -> String {
|
||||||
|
// A command with its own wording keeps it whatever is selected, the
|
||||||
|
// way the reference repeats its prompt after every selection.
|
||||||
|
if let (Some(text), None) = (self.prompt_text, self.pick.as_ref()) {
|
||||||
|
return format!("{} {}", self.prompt_cmd, t!(text));
|
||||||
|
}
|
||||||
if let Some(pick) = &self.pick {
|
if let Some(pick) = &self.pick {
|
||||||
let label = if !pick.closed {
|
let label = if !pick.closed {
|
||||||
t!("Fence")
|
t!("Fence")
|
||||||
@@ -161,6 +192,10 @@ impl CadCommand for SelectObjectsCommand {
|
|||||||
self.pick.is_none()
|
self.pick.is_none()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn selection_keeps_associative_dimensions(&self) -> bool {
|
||||||
|
self.associative_dimensions_only
|
||||||
|
}
|
||||||
|
|
||||||
// Clickable selection keywords (#426, #596). Window and Crossing fix the
|
// Clickable selection keywords (#426, #596). Window and Crossing fix the
|
||||||
// sense of the next box, which dragging would otherwise take from the
|
// sense of the next box, which dragging would otherwise take from the
|
||||||
// direction the corner travels; All takes the whole space; Add and Remove
|
// direction the corner travels; All takes the whole space; Add and Remove
|
||||||
|
|||||||
Reference in new issue
Block a user