geop_ops_surface/
offset.rs1use geop_core_editor::{
5 edit_form::{EditForm, FormState},
6 editable::{Context, OperationGroup, OperationInfo, OperationTier},
7 editing::{Number, Unit},
8 part_operation,
9 part_operation::PartOperation,
10};
11use geop_core_math::{
12 geop_error::{GeopError, GeopResult},
13 scalars::Scalar,
14};
15use geop_core_program::{element::ElementReference, part::StepContext, reader::Reader};
16use geop_lib_shell::offset_faces;
17use geop_ops_model::{Face, ModelOperation, NamedModel, Namer, build_model};
18use serde::{Deserialize, Serialize};
19
20#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
30pub struct OffsetSurface;
31
32#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
33pub struct OffsetSurfaceArgs {
34 pub faces: Vec<ElementReference<NamedModel, Face>>,
36 pub distance: f64,
38}
39
40part_operation!(OffsetSurface(OffsetSurfaceArgs));
41
42impl PartOperation for OffsetSurface {
43 const KIND: &'static str = "offset_surface";
44 const INFO: OperationInfo = OperationInfo::new(
45 "Offset surface",
46 "Copy faces a distance along their normals into a face standing on its own.",
47 OperationGroup::Surface,
48 OperationTier::Menu,
49 )
50 .experimental()
51 .icon(crate::icons::OFFSET_SURFACE);
52 type Args = OffsetSurfaceArgs;
53 type Session = ();
54
55 fn new_args(_: &Context) -> OffsetSurfaceArgs {
57 OffsetSurfaceArgs {
58 faces: Vec::new(),
59 distance: 0.1,
60 }
61 }
62
63 fn run(ctx: &mut StepContext, args: &OffsetSurfaceArgs) -> GeopResult<()> {
64 let _step = ctx.step();
65 build_model::<Self>(ctx, args)
66 }
67
68 fn form<'a>(
70 context: &'a Context<'a>,
71 args: &OffsetSurfaceArgs,
72 _: &FormState<()>,
73 ) -> EditForm<'a, OffsetSurfaceArgs> {
74 let mut f = EditForm::<OffsetSurfaceArgs>::default();
75 f.pick(
76 "faces",
77 "faces",
78 &context.before,
79 &args.faces,
80 true,
81 |edit, faces| edit.args.faces = faces,
82 );
83 let distance = Number::new("distance", args.distance, Unit::Length).range(-1.0, 1.0);
84 f.number("distance", distance, |edit, d| edit.args.distance = d);
85 f
86 }
87}
88
89impl ModelOperation for OffsetSurface {
90 fn build(
91 model: &mut NamedModel,
92 _: &Reader,
93 step: &str,
94 args: &OffsetSurfaceArgs,
95 ) -> GeopResult<()> {
96 let namer = Namer::new("offset", step)?;
97 if !args.distance.is_finite() {
98 return Err(GeopError::new("the distance is not a number"));
99 }
100 if args.faces.is_empty() {
101 return Err(GeopError::new("no faces to offset"));
102 }
103 let faces = (args.faces.iter())
104 .map(|face| model.face_id(face.name()))
105 .collect::<GeopResult<Vec<_>>>()?;
106 offset_faces(model, &namer, &faces, Scalar::from_f64(args.distance))?;
107 Ok(())
108 }
109}
110
111#[cfg(test)]
112mod tests;