Skip to main content

geop_ops_assembly/
stl.rs

1//! [`Stl`]: the solids of the part as an STL mesh — the mesh the editor
2//! draws (see [`geop_core_rasterize::stl`]), so a file looks exactly like
3//! the part on screen, the parts it places included, each where it is.
4
5use geop_core_math::geop_error::{GeopError, GeopResult};
6use geop_core_rasterize::stl::StlFormat;
7use serde::Deserialize;
8
9use geop_core_editor::output::{Content, Export, Exporter, ExporterInfo, Output};
10use geop_ops_model::{STL_QUALITY, parameters::stem, placed::solid_meshes, stl_bytes};
11
12/// Writes the solids of the part as an STL mesh.
13pub struct Stl;
14
15/// How an STL file is written.
16#[derive(Clone, Debug, PartialEq, Deserialize)]
17#[serde(default)]
18pub struct StlOptions {
19    /// Only the solids of these names (`/pin:extrude(pin)`), if any are
20    /// named; a name no solid has fails, listing the names there are.
21    pub solids: Vec<String>,
22    /// Plain text rather than binary.
23    pub ascii: bool,
24    /// How finely curved faces are meshed: higher is smoother, and bigger
25    /// (see [`solid_meshes`]). Flat faces are meshed exactly whatever this
26    /// is.
27    pub quality: usize,
28}
29
30impl Default for StlOptions {
31    fn default() -> Self {
32        Self {
33            solids: Vec::new(),
34            ascii: false,
35            quality: STL_QUALITY,
36        }
37    }
38}
39
40impl Exporter for Stl {
41    const INFO: ExporterInfo = ExporterInfo {
42        kind: "stl",
43        label: "STL",
44        doc: "mesh, for 3-D printing",
45        formats: &["stl"],
46    };
47    type Options = StlOptions;
48
49    fn export(output: &mut Output, _: &str, options: StlOptions) -> GeopResult<Export> {
50        let all = solid_meshes(&output.part(), options.quality)?;
51        let chosen: Vec<_> = match options.solids.is_empty() {
52            true => all.iter().collect(),
53            false => (options.solids.iter())
54                .map(|solid| {
55                    all.iter().find(|(n, _)| n == solid).ok_or_else(|| {
56                        let known: Vec<&str> = all.iter().map(|(n, _)| n.as_str()).collect();
57                        GeopError::new(format!("no solid is named {solid:?}"))
58                            .with_context(format!("the part's solids are: {}", known.join(", ")))
59                    })
60                })
61                .collect::<GeopResult<_>>()?,
62        };
63        let format = match options.ascii {
64            true => StlFormat::Ascii,
65            false => StlFormat::Binary,
66        };
67        let name = stem(output.file_name());
68        let triangles = chosen.iter().flat_map(|(_, triangles)| triangles);
69        let bytes = stl_bytes(triangles, name, format)?;
70        Ok(Export {
71            name: format!("{name}.stl"),
72            media_type: "model/stl".into(),
73            content: Content::Bytes(bytes),
74        })
75    }
76}