forked from 1b-agent/opencadstudio-upstream
Keep both sides: main's ViewBackground colour target and PREVIEW icon next to this branch's Interface target and LIST/SEARCH icons. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013rGmDUNr1hUf4zo1X3dak8
1239 lines
50 KiB
Rust
1239 lines
50 KiB
Rust
use super::*;
|
|
|
|
impl OpenCADStudio {
|
|
pub(super) fn handle_commit_entity(
|
|
&mut self,
|
|
entity: codec::EntityType,
|
|
preserve_commit_layer: bool,
|
|
) -> Option<Task<Message>> {
|
|
let i = self.active_tab;
|
|
let source_handle = entity.common().handle;
|
|
if !source_handle.is_null()
|
|
&& self.tabs[i]
|
|
.scene
|
|
.document
|
|
.get_entity(source_handle)
|
|
.is_some()
|
|
&& self.reject_locked_edit(i, source_handle)
|
|
{
|
|
return Some(Task::none());
|
|
}
|
|
// A line/arc drawn by a repeating command advances the ARC_CONT
|
|
// continuation anchor, so ending one run and launching another
|
|
// keeps continuing from the last segment (mirrors the
|
|
// CommitAndExit arm). Non-continuable repeats (point/ray)
|
|
// leave the anchor untouched. (#327)
|
|
if matches!(
|
|
entity,
|
|
codec::EntityType::Line(_)
|
|
| codec::EntityType::Arc(_)
|
|
| codec::EntityType::LwPolyline(_)
|
|
| codec::EntityType::Polyline2D(_)
|
|
) {
|
|
self.update_cont_anchor(&entity);
|
|
}
|
|
let label = self.history_label_from_active_cmd(i, "ENTITY");
|
|
// Ordinary drawables, viewports and raster images use targeted
|
|
// entity/object deltas. Block sentinels and novel layers retain
|
|
// the structure snapshot fallback.
|
|
let delta_safe = self.delta_add_safe(i, &entity);
|
|
let pending = self.begin_undo(i, label, 1, delta_safe);
|
|
let is_associative_dimension = matches!(
|
|
entity,
|
|
codec::EntityType::Dimension(
|
|
codec::entities::Dimension::Linear(_) | codec::entities::Dimension::Aligned(_)
|
|
)
|
|
);
|
|
let association_enabled = self.tabs[i].scene.document.header.dimension_associativity == 2;
|
|
let committed = if preserve_commit_layer {
|
|
self.commit_entity_handle_preserve_layer(entity)
|
|
} else {
|
|
self.commit_entity_handle(entity)
|
|
};
|
|
if is_associative_dimension && association_enabled {
|
|
if let Some(handle) = committed {
|
|
let sources = self.infer_dimension_sources_guarded(i, handle);
|
|
self.tabs[i]
|
|
.scene
|
|
.attach_dimension_association(handle, sources);
|
|
}
|
|
}
|
|
if let Some(handle) = committed {
|
|
self.apply_continuous_constraints(i, &[handle]);
|
|
self.notify_entities_committed(i, &[handle]);
|
|
}
|
|
self.tabs[i].dirty = true;
|
|
let prompt = self.tabs[i].active_cmd.as_ref().map(|c| c.prompt());
|
|
if let Some(p) = prompt {
|
|
self.command_line.push_info(&p);
|
|
}
|
|
if let Some(pd) = pending {
|
|
self.commit_undo_delta(i, pd);
|
|
}
|
|
None
|
|
}
|
|
|
|
pub(super) fn handle_commit_entities(
|
|
&mut self,
|
|
mut entities: Vec<codec::EntityType>,
|
|
preserve_commit_style: bool,
|
|
preserve_commit_layer: bool,
|
|
) -> Option<Task<Message>> {
|
|
let i = self.active_tab;
|
|
let locked_source = entities.iter().find_map(|entity| {
|
|
let handle = entity.common().handle;
|
|
(!handle.is_null()
|
|
&& self.tabs[i].scene.document.get_entity(handle).is_some()
|
|
&& self.tabs[i].scene.is_layer_locked(handle))
|
|
.then_some(handle)
|
|
});
|
|
if let Some(handle) = locked_source {
|
|
self.reject_locked_edit(i, handle);
|
|
return Some(Task::none());
|
|
}
|
|
let label = self.history_label_from_active_cmd(i, "ENTITY");
|
|
let symbol_names = self.tabs[i]
|
|
.active_cmd
|
|
.as_ref()
|
|
.and_then(|command| command.nested_copy_symbol_names())
|
|
.cloned();
|
|
let delta_safe =
|
|
symbol_names.is_none() && entities.iter().all(|entity| self.delta_add_safe(i, entity));
|
|
let pending = self.begin_undo(i, label, entities.len(), delta_safe);
|
|
if let Some(names) = symbol_names {
|
|
let retained = self.tabs[i]
|
|
.scene
|
|
.document
|
|
.localize_nested_copy_symbols(&mut entities, &names);
|
|
if retained > 0 {
|
|
self.command_line.push_info(&format!("{} copied object(s) retain imported style references whose dependencies cannot be localized.", retained));
|
|
}
|
|
self.refresh_layer_panel();
|
|
}
|
|
let mut committed = Vec::new();
|
|
for entity in entities {
|
|
let handle = if preserve_commit_style {
|
|
self.commit_entity_handle_preserve_style(entity)
|
|
} else if preserve_commit_layer {
|
|
self.commit_entity_handle_preserve_layer(entity)
|
|
} else {
|
|
self.commit_entity_handle(entity)
|
|
};
|
|
committed.extend(handle);
|
|
}
|
|
self.apply_continuous_constraints(i, &committed);
|
|
self.notify_entities_committed(i, &committed);
|
|
self.tabs[i].dirty = true;
|
|
self.tabs[i].scene.clear_preview_wire();
|
|
let prompt = self.tabs[i].active_cmd.as_ref().map(|c| c.prompt());
|
|
if let Some(p) = prompt {
|
|
self.command_line.push_info(&p);
|
|
}
|
|
if let Some(pd) = pending {
|
|
self.commit_undo_delta(i, pd);
|
|
}
|
|
None
|
|
}
|
|
|
|
/// Hand the stored copies of freshly committed entities to the command
|
|
/// that is still running.
|
|
fn notify_entities_committed(&mut self, i: usize, handles: &[codec::Handle]) {
|
|
let entities: Vec<codec::EntityType> = handles
|
|
.iter()
|
|
.filter_map(|handle| self.tabs[i].scene.document.get_entity(*handle).cloned())
|
|
.collect();
|
|
if let Some(command) = self.tabs[i].active_cmd.as_mut() {
|
|
command.on_entities_committed(&entities);
|
|
}
|
|
}
|
|
|
|
pub(super) fn handle_commit_entities_and_exit(
|
|
&mut self,
|
|
mut entities: Vec<codec::EntityType>,
|
|
preserve_commit_style: bool,
|
|
preserve_commit_layer: bool,
|
|
) {
|
|
let i = self.active_tab;
|
|
let label = self.history_label_from_active_cmd(i, "ENTITY");
|
|
let symbol_names = self.tabs[i]
|
|
.active_cmd
|
|
.as_ref()
|
|
.and_then(|command| command.nested_copy_symbol_names())
|
|
.cloned();
|
|
let delta_safe =
|
|
symbol_names.is_none() && entities.iter().all(|entity| self.delta_add_safe(i, entity));
|
|
let pending = self.begin_undo(i, label, entities.len(), delta_safe);
|
|
if let Some(names) = symbol_names {
|
|
let retained = self.tabs[i]
|
|
.scene
|
|
.document
|
|
.localize_nested_copy_symbols(&mut entities, &names);
|
|
if retained > 0 {
|
|
self.command_line.push_info(&format!("{} copied object(s) retain imported style references whose dependencies cannot be localized.", retained));
|
|
}
|
|
self.refresh_layer_panel();
|
|
}
|
|
let mut committed = Vec::new();
|
|
for entity in entities {
|
|
let handle = if preserve_commit_style {
|
|
self.commit_entity_handle_preserve_style(entity)
|
|
} else if preserve_commit_layer {
|
|
self.commit_entity_handle_preserve_layer(entity)
|
|
} else {
|
|
self.commit_entity_handle(entity)
|
|
};
|
|
committed.extend(handle);
|
|
}
|
|
self.apply_continuous_constraints(i, &committed);
|
|
self.tabs[i].dirty = true;
|
|
self.tabs[i].scene.clear_preview_wire();
|
|
self.tabs[i].active_cmd = None;
|
|
self.tabs[i].snap_result = None;
|
|
self.restore_pre_cmd_tangent();
|
|
if let Some(pd) = pending {
|
|
self.commit_undo_delta(i, pd);
|
|
}
|
|
}
|
|
|
|
pub(super) fn handle_commit_and_exit(
|
|
&mut self,
|
|
entity: codec::EntityType,
|
|
preserve_commit_layer: bool,
|
|
) -> Option<Task<Message>> {
|
|
let i = self.active_tab;
|
|
// XATTACH: the definition and its INSERT are created together
|
|
// in one undo step.
|
|
let xattach_request = self.tabs[i]
|
|
.active_cmd
|
|
.as_ref()
|
|
.filter(|cmd| cmd.name() == "XATTACH")
|
|
.and_then(|cmd| cmd.xattach_request());
|
|
if let Some(request) = xattach_request {
|
|
self.commit_xref_attach(i, request, entity);
|
|
return Some(Task::none());
|
|
}
|
|
let insert_block_name = match &entity {
|
|
codec::EntityType::Insert(ins) => Some(ins.block_name.clone()),
|
|
_ => None,
|
|
};
|
|
// Record where this draw ended so ARC_CONT can continue from it
|
|
// (before `entity` is moved into commit_entity).
|
|
self.update_cont_anchor(&entity);
|
|
// ATTDEF's height becomes the next text height (TEXTSIZE), as TEXT's does.
|
|
if let codec::EntityType::AttributeDefinition(attribute) = &entity {
|
|
if self.tabs[i]
|
|
.active_cmd
|
|
.as_ref()
|
|
.is_some_and(|cmd| cmd.name().ends_with("ATTDEF"))
|
|
{
|
|
self.tabs[i].scene.document.header.text_height = attribute.height;
|
|
}
|
|
}
|
|
// A field the committed text shows (FIELD, an ATTDEF Default from the
|
|
// Field dialog) is attached once the entity has its handle; the field
|
|
// objects are not part of the add delta, so the step is a snapshot.
|
|
let text_field = self.tabs[i]
|
|
.active_cmd
|
|
.as_ref()
|
|
.and_then(|cmd| cmd.text_field());
|
|
let label = self.history_label_from_active_cmd(i, "ENTITY");
|
|
// A block reference whose attribute definitions carry fields gets
|
|
// them copied onto its attributes (field objects: snapshot step).
|
|
let attribute_fields = match &entity {
|
|
codec::EntityType::Insert(insert) if !insert.attributes.is_empty() => {
|
|
crate::entities::field::block_has_attribute_fields(
|
|
&self.tabs[i].scene.document,
|
|
&insert.block_name,
|
|
)
|
|
}
|
|
_ => false,
|
|
};
|
|
let delta_safe =
|
|
text_field.is_none() && !attribute_fields && self.delta_add_safe(i, &entity);
|
|
let field_value = match &entity {
|
|
codec::EntityType::MText(m) => m.value.clone(),
|
|
codec::EntityType::AttributeDefinition(a) => a.default_value.clone(),
|
|
_ => String::new(),
|
|
};
|
|
let pending = self.begin_undo(i, label, 1, delta_safe);
|
|
let is_associative_dimension = matches!(
|
|
entity,
|
|
codec::EntityType::Dimension(
|
|
codec::entities::Dimension::Linear(_) | codec::entities::Dimension::Aligned(_)
|
|
)
|
|
);
|
|
let association_enabled = self.tabs[i].scene.document.header.dimension_associativity == 2;
|
|
let committed = if preserve_commit_layer {
|
|
self.commit_entity_handle_preserve_layer(entity)
|
|
} else {
|
|
self.commit_entity_handle(entity)
|
|
};
|
|
if is_associative_dimension && association_enabled {
|
|
if let Some(handle) = committed {
|
|
let sources = self.infer_dimension_sources_guarded(i, handle);
|
|
self.tabs[i]
|
|
.scene
|
|
.attach_dimension_association(handle, sources);
|
|
}
|
|
}
|
|
if let (Some(handle), Some(field)) = (committed, text_field) {
|
|
crate::entities::field::set_text_field(
|
|
&mut self.tabs[i].scene.document,
|
|
handle,
|
|
Some(field),
|
|
&field_value,
|
|
);
|
|
}
|
|
if let (Some(handle), true) = (committed, attribute_fields) {
|
|
crate::entities::field::attach_attribute_fields(
|
|
&mut self.tabs[i].scene.document,
|
|
handle,
|
|
);
|
|
self.tabs[i]
|
|
.scene
|
|
.bump_entities(&[(handle, crate::scene::ChangeKind::Modified)]);
|
|
}
|
|
if let Some(handle) = committed {
|
|
self.apply_continuous_constraints(i, &[handle]);
|
|
}
|
|
self.tabs[i].dirty = true;
|
|
self.tabs[i].scene.clear_preview_wire();
|
|
self.tabs[i].active_cmd = None;
|
|
self.tabs[i].snap_result = None;
|
|
self.restore_pre_cmd_tangent();
|
|
if let Some(name) = insert_block_name {
|
|
self.record_block_insert(&name);
|
|
}
|
|
if let Some(pd) = pending {
|
|
self.commit_undo_delta(i, pd);
|
|
}
|
|
None
|
|
}
|
|
|
|
pub(super) fn handle_commit_dimension(&mut self, result: CmdResult) -> Option<Task<Message>> {
|
|
let i = self.active_tab;
|
|
let CmdResult::CommitDimension {
|
|
mut entity,
|
|
association,
|
|
preserve_base_style,
|
|
continue_command,
|
|
} = result
|
|
else {
|
|
unreachable!("router only routes the matching variant");
|
|
};
|
|
let label = self.history_label_from_active_cmd(i, "DIMENSION");
|
|
let association_mode = self.tabs[i].scene.document.header.dimension_associativity;
|
|
let single_source_dimension = matches!(
|
|
&entity,
|
|
codec::EntityType::Dimension(codec::entities::Dimension::Ordinate(_))
|
|
);
|
|
// A dimension placed on the sheet but measuring model geometry
|
|
// through a viewport carries the compensation as a negative
|
|
// DIMLFAC override.
|
|
if !preserve_base_style {
|
|
crate::scene::creation_style::apply_current_creation_styles(
|
|
&self.tabs[i].scene.document,
|
|
&mut entity,
|
|
);
|
|
}
|
|
if !self.apply_viewport_dimension_measurement(i, &mut entity) {
|
|
return Some(Task::none());
|
|
}
|
|
// Projected points cannot use direct paper-space source inference.
|
|
let association_allowed = self.dimension_association_allowed(i);
|
|
let inherited_dimension = if preserve_base_style {
|
|
match &entity {
|
|
codec::EntityType::Dimension(dimension) => Some((
|
|
dimension.base().common.layer.clone(),
|
|
dimension.base().style_name.clone(),
|
|
)),
|
|
_ => None,
|
|
}
|
|
} else {
|
|
None
|
|
};
|
|
let pending =
|
|
if association_mode == 0 {
|
|
let layer = self.tabs[i].active_layer.clone();
|
|
if layer != "0" || entity.as_entity().layer().is_empty() {
|
|
entity.as_entity_mut().set_layer(layer);
|
|
}
|
|
crate::scene::view::dispatch::apply_named_color(
|
|
&mut entity,
|
|
self.ribbon.active_color,
|
|
self.ribbon.active_color_name(),
|
|
);
|
|
crate::scene::view::dispatch::apply_common_prop(
|
|
&mut entity,
|
|
"linetype",
|
|
&self.ribbon.active_linetype.clone(),
|
|
);
|
|
crate::scene::view::dispatch::apply_line_weight(
|
|
&mut entity,
|
|
self.ribbon.active_lineweight,
|
|
);
|
|
let celtscale = self.tabs[i]
|
|
.scene
|
|
.document
|
|
.header
|
|
.current_entity_linetype_scale;
|
|
if (celtscale - 1.0).abs() > 1e-9 && celtscale.abs() > 1e-9 {
|
|
entity.common_mut().linetype_scale = celtscale;
|
|
}
|
|
crate::scene::creation_style::apply_current_creation_styles(
|
|
&self.tabs[i].scene.document,
|
|
&mut entity,
|
|
);
|
|
if let (Some((layer, style_name)), codec::EntityType::Dimension(dimension)) =
|
|
(inherited_dimension, &mut entity)
|
|
{
|
|
dimension.base_mut().common.layer = layer;
|
|
dimension.base_mut().style_name = style_name;
|
|
}
|
|
let pieces = crate::modules::draw::modify::explode::explode_entity(
|
|
&entity,
|
|
&self.tabs[i].scene.document,
|
|
);
|
|
let delta_safe = pieces.iter().all(|piece| self.delta_add_safe(i, piece));
|
|
let pending = self.begin_undo(i, label, pieces.len(), delta_safe);
|
|
for piece in pieces {
|
|
self.tabs[i].scene.add_entity(piece);
|
|
}
|
|
pending
|
|
} else {
|
|
let delta_safe = self.delta_add_safe(i, &entity);
|
|
let pending = self.begin_undo(i, label, 1, delta_safe);
|
|
if let Some(handle) =
|
|
self.commit_entity_handle_with_dimension_policy(entity, preserve_base_style)
|
|
{
|
|
if association_mode == 2 && association_allowed {
|
|
let mut changes = vec![(handle, crate::scene::ChangeKind::Modified)];
|
|
match association {
|
|
crate::command::DimensionAssociationInput::Infer(source) => {
|
|
let sources: Vec<_> = source.map_or_else(
|
|
|| self.infer_dimension_sources_guarded(i, handle),
|
|
|source| {
|
|
if single_source_dimension {
|
|
vec![Some(source)]
|
|
} else {
|
|
vec![Some(source), Some(source)]
|
|
}
|
|
},
|
|
);
|
|
self.tabs[i]
|
|
.scene
|
|
.attach_dimension_association(handle, sources.clone());
|
|
changes.extend(
|
|
sources
|
|
.into_iter()
|
|
.flatten()
|
|
.map(|source| (source, crate::scene::ChangeKind::Modified)),
|
|
);
|
|
}
|
|
crate::command::DimensionAssociationInput::Explicit(sources) => {
|
|
self.tabs[i]
|
|
.scene
|
|
.attach_dimension_association_sources(handle, sources.clone());
|
|
changes.extend(sources.into_iter().flatten().map(|source| {
|
|
(source.handle, crate::scene::ChangeKind::Modified)
|
|
}));
|
|
}
|
|
}
|
|
changes.sort_by_key(|(handle, _)| handle.value());
|
|
changes.dedup_by_key(|(handle, _)| handle.value());
|
|
self.tabs[i].scene.bump_entities(&changes);
|
|
} else if association_mode == 2 {
|
|
// Preserve the acquired viewport and source paths.
|
|
self.attach_viewport_dimension_association(i, handle);
|
|
}
|
|
}
|
|
pending
|
|
};
|
|
self.tabs[i].dirty = true;
|
|
self.tabs[i].scene.clear_preview_wire();
|
|
self.tabs[i].snap_result = None;
|
|
if let Some(pd) = pending {
|
|
self.commit_undo_delta(i, pd);
|
|
}
|
|
if continue_command {
|
|
if let Some(prompt) = self.tabs[i].active_cmd.as_ref().map(|cmd| cmd.prompt()) {
|
|
self.command_line.push_info(&prompt);
|
|
}
|
|
} else {
|
|
self.tabs[i].active_cmd = None;
|
|
self.restore_pre_cmd_tangent();
|
|
}
|
|
None
|
|
}
|
|
|
|
pub(super) fn handle_commit_dimensions_and_exit(
|
|
&mut self,
|
|
dimensions: Vec<(codec::EntityType, crate::command::DimensionAssociationInput)>,
|
|
) {
|
|
let i = self.active_tab;
|
|
let association_mode = self.tabs[i].scene.document.header.dimension_associativity;
|
|
let mut made = 0;
|
|
let pending = if association_mode == 0 {
|
|
let mut pieces = Vec::new();
|
|
for (mut entity, _) in dimensions {
|
|
let layer = self.tabs[i].active_layer.clone();
|
|
if layer != "0" || entity.as_entity().layer().is_empty() {
|
|
entity.as_entity_mut().set_layer(layer);
|
|
}
|
|
crate::scene::view::dispatch::apply_named_color(
|
|
&mut entity,
|
|
self.ribbon.active_color,
|
|
self.ribbon.active_color_name(),
|
|
);
|
|
crate::scene::view::dispatch::apply_common_prop(
|
|
&mut entity,
|
|
"linetype",
|
|
&self.ribbon.active_linetype.clone(),
|
|
);
|
|
crate::scene::view::dispatch::apply_line_weight(
|
|
&mut entity,
|
|
self.ribbon.active_lineweight,
|
|
);
|
|
let celtscale = self.tabs[i]
|
|
.scene
|
|
.document
|
|
.header
|
|
.current_entity_linetype_scale;
|
|
if (celtscale - 1.0).abs() > 1e-9 && celtscale.abs() > 1e-9 {
|
|
entity.common_mut().linetype_scale = celtscale;
|
|
}
|
|
crate::scene::creation_style::apply_current_creation_styles(
|
|
&self.tabs[i].scene.document,
|
|
&mut entity,
|
|
);
|
|
let exploded = crate::modules::draw::modify::explode::explode_entity(
|
|
&entity,
|
|
&self.tabs[i].scene.document,
|
|
);
|
|
if !exploded.is_empty() {
|
|
made += 1;
|
|
}
|
|
pieces.extend(exploded);
|
|
}
|
|
let delta_safe = pieces.iter().all(|piece| self.delta_add_safe(i, piece));
|
|
let pending = self.begin_undo(i, "QDIM", pieces.len(), delta_safe);
|
|
for piece in pieces {
|
|
self.tabs[i].scene.add_entity(piece);
|
|
}
|
|
pending
|
|
} else {
|
|
let delta_safe = dimensions
|
|
.iter()
|
|
.all(|(entity, _)| self.delta_add_safe(i, entity));
|
|
let pending = self.begin_undo(i, "QDIM", dimensions.len(), delta_safe);
|
|
for (entity, association) in dimensions {
|
|
let Some(handle) = self.commit_entity_handle(entity) else {
|
|
continue;
|
|
};
|
|
made += 1;
|
|
if association_mode != 2 {
|
|
continue;
|
|
}
|
|
let sources = match association {
|
|
crate::command::DimensionAssociationInput::Infer(source) => source.map_or_else(
|
|
|| {
|
|
self.infer_dimension_sources_guarded(i, handle)
|
|
.into_iter()
|
|
.map(|source| {
|
|
source.map(crate::command::DimensionAssociationSource::inferred)
|
|
})
|
|
.collect()
|
|
},
|
|
|source| {
|
|
vec![Some(crate::command::DimensionAssociationSource::inferred(
|
|
source,
|
|
))]
|
|
},
|
|
),
|
|
crate::command::DimensionAssociationInput::Explicit(sources) => sources,
|
|
};
|
|
self.tabs[i]
|
|
.scene
|
|
.attach_dimension_association_sources(handle, sources.clone());
|
|
let mut changes = vec![(handle, crate::scene::ChangeKind::Modified)];
|
|
changes.extend(
|
|
sources
|
|
.into_iter()
|
|
.flatten()
|
|
.map(|source| (source.handle, crate::scene::ChangeKind::Modified)),
|
|
);
|
|
changes.sort_by_key(|(handle, _)| handle.value());
|
|
changes.dedup_by_key(|(handle, _)| handle.value());
|
|
self.tabs[i].scene.bump_entities(&changes);
|
|
}
|
|
pending
|
|
};
|
|
if made > 0 {
|
|
self.tabs[i].dirty = true;
|
|
}
|
|
self.tabs[i].scene.clear_preview_wire();
|
|
self.tabs[i].active_cmd = None;
|
|
self.tabs[i].snap_result = None;
|
|
self.restore_pre_cmd_tangent();
|
|
self.command_line
|
|
.push_output(crate::tf!("QDIM {made} dimensions created.").as_ref());
|
|
if let Some(pd) = pending {
|
|
self.commit_undo_delta(i, pd);
|
|
}
|
|
}
|
|
|
|
pub(super) fn handle_commit_solid(&mut self, result: CmdResult) {
|
|
let i = self.active_tab;
|
|
let CmdResult::CommitSolid {
|
|
entity,
|
|
solid,
|
|
history,
|
|
erase_source,
|
|
} = result
|
|
else {
|
|
unreachable!("router only routes the matching variant");
|
|
};
|
|
let label = self.history_label_from_active_cmd(i, "SOLID");
|
|
let erase_source = erase_source.filter(|handle| {
|
|
self.delete_objects != 0 && !self.tabs[i].scene.is_layer_locked(*handle)
|
|
});
|
|
let pending = self.begin_undo(i, label, 1 + usize::from(erase_source.is_some()), true);
|
|
let handle = self.add_solid_model(entity, *solid, history);
|
|
if !handle.is_null() {
|
|
if let Some(source) = erase_source {
|
|
self.tabs[i].scene.erase_entities(&[source]);
|
|
}
|
|
self.tabs[i].dirty = true;
|
|
if let Some(pd) = pending {
|
|
self.commit_undo_delta(i, pd);
|
|
}
|
|
}
|
|
self.tabs[i].scene.clear_preview_wire();
|
|
self.tabs[i].active_cmd = None;
|
|
self.tabs[i].snap_result = None;
|
|
self.restore_pre_cmd_tangent();
|
|
}
|
|
|
|
pub(super) fn handle_commit_and_edit_text(
|
|
&mut self,
|
|
entity: codec::EntityType,
|
|
) -> Option<Task<Message>> {
|
|
let i = self.active_tab;
|
|
let annotative_mleader = matches!(
|
|
&entity,
|
|
codec::EntityType::MultiLeader(ml) if ml.enable_annotation_scale
|
|
);
|
|
let label = self.history_label_from_active_cmd(i, "ENTITY");
|
|
let delta_safe = self.delta_add_safe(i, &entity) && !annotative_mleader;
|
|
let pending = self.begin_undo(i, label, 1, delta_safe);
|
|
let handle = self.commit_entity_handle(entity);
|
|
|
|
if annotative_mleader {
|
|
let scale_handle = self.tabs[i].scene.creation_annotation_scale_handle();
|
|
if let (Some(handle), Some(scale_handle)) = (handle, scale_handle) {
|
|
crate::scene::annotative::create_annotation_context(
|
|
&mut self.tabs[i].scene.document,
|
|
handle,
|
|
scale_handle,
|
|
);
|
|
self.tabs[i]
|
|
.scene
|
|
.bump_entities(&[(handle, crate::scene::ChangeKind::Modified)]);
|
|
}
|
|
}
|
|
|
|
self.tabs[i].dirty = true;
|
|
self.tabs[i].scene.clear_preview_wire();
|
|
self.tabs[i].active_cmd = None;
|
|
self.tabs[i].snap_result = None;
|
|
self.restore_pre_cmd_tangent();
|
|
self.ribbon.deactivate_tool();
|
|
if let Some(pd) = pending {
|
|
self.commit_undo_delta(i, pd);
|
|
}
|
|
if let Some(h) = handle {
|
|
return Some(self.begin_text_edit(h));
|
|
}
|
|
None
|
|
}
|
|
|
|
pub(super) fn handle_commit_many_and_edit_text(
|
|
&mut self,
|
|
entities: Vec<codec::EntityType>,
|
|
edit_index: usize,
|
|
open_editor: bool,
|
|
) -> Option<Task<Message>> {
|
|
let i = self.active_tab;
|
|
let label = self.history_label_from_active_cmd(i, "ENTITY");
|
|
let delta_safe = entities.iter().all(|entity| self.delta_add_safe(i, entity));
|
|
let pending = self.begin_undo(i, label, entities.len(), delta_safe);
|
|
let mut edit_handle = None;
|
|
let mut leader_handle = None;
|
|
for (idx, entity) in entities.into_iter().enumerate() {
|
|
let is_leader = matches!(entity, codec::EntityType::Leader(_));
|
|
let h = self.commit_entity_handle(entity);
|
|
if idx == edit_index {
|
|
edit_handle = h;
|
|
}
|
|
if is_leader {
|
|
leader_handle = h;
|
|
}
|
|
}
|
|
// Link the leader to its annotation so the pair edits as a unit
|
|
// (double-click on the leader resolves to the text entity).
|
|
if let (Some(lh), Some(ah)) = (leader_handle, edit_handle) {
|
|
let linked = if let Some(codec::EntityType::Leader(l)) =
|
|
self.tabs[i].scene.document.get_entity_mut(lh)
|
|
{
|
|
l.annotation_handle = ah;
|
|
true
|
|
} else {
|
|
false
|
|
};
|
|
|
|
if linked {
|
|
// The LEADER may already have received its annotation context while
|
|
// annotation_handle was still NULL. Refresh it now that the MTEXT link
|
|
// is known so the context represents the finished leader.
|
|
self.tabs[i].scene.sync_displayed_annotation_context(lh);
|
|
|
|
self.tabs[i]
|
|
.scene
|
|
.bump_entities(&[(lh, crate::scene::ChangeKind::Modified)]);
|
|
}
|
|
}
|
|
self.tabs[i].dirty = true;
|
|
self.tabs[i].scene.clear_preview_wire();
|
|
self.tabs[i].active_cmd = None;
|
|
self.tabs[i].snap_result = None;
|
|
self.restore_pre_cmd_tangent();
|
|
self.ribbon.deactivate_tool();
|
|
if let Some(pd) = pending {
|
|
self.commit_undo_delta(i, pd);
|
|
}
|
|
if open_editor {
|
|
if let Some(h) = edit_handle {
|
|
return Some(self.begin_text_edit(h));
|
|
}
|
|
}
|
|
None
|
|
}
|
|
|
|
pub(super) fn handle_create_block(
|
|
&mut self,
|
|
mut handles: Vec<Handle>,
|
|
name: String,
|
|
base: glam::DVec3,
|
|
) -> Option<Task<Message>> {
|
|
let i = self.active_tab;
|
|
handles.retain(|handle| !self.tabs[i].scene.is_layer_locked(*handle));
|
|
if handles.is_empty() {
|
|
self.command_line
|
|
.push_info(crate::t!("No editable objects selected.").as_ref());
|
|
return Some(Task::none());
|
|
}
|
|
self.push_undo_snapshot(i, "BLOCK");
|
|
let ucs = self.tabs[i].ucs_xform();
|
|
let world_to_block = ucs.to_ucs_transform_at(base);
|
|
let block_to_world = ucs.to_wcs_transform_at(base);
|
|
match self.tabs[i].scene.create_block_from_entities(
|
|
&handles,
|
|
&name,
|
|
&world_to_block,
|
|
&block_to_world,
|
|
) {
|
|
Ok(insert_handle) => {
|
|
self.tabs[i].dirty = true;
|
|
self.tabs[i].scene.deselect_all();
|
|
if !insert_handle.is_null() {
|
|
self.tabs[i].scene.select_entity(insert_handle, false);
|
|
}
|
|
self.tabs[i].scene.clear_preview_wire();
|
|
self.tabs[i].active_cmd = None;
|
|
self.tabs[i].snap_result = None;
|
|
self.command_line
|
|
.push_output(crate::tf!("Block \"{name}\" created.").as_ref());
|
|
self.refresh_properties();
|
|
}
|
|
Err(err) => {
|
|
self.discard_last_undo_entry(i);
|
|
self.command_line.push_error(&err);
|
|
let prompt = self.tabs[i].active_cmd.as_ref().map(|c| c.prompt());
|
|
if let Some(p) = prompt {
|
|
self.command_line.push_info(&p);
|
|
}
|
|
}
|
|
}
|
|
None
|
|
}
|
|
|
|
pub(super) fn handle_create_block_with_options(
|
|
&mut self,
|
|
mut options: Box<crate::scene::CreateBlockOptions>,
|
|
) -> Option<Task<Message>> {
|
|
let i = self.active_tab;
|
|
options
|
|
.handles
|
|
.retain(|handle| !self.tabs[i].scene.is_layer_locked(*handle));
|
|
if options.handles.is_empty() {
|
|
self.command_line
|
|
.push_info(crate::t!("No editable objects selected.").as_ref());
|
|
return Some(Task::none());
|
|
}
|
|
self.push_undo_snapshot(i, "BLOCK");
|
|
let name = options.name.clone();
|
|
match self.tabs[i].scene.create_block_with_options(*options) {
|
|
Ok(insert_handle) => {
|
|
self.tabs[i].dirty = true;
|
|
self.tabs[i].scene.deselect_all();
|
|
if !insert_handle.is_null() {
|
|
self.tabs[i].scene.select_entity(insert_handle, false);
|
|
}
|
|
self.tabs[i].scene.clear_preview_wire();
|
|
self.tabs[i].active_cmd = None;
|
|
self.tabs[i].snap_result = None;
|
|
self.command_line
|
|
.push_output(crate::tf!("Block \"{name}\" created.").as_ref());
|
|
self.refresh_properties();
|
|
self.note_recent_block(&name);
|
|
self.refresh_block_palette();
|
|
}
|
|
Err(err) => {
|
|
self.discard_last_undo_entry(i);
|
|
self.command_line.push_error(&err);
|
|
}
|
|
}
|
|
None
|
|
}
|
|
|
|
pub(super) fn handle_commit_hatch(&mut self, hatch: crate::scene::HatchModel) {
|
|
let i = self.active_tab;
|
|
let label = self.history_label_from_active_cmd(i, "HATCH");
|
|
let pending = self.begin_undo(i, label, 1, true);
|
|
let layer = self.tabs[i].active_layer.clone();
|
|
let new_handle = self.tabs[i].scene.add_hatch(hatch, Some(&layer), None);
|
|
if !new_handle.is_null() {
|
|
self.tabs[i].scene.select_entity(new_handle, true);
|
|
}
|
|
self.tabs[i].dirty = true;
|
|
self.tabs[i].scene.clear_preview_wire();
|
|
self.tabs[i].active_cmd = None;
|
|
self.tabs[i].snap_result = None;
|
|
self.restore_pre_cmd_tangent();
|
|
self.refresh_properties();
|
|
if let Some(pd) = pending {
|
|
self.commit_undo_delta(i, pd);
|
|
}
|
|
}
|
|
|
|
pub(super) fn handle_commit_styled_hatch(
|
|
&mut self,
|
|
hatch: crate::scene::HatchModel,
|
|
color: codec::types::Color,
|
|
transparency: codec::types::Transparency,
|
|
) {
|
|
let i = self.active_tab;
|
|
let label = self.history_label_from_active_cmd(i, "HATCH");
|
|
let pending = self.begin_undo(i, label, 1, true);
|
|
let layer = self.tabs[i].active_layer.clone();
|
|
let new_handle =
|
|
self.tabs[i]
|
|
.scene
|
|
.add_hatch(hatch, Some(&layer), Some((color, transparency)));
|
|
if !new_handle.is_null() {
|
|
self.tabs[i].scene.select_entity(new_handle, true);
|
|
}
|
|
self.tabs[i].dirty = true;
|
|
self.tabs[i].scene.clear_preview_wire();
|
|
self.tabs[i].active_cmd = None;
|
|
self.tabs[i].snap_result = None;
|
|
self.restore_pre_cmd_tangent();
|
|
self.refresh_properties();
|
|
if let Some(pd) = pending {
|
|
self.commit_undo_delta(i, pd);
|
|
}
|
|
}
|
|
|
|
pub(super) fn handle_commit_hatch_with_boundaries(
|
|
&mut self,
|
|
mut hatch: crate::scene::HatchModel,
|
|
boundaries: Vec<codec::EntityType>,
|
|
entity_style: Option<(codec::types::Color, codec::types::Transparency)>,
|
|
) {
|
|
let i = self.active_tab;
|
|
let label = self.history_label_from_active_cmd(i, "HATCH");
|
|
let pending = self.begin_undo(i, label, boundaries.len() + 1, true);
|
|
let mut sources = Vec::with_capacity(boundaries.len());
|
|
for boundary in boundaries {
|
|
let handles = self
|
|
.commit_entity_handle(boundary)
|
|
.into_iter()
|
|
.collect::<Vec<_>>();
|
|
sources.push(handles);
|
|
}
|
|
if let Some(paths) = hatch.boundary_paths.as_mut() {
|
|
for (path, handles) in std::sync::Arc::make_mut(paths).iter_mut().zip(&sources) {
|
|
path.boundary_handles = handles.clone();
|
|
path.flags.set_external(!handles.is_empty());
|
|
}
|
|
}
|
|
hatch.boundary_sources = Some(std::sync::Arc::new(sources));
|
|
let layer = self.tabs[i].active_layer.clone();
|
|
let new_handle = self.tabs[i]
|
|
.scene
|
|
.add_hatch(hatch, Some(&layer), entity_style);
|
|
if !new_handle.is_null() {
|
|
self.tabs[i].scene.select_entity(new_handle, true);
|
|
}
|
|
self.tabs[i].dirty = true;
|
|
self.tabs[i].scene.clear_preview_wire();
|
|
self.tabs[i].active_cmd = None;
|
|
self.tabs[i].snap_result = None;
|
|
self.restore_pre_cmd_tangent();
|
|
self.refresh_properties();
|
|
if let Some(pd) = pending {
|
|
self.commit_undo_delta(i, pd);
|
|
}
|
|
}
|
|
|
|
pub(super) fn handle_commit_hatches(
|
|
&mut self,
|
|
hatches: Vec<crate::scene::HatchModel>,
|
|
entity_style: Option<(codec::types::Color, codec::types::Transparency)>,
|
|
) {
|
|
let i = self.active_tab;
|
|
let label = self.history_label_from_active_cmd(i, "HATCH");
|
|
let pending = self.begin_undo(i, label, hatches.len(), true);
|
|
let layer = self.tabs[i].active_layer.clone();
|
|
for hatch in hatches {
|
|
let new_handle =
|
|
self.tabs[i]
|
|
.scene
|
|
.add_hatch(hatch, Some(&layer), entity_style.clone());
|
|
if !new_handle.is_null() {
|
|
self.tabs[i].scene.select_entity(new_handle, true);
|
|
}
|
|
}
|
|
self.tabs[i].dirty = true;
|
|
self.tabs[i].scene.clear_preview_wire();
|
|
self.tabs[i].active_cmd = None;
|
|
self.tabs[i].snap_result = None;
|
|
self.restore_pre_cmd_tangent();
|
|
self.refresh_properties();
|
|
if let Some(pd) = pending {
|
|
self.commit_undo_delta(i, pd);
|
|
}
|
|
}
|
|
|
|
pub(super) fn handle_update_entity_and_finish(
|
|
&mut self,
|
|
handle: Handle,
|
|
entity: codec::EntityType,
|
|
) -> Option<Task<Message>> {
|
|
let i = self.active_tab;
|
|
if self.reject_locked_edit(i, handle) {
|
|
self.tabs[i].active_cmd = None;
|
|
return Some(Task::none());
|
|
}
|
|
let label = self.history_label_from_active_cmd(i, "EDIT");
|
|
self.push_undo_snapshot(i, label);
|
|
let is_dimension = matches!(entity, codec::EntityType::Dimension(_));
|
|
if let Some(current) = self.tabs[i].scene.document.get_entity_mut(handle) {
|
|
*current = entity;
|
|
if is_dimension {
|
|
self.tabs[i].scene.invalidate_dim_block_recorded(handle);
|
|
}
|
|
self.tabs[i]
|
|
.scene
|
|
.bump_entities(&[(handle, crate::scene::ChangeKind::Modified)]);
|
|
self.tabs[i].dirty = true;
|
|
} else {
|
|
self.command_line
|
|
.push_error(crate::t!("The selected object is no longer available.").as_ref());
|
|
}
|
|
self.tabs[i].scene.clear_preview_wire();
|
|
self.tabs[i].active_cmd = None;
|
|
self.tabs[i].snap_result = None;
|
|
self.refresh_properties();
|
|
None
|
|
}
|
|
|
|
pub(super) fn handle_reassociate_center_mark(
|
|
&mut self,
|
|
target: Handle,
|
|
source: Handle,
|
|
point: glam::DVec3,
|
|
) -> Option<Task<Message>> {
|
|
let i = self.active_tab;
|
|
if self.reject_locked_edit(i, target) {
|
|
return Some(Task::none());
|
|
}
|
|
self.push_undo_snapshot(i, "CENTERREASSOCIATE");
|
|
if self.tabs[i]
|
|
.scene
|
|
.reassociate_center_mark(target, source, point)
|
|
{
|
|
self.tabs[i].dirty = true;
|
|
self.command_line
|
|
.push_output(crate::t!("CENTERREASSOCIATE: center mark associated.").as_ref());
|
|
} else {
|
|
self.command_line.push_error(
|
|
crate::t!("CENTERREASSOCIATE: the selected source is not circular.").as_ref(),
|
|
);
|
|
}
|
|
self.tabs[i].active_cmd = None;
|
|
self.tabs[i].snap_result = None;
|
|
self.tabs[i].scene.clear_preview_wire();
|
|
self.refresh_properties();
|
|
None
|
|
}
|
|
|
|
pub(super) fn handle_attreq_needed(&mut self, block_name: String) -> Option<Task<Message>> {
|
|
let i = self.active_tab;
|
|
// When active space is a block editor session (BEDIT), refuse inserting
|
|
// any block that would create a circular nesting dependency.
|
|
if let Some(edit_handle) = self.tabs[i].scene.block_edit_block {
|
|
let container_name = self.tabs[i]
|
|
.scene
|
|
.document
|
|
.block_records
|
|
.iter()
|
|
.find(|br| br.handle == edit_handle)
|
|
.map(|br| br.name.clone());
|
|
if let Some(container) = container_name {
|
|
if crate::scene::block_service::would_create_cycle(
|
|
&self.tabs[i].scene.document,
|
|
&container,
|
|
&block_name,
|
|
) {
|
|
self.command_line.push_error(
|
|
&crate::scene::block_service::circular_dependency_error(
|
|
&container,
|
|
&block_name,
|
|
),
|
|
);
|
|
self.tabs[i].active_cmd = None;
|
|
self.tabs[i].snap_result = None;
|
|
self.tabs[i].scene.clear_preview_wire();
|
|
return None;
|
|
}
|
|
}
|
|
}
|
|
// Collect the full AttributeDefinitions owned by this block
|
|
// record so each created attribute keeps its geometry (#255).
|
|
let mut attdefs: Vec<codec::entities::AttributeDefinition> = {
|
|
let doc = &self.tabs[i].scene.document;
|
|
if let Some(br) = doc.block_records.get(&block_name) {
|
|
br.entity_handles
|
|
.iter()
|
|
.filter_map(|&h| {
|
|
if let Some(codec::EntityType::AttributeDefinition(ad)) = doc.get_entity(h)
|
|
{
|
|
Some(ad.clone())
|
|
} else {
|
|
None
|
|
}
|
|
})
|
|
.collect()
|
|
} else {
|
|
vec![]
|
|
}
|
|
};
|
|
|
|
// ATTREQ 0: attributes take their defaults without prompting.
|
|
if !self.tabs[i].scene.document.header.attribute_request {
|
|
for attdef in &mut attdefs {
|
|
attdef.flags.preset = true;
|
|
}
|
|
}
|
|
if attdefs.is_empty() {
|
|
// No attribute definitions — commit the INSERT directly.
|
|
let entity = self.tabs[i]
|
|
.active_cmd
|
|
.as_mut()
|
|
.and_then(|c| c.attreq_take_insert());
|
|
if let Some(entity) = entity {
|
|
let insert_name = match &entity {
|
|
codec::EntityType::Insert(ins) => Some(ins.block_name.clone()),
|
|
_ => None,
|
|
};
|
|
let label = self.history_label_from_active_cmd(i, "INSERT");
|
|
let delta_safe = self.delta_add_safe(i, &entity);
|
|
let pending = self.begin_undo(i, label, 1, delta_safe);
|
|
self.commit_entity(entity);
|
|
self.tabs[i].dirty = true;
|
|
self.tabs[i].scene.clear_preview_wire();
|
|
// A repeating insertion keeps its command for the next block.
|
|
let keep = self.tabs[i]
|
|
.active_cmd
|
|
.as_ref()
|
|
.is_some_and(|c| c.attreq_continue());
|
|
if keep {
|
|
let prompt = self.tabs[i].active_cmd.as_ref().map(|c| c.prompt());
|
|
if let Some(p) = prompt {
|
|
self.command_line.push_info(&p);
|
|
}
|
|
} else {
|
|
self.tabs[i].active_cmd = None;
|
|
}
|
|
self.tabs[i].snap_result = None;
|
|
if let Some(n) = insert_name {
|
|
self.record_block_insert(&n);
|
|
}
|
|
self.restore_pre_cmd_tangent();
|
|
if let Some(pd) = pending {
|
|
self.commit_undo_delta(i, pd);
|
|
}
|
|
}
|
|
} else {
|
|
// Attributes are placed through the same base-point convention as
|
|
// the render path, so hand the command the definition's base point.
|
|
let block_base = crate::scene::render_graph::block_base_point(
|
|
&self.tabs[i].scene.document,
|
|
&block_name,
|
|
);
|
|
if let Some(cmd) = self.tabs[i].active_cmd.as_mut() {
|
|
cmd.attreq_set_block_base(block_base);
|
|
}
|
|
let completed = self.tabs[i]
|
|
.active_cmd
|
|
.as_mut()
|
|
.and_then(|cmd| cmd.attreq_set_attdefs(attdefs));
|
|
if let Some(entity) = completed {
|
|
if let codec::EntityType::Insert(ins) = &entity {
|
|
let name = ins.block_name.clone();
|
|
self.record_block_insert(&name);
|
|
}
|
|
let keep = self.tabs[i]
|
|
.active_cmd
|
|
.as_ref()
|
|
.is_some_and(|c| c.attreq_continue());
|
|
return Some(self.apply_cmd_result(if keep {
|
|
CmdResult::CommitEntity(entity)
|
|
} else {
|
|
CmdResult::CommitAndExit(entity)
|
|
}));
|
|
}
|
|
let prompt = self.tabs[i].active_cmd.as_ref().map(|c| c.prompt());
|
|
if let Some(p) = prompt {
|
|
self.command_line.push_info(&p);
|
|
}
|
|
}
|
|
None
|
|
}
|
|
|
|
pub(super) fn handle_commit_live_entity(&mut self, entity: codec::EntityType) {
|
|
let i = self.active_tab;
|
|
let label = self.history_label_from_active_cmd(i, "ENTITY");
|
|
let delta_safe = self.delta_add_safe(i, &entity);
|
|
let pending = self.begin_undo(i, label, 1, delta_safe);
|
|
let handle = self.commit_entity_handle(entity);
|
|
self.tabs[i].dirty = true;
|
|
self.tabs[i].scene.clear_preview_wire();
|
|
if let Some(pd) = pending {
|
|
self.commit_undo_delta(i, pd);
|
|
}
|
|
if let Some(h) = handle {
|
|
// Keep the live document Arc unique while later vertices
|
|
// replace the geometry in place. The final Arc is captured
|
|
// by UpdateLiveEntity when the command completes.
|
|
self.defer_live_entity_history_after(i, h);
|
|
if let Some(cmd) = self.tabs[i].active_cmd.as_mut() {
|
|
cmd.set_live_handle(h);
|
|
}
|
|
}
|
|
let prompt = self.tabs[i].active_cmd.as_ref().map(|c| c.prompt());
|
|
if let Some(p) = prompt {
|
|
self.command_line.push_info(&p);
|
|
}
|
|
}
|
|
|
|
pub(super) fn handle_update_live_entity(
|
|
&mut self,
|
|
handle: Handle,
|
|
entity: codec::EntityType,
|
|
finish: bool,
|
|
) {
|
|
let i = self.active_tab;
|
|
let tracks_draw_anchor = matches!(
|
|
&entity,
|
|
codec::EntityType::Line(_)
|
|
| codec::EntityType::Arc(_)
|
|
| codec::EntityType::LwPolyline(_)
|
|
| codec::EntityType::Polyline(_)
|
|
| codec::EntityType::Polyline2D(_)
|
|
| codec::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
|
|
// map key and drop it from rendering / hit-test). No undo
|
|
// snapshot — the create already pushed one, so the whole object
|
|
// reverts as a unit.
|
|
if let Some(old) = self.tabs[i].scene.document.get_entity_mut(handle) {
|
|
// The initial live commit assigns the handle, owning block,
|
|
// current layer and common display properties. Geometry
|
|
// refreshes must retain all of that identity; replacing only
|
|
// the handle and layer reset owner_handle to NULL and made
|
|
// the completed entity unavailable to scoped operations such
|
|
// as parametric constraints.
|
|
let common = old.common().clone();
|
|
let mut new = entity;
|
|
*new.common_mut() = common;
|
|
*old = new;
|
|
self.tabs[i]
|
|
.scene
|
|
.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();
|
|
self.tabs[i].active_cmd = None;
|
|
self.tabs[i].snap_result = None;
|
|
self.restore_pre_cmd_tangent();
|
|
} else {
|
|
let prompt = self.tabs[i].active_cmd.as_ref().map(|c| c.prompt());
|
|
if let Some(p) = prompt {
|
|
self.command_line.push_info(&p);
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(super) fn handle_finalize_live_entity(&mut self, handle: Handle) {
|
|
let i = self.active_tab;
|
|
// The live geometry already matches the command's committed
|
|
// vertices. Close the deferred history image and UI state
|
|
// without publishing a redundant Modified delta.
|
|
self.finish_live_entity_history(i, handle);
|
|
self.tabs[i].scene.clear_preview_wire();
|
|
self.tabs[i].active_cmd = None;
|
|
self.tabs[i].snap_result = None;
|
|
self.restore_pre_cmd_tangent();
|
|
}
|
|
|
|
pub(super) fn handle_remove_live_entity(&mut self, handle: Handle) {
|
|
let i = self.active_tab;
|
|
// The command backed off below a valid entity (PLINE Undo at
|
|
// one remaining vertex): take the live entity out of the
|
|
// 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());
|
|
if let Some(p) = prompt {
|
|
self.command_line.push_info(&p);
|
|
}
|
|
}
|
|
}
|