feat(sweep): associative swept surfaces in the drawing's network

Swept surfaces made with SURFACEASSOCIATIVITY on keep their profile and
path and are written as a swept-surface action of the drawing's
associative network, reading the profile and path and their scale and
twist expressions. They follow edits of their sources.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ramox81andClaude Opus 5.5 committed 2026-10-08 13:29:55 +03:00
1 parent 018ac455cb
commit 48c3c433a5
10 files changed
+627 -22

No files matched your search

+70
View File
@@ -1130,6 +1130,49 @@ impl Scene {
rebuilt
}
/// Associative swept surfaces follow edits of their profile or path:
/// swept again from them with the options they were made with. A
/// surface edited along with its sources keeps its own edit. Returns the
/// surfaces rebuilt.
pub(crate) fn refresh_associative_sweeps(&mut self, changes: &[(Handle, crate::scene::ChangeKind)]) -> Vec<(Handle, crate::scene::ChangeKind)> {
use crate::scene::model::sweep_model;
let changed = changes.iter()
.filter(|(_, kind)| !matches!(kind, crate::scene::ChangeKind::Removed))
.map(|(handle, _)| *handle)
.collect::<rustc_hash::FxHashSet<_>>();
if changed.is_empty() { return Vec::new(); }
let linked = self.document.entities()
.filter_map(|entity| {
let handle = entity.common().handle;
let (profile, path) = sweep_model::sweep_sources(entity)?;
(!changed.contains(&handle) && (changed.contains(&profile) || changed.contains(&path)))
.then_some((handle, profile, path))
})
.collect::<Vec<_>>();
let mut rebuilt = Vec::new();
for (handle, profile, path) in linked {
let (Some(surface), Some(profile), Some(path)) = (
self.document.get_entity(handle),
self.document.get_entity(profile),
self.document.get_entity(path),
) else { continue };
let Some((record, body)) = sweep_model::resweep_from_sources(surface, profile, path) else { continue };
let EntityType::Surface(fresh) = sweep_model::swept_surface_entity(&record) else { continue };
let Some(document) = crate::scene::convert::acis_export::solid_to_sat(&body) else { continue };
let Some(display) = self.prepare_solid_model_display(handle, &body) else { continue };
let before = self.document.get_entity(handle).cloned().map(std::sync::Arc::new);
self.record_undo_before(handle, before);
if let Some(EntityType::Surface(surface)) = self.document.get_entity_mut(handle) {
surface.acis_data = codec::entities::AcisData::from_sat(&document.to_sat_string());
surface.surface_data = fresh.surface_data;
surface.point_of_reference = fresh.point_of_reference;
}
self.register_prepared_solid_model(handle, body, display);
rebuilt.push((handle, crate::scene::ChangeKind::Modified));
}
rebuilt
}
/// Swept surfaces the reference keeps associative to a scale or twist
/// expression (an associative swept surface action whose value parameter
/// depends on an expression variable) are linked the same way here.
@@ -1140,6 +1183,7 @@ impl Scene {
_ => None,
};
let mut links = Vec::new();
let mut sources = Vec::new();
for item in self.document.objects.values() {
let ObjectType::Associative(action) = item else { continue };
let AssociativeData::Action(action) = &action.data else { continue };
@@ -1160,6 +1204,32 @@ impl Scene {
if expressions.iter().any(Option::is_some) {
links.push((written.dependent_on, expressions));
}
// The profile and path the action reads.
let source = |name: &str| {
action.owned_parameters.iter().find_map(|parameter| {
let Some(AssociativeData::PathActionParam(path)) = object(*parameter) else { return None };
if path.compound.action_param.name != name { return None; }
let Some(AssociativeData::EdgeActionParam(edge)) = object(*path.compound.parameters.first()?) else { return None };
match object(edge.single_dependency.dependency) {
Some(AssociativeData::Dependency(dependency)) => Some(dependency.dependent_on),
_ => None,
}
})
};
if let (Some(profile), Some(path)) = (source("SweepProfile"), source("SweepPath")) {
sources.push((written.dependent_on, profile, path));
}
}
for (surface, profile, path) in sources {
let Some(entity @ EntityType::Surface(value)) = self.document.get_entity(surface) else { continue };
if crate::scene::model::sweep_model::sweep_sources(entity).is_some() { continue; }
let codec::entities::SurfaceData::Swept { options, .. } = &value.surface_data else { continue };
let linked = crate::command::SweepOptions {
align: options.sweep_alignment_flags == 1,
bank: options.bank,
..crate::command::SweepOptions::default()
};
crate::scene::model::sweep_model::link_sweep_sources(&mut self.document, surface, profile, path, &linked);
}
for (handle, expressions) in links {
let Some(entity) = self.document.get_entity(handle) else { continue };