Skip to main content

rscad_openscad/
lib.rs

1pub use rscad_core::*;
2#[cfg(feature = "eval")]
3pub mod eval;
4pub mod export;
5pub mod import;
6
7pub trait OpenscadCodegen: core::fmt::Debug + rscad_core::Object {
8    fn render_fmt(&self, writer: &mut dyn core::fmt::Write) -> Result<()> {
9        self.render_with(writer, &Empty)
10    }
11    fn render_with(
12        &self,
13        writer: &mut dyn ::core::fmt::Write,
14        #[allow(unused_variables)] item: &dyn OpenscadCodegen,
15    ) -> Result<()> {
16        self.render_fmt(writer)
17    }
18    fn render(&self) -> Result<String> {
19        let mut out = String::new();
20        self.render_fmt(&mut out)
21            .change_context(Error)
22            .attach("Failed to render object to scad string")?;
23        Ok(out)
24    }
25}
26impl OpenscadCodegen for rscad_core::Empty {
27    fn render_with(&self, _: &mut dyn core::fmt::Write, _: &dyn OpenscadCodegen) -> Result<()> {
28        Ok(())
29    }
30}
31impl<T: OpenscadCodegen> OpenscadCodegen for Option<T> {
32    fn render_with(
33        &self,
34        writer: &mut dyn core::fmt::Write,
35        item: &dyn OpenscadCodegen,
36    ) -> Result<()> {
37        if let Some(s) = self {
38            s.render_with(writer, item)?
39        }
40        Ok(())
41    }
42}
43
44impl<T: OpenscadCodegen, const N: usize> OpenscadCodegen for [T; N] {
45    fn render_with(
46        &self,
47        writer: &mut dyn core::fmt::Write,
48        item: &dyn OpenscadCodegen,
49    ) -> Result<()> {
50        if N == 1 {
51            self.iter().try_for_each(|f| f.render_with(writer, item))
52        } else {
53            writeln!(writer, "{{").cc()?;
54            self.iter().try_for_each(|f| f.render_with(writer, item))?;
55            write!(writer, "}}").cc()
56        }
57    }
58}
59
60#[cfg(feature = "std")]
61impl<T: OpenscadCodegen> OpenscadCodegen for Vec<T> {
62    fn render_with(
63        &self,
64        writer: &mut dyn core::fmt::Write,
65        item: &dyn OpenscadCodegen,
66    ) -> Result<()> {
67        if self.len() == 1 {
68            self.iter().try_for_each(|f| f.render_with(writer, item))
69        } else {
70            writeln!(writer, "{{").cc()?;
71            self.iter().try_for_each(|f| f.render_with(writer, item))?;
72            write!(writer, "}}").cc()
73        }
74    }
75}
76
77#[cfg(feature = "std")]
78impl<T: OpenscadCodegen> OpenscadCodegen for Box<T> {
79    fn render_with(
80        &self,
81        writer: &mut dyn core::fmt::Write,
82        item: &dyn OpenscadCodegen,
83    ) -> Result<()> {
84        self.as_ref().render_with(writer, item)
85    }
86}
87
88macro_rules! impl_object_tuples {
89    ($($name:ident),+) => {
90        impl<$($name: OpenscadCodegen + rscad_core::Object),+> OpenscadCodegen for ($($name,)+) {
91            fn render_with(&self, writer: &mut dyn core::fmt::Write, item: &dyn OpenscadCodegen) -> Result<()> {
92                #[allow(non_snake_case)]
93                let ($($name,)+) = self;
94                $(
95                    $name.render_with(writer, item)?;
96                )+
97                Ok(())
98            }
99        }
100    };
101}
102
103impl_object_tuples!(A);
104impl_object_tuples!(A, B);
105impl_object_tuples!(A, B, C);
106impl_object_tuples!(A, B, C, D);
107impl_object_tuples!(A, B, C, D, E);
108impl_object_tuples!(A, B, C, D, E, F);
109impl_object_tuples!(A, B, C, D, E, F, G);
110impl_object_tuples!(A, B, C, D, E, F, G, H);
111impl_object_tuples!(A, B, C, D, E, F, G, H, I);
112impl_object_tuples!(A, B, C, D, E, F, G, H, I, J);
113impl_object_tuples!(A, B, C, D, E, F, G, H, I, J, K);
114impl_object_tuples!(A, B, C, D, E, F, G, H, I, J, K, L);
115
116impl<T: OpenscadCodegen, O: OpenscadCodegen> OpenscadCodegen for Difference<T, O> {
117    fn render_fmt(&self, writer: &mut dyn core::fmt::Write) -> Result<()> {
118        writeln!(writer, "difference() {{").change_context(Error)?;
119        self.this.render_fmt(writer)?;
120        self.other.render_fmt(writer)?;
121        writeln!(writer, "}};").change_context(Error)?;
122        Ok(())
123    }
124}
125impl<T: OpenscadCodegen, O: OpenscadCodegen> OpenscadCodegen for Intersection<T, O> {
126    fn render_fmt(&self, writer: &mut dyn core::fmt::Write) -> Result<()> {
127        writeln!(writer, "intersection() {{").change_context(Error)?;
128        self.this.render_fmt(writer)?;
129        self.other.render_fmt(writer)?;
130        writeln!(writer, "}};").change_context(Error)?;
131        Ok(())
132    }
133}
134impl<T: OpenscadCodegen, O: OpenscadCodegen> OpenscadCodegen for Union<T, O> {
135    fn render_fmt(&self, writer: &mut dyn core::fmt::Write) -> Result<()> {
136        writeln!(writer, "union() {{").change_context(Error)?;
137        self.this.render_fmt(writer)?;
138        self.other.render_fmt(writer)?;
139        writeln!(writer, "}};").change_context(Error)?;
140        Ok(())
141    }
142}
143impl<T: OpenscadCodegen> OpenscadCodegen for Colored<T> {
144    fn render_with(
145        &self,
146        writer: &mut dyn ::core::fmt::Write,
147        item: &dyn OpenscadCodegen,
148    ) -> Result<()> {
149        writeln!(
150            writer,
151            "color([{}/255, {}/255, {}/255, {}/255]) ",
152            self.color.r, self.color.g, self.color.b, self.color.a
153        )
154        .change_context(Error)
155        .attach("Failed to render colored object")?;
156        self.object.render_with(writer, item)?;
157        Ok(())
158    }
159}
160impl<A: OpenscadCodegen, B: OpenscadCodegen> OpenscadCodegen for TwoContainer<A, B> {
161    fn render_fmt(&self, writer: &mut dyn core::fmt::Write) -> Result<()> {
162        writeln!(writer, "{{").cc()?;
163        self.a.render_fmt(writer)?;
164        writeln!(writer).cc()?;
165        self.b.render_fmt(writer)?;
166        write!(writer, "\n}}").cc()?;
167        Ok(())
168    }
169}
170impl<T: OpenscadCodegen, const N: usize> OpenscadCodegen for ArrayContainer<T, N> {
171    fn render_with(
172        &self,
173        writer: &mut dyn ::core::fmt::Write,
174        item: &dyn OpenscadCodegen,
175    ) -> Result<()> {
176        writeln!(writer, "{{").cc()?;
177        for i in 0..N {
178            self.array[i].render_with(writer, item)?;
179            writeln!(writer).cc()?;
180        }
181        write!(writer, "\n}}").cc()?;
182        Ok(())
183    }
184}
185
186impl crate::OpenscadCodegen for Point2D {
187    fn render_with(
188        &self,
189        writer: &mut dyn core::fmt::Write,
190        _item: &dyn crate::OpenscadCodegen,
191    ) -> Result<()> {
192        write!(writer, "[{:.3}, {:.3}]", self.x, self.y).cc()?;
193        Ok(())
194    }
195}
196impl OpenscadCodegen for Cube {
197    fn render_fmt(&self, writer: &mut dyn core::fmt::Write) -> Result<()> {
198        self.render_with(writer, &Option::<Cube>::None)?;
199        Ok(())
200    }
201
202    fn render_with(
203        &self,
204        writer: &mut dyn ::core::fmt::Write,
205        item: &dyn OpenscadCodegen,
206    ) -> Result<()> {
207        write!(writer, "cube(").cc()?;
208        write!(writer, "[{}, {}, {}]", self.x, self.y, self.z).change_context(Error)?;
209        item.render_fmt(writer)?;
210        write!(writer, ");").cc()?;
211        Ok(())
212    }
213}
214
215impl OpenscadCodegen for Cylinder {
216    fn render_fmt(&self, writer: &mut dyn core::fmt::Write) -> Result<()> {
217        write!(
218            writer,
219            "cylinder(h = {}, r = {});",
220            self.height, self.radius
221        )
222        .change_context(Error)
223        .attach("Failed to render cylinder")?;
224        Ok(())
225    }
226}
227
228impl OpenscadCodegen for Circle {
229    fn render_fmt(&self, writer: &mut dyn core::fmt::Write) -> Result<()> {
230        self.render_with(writer, &Empty)
231    }
232    fn render_with(
233        &self,
234        writer: &mut dyn ::core::fmt::Write,
235        item: &dyn OpenscadCodegen,
236    ) -> Result<()> {
237        write!(
238            writer,
239            "circle(r = {},$fa = {}, $fs = {}, $fn = {}",
240            self.radius, self.fa, self.fs, self.fn_
241        )
242        .change_context(Error)?;
243        item.render_fmt(writer)?;
244        write!(writer, ");").change_context(Error)?;
245        Ok(())
246    }
247}
248impl OpenscadCodegen for Square {
249    fn render_fmt(&self, writer: &mut dyn core::fmt::Write) -> Result<()> {
250        self.render_with(writer, &Empty)
251    }
252    fn render_with(
253        &self,
254        writer: &mut dyn ::core::fmt::Write,
255        item: &dyn OpenscadCodegen,
256    ) -> Result<()> {
257        write!(writer, "square([{}, {}]", self.x, self.y).change_context(Error)?;
258        item.render_fmt(writer)?;
259        write!(writer, ");").change_context(Error)?;
260        Ok(())
261    }
262}
263impl<const N: usize> OpenscadCodegen for Polygon<N> {
264    fn render_with(
265        &self,
266        writer: &mut dyn core::fmt::Write,
267        _item: &dyn OpenscadCodegen,
268    ) -> Result<()> {
269        Circle::default()
270            .with_radius(self.radius)
271            .with_fn(N)
272            .with_fa(0.01) // min
273            .with_fs(0.01) // min
274            .render_with(writer, _item)?;
275        Ok(())
276    }
277}
278
279impl OpenscadCodegen for PolygonDynamic {
280    fn render_with(
281        &self,
282        writer: &mut dyn core::fmt::Write,
283        item: &dyn OpenscadCodegen,
284    ) -> Result<()> {
285        Circle::default()
286            .with_radius(self.radius)
287            .with_fn(self.points)
288            .with_fa(0.01) // min
289            .with_fs(0.01) // min
290            .render_with(writer, item)?;
291        Ok(())
292    }
293}
294
295impl OpenscadCodegen for Center {
296    fn render_fmt(&self, writer: &mut dyn core::fmt::Write) -> Result<()> {
297        write!(writer, ",center = {}", self.centered).change_context(Error)
298    }
299}
300impl rscad_core::Object for Center {}
301
302#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)]
303struct Center {
304    pub centered: bool,
305}
306
307impl<T: OpenscadCodegen> OpenscadCodegen for Centered<T> {
308    fn render_fmt(&self, writer: &mut dyn core::fmt::Write) -> Result<()> {
309        self.object.render_with(
310            writer,
311            &Center {
312                centered: self.centered,
313            },
314        )?;
315        Ok(())
316    }
317}
318#[test]
319pub fn centering_cube() {
320    use crate::cube::Cube;
321    let foo = Cube::new([3, 3, 4]).centered(true);
322    let out = foo.render().unwrap();
323    assert_eq!("cube([3, 3, 4],center = true);", out);
324}
325
326impl<T: OpenscadCodegen> OpenscadCodegen for Mirror<T> {
327    fn render_fmt(&self, writer: &mut dyn core::fmt::Write) -> Result<()> {
328        writeln!(
329            writer,
330            "mirror([{x}, {y}, {z}]) ",
331            x = self.mirror.x,
332            y = self.mirror.y,
333            z = self.mirror.z
334        )
335        .cc()?;
336        self.object.render_fmt(writer)?;
337        Ok(())
338    }
339}
340impl<T: OpenscadCodegen + Clone, const N: usize> OpenscadCodegen for Pattern<T, N> {
341    fn render_with(
342        &self,
343        writer: &mut dyn ::core::fmt::Write,
344        item: &dyn OpenscadCodegen,
345    ) -> Result<()> {
346        let array: [_; N] = core::array::from_fn(|i| {
347            let object = self.object.clone();
348            let t = self.pattern_type.translation();
349            object.translate(t.x * i as f64, t.y * i as f64, t.z * i as f64)
350        });
351        array.render_with(writer, item)
352    }
353}
354impl<T: OpenscadCodegen + Clone> OpenscadCodegen for PatternRuntime<T> {
355    fn render_with(
356        &self,
357        writer: &mut dyn ::core::fmt::Write,
358        item: &dyn OpenscadCodegen,
359    ) -> Result<()> {
360        for i in 0..self.times {
361            let object = self.object.clone();
362            let t = self.pattern_type.translation();
363            let translated = object.translate(t.x * i as f64, t.y * i as f64, t.z * i as f64);
364            // Render each item in the pattern
365            translated.render_with(writer, item)?;
366        }
367        Ok(())
368    }
369}
370
371impl<T: OpenscadCodegen> OpenscadCodegen for Projection<T> {
372    fn render_with(
373        &self,
374        writer: &mut dyn core::fmt::Write,
375        item: &dyn OpenscadCodegen,
376    ) -> Result<()> {
377        writeln!(
378            writer,
379            "projection(cut = {}) {{",
380            if self.cut { "true" } else { "false" }
381        )
382        .change_context(Error)
383        .attach("Failed to render projection")?;
384        self.object
385            .render_with(writer, item)
386            .change_context(Error)
387            .attach("Failed to render projection object")?;
388        writeln!(writer, "}}").change_context(Error)?;
389        Ok(())
390    }
391}
392impl<T: OpenscadCodegen> OpenscadCodegen for Rotate<T> {
393    fn render_fmt(&self, writer: &mut dyn core::fmt::Write) -> Result<()> {
394        writeln!(
395            writer,
396            "rotate([{}, {}, {}]) ",
397            self.rotation.x, self.rotation.y, self.rotation.z,
398        )
399        .change_context(Error)
400        .attach("Failed to render rotated object")?;
401        self.object.render_fmt(writer)?;
402        Ok(())
403    }
404}
405impl<T: OpenscadCodegen> OpenscadCodegen for Scale<T> {
406    fn render_fmt(&self, writer: &mut dyn core::fmt::Write) -> Result<()> {
407        writeln!(
408            writer,
409            "scale([{}, {}, {}]) ",
410            self.scale.x, self.scale.y, self.scale.z
411        )
412        .cc()?;
413        self.object.render_fmt(writer)?;
414        Ok(())
415    }
416}
417impl<T: OpenscadCodegen> OpenscadCodegen for Translate<T> {
418    fn render_with(
419        &self,
420        writer: &mut dyn core::fmt::Write,
421        item: &dyn OpenscadCodegen,
422    ) -> Result<()> {
423        writeln!(
424            writer,
425            "translate([{x}, {y}, {z}]) ",
426            x = self.translation.x,
427            y = self.translation.y,
428            z = self.translation.z
429        )
430        .cc()?;
431        self.object.render_with(writer, item)?;
432        Ok(())
433    }
434}
435impl<T: OpenscadCodegen> OpenscadCodegen for Offset<T> {
436    fn render_fmt(&self, writer: &mut dyn core::fmt::Write) -> Result<()> {
437        let offset_var = match self.offset_type {
438            OffsetType::Normal => "delta",
439            OffsetType::Rounded => "r",
440        };
441        write!(writer, "offset({offset_var} = {}) ", self.offset).cc()?;
442        self.object.render_fmt(writer)?;
443        Ok(())
444    }
445}
446impl<T: TwoDimensional + OpenscadCodegen> OpenscadCodegen
447    for rscad_core::extrude::LinearExtrude<T>
448{
449    fn render_fmt(&self, writer: &mut dyn core::fmt::Write) -> Result<()> {
450        write!(writer, "linear_extrude(").cc()?;
451        if self.height != 0.0 {
452            write!(writer, "height = {}, ", self.height).cc()?;
453        }
454        if self.convexity != 0.0 {
455            write!(writer, "convexity = {}, ", self.convexity).cc()?;
456        }
457        if self.twist != 0.0 {
458            write!(writer, "twist = {}, ", self.twist).cc()?;
459        }
460        if self.slices != 0 {
461            write!(writer, "slices = {}, ", self.slices).cc()?;
462        }
463        if self.scale != 1.0 {
464            write!(writer, "scale = {}, ", self.scale).cc()?;
465        }
466        if self.fn_ != 0.0 {
467            write!(writer, "fn = {}, ", self.fn_).cc()?;
468        }
469        writeln!(writer, ") {{").cc()?;
470        self.object.render_fmt(writer)?;
471        write!(writer, "\n}}").cc()?;
472        Ok(())
473    }
474}
475impl<T: TwoDimensional + OpenscadCodegen> OpenscadCodegen
476    for rscad_core::extrude::RotateExtrude<T>
477{
478    fn render_fmt(&self, writer: &mut dyn core::fmt::Write) -> Result<()> {
479        write!(writer, "rotate_extrude(").cc()?;
480        if self.angle_degrees != 360.0 {
481            write!(writer, "angle = {}, ", self.angle_degrees).cc()?;
482        }
483        if self.convexity != 0.0 {
484            write!(writer, "convexity = {}, ", self.convexity).cc()?;
485        }
486        if self.fn_ != 0 {
487            write!(writer, "$fn = {}, ", self.fn_).cc()?;
488        }
489        writeln!(writer, ") {{").cc()?;
490        self.object.render_fmt(writer)?;
491        write!(writer, "\n}}").cc()?;
492        Ok(())
493    }
494}