fix(commands): ADDSELECTED keeps CLAYER, adopts dimension style (#239)

Two follow-ups to ADDSELECTED:

- It no longer permanently changes the current layer/colour/linetype/
  lineweight. The template's properties are captured as the drawing
  defaults only while the launched draw command runs; a snapshot taken in
  cmd_add_selected is restored the moment that command ends (central hook
  in apply_cmd_result) or is interrupted by another command.
- Dimensions are supported: add_selected_verb maps each DimensionType to
  its DIM* command, and the template's dimension style is adopted as the
  current DIMSTYLE (restored afterwards). commit_entity_handle now stamps
  every new dimension with the current DIMSTYLE — the DIM commands left the
  default "Standard" on the entity, so a normal dimension also picks up
  the active style now.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Hakan SevenandClaude Opus 4.8 committed 2026-07-03 16:05:54 +03:00
1 parent f9b6834ad9
commit 5e4e7c1020
5 files changed
+145 -4

No files matched your search

+12 -3
View File
@@ -165,6 +165,12 @@ impl OpenCADStudio {
self.tabs[i].scene.deselect_all();
self.refresh_properties();
}
// A command just ended (any terminal result) — if it was the draw
// command an ADDSELECTED launched, revert the template-property override
// so CLAYER / CECOLOR / … are left unchanged (#239). No-op otherwise.
if was_active && self.tabs[i].active_cmd.is_none() {
self.restore_add_selected_defaults();
}
task
}
@@ -1385,7 +1391,7 @@ impl OpenCADStudio {
verts_low,
normals,
indices,
} => Some(crate::scene::model::mesh_model::MeshModel {
} => Some((crate::scene::model::mesh_model::MeshModel {
name: String::new(),
verts,
verts_low,
@@ -1393,19 +1399,22 @@ impl OpenCADStudio {
indices,
color,
selected: false,
}),
}, solid)),
_ => None,
}
}
_ => None,
}
});
if let Some(mut mesh) = result {
if let Some((mut mesh, solid)) = result {
self.push_undo_snapshot(i, "EXTRUDE");
let new_entity = empty_solid3d();
let new_handle = self.tabs[i].scene.add_entity(new_entity);
mesh.name = format!("{}", new_handle.value());
self.tabs[i].scene.meshes.insert(new_handle, crate::scene::MeshLodSet::from_single(mesh));
// Keep the truck B-rep so the save path can export exact
// ACIS geometry (else the solid is dropped by other CAD apps).
self.tabs[i].scene.solid_models.insert(new_handle, solid);
self.tabs[i].dirty = true;
self.command_line.push_output("EXTRUDE: solid created.");
} else {
+88 -1
View File
@@ -813,9 +813,16 @@ impl OpenCADStudio {
c.linetype.clone(),
c.linetype_scale,
c.line_weight,
// A dimension template also carries its dimension style, adopted
// as the current DIMSTYLE so the cloned dimension matches (#239).
match e {
acadrust::EntityType::Dimension(d) => Some(d.base().style_name.clone()),
_ => None,
},
)
});
let Some((verb, kind, layer, color, linetype, lt_scale, lw)) = info else {
let Some((verb, kind, layer, color, linetype, lt_scale, lw, template_dimstyle)) = info
else {
self.command_line
.push_error("ADDSELECTED: selected object not found.");
return Task::none();
@@ -830,6 +837,29 @@ impl OpenCADStudio {
// rewrite the template, and so the draw starts on a clean slate.
self.tabs[i].scene.deselect_all();
// Snapshot the current drawing defaults so the override below reverts
// once the launched draw command ends — ADDSELECTED must adopt the
// template's properties for the new object without permanently changing
// CLAYER / CECOLOR / CELTYPE / CELWEIGHT (issue #239).
let restore = crate::app::AddSelectedRestore {
layer_name: self.tabs[i].scene.document.header.current_layer_name.clone(),
layer_handle: self.tabs[i].scene.document.header.current_layer_handle,
color: self.tabs[i].scene.document.header.current_entity_color,
linetype_name: self.tabs[i].scene.document.header.current_linetype_name.clone(),
linetype_handle: self.tabs[i].scene.document.header.current_linetype_handle,
line_weight: self.tabs[i].scene.document.header.current_line_weight,
lt_scale: self.tabs[i].scene.document.header.current_entity_linetype_scale,
dimstyle_name: self.tabs[i].scene.document.header.current_dimstyle_name.clone(),
dimstyle_handle: self.tabs[i].scene.document.header.current_dimstyle_handle,
tab_active_layer: self.tabs[i].active_layer.clone(),
tab_layers_current: self.tabs[i].layers.current_layer.clone(),
ribbon_layer: self.ribbon.active_layer.clone(),
ribbon_color: self.ribbon.active_color,
ribbon_linetype: self.ribbon.active_linetype.clone(),
ribbon_lineweight: self.ribbon.active_lineweight,
};
self.add_selected_restore = Some(restore);
// Adopt the template's general properties as the current defaults. The
// entity-creation path stamps new objects from the tab's active layer
// and the ribbon's active colour / linetype / lineweight, mirrored into
@@ -860,6 +890,21 @@ impl OpenCADStudio {
header.current_line_weight = lw.value();
header.current_entity_linetype_scale = lt_scale;
}
// A dimension template also sets the current DIMSTYLE so the cloned
// dimension inherits the template's dimension style (#239).
if let Some(ds) = &template_dimstyle {
let ds_handle = self.tabs[i]
.scene
.document
.dim_styles
.iter()
.find(|s| s.name.eq_ignore_ascii_case(ds))
.map(|s| s.handle)
.unwrap_or(acadrust::types::Handle::NULL);
let header = &mut self.tabs[i].scene.document.header;
header.current_dimstyle_name = ds.clone();
header.current_dimstyle_handle = ds_handle;
}
self.tabs[i].active_layer = layer.clone();
self.tabs[i].layers.current_layer = layer.clone();
self.tabs[i].dirty = true;
@@ -875,6 +920,35 @@ impl OpenCADStudio {
// Launch the matching draw command (installs its interactive step).
self.dispatch_command(verb)
}
/// Restore the drawing defaults ADDSELECTED overrode, once the draw command
/// it launched ends (commit / cancel / interrupt). No-op unless an
/// ADDSELECTED override is pending. Issue #239.
pub(crate) fn restore_add_selected_defaults(&mut self) {
let Some(r) = self.add_selected_restore.take() else {
return;
};
let i = self.active_tab;
{
let h = &mut self.tabs[i].scene.document.header;
h.current_layer_name = r.layer_name;
h.current_layer_handle = r.layer_handle;
h.current_entity_color = r.color;
h.current_linetype_name = r.linetype_name;
h.current_linetype_handle = r.linetype_handle;
h.current_line_weight = r.line_weight;
h.current_entity_linetype_scale = r.lt_scale;
h.current_dimstyle_name = r.dimstyle_name;
h.current_dimstyle_handle = r.dimstyle_handle;
}
self.tabs[i].active_layer = r.tab_active_layer;
self.tabs[i].layers.current_layer = r.tab_layers_current;
self.ribbon.active_layer = r.ribbon_layer;
self.ribbon.active_color = r.ribbon_color;
self.ribbon.active_linetype = r.ribbon_linetype;
self.ribbon.active_lineweight = r.ribbon_lineweight;
self.refresh_properties();
}
}
/// Map a template entity to the draw-command verb that creates the same kind of
@@ -899,6 +973,19 @@ fn add_selected_verb(entity: &acadrust::EntityType) -> Option<&'static str> {
EntityType::Solid(_) => "SOLID",
EntityType::Ray(_) => "RAY",
EntityType::XLine(_) => "XLINE",
// Dimensions launch the matching dimension command by their stored type
// (issue #239). Angular 2-line and 3-point both use DIMANGULAR.
EntityType::Dimension(d) => {
use acadrust::entities::DimensionType;
match d.base().dimension_type {
DimensionType::Linear => "DIMLINEAR",
DimensionType::Aligned => "DIMALIGNED",
DimensionType::Angular | DimensionType::Angular3Point => "DIMANGULAR",
DimensionType::Diameter => "DIMDIAMETER",
DimensionType::Radius => "DIMRADIUS",
DimensionType::Ordinate => "DIMORDINATE",
}
}
_ => return None,
})
}
+3
View File
@@ -66,6 +66,9 @@ impl OpenCADStudio {
if self.tabs[i].active_cmd.is_some() {
self.tabs[i].scene.clear_preview_wire();
self.tabs[i].active_cmd = None;
// Interrupting an ADDSELECTED draw with another command reverts its
// template-property override too (#239).
self.restore_add_selected_defaults();
}
// Starting any command leaves interactive PAN mode (the PAN arm below
// re-enables it).
+26
View File
@@ -187,6 +187,27 @@ pub struct OpenProgress {
// ── Application state ──────────────────────────────────────────────────────
/// Drawing defaults ADDSELECTED overrides while it draws a new object matching a
/// template, then restores so the current layer/colour/linetype/lineweight are
/// left unchanged (issue #239).
struct AddSelectedRestore {
layer_name: String,
layer_handle: acadrust::types::Handle,
color: AcadColor,
linetype_name: String,
linetype_handle: acadrust::types::Handle,
line_weight: i16,
lt_scale: f64,
dimstyle_name: String,
dimstyle_handle: acadrust::types::Handle,
tab_active_layer: String,
tab_layers_current: String,
ribbon_layer: String,
ribbon_color: AcadColor,
ribbon_linetype: String,
ribbon_lineweight: LineWeight,
}
pub(super) struct OpenCADStudio {
start: Instant,
tabs: Vec<DocumentTab>,
@@ -242,6 +263,10 @@ pub(super) struct OpenCADStudio {
otrack_active: Option<(glam::Vec3, glam::Vec3)>,
/// Whether Tangent snap was enabled before a tangent-pick command started.
pre_cmd_tangent: Option<bool>,
/// Drawing defaults captured by ADDSELECTED before it adopts the template
/// object's properties, restored when the launched draw command ends so
/// ADDSELECTED doesn't permanently change CLAYER / CECOLOR / … (issue #239).
add_selected_restore: Option<AddSelectedRestore>,
/// Whether Ortho mode was temporarily suppressed by a command (e.g. RECTANG).
rect_suppressed_ortho: bool,
/// Orthogonal drawing constraint (F8): constrains picks to 0°/90°/180°/270°.
@@ -1949,6 +1974,7 @@ impl OpenCADStudio {
perf_hud: false,
cycle_candidates: None,
pre_cmd_tangent: None,
add_selected_restore: None,
rect_suppressed_ortho: false,
ortho_mode: false,
polar_mode: false,
+16
View File
@@ -667,6 +667,22 @@ impl OpenCADStudio {
entity.as_entity_mut().set_layer(layer.clone());
}
// A new dimension adopts the current dimension style (DIMSTYLE), like
// AutoCAD — the DIM commands leave the default "Standard" on the entity,
// so stamp the header's current style here. ADDSELECTED sets DIMSTYLE to
// the template's first, so a cloned dimension keeps its style (#239).
if let acadrust::EntityType::Dimension(ref mut d) = entity {
let cur = self.tabs[i]
.scene
.document
.header
.current_dimstyle_name
.clone();
if !cur.trim().is_empty() {
d.base_mut().style_name = cur;
}
}
// INSUNITS: when inserting a block whose BlockRecord.units differ
// from the host's header.insertion_units, scale the new INSERT so
// 1 source-unit equals the matching host length. When either side