fix(model): repair solid booleans

Track selection order for directional subtraction and use the kernel fixes for periodic curved-face intersections.\n\nRefs #816
This commit is contained in:
Hakan Seven 2026-08-20 16:00:20 +03:00
commit bb49f92e99
5 changed files with 57 additions and 10 deletions

2
Cargo.lock generated
View file

@ -878,7 +878,7 @@ checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
[[package]]
name = "cadkernel"
version = "0.1.0"
source = "git+https://github.com/HakanSeven12/cadkernel.git?rev=eda1263#eda1263da23c1b18e7593151a277a0593bb317ef"
source = "git+https://github.com/HakanSeven12/cadkernel.git?rev=3d5de36#3d5de36cb5b391f17da924c5f80e34a0cdc1d948"
dependencies = [
"acadrust",
"cavalier_contours",

View file

@ -28,7 +28,7 @@ rfd = "0.17"
clap = { version = "4", features = ["derive"] }
env_logger = "0.11"
acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "0975677", features = ["serde"] }
cadkernel = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "eda1263", features = ["acis", "offset"] }
cadkernel = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "3d5de36", features = ["acis", "offset"] }
dwg-thumbnailer = { path = "crates/dwg-thumbnailer" }
flate2 = "1"
image = { version = "0.25", default-features = false, features = ["png", "jpeg", "bmp", "tiff"] }

View file

@ -43,9 +43,8 @@ impl super::OpenCADStudio {
// Selected entities that have a cached B-rep.
let handles: Vec<Handle> = self.tabs[i]
.scene
.selected
.iter()
.copied()
.selected_handles_in_order()
.into_iter()
.filter(|h| !self.tabs[i].scene.is_layer_locked(*h))
.filter(|h| self.tabs[i].scene.solid_models.contains_key(h))
.collect();

View file

@ -1422,6 +1422,7 @@ pub struct Scene {
lighting_cache: RefCell<HashMap<(Handle, u64), Vec<SceneLight>>>,
/// Currently selected entity handles.
pub selected: HashSet<Handle>,
selected_order: Vec<Handle>,
/// Session-only ISOLATEOBJECTS / HIDEOBJECTS state. Never written to DWG/DXF.
pub object_isolation: ObjectIsolationState,
/// Entity handles temporarily removed from the base render while an
@ -1841,6 +1842,7 @@ impl Scene {
object_data_cache: crate::entities::object_data::ObjectDataCache::default(),
lighting_cache: RefCell::new(HashMap::default()),
selected: HashSet::default(),
selected_order: Vec::new(),
object_isolation: ObjectIsolationState::default(),
preview_hidden: HashSet::default(),
command_preview_hidden: HashSet::default(),
@ -4377,6 +4379,7 @@ impl Scene {
.hidden
.extend(self.selected.iter().copied());
self.selected.clear();
self.selected_order.clear();
self.bump_entities(&changes);
}

View file

@ -40,17 +40,42 @@ impl Scene {
if exclusive {
self.selected.clear();
self.selected_order.clear();
}
self.selected.extend(handles);
for handle in handles {
if self.selected.insert(handle) {
self.selected_order.push(handle);
}
}
self.bump_selection();
}
pub fn deselect_all(&mut self) {
self.selected.clear();
self.selected_order.clear();
self.bump_selection();
}
pub(crate) fn selected_handles_in_order(&self) -> Vec<Handle> {
let mut seen = HashSet::default();
let mut ordered: Vec<_> = self
.selected_order
.iter()
.copied()
.filter(|handle| self.selected.contains(handle) && seen.insert(*handle))
.collect();
let mut missing: Vec<_> = self
.selected
.iter()
.copied()
.filter(|handle| seen.insert(*handle))
.collect();
missing.sort_unstable_by_key(|handle| handle.value());
ordered.extend(missing);
ordered
}
/// Replace the complete selection and invalidate the GPU highlight overlay
/// only when its contents actually changed. History/file/command paths must
/// use this instead of assigning `selected` directly.
@ -62,7 +87,22 @@ impl Scene {
.collect();
if self.selected != selected {
let mut seen = HashSet::default();
let mut order: Vec<_> = self
.selected_order
.iter()
.copied()
.filter(|handle| selected.contains(handle) && seen.insert(*handle))
.collect();
let mut added: Vec<_> = selected
.iter()
.copied()
.filter(|handle| seen.insert(*handle))
.collect();
added.sort_unstable_by_key(|handle| handle.value());
order.extend(added);
self.selected = selected;
self.selected_order = order;
self.bump_selection();
}
}
@ -74,6 +114,7 @@ impl Scene {
for handle in handles {
changed |= self.selected.remove(&handle);
self.selected_order.retain(|selected| *selected != handle);
}
if changed {
@ -82,9 +123,9 @@ impl Scene {
}
pub fn selected_entities(&self) -> Vec<(Handle, &EntityType)> {
self.selected
.iter()
.filter_map(|&h| self.document.get_entity(h).map(|e| (h, e)))
self.selected_handles_in_order()
.into_iter()
.filter_map(|h| self.document.get_entity(h).map(|e| (h, e)))
.collect()
}
@ -107,7 +148,7 @@ impl Scene {
match scope {
crate::app::QSelectScope::CurrentSpace => self.current_layout_entity_handles(),
crate::app::QSelectScope::CurrentSelection => {
self.selected.iter().copied().collect()
self.selected_handles_in_order()
}
}
}
@ -152,6 +193,7 @@ impl Scene {
let key = (entity_type_name(e), e.as_entity().layer().to_string());
if pairs.contains(&key) {
self.selected.insert(h);
self.selected_order.push(h);
added += 1;
}
}
@ -174,9 +216,11 @@ impl Scene {
.filter_map(|w| Self::handle_from_wire_name(&w.name))
.collect();
self.selected.clear();
self.selected_order.clear();
for h in all {
if !prev.contains(&h) {
self.selected.insert(h);
self.selected_order.push(h);
}
}
self.bump_selection();
@ -679,6 +723,7 @@ impl Scene {
self.remember_removed_cache_categories(h);
self.document.remove_entity_arc(h);
highlight_changed |= self.selected.remove(&h);
self.selected_order.retain(|selected| *selected != h);
if self.hover_highlight == Some(h) {
self.hover_highlight = None;
highlight_changed = true;