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) .with_fs(0.01) .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) .with_fs(0.01) .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 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}