1use 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::{Body, ModelOperation, NamedModel, build_model, resolve_bodies};
13use serde::{Deserialize, Serialize};
14
15#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
23pub struct DeleteBody;
24
25#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
26pub struct DeleteBodyArgs {
27 pub bodies: Vec<ElementReference<NamedModel, Body>>,
29}
30
31part_operation!(DeleteBody(DeleteBodyArgs));
32
33impl PartOperation for DeleteBody {
34 const KIND: &'static str = "delete_body";
35 const INFO: OperationInfo = OperationInfo::new(
36 "Delete body",
37 "Delete solids, and faces standing on their own.",
38 OperationGroup::Bodies,
39 OperationTier::Menu,
40 )
41 .icon(crate::icons::DELETE_BODY);
42 type Args = DeleteBodyArgs;
43 type Session = ();
44
45 fn new_args(_: &Context) -> DeleteBodyArgs {
47 DeleteBodyArgs::default()
48 }
49
50 fn run(ctx: &mut StepContext, args: &DeleteBodyArgs) -> GeopResult<()> {
51 let _step = ctx.step();
52 build_model::<Self>(ctx, args)
53 }
54
55 fn form<'a>(
57 context: &'a Context<'a>,
58 args: &DeleteBodyArgs,
59 _: &FormState<()>,
60 ) -> EditForm<'a, DeleteBodyArgs> {
61 let mut f = EditForm::<DeleteBodyArgs>::default();
62 f.pick(
63 "bodies",
64 "bodies",
65 &context.before,
66 &args.bodies,
67 true,
68 |edit, picked| edit.args.bodies = picked,
69 );
70 f
71 }
72}
73
74impl ModelOperation for DeleteBody {
75 fn build(
78 model: &mut NamedModel,
79 reader: &Reader,
80 _: &str,
81 args: &DeleteBodyArgs,
82 ) -> GeopResult<()> {
83 let bodies = resolve_bodies(reader, &args.bodies)?;
84 model.delete_bodies(&bodies)?;
85 Ok(())
86 }
87}
88
89#[cfg(test)]
90mod tests {
91 use super::*;
92 use geop_lib_sweep::shapes::{block, block_part};
93 use geop_ops_model::{model, testing::apply_to_model};
94
95 fn body(name: impl Into<String>) -> ElementReference<NamedModel, Body> {
96 ElementReference::new(model(), name)
97 }
98
99 fn two_cubes() -> NamedModel {
100 let mut part = block_part("a", [0.0, 0.0, 0.0], [1.0, 1.0, 1.0]);
101 block(&mut part, "b", [2.0, 0.0, 0.0], [3.0, 1.0, 1.0]);
102 part
103 }
104
105 #[test]
108 fn deletes_one_solid_and_keeps_the_other() {
109 let part = two_cubes();
110 let b = part.solid_names()[1].clone();
111 let kept_faces = part
112 .topology()
113 .solid_faces(part.solid_id(&part.solid_names()[0]).unwrap())
114 .unwrap()
115 .len();
116 let args = DeleteBodyArgs {
117 bodies: vec![body(b)],
118 };
119 let part = apply_to_model::<DeleteBody>(part, "del", &args).unwrap();
120 assert_eq!(part.solid_names().len(), 1);
121 assert_eq!(part.topology().faces.len(), kept_faces);
122 assert_eq!(part.topology().vertices.len(), 8);
123 part.check_names().unwrap();
124 }
125
126 #[test]
129 fn a_face_deletes_its_whole_body() {
130 let part = two_cubes();
131 let a = part.solid_id(&part.solid_names()[0]).unwrap();
132 let face = part.topology().solid_faces(a).unwrap()[0];
133 let face = part.name_of(face).unwrap().to_string();
134 let name = part.name_of(a).unwrap().to_string();
135 let args = DeleteBodyArgs {
136 bodies: vec![body(face), body(name)],
137 };
138 let part = apply_to_model::<DeleteBody>(part, "del", &args).unwrap();
139 assert_eq!(part.solid_names().len(), 1);
140 assert_eq!(part.topology().vertices.len(), 8);
141 part.check_names().unwrap();
142 }
143
144 #[test]
147 fn refuses_what_is_no_body() {
148 let error = apply_to_model::<DeleteBody>(two_cubes(), "del", &DeleteBodyArgs::default())
149 .expect_err("refused");
150 assert!(error.root_message().contains("pick"), "{error:?}");
151 let part = two_cubes();
152 let edge = part
153 .topology()
154 .edges
155 .keys()
156 .copied()
157 .min_by_key(|e| e.0)
158 .unwrap();
159 let args = DeleteBodyArgs {
160 bodies: vec![body(part.name_of(edge).unwrap())],
161 };
162 let error = apply_to_model::<DeleteBody>(part, "del", &args).expect_err("refused");
163 assert!(error.root_message().contains("no body"), "{error:?}");
164 }
165}