geop_lib_booleans/
trim.rs1use std::collections::{HashMap, HashSet};
14
15use geop_core_math::{
16 geop_error::{GeopError, GeopResult, WithContext},
17 union_find::UnionFind,
18 with_context,
19};
20use geop_core_topology::{Body, EdgeId, FaceId, ShellId, SolidId, contains::shell::Classified};
21use geop_ops_model::{NamedModel, Namer};
22
23use crate::{
24 boolean::{classify_face, copy_of},
25 remesh::{RemeshParams, remesh},
26};
27
28pub fn trim_up_to_next(
36 model: &mut NamedModel,
37 namer: &Namer,
38 sheet: ShellId,
39 targets: &[SolidId],
40 (start, end): (&HashSet<String>, &HashSet<String>),
41 params: RemeshParams,
42) -> GeopResult<ShellId> {
43 let ctx = with_context!(
44 "trim_up_to_next(name={}, sheet={sheet}, targets={targets:?})",
45 namer.root()
46 );
47 let copy = copy_of(model, namer, targets).with_context(&ctx)?;
49
50 let origins = remesh(model, namer, Body::Sheet(sheet), copy, params).with_context(&ctx)?;
51 let topology = model.topology();
52 let faces = topology.body_faces(Body::Sheet(sheet)).with_context(&ctx)?;
53 let copy_faces: HashSet<FaceId> = topology
54 .solid_faces(copy)
55 .with_context(&ctx)?
56 .into_iter()
57 .collect();
58 let mut classes = Vec::with_capacity(faces.len());
59 let mut classified = Classified::default();
60 for &face in &faces {
61 classes.push(
62 classify_face(topology, face, copy, params, &mut classified)
63 .with_context(&ctx)
64 .with_context(with_context!("classifying face {face}"))?,
65 );
66 }
67
68 let mut users: HashMap<EdgeId, Vec<usize>> = HashMap::new();
71 let mut crossed: HashSet<EdgeId> = HashSet::new();
72 for (k, &face) in faces.iter().enumerate() {
73 for edge in topology.face_edges(face) {
74 users.entry(edge).or_default().push(k);
75 }
76 }
77 for &face in ©_faces {
78 crossed.extend(topology.face_edges(face));
79 }
80 let mut pieces = UnionFind::new(faces.len());
81 for (edge, users) in &users {
82 if crossed.contains(edge) {
83 continue;
84 }
85 for pair in users.windows(2) {
86 if classes[pair[0]] == classes[pair[1]] {
87 pieces.union(pair[0], pair[1]);
88 }
89 }
90 }
91 let on = |names: &HashSet<String>, edge: &EdgeId| {
92 origins
93 .edges
94 .get(edge)
95 .is_some_and(|origin| names.contains(origin))
96 };
97 let mut reached = HashSet::new();
98 for (edge, users) in &users {
99 if on(start, edge) {
100 reached.extend(users.iter().map(|&k| pieces.find(k)));
101 }
102 }
103 if reached.is_empty() {
104 return Err(ctx(GeopError::new("trim: the sheet's start is gone")));
105 }
106 if users.iter().any(|(edge, users)| {
107 on(end, edge) && users.iter().any(|&k| reached.contains(&pieces.find(k)))
108 }) {
109 return Err(ctx(GeopError::new(
110 "up to next: nothing stops the face — it reaches its end without meeting the target all along",
111 )));
112 }
113 let keep: Vec<FaceId> = (0..faces.len())
114 .filter(|&k| reached.contains(&pieces.find(k)))
115 .map(|k| faces[k])
116 .collect();
117 let kept = model
118 .assemble_sheet(&[Body::Sheet(sheet), copy.into()], &keep)
119 .with_context(&ctx)?;
120 Ok(kept.expect("the start's piece is kept"))
121}