Complete LOFT commands, surfaces, previews and editable history

This commit is contained in:
ramox81 committed 2026-09-04 13:03:33 +03:00
1 parent 8e3d851133
commit 479bf87988
15 files changed
+1505 -161

No files matched your search

+60 -40
View File
@@ -4082,53 +4082,73 @@ impl OpenCADStudio {
}
// ── LOFT ──────────────────────────────────────────────────────
CmdResult::LoftEntities { handles, color: _ } => {
if let Some(handle) = handles
.iter()
.find(|handle| self.tabs[i].scene.is_layer_locked(**handle))
.copied()
{
self.reject_locked_edit(i, handle);
self.tabs[i].active_cmd = None;
return Task::none();
}
CmdResult::LoftEntities { sections, guides, path, mode, options, color: _ } => {
use crate::command::LoftSectionSelection;
use crate::modules::insert::solid3d_cmds::empty_solid3d;
use crate::scene::model::{solid_history, sweep_model};
let profiles: Vec<acadrust::EntityType> = handles
.iter()
.filter_map(|handle| self.tabs[i].scene.document.get_entity(*handle).cloned())
.collect();
let result = sweep_model::loft_history(&profiles)
.and_then(|history| {
cadkernel::acis::rebuild_body(&history)
.ok()
.map(|solid| (solid, history))
})
.or_else(|| {
sweep_model::lofted(&profiles).map(|solid| {
let history = solid_history::brep_op(&solid);
(solid, history)
})
});
if let Some((solid, history)) = result {
let pending = self.begin_undo(i, "LOFT", 1, true);
let created = self.add_solid_model(empty_solid3d(), solid, history);
if !created.is_null() {
self.tabs[i].dirty = true;
self.command_line
.push_output(crate::t!("LOFT: solid created.").as_ref());
if let Some(pd) = pending {
self.commit_undo_delta(i, pd);
}
}
use crate::scene::model::loft_command_model;
let mut sources = sections.iter().flat_map(|section| match section {
LoftSectionSelection::Entity(handle) => vec![*handle],
LoftSectionSelection::Join(handles) => handles.clone(),
LoftSectionSelection::Point(_) => Vec::new(),
}).collect::<Vec<_>>();
sources.sort_unstable_by_key(|handle| handle.value());
sources.dedup();
let delete_sources = self.tabs[i].scene.document.header.delete_objects;
let locked = delete_sources && sources.iter().any(|handle| self.tabs[i].scene.is_layer_locked(*handle));
let available = sources.iter().chain(guides.iter()).copied().chain(path)
.filter_map(|handle| self.tabs[i].scene.document.get_entity(handle)
.cloned().map(|entity| (handle, entity))).collect::<Vec<_>>();
let result = if locked {
Err("LOFT: a source is on a locked layer; disable source deletion or unlock it.".to_string())
} else {
self.command_line.push_error(crate::t!("LOFT: select at least two closed profiles.").as_ref());
loft_command_model::record(&sections, &guides, path, &available, mode, options)
.and_then(|record| cadkernel::acis::rebuild_loft_with_options(&record).map(|body| (body, record)))
};
match result {
Ok((body, record)) => {
let surface = record.parameters.as_ref().is_some_and(|settings| settings.surface);
let dirty_before = self.tabs[i].dirty;
let pending = self.begin_undo(i, "LOFT", 1, true);
let created = if surface {
let handle = self.add_surface_model(loft_command_model::surface_entity(&record), body);
if !handle.is_null() {
self.tabs[i].scene.create_solid_history(handle,
acadrust::objects::SolidHistoryOperation::Loft(record));
}
handle
} else {
self.add_solid_model(empty_solid3d(), body,
acadrust::objects::SolidHistoryOperation::Loft(record))
};
if created.is_null() {
// The creation helper already removed its provisional
// result. Discard its empty undo recording as well.
self.tabs[i].scene.take_undo_recording();
self.tabs[i].dirty = dirty_before;
self.command_line.push_error(crate::t!("LOFT could not create a complete display. The source sections were preserved.").as_ref());
return Task::none();
} else {
if delete_sources { self.tabs[i].scene.erase_entities(&sources); }
self.tabs[i].scene.deselect_all();
self.tabs[i].scene.select_entity(created, true);
self.tabs[i].dirty = true;
let message = if surface {
crate::t!("LOFT: surface created.")
} else { crate::t!("LOFT: solid created.") };
self.command_line.push_output(message.as_ref());
}
if let Some(pending) = pending { self.commit_undo_delta(i, pending); }
}
Err(error) => {
self.command_line.push_error(&error);
return Task::none();
}
}
self.tabs[i].active_cmd = None;
self.tabs[i].snap_result = None;
self.tabs[i].scene.clear_preview_wire();
self.restore_pre_cmd_tangent();
self.refresh_properties();
}
CmdResult::SolidEdgeBlend {
+7 -1
View File
@@ -832,7 +832,13 @@ impl OpenCADStudio {
"LOFT" => {
use crate::modules::insert::solid3d_cmds::LoftCommand;
let color = self.tabs[i].scene.layer_color(&self.tabs[i].active_layer);
let cmd = LoftCommand::new(color);
let isolines = self.tabs[i].scene.document.header.isolines.max(0) as usize;
let selected = self.tabs[i].scene.selected_handles_in_order().into_iter()
.filter_map(|handle| self.tabs[i].scene.document.get_entity(handle)
.cloned().map(|entity| (handle, entity))).collect();
let available = self.tabs[i].scene.document.entities()
.map(|entity| (entity.common().handle, entity.clone())).collect();
let cmd = LoftCommand::new(color, isolines, crate::command::ExtrudeMode::Solid, selected, available);
self.command_line.push_info(&cmd.prompt());
self.tabs[i].active_cmd = Some(Box::new(cmd));
}
+33
View File
@@ -830,6 +830,8 @@ impl OpenCADStudio {
| "BLIPMODE"
| "SPLFRAME"
| "DELOBJ"
| "SOLIDHIST"
| "SHOWHIST"
| "PLINEGEN"
| "PSLTSCALE"
| "DISPSILH"
@@ -926,6 +928,37 @@ impl OpenCADStudio {
"SETVAR: LTSCALE CELTSCALE PDMODE PDSIZE TEXTSIZE ORTHOMODE FILLMODE MIRRTEXT FRAME IMAGEFRAME PDFFRAME WIPEOUTFRAME XCLIPFRAME POINTCLOUDCLIPFRAME ZOOMWHEEL ZOOMFACTOR CURSORSIZE PICKBOX CURSORTYPE SNAPANG TEXTFILL CLIPROMPTLINES ATTREQ ATTDIA DIMASSOC DIMCONTINUEMODE ANGBASE ANGDIR SKETCHINC SKPOLY SKTOLERANCE DONUTID DONUTOD CENTEREXE CENTERLAYER CENTERLTYPE CENTERLTSCALE CENTERLTYPEFILE CENTERCROSSSIZE CENTERCROSSGAP CENTERMARKEXE COLORTHEME SELECTIONAREA SELECTIONAREAOPACITY SELECTIONEFFECT SELECTIONEFFECTCOLOR WINDOWSAREACOLOR CROSSINGAREACOLOR SELECTIONPREVIEW GRIPSIZE GRIPCOLOR GRIPHOT GRIPHOVER | CLAYER CELTYPE TEXTSTYLE (read-only)",
);
} else {
if matches!(name.as_str(), "SHOWHIST" | "SOLIDHIST") {
let current = if name == "SHOWHIST" {
self.tabs[i].scene.document.header.show_solid_history.clamp(0, 2)
} else {
i16::from(self.tabs[i].scene.document.header.record_solid_history)
};
if let Some(value) = &value {
let maximum = if name == "SHOWHIST" { 2 } else { 1 };
match value.parse::<i16>().ok().filter(|value| (0..=maximum).contains(value)) {
Some(mode) => {
if current != mode {
self.push_undo_snapshot(i, &name);
if name == "SHOWHIST" {
self.tabs[i].scene.document.header.show_solid_history = mode;
self.tabs[i].scene.bump_geometry();
} else {
self.tabs[i].scene.document.header.record_solid_history = mode != 0;
}
self.tabs[i].dirty = true;
self.refresh_properties();
}
self.command_line.push_output(&format!("{name} = {mode}"));
}
None => self.command_line.push_error(&format!("{name}: expected an integer from 0 to {maximum}.")),
}
} else {
self.command_line.push_output(&format!("Enter new value for {name} <{current}>:"));
self.pending_setvar = Some(name.clone());
}
return Some(self.finish_dispatch(cmd));
}
let frame_kind = crate::scene::frame::kind_for_name(&name);
if name == "FRAME" || frame_kind.is_some() {
let current = frame_kind.map_or_else(
+7 -2
View File
@@ -35,8 +35,10 @@ impl super::OpenCADStudio {
let Some(handle) = self.commit_entity_handle(entity) else {
return Handle::NULL;
};
let require_complete = matches!(history, SolidHistoryOperation::Loft(_));
self.tabs[i].scene.create_solid_history(handle, history);
if !self.tabs[i].scene.register_solid_model(handle, solid) {
if !self.tabs[i].scene.register_solid_model(handle, solid)
|| (require_complete && self.tabs[i].scene.meshes.get(&handle).is_none_or(|mesh| !mesh.complete)) {
// A command result is not successful until it has renderable
// geometry. Roll the new entity back so DELOBJ cannot consume a
// visible source in exchange for an invisible result.
@@ -69,7 +71,10 @@ impl super::OpenCADStudio {
let Some(handle) = self.commit_entity_handle(entity) else {
return Handle::NULL;
};
if !self.tabs[i].scene.register_solid_model(handle, surface) {
let require_complete = self.tabs[i].scene.document.get_entity(handle).is_some_and(|entity|
matches!(entity, EntityType::Surface(value) if value.kind == acadrust::entities::SurfaceKind::Lofted));
if !self.tabs[i].scene.register_solid_model(handle, surface)
|| (require_complete && self.tabs[i].scene.meshes.get(&handle).is_none_or(|mesh| !mesh.complete)) {
self.tabs[i].scene.rollback_new_entities(&[handle]);
return Handle::NULL;
}
+12 -1
View File
@@ -2001,7 +2001,18 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
}
self.push_undo_snapshot(i, "CHPROP");
if crate::scene::model::solid_history::is_history_choice(field) {
if crate::scene::model::solid_history::is_loft_geometry_choice(field) {
// These choices change the generated body, not just history
// flags. Use the same transactional rebuild as numeric edits.
for &handle in &handles {
if self.tabs[i].scene.is_layer_locked(handle) {
continue;
}
self.tabs[i]
.scene
.apply_solid_history_property(handle, field, &value);
}
} else if crate::scene::model::solid_history::is_history_choice(field) {
for &handle in &handles {
if self.tabs[i].scene.is_layer_locked(handle) {
continue;
+13 -3
View File
@@ -3152,6 +3152,19 @@ impl OpenCADStudio {
is_added_polyline_vertex(original, current, target.grip_id)
.then_some(target.grip_id)
});
// Keep originals available until every history shape has a valid
// final display. A rejected rebuild cancels the entire gesture.
let history_handles: Vec<_> = self.grip_preview_handles.iter().copied()
.filter(|handle| self.tabs[i].scene.document.solid_history_operation(*handle).is_some())
.collect();
for handle in history_handles {
if !self.tabs[i].scene.finalize_solid_history(handle) {
self.cancel_active_grip_edit();
self.command_line.push_error(crate::t!("The edited shape could not be displayed; the original geometry was restored.").as_ref());
self.refresh_properties();
return Task::none();
}
}
self.tabs[i].active_grip = None;
// Commit the grip drag as one undoable group, then put every
// edited entity back into the resident tessellation.
@@ -3160,9 +3173,6 @@ impl OpenCADStudio {
let history_originals = std::mem::take(&mut self.grip_history_originals);
let dirty_before = self.grip_dirty_before.take().unwrap_or(self.tabs[i].dirty);
if !handles.is_empty() {
for &handle in &handles {
let _ = self.tabs[i].scene.finalize_solid_history(handle);
}
if !originals.is_empty() {
self.push_entity_group_history(
i,