Edit Sketch no longer shows bodies a later feature deletes (#1329)

Fixes #1167. While a sketch is edited, a body that a rolled-off, unsuppressed Remove deletes is not shown (unless the sketch is built on it); the history stays rolled back, only the view changes. Drawing, picking, screenshots, thumbnails and the browser's Bodies folder agree; document.inspect reports removed_later. Checked on main + this branch: cargo check --workspace, the engine sketch_edit_removed/occurrences tests and the UI viewport/browser tests.
This commit is contained in:
Lasse Lian authored and GitHub committed 2026-10-15 06:34:01 -04:00
1 parent 5fa8e7d60a
commit 9fbbf8dc3a
9 files changed
+332 -4

No files matched your search

+70 -2
View File
@@ -1,6 +1,6 @@
//! Exact placement identity and session view/authoring state.
use crate::{EngineError, Result, Sel, Session};
use solidcraft_doc::{IDENTITY, Mat, mat_mul};
use solidcraft_doc::{FeatureKind, IDENTITY, Mat, PlaneRef, mat_mul};
#[derive(Clone, Debug)]
pub struct OccurrenceBody {
@@ -31,6 +31,14 @@ pub(crate) struct VisibilityCache {
bodies: std::collections::BTreeSet<String>,
}
/// [`Session::body_removed_later`] for one revision, sketch and history marker.
pub(crate) struct RemovedLaterCache {
revision: u64,
sketch: u64,
marker: usize,
bodies: std::collections::BTreeSet<String>,
}
impl Session {
/// Document with uncaptured occurrence positions applied, without changing saved geometry.
pub fn placed_document(&self) -> std::borrow::Cow<'_, solidcraft_doc::Document> {
@@ -183,7 +191,7 @@ impl Session {
}
pub fn body_visible(&self, name: &str) -> bool {
if self.visibility.hidden_bodies.iter().any(|n| n == name) {
if self.visibility.hidden_bodies.iter().any(|n| n == name) || self.body_removed_later(name) {
return false;
}
if self.visibility.hidden_occurrences.is_empty() {
@@ -205,6 +213,66 @@ impl Session {
self.doc.body_frames(&self.model.state()).iter().find(|(n, _, _, _)| n == name).is_none_or(|(_, _, path, _)| self.occurrence_visible(path))
}
/// Is this body deleted by a feature the history marker has rolled off, while a sketch is
/// being edited? Edit Sketch rolls the timeline back to the sketch, so a body made before the
/// sketch and removed after it is part of the shown model again; it stays out of the view,
/// as it does once the timeline runs to the end. Nothing else changes: the model, its state
/// and every command still see the history as it was rolled to. A body the sketch is
/// built on (the face it sits on, an edge it projects) stays.
pub fn body_removed_later(&self, name: &str) -> bool {
let (Some(sketch), Some(marker)) = (self.active_sketch, self.doc.marker) else { return false };
if marker >= self.doc.features.len() {
return false;
}
let Ok(mut cache) = self.removed_later_cache.lock() else { return false };
if cache.as_ref().is_none_or(|c| c.revision != self.revision || c.sketch != sketch || c.marker != marker) {
*cache = Some(RemovedLaterCache { revision: self.revision, sketch, marker, bodies: self.removed_later_bodies(sketch, marker) });
}
cache.as_ref().is_some_and(|c| c.bodies.contains(name))
}
/// The names (as shown in the world) of the bodies that features from `marker` on remove,
/// leaving out the bodies `sketch` is built on.
fn removed_later_bodies(&self, sketch: u64, marker: usize) -> std::collections::BTreeSet<String> {
let none = std::collections::BTreeSet::new();
let Some(FeatureKind::Sketch { plane, sketch: made }) = self.doc.feature(sketch).map(|f| &f.kind) else { return none };
let mut kept: std::collections::BTreeSet<&str> = std::collections::BTreeSet::new();
let mut plane = plane;
loop {
match plane {
PlaneRef::Offset { base, .. } | PlaneRef::AtAngle { base, .. } => plane = base,
// A face attachment that names no exact source: which body it sits on is unknown,
// so none is taken from the view.
PlaneRef::Face { anchor: None, .. } => return none,
PlaneRef::Face { anchor: Some(anchor), .. } => {
kept.insert(anchor.target.body.as_str());
break;
}
_ => break,
}
}
for link in &made.links {
kept.extend(link.source.bodies());
}
let shown = self.model.state();
let mut removed = std::collections::BTreeSet::new();
for feature in self.doc.features.iter().skip(marker).filter(|f| !f.suppressed) {
if let FeatureKind::Remove { bodies } = &feature.kind {
removed.extend(bodies.iter().filter(|b| !kept.contains(b.as_str()) && shown.body(b).is_some()).cloned());
}
}
if removed.is_empty() || self.doc.occurrences.is_empty() {
return removed;
}
// A component's bodies are shown once per placement, each under its own name.
self.doc
.body_frames_with_source(&shown)
.into_iter()
.filter(|(_, source, _, _, _)| removed.contains(source))
.map(|(world, ..)| world)
.collect()
}
pub(crate) fn prune_occurrence_state(&mut self) {
self.pending_moves.retain(|id, _| self.doc.occurrences.iter().any(|o| o.id == *id));
if self.active_component != 0 && !self.doc.components.iter().any(|c| c.id == self.active_component) {