libfive/
lib.rs

1#![doc(
2    html_logo_url = "https://raw.githubusercontent.com/virtualritz/libfive-rs/HEAD/libfive/libfive-logo.png"
3)]
4//! A high level wrapper around [`libfive`](https://libfive.com/) -- a set of
5//! tools for solid modeling based on
6//! [functional representation](https://en.wikipedia.org/wiki/Function_representation).
7//!
8//! Particularly suited for parametric- and procedural modeling. An
9//! infrastructure for generative design, mass customization, and
10//! domain-specific CAD tools.
11//!
12//! ## Example
13//!
14//! ```ignore
15//! # use libfive::*;
16//! # fn example() -> Result<()> {
17//! let f_rep_shape = Tree::sphere(1.0.into(), TreeVec3::default())
18//!     .difference_multi(vec![
19//!         Tree::sphere(0.6.into(), TreeVec3::default()),
20//!         Tree::cylinder_z(
21//!             0.6.into(),
22//!             2.0.into(),
23//!             TreeVec3::new(0.0, 0.0, -1.0),
24//!         ),
25//!         Tree::cylinder_z(
26//!             0.6.into(),
27//!             2.0.into(),
28//!             TreeVec3::new(0.0, 0.0, -1.0),
29//!         )
30//!         .reflect_xz(),
31//!         Tree::cylinder_z(
32//!             0.6.into(),
33//!             2.0.into(),
34//!             TreeVec3::new(0.0, 0.0, -1.0),
35//!         )
36//!         .reflect_yz(),
37//!     ]);
38//!
39//! f_rep_shape.write_stl(
40//!     "f-rep-shape.stl",
41//!     &Region3::new(-2.0, 2.0, -2.0, 2.0, -2.0, 2.0),
42//!     // Subdivisions
43//!     10.0,
44//! )?;
45//! # }
46//! ```
47//! The STL file generated from this code is show below.
48//!
49//! ## Features
50//!
51//! <img src="https://raw.githubusercontent.com/virtualritz/libfive-rs/HEAD/libfive/f-rep-shape.png" alt="Generated CSG Shape" width="38%" padding-left="15%" align="right" align="top">
52//!
53//! * [`ahash`](https://crates.io/crates/ahash) -- On by default. Use [`AHashMap`](https://docs.rs/ahash/latest/ahash/struct.AHashMap.html)
54//!   for hashing when resolving variable names. Disabling this will fall back
55//!   to the slower [`HashMap`](std::collections::HashMap).
56//!
57//! * `stdlib` -- On by default. Add an extensive list of higher level
58//!   operations -- the [`libfive stdlib`](Tree#standard-library).
59//!
60//!   To disable either/both of the above features unset default features in
61//!   `Cargo.toml`:
62//!
63//!   ```toml
64//!   [dependencies.libfive]
65//!   default-features = false
66//!   ```
67//!
68//! * `packed_opcodes` -- Tightly pack opcodes. This breaks compatibility with
69//!   older saved f-rep files.
70//!
71//!   See [`Tree::save()`](Tree::save)/[`load()`](Tree::load).
72use core::{
73    ffi::c_void,
74    ops::{Add, Div, Mul, Neg, Rem, Sub},
75    ptr, result, slice,
76};
77use libfive_sys as sys;
78use std::{ffi::CString, path::Path};
79use derive_more::{Display, Error, From};
80
81#[cfg(feature = "ahash")]
82type HashMap<K, V> = ahash::AHashMap<K, V>;
83#[cfg(not(feature = "ahash"))]
84type HashMap<K, V> = std::collections::HashMap<K, V>;
85
86#[cfg(feature = "stdlib")]
87mod stdlib;
88#[cfg(feature = "stdlib")]
89pub use stdlib::*;
90
91/// A specialized [`Result`] type for `libfive` operations.
92///
93/// This type is broadly used across `libvive` for any operation which may
94/// produce an error.
95///
96/// This type is generally used to avoid writing out [`enum@Error`] directly and
97/// is otherwise a direct mapping to [`Result`].
98pub type Result<T> = result::Result<T, Error>;
99
100/// A list specifying general categories of errors.
101///
102/// This list is intended to grow over time and it is not recommended to
103/// exhaustively match against it.
104///
105/// [`libfive::Error`]: Error
106#[derive(Clone, Copy, Debug, Display, Eq, Error, From, Hash, Ord, PartialEq, PartialOrd)]
107#[non_exhaustive]
108pub enum Error {
109    /// The specified variable could not be updated.
110    VariablesCouldNotBeUpdated,
111    /// The requested variable could not be found.
112    VariableNotFound,
113    /// The variable with this name was already added.
114    VariableAlreadyAdded,
115    /// The resp. file could not be opened for writing.
116    FileWriteFailed,
117    /// The resp. file could not be opened for reading.
118    FileReadFailed,
119    /// The queried tree is not a constant.
120    TreeIsNotConstant,
121}
122
123/// Trait to aid with using arbitrary 2D point types on a [`Contour`].
124pub trait Point2 {
125    fn new(x: f32, y: f32) -> Self;
126    fn x(&self) -> f32;
127    fn y(&self) -> f32;
128}
129
130/// Trait to aid with using arbitrary 3D point types on a [`TriangleMesh`].
131pub trait Point3 {
132    fn new(x: f32, y: f32, z: f32) -> Self;
133    fn x(&self) -> f32;
134    fn y(&self) -> f32;
135    fn z(&self) -> f32;
136}
137
138/// Series of 2D or 3D points forming a
139/// [polygonal chain](https://en.wikipedia.org/wiki/Polygonal_chain).
140pub type Contour<T> = Vec<T>;
141
142/// Bitmap representing occupancy in a slice of a [`Tree`].
143///
144/// It contains `width()` * `height()` pixels, in row-major order.
145pub struct Bitmap(*mut sys::libfive_pixels);
146
147impl Bitmap {
148    /// Returns the bitmap pixel buffer as a flat `[bool]` slice.
149    ///
150    /// The length is `width()` × `height()`.
151    pub fn as_slice(&self) -> &[bool] {
152        let bitmap = unsafe { self.0.as_ref() }.unwrap();
153        unsafe {
154            slice::from_raw_parts(
155                bitmap.pixels,
156                (bitmap.width * bitmap.height) as _,
157            )
158        }
159    }
160
161    /// Returns the bitmap pixel buffer as a flat, mutable `[bool]` slice.
162    ///
163    /// The length is `width()` × `height()`.
164    pub fn as_slice_mut(&mut self) -> &mut [bool] {
165        let bitmap = unsafe { self.0.as_mut() }.unwrap();
166        unsafe {
167            slice::from_raw_parts_mut(
168                bitmap.pixels,
169                (bitmap.width * bitmap.height) as _,
170            )
171        }
172    }
173
174    /// Returns the value of the pixel `x`, `y`.
175    pub fn pixel(&self, x: u32, y: u32) -> bool {
176        assert!(x < self.width() && y < self.height());
177        self.as_slice()[(y * self.height() + x) as usize]
178    }
179
180    /// Returns the width of the bitmap.
181    pub fn width(&self) -> u32 {
182        unsafe { self.0.as_ref() }.unwrap().width
183    }
184
185    /// Returns the height of the bitmap.
186    pub fn height(&self) -> u32 {
187        unsafe { self.0.as_ref() }.unwrap().height
188    }
189}
190
191impl Drop for Bitmap {
192    fn drop(&mut self) {
193        unsafe { sys::libfive_pixels_delete(&mut self.0 as *mut _ as _) };
194    }
195}
196
197/// Triangle mesh.
198///
199/// The `positions` type is generic. You can use whatever type you like. Just
200/// implement the [`Point3`] trait on it.
201///
202/// The `triangles` are a list of indices into the `positions`.
203pub struct TriangleMesh<T: Point3> {
204    pub positions: Vec<T>,
205    pub triangles: Vec<[u32; 3]>,
206}
207
208/// Flat triangle mesh.
209///
210/// The `positions` list has layout `[x0, y0, z0, x1, y1, z1, ...]`.
211///
212/// The `triangles` list has layout `[t0.v0, t0.v1, t0.v2, t1.v0, t1.v1, t1.v2,
213/// ...]` where `t`*n* is triangle *n* and `v`*m* is vertex index *m*.
214pub struct FlatTriangleMesh {
215    pub positions: Vec<f32>,
216    pub triangles: Vec<u32>,
217}
218
219impl<T: Point3> From<TriangleMesh<T>> for FlatTriangleMesh {
220    fn from(mesh: TriangleMesh<T>) -> FlatTriangleMesh {
221        FlatTriangleMesh {
222            positions: mesh
223                .positions
224                .into_iter()
225                .flat_map(|point| [point.x(), point.y(), point.z()])
226                .collect(),
227            triangles: mesh.triangles.into_iter().flatten().collect(),
228        }
229    }
230}
231
232/// Set of variables to parameterize a [`Tree`].
233pub struct Variables {
234    map: HashMap<String, usize>,
235    variables: Vec<*const c_void>,
236    values: Vec<f32>,
237    sys_variables: sys::libfive_vars,
238}
239
240impl Default for Variables {
241    fn default() -> Self {
242        Variables::new()
243    }
244}
245
246impl Variables {
247    /// Creates a new, empty set of variables.
248    pub fn new() -> Self {
249        Self {
250            map: HashMap::new(),
251            variables: Vec::new(),
252            values: Vec::new(),
253            sys_variables: sys::libfive_vars {
254                vars: ptr::null(),
255                values: ptr::null_mut(),
256                size: 0,
257            },
258        }
259    }
260
261    /// Adds the variable `name` to the set.
262    ///
263    /// # Errors
264    ///
265    /// Returns [`Error::VariableAlreadyAdded`] if the variable already exists
266    /// in the set.
267    pub fn add(&mut self, name: &str, value: f32) -> Result<Tree> {
268        let name = name.to_string();
269        if self.map.contains_key(&name) {
270            Err(Error::VariableAlreadyAdded)
271        } else {
272            let tree = unsafe { sys::libfive_tree_var() };
273            let id = unsafe { sys::libfive_tree_id(tree) };
274
275            self.map.insert(name, self.variables.len());
276            self.variables.push(id);
277            self.values.push(value);
278            // Update struct.
279            self.sys_variables.vars = self.variables.as_ptr() as *const _ as _;
280            self.sys_variables.values = self.values.as_ptr() as *const _ as _;
281            self.sys_variables.size = self.variables.len().try_into().unwrap();
282
283            Ok(Tree(tree))
284        }
285    }
286
287    /// Sets the variable `name` to `value`.
288    ///
289    /// # Errors
290    ///
291    /// Returns [`Error::VariableNotFound`] if the variable does not exist in
292    /// the set.
293    pub fn set(&mut self, name: &str, value: f32) -> Result<()> {
294        if let Some(&index) = self.map.get(name) {
295            self.values[index] = value;
296            Ok(())
297        } else {
298            Err(Error::VariableNotFound)
299        }
300    }
301}
302
303impl Drop for Variables {
304    fn drop(&mut self) {
305        unsafe {
306            sys::libfive_vars_delete(&mut self.sys_variables as *mut _ as _)
307        };
308    }
309}
310
311/// Helper for controlling evaluation of [`Variables`] on a [`Tree`].
312pub struct Evaluator(sys::libfive_evaluator);
313
314impl Evaluator {
315    pub fn new(tree: &Tree, variables: &Variables) -> Self {
316        Self(unsafe {
317            sys::libfive_tree_evaluator(tree.0, variables.sys_variables)
318        })
319    }
320
321    pub fn update(&mut self, variables: &Variables) -> Result<()> {
322        if unsafe {
323            sys::libfive_evaluator_update_vars(self.0, variables.sys_variables)
324        } {
325            Err(Error::VariablesCouldNotBeUpdated)
326        } else {
327            Ok(())
328        }
329    }
330
331    /// Computes a mesh and saves it to `path` in
332    /// [`STL`](https://en.wikipedia.org/wiki/STL_(file_format)) format.
333    pub fn write_stl(
334        &self,
335        path: impl AsRef<Path>,
336        region: &Region3,
337    ) -> Result<()> {
338        let path = c_string_from_path(path);
339
340        if unsafe {
341            sys::libfive_evaluator_save_mesh(self.0, region.0, path.as_ptr())
342        } {
343            Ok(())
344        } else {
345            Err(Error::FileWriteFailed)
346        }
347    }
348}
349
350impl Drop for Evaluator {
351    fn drop(&mut self) {
352        unsafe { sys::libfive_evaluator_delete(self.0) };
353    }
354}
355
356/// 2D bounding region.
357#[derive(Clone, Copy, Debug, PartialEq)]
358pub struct Region2(sys::libfive_region2);
359
360impl Region2 {
361    pub fn new(x_min: f32, x_max: f32, y_min: f32, y_max: f32) -> Self {
362        Self(sys::libfive_region2 {
363            X: sys::libfive_interval {
364                lower: x_min,
365                upper: x_max,
366            },
367            Y: sys::libfive_interval {
368                lower: y_min,
369                upper: y_max,
370            },
371        })
372    }
373}
374
375/// 3D bounding region.
376#[derive(Clone, Copy, Debug, PartialEq)]
377pub struct Region3(sys::libfive_region3);
378
379impl Region3 {
380    pub fn new(
381        x_min: f32,
382        x_max: f32,
383        y_min: f32,
384        y_max: f32,
385        z_min: f32,
386        z_max: f32,
387    ) -> Self {
388        Self(sys::libfive_region3 {
389            X: sys::libfive_interval {
390                lower: x_min,
391                upper: x_max,
392            },
393            Y: sys::libfive_interval {
394                lower: y_min,
395                upper: y_max,
396            },
397            Z: sys::libfive_interval {
398                lower: z_min,
399                upper: z_max,
400            },
401        })
402    }
403}
404
405#[allow(dead_code)]
406#[repr(i32)]
407enum Op {
408    Invalid = 0,
409
410    Constant = 1,
411    VarX = 2,
412    VarY = 3,
413    VarZ = 4,
414    VarFree = 5,
415    ConstVar = 6,
416
417    Square = 7,
418    Sqrt = 8,
419    Neg = 9,
420    Sin = 10,
421    Cos = 11,
422    Tan = 12,
423    Asin = 13,
424    Acos = 14,
425    Atan = 15,
426    Exp = 16,
427    Abs = 28,
428    Log = 30,
429    Recip = 29,
430
431    Add = 17,
432    Mul = 18,
433    Min = 19,
434    Max = 20,
435    Sub = 21,
436    Div = 22,
437    Atan2 = 23,
438    Pow = 24,
439    NthRoot = 25,
440    Mod = 26,
441    NanFill = 27,
442    Compare = 31,
443
444    Oracle = 32,
445}
446
447macro_rules! fn_unary {
448    ($func_name:ident, $op_code:ident) => {
449        #[inline]
450        pub fn $func_name(&self) -> Self {
451            Self(unsafe { sys::libfive_tree_unary(Op::$op_code as _, self.0) })
452        }
453    };
454}
455
456macro_rules! fn_binary {
457    ($func_name:ident, $op_code:ident, $other:ident) => {
458        #[inline]
459        pub fn $func_name(self, $other: Self) -> Self {
460            Self(unsafe {
461                sys::libfive_tree_binary(Op::$op_code as _, self.0, $other.0)
462            })
463        }
464    };
465}
466
467macro_rules! op_binary {
468    ($func_name:ident, $op_code:ident) => {
469        impl $op_code for Tree {
470            type Output = Tree;
471            #[inline]
472            fn $func_name(self, rhs: Tree) -> Self::Output {
473                self.$func_name(rhs)
474            }
475        }
476    };
477}
478
479/// Tree of operations.
480///
481/// # Core
482///
483/// * [Constant][`TreeFloat::from::<f32>()`]
484/// * [Bases](#bases)
485/// * [Functions](#functions)
486/// * [Evaluation, import & export](#eval)
487///
488/// # Standard Library
489///
490/// These features are dependent on the `stdlib` feature being enabled.
491///
492/// * [Shapes](#shapes)
493/// * [Generators](#generators)
494/// * [Constructive solid geometry](#csg)
495/// * [Transformations](#transforms)
496/// * [Text](#text)
497#[derive(Eq, PartialEq)]
498pub struct Tree(sys::libfive_tree);
499
500/// An alias for [`Tree`].
501///
502/// Used to make the kind of sensible input more obvious for some operators.
503pub type TreeFloat = Tree;
504
505/// # Constants <a name="constant"></a>
506impl From<f32> for Tree {
507    /// Creates a constant [`Tree`].
508    fn from(constant: f32) -> Self {
509        Self(unsafe { sys::libfive_tree_const(constant) })
510    }
511}
512
513/// # Bases <a name="bases"></a>
514impl Tree {
515    #[inline]
516    pub fn x() -> Self {
517        Self(unsafe { sys::libfive_tree_x() })
518    }
519
520    #[inline]
521    pub fn y() -> Self {
522        Self(unsafe { sys::libfive_tree_y() })
523    }
524
525    #[inline]
526    pub fn z() -> Self {
527        Self(unsafe { sys::libfive_tree_z() })
528    }
529
530    //pub fn variable() -> Self {}
531}
532
533/// # Functions <a name="functions"></a>
534impl Tree {
535    fn_unary!(square, Square);
536    fn_unary!(sqrt, Sqrt);
537    fn_unary!(neg, Neg);
538    fn_unary!(sin, Sin);
539    fn_unary!(cos, Cos);
540    fn_unary!(tan, Tan);
541    fn_unary!(asin, Asin);
542    fn_unary!(acos, Acos);
543    fn_unary!(atan, Atan);
544    fn_unary!(exp, Exp);
545    fn_unary!(abs, Abs);
546    fn_unary!(log, Log);
547    fn_unary!(recip, Recip);
548
549    fn_binary!(add, Add, rhs);
550    fn_binary!(mul, Mul, rhs);
551    fn_binary!(min, Min, rhs);
552    fn_binary!(max, Max, rhs);
553    fn_binary!(sub, Sub, rhs);
554    fn_binary!(div, Div, rhs);
555    fn_binary!(atan2, Atan2, other);
556    fn_binary!(pow, Pow, exp);
557    fn_binary!(nth_root, NthRoot, n);
558    fn_binary!(rem, Mod, rhs);
559    fn_binary!(nan_fill, NanFill, rhs);
560    fn_binary!(compare, Compare, rhs);
561
562    /// Checks if the tree is a variable.
563    pub fn is_variable(&self) -> bool {
564        unsafe { sys::libfive_tree_is_var(self.0) }
565    }
566
567    /// Returns the value of the tree if it is constant.
568    ///
569    /// I.e. if it was created from an [`f32`] value.
570    ///
571    /// # Errors
572    ///
573    /// Returns [`TreeIsNotConstant`](Error::TreeIsNotConstant) if the tree is
574    /// not constant.
575    pub fn as_f32(&self) -> Result<f32> {
576        let mut success = false;
577        let value = unsafe {
578            sys::libfive_tree_get_const(self.0, &mut success as *mut _)
579        };
580
581        if success {
582            Ok(value)
583        } else {
584            Err(Error::TreeIsNotConstant)
585        }
586    }
587}
588
589/// # Evaluation, Import & Export <a name="eval"></a>
590///
591/// ## Common Arguments
592///
593/// * `region` -- A bounding box that will be subdivided into an
594///   quadtree/octree. For clean lines/triangles, it should be near-cubical.
595///   But this is not a hard requirement.
596///
597/// * `resolution` -- The resolution used for meshing. Make this larger to get a
598///   higher-resolution model.
599///
600///   To not loose any detail this should be approximately one over half the
601///   model's smallest feature size.
602///
603///   For methods generating 3D data another way to think of resolution is as
604///   the number of subdivision, per unit length, on each axis.
605impl Tree {
606    /// Renders a 2D slice of `region` at the given `z` height into a
607    /// [`Bitmap`].
608    #[inline]
609    pub fn to_bitmap(
610        &self,
611        region: &Region2,
612        z: f32,
613        resolution: f32,
614    ) -> Bitmap {
615        Bitmap(unsafe {
616            sys::libfive_tree_render_pixels(self.0, region.0, z, resolution)
617        })
618    }
619
620    /// Renders `region` to a [`TriangleMesh`].
621    pub fn to_triangle_mesh<T: Point3>(
622        &self,
623        region: &Region3,
624        resolution: f32,
625    ) -> Option<TriangleMesh<T>> {
626        match unsafe {
627            sys::libfive_tree_render_mesh(self.0, region.0, resolution).as_mut()
628        } {
629            Some(raw_mesh) => {
630                let mesh = TriangleMesh::<T> {
631                    positions: (0..raw_mesh.vert_count)
632                        .map(|index| {
633                            let vertex =
634                                &unsafe { *raw_mesh.verts.add(index as _) };
635                            T::new(vertex.x, vertex.y, vertex.z)
636                        })
637                        .collect(),
638                    triangles: (0..raw_mesh.tri_count)
639                        .map(|index| {
640                            let triangle =
641                                &unsafe { *raw_mesh.tris.add(index as _) };
642                            [triangle.a, triangle.b, triangle.c]
643                        })
644                        .collect(),
645                };
646
647                unsafe {
648                    sys::libfive_mesh_delete(raw_mesh as *mut _ as _);
649                }
650
651                Some(mesh)
652            }
653            None => None,
654        }
655    }
656
657    /// Renders a 2D slice of `region` at the given `z` height to a set of 2D
658    /// contours.
659    pub fn to_contour_2d<T: Point2>(
660        &self,
661        region: Region2,
662        z: f32,
663        resolution: f32,
664    ) -> Option<Vec<Contour<T>>> {
665        match unsafe {
666            sys::libfive_tree_render_slice(self.0, region.0, z, resolution)
667                .as_mut()
668        } {
669            Some(raw_contours) => {
670                let contours = (0..raw_contours.count)
671                    .map(|index| {
672                        let contour =
673                            unsafe { raw_contours.cs.add(index as _).as_ref() }
674                                .unwrap();
675                        (0..contour.count)
676                            .map(|index| {
677                                let point = unsafe {
678                                    contour.pts.add(index as _).as_ref()
679                                }
680                                .unwrap();
681                                T::new(point.x, point.y)
682                            })
683                            .collect()
684                    })
685                    .collect();
686
687                unsafe {
688                    sys::libfive_contours_delete(raw_contours as *mut _ as _);
689                }
690
691                Some(contours)
692            }
693            None => None,
694        }
695    }
696
697    /// Renders `region` to a set of 3D contours.
698    pub fn to_contour_3d<T: Point3>(
699        &self,
700        region: Region2,
701        z: f32,
702        resolution: f32,
703    ) -> Option<Vec<Contour<T>>> {
704        let raw_contours = unsafe {
705            sys::libfive_tree_render_slice3(self.0, region.0, z, resolution)
706                .as_ref()
707        };
708
709        if let Some(raw_contours) = raw_contours {
710            let contours = (0..raw_contours.count)
711                .map(|index| {
712                    let contour =
713                        unsafe { raw_contours.cs.add(index as _).as_ref() }
714                            .unwrap();
715
716                    (0..contour.count)
717                        .map(|index| {
718                            let point =
719                                unsafe { contour.pts.add(index as _).as_ref() }
720                                    .unwrap();
721                            T::new(point.x, point.y, point.z)
722                        })
723                        .collect()
724                })
725                .collect();
726
727            unsafe {
728                sys::libfive_contours_delete(&raw_contours as *const _ as _);
729            }
730
731            Some(contours)
732        } else {
733            None
734        }
735    }
736
737    /// Computes a 2D slice of `region` at the given `z` height and saves it to
738    /// `path` in [`SVG`](https://en.wikipedia.org/wiki/Scalable_Vector_Graphics)
739    /// format.
740    pub fn write_svg(
741        &self,
742        path: impl AsRef<Path>,
743        region: &Region2,
744        z: f32,
745        resolution: f32,
746    ) {
747        let path = c_string_from_path(path);
748
749        unsafe {
750            sys::libfive_tree_save_slice(
751                self.0,
752                region.0,
753                z,
754                resolution,
755                path.as_ptr(),
756            );
757        }
758    }
759
760    /// Computes a mesh of `region` and saves it to `path` in
761    /// [`STL`](https://en.wikipedia.org/wiki/STL_(file_format)) format.
762    pub fn write_stl(
763        &self,
764        path: impl AsRef<Path>,
765        region: &Region3,
766        resolution: f32,
767    ) -> Result<()> {
768        let path = c_string_from_path(path);
769
770            println!("Foobar! {:?}", path);
771
772        if unsafe {
773            sys::libfive_tree_save_mesh(
774                self.0,
775                region.0,
776                resolution,
777                path.as_ptr(),
778            )
779        } {
780            Ok(())
781        } else {
782            Err(Error::FileWriteFailed)
783        }
784    }
785
786    /// Serializes the tree to a file.
787    ///
788    /// <div class="warning">
789    ///
790    /// The file format is not archival and may change without notice.
791    ///
792    /// Saved files may fail to load with older versions of `libfive` if the
793    /// `packed_opcodes` feature is enabled.
794    ///
795    /// </div>
796    pub fn save(&self, path: impl AsRef<Path>) -> Result<()> {
797        let path = c_string_from_path(path);
798
799        if unsafe { sys::libfive_tree_save(self.0, path.as_ptr()) } {
800            Ok(())
801        } else {
802            Err(Error::FileWriteFailed)
803        }
804    }
805
806    /// Deserializes a tree from a file.
807    ///
808    /// <div class="warning">
809    ///
810    /// Old files may fail to load if the `packed_opcodes` feature is enabled.
811    ///
812    /// </div>
813    pub fn load(&self, path: impl AsRef<Path>) -> Result<Tree> {
814        let path = c_string_from_path(path);
815
816        match unsafe { sys::libfive_tree_load(path.as_ptr()).as_mut() } {
817            Some(tree) => Ok(Self(tree as _)),
818            None => Err(Error::FileReadFailed),
819        }
820    }
821}
822
823impl Drop for Tree {
824    fn drop(&mut self) {
825        unsafe { sys::libfive_tree_delete(self.0) };
826    }
827}
828
829op_binary!(add, Add);
830op_binary!(div, Div);
831op_binary!(mul, Mul);
832op_binary!(rem, Rem);
833op_binary!(sub, Sub);
834
835impl Neg for Tree {
836    type Output = Tree;
837
838    fn neg(self) -> Self::Output {
839        Self(unsafe { sys::libfive_tree_unary(Op::Neg as _, self.0) })
840    }
841}
842
843fn c_string_from_path<P: AsRef<Path>>(path: P) -> CString {
844    CString::new(path.as_ref().as_os_str().as_encoded_bytes()).unwrap()
845}
846
847#[test]
848fn test_2d() -> Result<()> {
849    let circle = Tree::x().square() + Tree::y().square() - 1.0.into();
850
851    circle.write_svg(
852        "circle.svg",
853        &Region2::new(-2.0, 2.0, -2.0, 2.0),
854        0.0,
855        10.0,
856    );
857
858    Ok(())
859}
860
861#[test]
862#[cfg(feature = "stdlib")]
863fn test_3d() -> Result<()> {
864    let f_rep_shape = Tree::sphere(1.0.into(), TreeVec3::default())
865        .difference_multi(vec![
866            Tree::sphere(0.6.into(), TreeVec3::default()),
867            Tree::cylinder_z(
868                0.6.into(),
869                2.0.into(),
870                TreeVec3::new(0.0, 0.0, -1.0),
871            ),
872            Tree::cylinder_z(
873                0.6.into(),
874                2.0.into(),
875                TreeVec3::new(0.0, 0.0, -1.0),
876            )
877            .reflect_xz(),
878            Tree::cylinder_z(
879                0.6.into(),
880                2.0.into(),
881                TreeVec3::new(0.0, 0.0, -1.0),
882            )
883            .reflect_yz(),
884        ]);
885
886    f_rep_shape.write_stl(
887        "f-rep-shape.stl",
888        &Region3::new(-2.0, 2.0, -2.0, 2.0, -2.0, 2.0),
889        // Subdivisions
890        10.0,
891    )?;
892
893    Ok(())
894}
895
896/*
897#[test]
898#[cfg(feature = "stdlib")]
899fn test_eval_3d() -> Result<()> {
900    //let mut variables = Variables::new();
901
902    //let inner_radius = variables.add("inner_radius", 0.6)?;
903
904    let csg_shape = Tree::sphere(1.0.into(), TreeVec3::default())
905        .difference_multi(vec![
906            Tree::sphere(0.6.into(), TreeVec3::default()),
907            Tree::cylinder_z(
908                0.6.into(),
909                2.0.into(),
910                TreeVec3::new(0.0, 0.0, -1.0),
911            ),
912            Tree::cylinder_z(
913                0.6.into(),
914                2.0.into(),
915                TreeVec3::new(0.0, 0.0, -1.0),
916            )
917            .reflect_xz(),
918            Tree::cylinder_z(
919                0.6.into(),
920                2.0.into(),
921                TreeVec3::new(0.0, 0.0, -1.0),
922            )
923            .reflect_yz(),
924        ]);
925
926    //let mut evaluator = Evaluator::new(&csg_shape, &variables);
927    //evaluator.update(&variables);
928
929    csg_shape.write_stl(
930        "csg_shape.stl",
931        &Region3::new(-2.0, 2.0, -2.0, 2.0, -2.0, 2.0),
932        10.0,
933    )?;
934    /*
935    variables.set("inner_radius", 0.4);
936    evaluator.update(&variables);
937
938    csg_shape.write_stl(
939        "csg_shape_0_4.stl",
940        &Region3::new(-2.0, 2.0, -2.0, 2.0, -2.0, 2.0),
941        10.0,
942    )?;*/
943
944    Ok(())
945}*/