1use 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
12pub struct Stl;
14
15#[derive(Clone, Debug, PartialEq, Deserialize)]
17#[serde(default)]
18pub struct StlOptions {
19 pub solids: Vec<String>,
22 pub ascii: bool,
24 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}