Skip to main content

geop_ops_model/
edit.rs

1//! Forwarding wrappers for every `Model` operation that creates or deletes
2//! entities: each registers what it creates under the caller's name and
3//! forgets what it deletes.
4
5use std::collections::HashSet;
6
7use geop_core_geometry::deform::Deformation;
8use geop_core_math::{
9    geop_error::{GeopError, GeopResult},
10    scalars::Scalar,
11    vector::Vector3,
12};
13use geop_core_topology::{
14    Body, Edge, EdgeId, FaceId, ShellId, SolidId, Vertex, VertexId,
15    build::{BodySpec, BuiltBody, SpecSources},
16};
17
18use super::NamedModel;
19use crate::ids::RefId;
20
21impl NamedModel {
22    /// Forwards to [`geop_core_topology::Model::insert_vertex`]: a vertex on
23    /// nothing yet, about to be split into an edge (see
24    /// [`NamedModel::split_edge_at_vertex`]).
25    pub fn insert_vertex(
26        &mut self,
27        point: Vector3,
28        name: impl Into<String>,
29    ) -> GeopResult<VertexId> {
30        let vertex = self.topology.insert_vertex(Vertex { point });
31        self.names.insert(vertex, name)?;
32        Ok(vertex)
33    }
34
35    /// Forwards to [`geop_core_topology::Model::insert_edge`]: an edge on no
36    /// face yet, about to be spliced into its faces (see
37    /// [`NamedModel::splice_edge_into_face`]).
38    pub fn insert_edge(&mut self, edge: Edge, name: impl Into<String>) -> GeopResult<EdgeId> {
39        let edge = self.topology.insert_edge(edge);
40        self.names.insert(edge, name)?;
41        Ok(edge)
42    }
43
44    /// Forwards to [`geop_core_topology::Model::merge_vertex`], forgetting
45    /// the name of the vertex it deletes. The survivor keeps its own name.
46    pub fn merge_vertex(
47        &mut self,
48        vertex_into_id: VertexId,
49        vertex_deleted_id: VertexId,
50    ) -> GeopResult<()> {
51        self.topology
52            .merge_vertex(vertex_into_id, vertex_deleted_id)?;
53        self.names.remove(vertex_deleted_id);
54        Ok(())
55    }
56
57    /// Forwards to [`geop_core_topology::Model::merge_edge`], forgetting the
58    /// name of the edge it deletes. The survivor keeps its own name.
59    pub fn merge_edge(
60        &mut self,
61        edge_into_id: EdgeId,
62        edge_deleted_id: EdgeId,
63        reversed: bool,
64    ) -> GeopResult<()> {
65        self.topology
66            .merge_edge(edge_into_id, edge_deleted_id, reversed)?;
67        self.names.remove(edge_deleted_id);
68        Ok(())
69    }
70
71    /// Forwards to [`geop_core_topology::Model::reverse_face`]. Creates and
72    /// deletes nothing.
73    pub fn reverse_face(&mut self, face_id: FaceId) -> GeopResult<()> {
74        self.topology.reverse_face(face_id)
75    }
76
77    /// Forwards to [`geop_core_topology::Model::splice_edge_into_face`].
78    ///
79    /// Whether that creates a face depends on how the edge's ends sit on the
80    /// face's boundary, which a caller imprinting a traced curve cannot know
81    /// beforehand — so `new_face_name` is the name for the face *if* one is
82    /// created, and unused otherwise.
83    pub fn splice_edge_into_face(
84        &mut self,
85        edge_id: EdgeId,
86        face_id: FaceId,
87        max_nodes: usize,
88        min_subdivision_size: Scalar,
89        new_face_name: impl Into<String>,
90    ) -> GeopResult<Option<FaceId>> {
91        let new_face = self.topology.splice_edge_into_face(
92            edge_id,
93            face_id,
94            max_nodes,
95            min_subdivision_size,
96        )?;
97        if let Some(face) = new_face {
98            self.names.insert(face, new_face_name)?;
99        }
100        Ok(new_face)
101    }
102
103    /// Forwards to [`geop_core_topology::Model::split_edge_at_vertex`],
104    /// naming the new edge it creates: the (vertex -> end) segment, while
105    /// `edge_id` keeps the (start -> vertex) one and its name.
106    pub fn split_edge_at_vertex(
107        &mut self,
108        edge_id: EdgeId,
109        edge_t: Scalar,
110        vertex_id: VertexId,
111        max_nodes: usize,
112        min_subdivision_size: Scalar,
113        new_edge_name: impl Into<String>,
114    ) -> GeopResult<EdgeId> {
115        let new_edge = self.topology.split_edge_at_vertex(
116            edge_id,
117            edge_t,
118            vertex_id,
119            max_nodes,
120            min_subdivision_size,
121        )?;
122        self.names.insert(new_edge, new_edge_name)?;
123        Ok(new_edge)
124    }
125
126    /// Forwards to [`geop_core_topology::Model::build_body`], naming
127    /// everything it builds after `names`, index for index. Fails, leaving
128    /// the part unchanged, if a name is missing, repeated or taken.
129    pub fn build_body(&mut self, spec: BodySpec, names: BodyNames) -> GeopResult<BuiltBody> {
130        let counts = [
131            (names.vertices.len(), spec.vertices.len(), "vertices"),
132            (names.edges.len(), spec.edges.len(), "edges"),
133            (names.faces.len(), spec.faces.len(), "faces"),
134        ];
135        if let Some((n, m, what)) = counts.iter().find(|(n, m, _)| n != m) {
136            return Err(GeopError::new(format!(
137                "NamedModel::build_body: {n} names for {m} {what}"
138            )));
139        }
140        if names.solid.is_some() != spec.solid {
141            return Err(GeopError::new(format!(
142                "NamedModel::build_body: a solid name {:?} for a body that {} a solid",
143                names.solid,
144                if spec.solid { "is" } else { "is not" }
145            )));
146        }
147        let all = names
148            .vertices
149            .iter()
150            .chain(&names.edges)
151            .chain(&names.faces)
152            .chain(&names.solid);
153        let mut seen = HashSet::new();
154        for name in all {
155            if !seen.insert(name) || self.names.is_taken(name) {
156                return Err(GeopError::new(format!(
157                    "NamedModel::build_body: the name {name:?} is taken"
158                )));
159            }
160        }
161        let built = self.topology.build_body(spec)?;
162        for (&id, name) in built.vertices.iter().zip(names.vertices) {
163            self.names.insert(id, name)?;
164        }
165        for (&id, name) in built.edges.iter().zip(names.edges) {
166            self.names.insert(id, name)?;
167        }
168        for (&id, name) in built.faces.iter().zip(names.faces) {
169            self.names.insert(id, name)?;
170        }
171        if let (Some(id), Some(name)) = (built.solid, names.solid) {
172            self.names.insert(id, name)?;
173        }
174        Ok(built)
175    }
176
177    /// Copies `faces`, with every edge and vertex they use, into a body of
178    /// their own that shares nothing with the originals (see
179    /// [`geop_core_topology::Model::body_spec`]): a solid named `solid`, if
180    /// given, or else a sheet. The copy of each entity named `X` is named
181    /// `rename(X)`.
182    pub fn copy_faces(
183        &mut self,
184        faces: &[FaceId],
185        solid: Option<String>,
186        rename: impl Fn(&str) -> String,
187    ) -> GeopResult<BuiltBody> {
188        let (spec, sources) = self.topology().body_spec(faces, solid.is_some())?;
189        let names = BodyNames {
190            solid,
191            ..self.source_names(&sources)?.renamed(rename)
192        };
193        self.build_body(spec, names)
194    }
195
196    /// Copies the whole of `body` — a solid, its shells kept apart, or a
197    /// sheet — into a body of its own that shares nothing with it. The copy
198    /// of each entity named `X`, the solid included, is named `rename(X)`.
199    pub fn copy_body(
200        &mut self,
201        body: Body,
202        rename: impl Fn(&str) -> String,
203    ) -> GeopResult<BuiltBody> {
204        let (spec, names) = self.body_record(body)?;
205        self.build_body(spec, names.renamed(rename))
206    }
207
208    /// The whole of `body` — a solid, its shells kept apart, or a sheet —
209    /// as a body to build again (see [`NamedModel::build_body`]), with the names
210    /// of its entities: what [`NamedModel::copy_body`] builds, kept to build
211    /// later.
212    pub fn body_record(&self, body: Body) -> GeopResult<(BodySpec, BodyNames)> {
213        // In the order they were made: ids are given in creation order.
214        let mut faces = self.topology().body_faces(body)?;
215        faces.sort();
216        let solid = match body {
217            Body::Solid(solid) => Some(self.name(solid)?.to_string()),
218            Body::Sheet(_) => None,
219        };
220        let (mut spec, sources) = self.topology().body_spec(&faces, solid.is_some())?;
221        // `body_spec` puts every face into one shell; a solid with a void
222        // has more, which the record keeps.
223        let mut shells: Vec<(ShellId, Vec<usize>)> = Vec::new();
224        for (index, &face) in sources.faces.iter().enumerate() {
225            let shell = self.topology().get_face(face)?.shell;
226            match shells.iter_mut().find(|(s, _)| *s == shell) {
227                Some((_, members)) => members.push(index),
228                None => shells.push((shell, vec![index])),
229            }
230        }
231        spec.shells = shells.into_iter().map(|(_, members)| members).collect();
232        let names = BodyNames {
233            solid,
234            ..self.source_names(&sources)?
235        };
236        Ok((spec, names))
237    }
238
239    /// The names of the entities `sources` says a body to build was made
240    /// from (see [`geop_core_topology::Model::body_spec`]), index for index,
241    /// with no solid.
242    pub fn source_names(&self, sources: &SpecSources) -> GeopResult<BodyNames> {
243        fn names<Id: Into<super::RefId> + Copy>(
244            model: &NamedModel,
245            ids: &[Id],
246        ) -> GeopResult<Vec<String>> {
247            (ids.iter())
248                .map(|&id| Ok(model.name(id)?.to_string()))
249                .collect()
250        }
251        Ok(BodyNames {
252            vertices: names(self, &sources.vertices)?,
253            edges: names(self, &sources.edges)?,
254            faces: names(self, &sources.faces)?,
255            solid: None,
256        })
257    }
258
259    /// Takes the topology `body` does not need out of it (see
260    /// [`BodySpec::merged`]): faces that lie on one surface made one, and
261    /// vertices between two edges of the same two faces dropped, the edges
262    /// made one. Returns the body as it is now, rebuilt if anything was
263    /// taken out, the very same otherwise.
264    ///
265    /// What is made one keeps the name of the oldest of what it is made
266    /// of; the names of the others stand for it from then on (see
267    /// [`crate::names::NameRegistry::alias`]), so a step that refers to any
268    /// of them still finds it. An edge between two faces made one is gone,
269    /// and so is its name; so is a vertex dropped. Only an edge `edge_may_go`
270    /// lets and a vertex `vertex_may_go` lets are taken out.
271    pub fn merge_faces_on_one_surface(
272        &mut self,
273        body: Body,
274        edge_may_go: impl Fn(EdgeId) -> bool,
275        vertex_may_go: impl Fn(VertexId) -> bool,
276    ) -> GeopResult<Body> {
277        let (spec, names) = self.body_record(body)?;
278        // The very entities `spec` was made from, index for index.
279        let mut faces = self.topology().body_faces(body)?;
280        faces.sort();
281        let (_, sources) = self.topology().body_spec(&faces, false)?;
282        let (merged, map) = spec.merged(
283            |e| edge_may_go(sources.edges[e]),
284            |v| vertex_may_go(sources.vertices[v]),
285        )?;
286        if merged.faces.len() == spec.faces.len() && merged.edges.len() == spec.edges.len() {
287            return Ok(body);
288        }
289        let face_into: Vec<Option<usize>> = map.faces.iter().map(|&f| Some(f)).collect();
290        let faces = self.merged_names(&sources.faces, &face_into, merged.faces.len())?;
291        let edges = self.merged_names(&sources.edges, &map.edges, merged.edges.len())?;
292        let vertices =
293            self.merged_names(&sources.vertices, &map.vertices, merged.vertices.len())?;
294        let new_names = BodyNames {
295            vertices: vertices.names,
296            edges: edges.names.clone(),
297            faces: faces.names.clone(),
298            solid: names.solid.clone(),
299        };
300        self.delete_bodies(&[body])?;
301        let built = self.build_body(merged, new_names)?;
302        for merged in [faces, edges] {
303            for (alias, k) in &merged.aliases {
304                self.names.alias(alias.clone(), merged.names[*k].clone())?;
305            }
306        }
307        built.body()
308    }
309
310    /// The names of what [`NamedModel::merge_faces_on_one_surface`] builds
311    /// of the entities `ids`, each made part of the entity `into` says of
312    /// `count`: each named after the oldest — the least id — of what it is
313    /// made of, the names of the others its aliases.
314    fn merged_names<Id: Into<RefId> + Copy + Ord>(
315        &self,
316        ids: &[Id],
317        into: &[Option<usize>],
318        count: usize,
319    ) -> GeopResult<MergedNames> {
320        let mut by: Vec<Vec<Id>> = vec![Vec::new(); count];
321        for (&id, &into) in ids.iter().zip(into) {
322            if let Some(into) = into {
323                by[into].push(id);
324            }
325        }
326        let mut out = MergedNames::default();
327        for (k, mut ids) in by.into_iter().enumerate() {
328            ids.sort();
329            let Some((&oldest, rest)) = ids.split_first() else {
330                return Err(GeopError::new(
331                    "NamedModel::merge_faces_on_one_surface: an entity made of nothing",
332                ));
333            };
334            out.names.push(self.name(oldest)?.to_string());
335            for &id in rest {
336                out.aliases.push((self.name(id)?.to_string(), k));
337            }
338        }
339        Ok(out)
340    }
341
342    /// Forwards to [`geop_core_topology::Model::transform_body`]. Creates
343    /// and deletes nothing, so every entity keeps its name.
344    pub fn transform_body(&mut self, body: Body, map: &dyn Deformation) -> GeopResult<()> {
345        self.topology.transform_body(body, map)
346    }
347
348    /// Forwards to [`geop_core_topology::Model::assemble_solid`], naming the
349    /// solid it creates (if any) and forgetting the names of everything it
350    /// deletes.
351    pub fn assemble_solid(
352        &mut self,
353        consumed: &[Body],
354        keep: &[FaceId],
355        solid_name: impl Into<String>,
356    ) -> GeopResult<Option<SolidId>> {
357        let solid = self.topology.assemble_solid(consumed, keep)?;
358        self.forget_dead_names();
359        if let Some(solid) = solid {
360            self.names.insert(solid, solid_name)?;
361        }
362        Ok(solid)
363    }
364
365    /// Forwards to [`geop_core_topology::Model::assemble_sheet`], forgetting
366    /// the names of everything it deletes.
367    pub fn assemble_sheet(
368        &mut self,
369        consumed: &[Body],
370        keep: &[FaceId],
371    ) -> GeopResult<Option<ShellId>> {
372        let sheet = self.topology.assemble_sheet(consumed, keep)?;
373        self.forget_dead_names();
374        Ok(sheet)
375    }
376
377    /// Deletes `bodies` whole, with every entity in them, forgetting their
378    /// names.
379    pub fn delete_bodies(&mut self, bodies: &[Body]) -> GeopResult<()> {
380        self.assemble_sheet(bodies, &[])?;
381        Ok(())
382    }
383
384    /// Deletes `bodies` and builds the solid `spec` in their place, named
385    /// after `names` (see [`NamedModel::build_body`]), which may take up
386    /// the names of what it replaces. Fails if `spec` builds no solid.
387    pub fn replace_with_solid(
388        &mut self,
389        bodies: &[Body],
390        spec: BodySpec,
391        names: BodyNames,
392    ) -> GeopResult<SolidId> {
393        self.delete_bodies(bodies)?;
394        self.build_body(spec, names)?
395            .solid
396            .ok_or_else(|| GeopError::new("building the result made no solid"))
397    }
398
399    /// Forwards to [`geop_core_topology::Model::merge_solids`], forgetting
400    /// the name of the solid it deletes.
401    pub fn merge_solids(&mut self, into: SolidId, from: SolidId) -> GeopResult<()> {
402        self.topology.merge_solids(into, from)?;
403        self.names.remove(from);
404        Ok(())
405    }
406}
407
408/// The names of entities made of others (see
409/// [`NamedModel::merge_faces_on_one_surface`]): each one's own, and the
410/// names of the others that stand for it, with its index.
411#[derive(Default)]
412struct MergedNames {
413    names: Vec<String>,
414    aliases: Vec<(String, usize)>,
415}
416
417/// The names [`NamedModel::build_body`] gives what it builds: one per vertex, edge
418/// and face of the [`BodySpec`], index for index, and the solid's, if it
419/// builds one.
420#[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
421pub struct BodyNames {
422    pub vertices: Vec<String>,
423    pub edges: Vec<String>,
424    pub faces: Vec<String>,
425    pub solid: Option<String>,
426}
427
428impl BodyNames {
429    /// The same names, each `X` as `rename(X)`.
430    pub fn renamed(&self, rename: impl Fn(&str) -> String) -> BodyNames {
431        let all = |names: &[String]| names.iter().map(|n| rename(n)).collect();
432        BodyNames {
433            vertices: all(&self.vertices),
434            edges: all(&self.edges),
435            faces: all(&self.faces),
436            solid: self.solid.as_deref().map(&rename),
437        }
438    }
439}