Skip to main content

geop_ops_edit/
extract.rs

1//! [`ExtractFace`]: copy a face out of its body, into a face standing on
2//! its own.
3
4use geop_core_editor::{
5    edit_form::{EditForm, FormState},
6    editable::{Context, OperationGroup, OperationInfo, OperationTier},
7    part_operation,
8    part_operation::PartOperation,
9};
10use geop_core_math::geop_error::GeopResult;
11use geop_core_program::{element::ElementReference, part::StepContext, reader::Reader};
12use geop_ops_model::{Face, ModelOperation, NamedModel, Namer, build_model, model};
13use serde::{Deserialize, Serialize};
14
15/// Copies the face named `face` — of a solid, or of a sheet — into a sheet
16/// of its own: a face standing on its own, on the same surface and within
17/// the same boundary, sharing nothing with the original, which stays as it
18/// is. A solid can be split with it.
19///
20/// The copy of the face, and of each of its edges and vertices `X`, is
21/// named `extract(E,X)` for the operation `E`.
22#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
23pub struct ExtractFace;
24
25#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
26pub struct ExtractFaceArgs {
27    /// The face to copy.
28    pub face: ElementReference<NamedModel, Face>,
29}
30
31part_operation!(ExtractFace(ExtractFaceArgs));
32
33impl PartOperation for ExtractFace {
34    const KIND: &'static str = "extract_face";
35    const INFO: OperationInfo = OperationInfo::new(
36        "Extract face",
37        "Copy a face out of its body into a face standing on its own, which a solid can be split with.",
38        OperationGroup::Surface,
39        OperationTier::Menu,
40    )
41    .experimental()
42    .icon(crate::icons::EXTRACT_FACE);
43    type Args = ExtractFaceArgs;
44    type Session = ();
45
46    /// Nothing picked yet: no face has the empty name.
47    fn new_args(_: &Context) -> ExtractFaceArgs {
48        ExtractFaceArgs {
49            face: ElementReference::new(model(), ""),
50        }
51    }
52
53    fn run(ctx: &mut StepContext, args: &ExtractFaceArgs) -> GeopResult<()> {
54        let _step = ctx.step();
55        build_model::<Self>(ctx, args)
56    }
57
58    /// The face, picked.
59    fn form<'a>(
60        context: &'a Context<'a>,
61        args: &ExtractFaceArgs,
62        _: &FormState<()>,
63    ) -> EditForm<'a, ExtractFaceArgs> {
64        let mut f = EditForm::<ExtractFaceArgs>::default();
65        f.pick_one(
66            "face",
67            "face",
68            &context.before,
69            Some(&args.face),
70            |edit, face| edit.args.face = face,
71        );
72        f
73    }
74}
75
76impl ModelOperation for ExtractFace {
77    fn build(
78        model: &mut NamedModel,
79        _: &Reader,
80        step: &str,
81        args: &ExtractFaceArgs,
82    ) -> GeopResult<()> {
83        let namer = Namer::new("extract", step)?;
84        let face = model.face_id(args.face.name())?;
85        model.copy_faces(&[face], None, |name| namer.name(&[name]))?;
86        Ok(())
87    }
88}
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93    use geop_lib_sweep::shapes::block;
94    use geop_ops_model::testing::{apply_to_model, assert_valid_sheets};
95
96    /// A face of a cube, extracted, stands on its own beside the cube,
97    /// which is left as it was.
98    #[test]
99    fn extracts_a_face_of_a_cube() {
100        let mut part = NamedModel::new();
101        let cube = block(&mut part, "c", [0.0; 3], [1.0; 3]);
102        let face = part.topology().solid_faces(cube).unwrap()[0];
103        let face = part.name_of(face).unwrap().to_string();
104        let args = ExtractFaceArgs {
105            face: ElementReference::new(model(), &face),
106        };
107        let part = apply_to_model::<ExtractFace>(part, "x", &args).unwrap();
108        assert_valid_sheets(&part);
109        assert_eq!(part.sheet_face_names(), [format!("extract(x,{face})")]);
110        assert_eq!(part.topology().solid_faces(cube).unwrap().len(), 6);
111        assert_eq!(part.topology().faces.len(), 7);
112        assert_eq!(part.topology().vertices.len(), 12);
113    }
114}