Skip to main content

geop_ops_surface/
offset.rs

1//! [`OffsetSurface`]: faces copied a distance along their normals — the
2//! inner side of a shell (see [`geop_lib_shell::offset_faces`]).
3
4use 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/// Copies the faces named `faces` — of a solid or standing on their own —
21/// `distance` along their normals, against them for a negative distance,
22/// into a sheet of their own, for the operation `O`: where faces meet,
23/// their copies meet where the offsets of their surfaces do. The faces stay
24/// as they are; the copy of each face, edge and vertex `X` is `offset(O,X)`.
25///
26/// What can be offset exactly — planes, and surfaces of revolution whose
27/// profile is straight or circular — is all that is offset: other faces,
28/// and edges that are neither straight nor circular, are refused.
29#[derive(Clone, Copy, Debug, Default, PartialEq, Serialize, Deserialize)]
30pub struct OffsetSurface;
31
32#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
33pub struct OffsetSurfaceArgs {
34    /// The faces to copy.
35    pub faces: Vec<ElementReference<NamedModel, Face>>,
36    /// How far along their normals.
37    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    /// Nothing picked yet, a tenth out.
56    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    /// The faces, picked, and the distance.
69    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;