geom3d/
model.rs

1use crate::Point3;
2use crate::{curve, CurveGroup, Face};
3
4pub struct Model<F: Face> {
5    pub faces: Vec<F>,
6    pub curves: Vec<CurveGroup>,
7}
8
9impl<F: Face> Model<F> {
10    pub fn new() -> Model<F> {
11        Model {
12            faces: Vec::new(),
13            curves: Vec::new(),
14        }
15    }
16
17    pub fn add_face(&mut self, face: F) {
18        self.faces.push(face);
19    }
20
21    pub fn add_curve(&mut self, curve: CurveGroup) {
22        self.curves.push(curve);
23    }
24
25    pub fn save_as_stl<P: AsRef<std::path::Path>>(&self, filename: P) -> std::io::Result<()> {
26        use std::io::{Seek, SeekFrom, Write};
27        let path = std::path::Path::new(filename.as_ref());
28        let name = path.file_stem().unwrap().to_string_lossy();
29        let file = std::fs::File::create(path)?;
30        let mut writer = std::io::BufWriter::new(file);
31
32        let header_size = 80;
33        let mut header = Vec::with_capacity(header_size);
34        writeln!(header, "Binary STL file\nName: {:57}", name)?;
35        header.truncate(header_size);
36        writer.write(&header)?;
37
38        let mut triangle_count: usize = 0;
39        writer.write(&(triangle_count as u32).to_le_bytes())?;
40
41        for face in &self.faces {
42            let mesh = face.get_triangle_mesh();
43            triangle_count += mesh.triangle_count();
44            for triangle in mesh.triangles.chunks(3) {
45                // normal
46                writer.write(&0.0_f32.to_le_bytes())?;
47                writer.write(&0.0_f32.to_le_bytes())?;
48                writer.write(&0.0_f32.to_le_bytes())?;
49                // vertices
50                for index in triangle {
51                    let point = &mesh.vertices[*index as usize];
52                    writer.write(&(point.x as f32).to_le_bytes())?;
53                    writer.write(&(point.y as f32).to_le_bytes())?;
54                    writer.write(&(point.z as f32).to_le_bytes())?;
55                }
56                // attribute byte count
57                writer.write(&[0u8, 0u8])?;
58            }
59        }
60        writer.seek(SeekFrom::Start(header_size as u64))?;
61        dbg!(triangle_count);
62        writer.write(&(triangle_count as u32).to_le_bytes())?;
63        Ok(())
64    }
65
66    pub fn save_as_obj<P: AsRef<std::path::Path>>(&self, filename: P) -> std::io::Result<()> {
67        use std::io::Write;
68        let file = std::fs::File::create(filename)?;
69        let mut writer = std::io::LineWriter::new(file);
70
71        let mut start = 1;
72        for face in &self.faces {
73            let mesh = face.get_triangle_mesh();
74            for point in &mesh.vertices {
75                writeln!(writer, "v {} {} {}", point.x, point.y, point.z)?;
76            }
77            for triangle in mesh.triangles.chunks(3) {
78                writeln!(
79                    writer,
80                    "f {} {} {}",
81                    start + triangle[0],
82                    start + triangle[1],
83                    start + triangle[2]
84                )?;
85            }
86            start += mesh.vertices.len() as u32;
87        }
88        Ok(())
89    }
90
91    pub fn save_as_svg<P: AsRef<std::path::Path>>(
92        &self,
93        filename: P,
94        (width, height): (f64, f64),
95    ) -> std::io::Result<()> {
96        use curve::Curve;
97        use svg::{
98            node::element::{Group, Path},
99            node::Node,
100            Document,
101        };
102        let mut document = Document::new()
103            .set("width", format!("{}mm", width))
104            .set("height", format!("{}mm", height))
105            .set("viewBox", (0, 0, width, height));
106        let mut group = Group::new()
107            .set("transform", format!("matrix(1 0 0 -1 0 {:.0})", height))
108            .set("fill", "none")
109            .set("stroke", "black")
110            .set("stroke-linecap", "round");
111
112        for curve in &self.curves {
113            let mut data = String::new();
114            for segment in &curve.segments {
115                if let Some(line) = segment.curve.downcast_ref::<curve::Line>() {
116                    let (u0, u1) = segment.parameter_range;
117                    let start = line.get_point(u0);
118                    let end = line.get_point(u1);
119                    data.push_str(&format!("M {:.2},{:.2}", start.x, start.y));
120                    data.push_str(&format!(" L {:.2},{:.2}", end.x, end.y));
121                    continue;
122                }
123                if let Some(polyline) = segment.curve.downcast_ref::<curve::Polyline>() {
124                    for (index, point) in polyline.vertices.iter().enumerate() {
125                        if index == 0 {
126                            data.push_str(&format!("M {:.2},{:.2}", point.x, point.y));
127                        } else {
128                            data.push_str(&format!(" L {:.2},{:.2}", point.x, point.y));
129                        }
130                    }
131                    continue;
132                }
133                if let Some(bspline) = segment.curve.downcast_ref::<curve::BSplineCurve<Point3>>() {
134                    if bspline.degree == 3 {
135                        for (index, bezier_curve) in
136                            bspline.to_piecewise_bezier().into_iter().enumerate()
137                        {
138                            let start = bezier_curve.control_points[0];
139                            let cp1 = bezier_curve.control_points[1];
140                            let cp2 = bezier_curve.control_points[2];
141                            let end = bezier_curve.control_points[3];
142                            if index == 0 {
143                                data.push_str(&format!("M {:.2},{:.2}", start.x, start.y));
144                                data.push_str(&format!(
145                                    " C {:.2},{:.2} {:.2},{:.2} {:.2},{:.2}",
146                                    cp1.x, cp1.y, cp2.x, cp2.y, end.x, end.y
147                                ));
148                            } else {
149                                data.push_str(&format!(
150                                    " {:.2},{:.2} {:.2},{:.2} {:.2},{:.2}",
151                                    cp1.x, cp1.y, cp2.x, cp2.y, end.x, end.y
152                                ));
153                            }
154                        }
155                        continue;
156                    }
157                }
158
159                let points = segment.get_points();
160                for point in points {
161                    if data.len() == 0 {
162                        data.push_str(&format!("M {:.2},{:.2}", point.x, point.y));
163                    } else {
164                        data.push_str(&format!(" L {:.2},{:.2}", point.x, point.y));
165                    }
166                }
167            }
168            let path = Path::new().set("d", data);
169            group.append(path);
170        }
171        document.append(group);
172        svg::save(filename, &document)?;
173        Ok(())
174    }
175}
176
177mod step_reader;
178pub use step_reader::ModelReader as StepReader;