honeycomb_core/cmap/builder/
structure.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
// ------ IMPORTS

use crate::prelude::{AttributeBind, CMap2, GridDescriptor};
use crate::{attributes::AttrStorageManager, geometry::CoordsFloat};

use thiserror::Error;
use vtkio::Vtk;

// ------ CONTENT

/// # Builder-level error enum
///
/// This enum is used to describe all non-panic errors that can occur when using the builder
/// structure.
#[derive(Error, Debug)]
pub enum BuilderError {
    // grid-related variants
    /// One or multiple of the specified grid characteristics are invalid.
    #[error("invalid grid parameters - {0}")]
    InvalidGridParameters(&'static str),
    /// The builder is missing one or multiple parameters to generate the grid.
    #[error("insufficient parameters - please specifiy at least 2")]
    MissingGridParameters,

    // vtk-related variants
    /// Specified VTK file contains inconsistent data.
    #[error("invalid/corrupted data in the vtk file - {0}")]
    BadVtkData(&'static str),
    /// Specified VTK file contains unsupported data.
    #[error("unsupported data in the vtk file - {0}")]
    UnsupportedVtkData(&'static str),
}

/// # Combinatorial map builder structure
///
/// ## Example
///
/// ```rust
/// # use honeycomb_core::prelude::BuilderError;
/// # fn main() -> Result<(), BuilderError> {
/// use honeycomb_core::prelude::{CMap2, CMapBuilder};
///
/// let builder = CMapBuilder::default().n_darts(10);
/// let map: CMap2<f64> = builder.build()?;
///
/// assert_eq!(map.n_darts(), 11); // 10 + null dart = 11
///
/// # Ok(())
/// # }
/// ```
#[derive(Default)]
pub struct CMapBuilder<T>
where
    T: CoordsFloat,
{
    pub(super) vtk_file: Option<Vtk>,
    pub(super) grid_descriptor: Option<GridDescriptor<T>>,
    pub(super) attributes: AttrStorageManager,
    pub(super) n_darts: usize,
    pub(super) coordstype: std::marker::PhantomData<T>,
}

// --- `From` impls & predefinite values

impl<T: CoordsFloat> From<GridDescriptor<T>> for CMapBuilder<T> {
    fn from(value: GridDescriptor<T>) -> Self {
        CMapBuilder {
            grid_descriptor: Some(value),
            ..Default::default()
        }
    }
}

/// # Regular methods
impl<T: CoordsFloat> CMapBuilder<T> {
    /// Set the number of dart that the created map will contain.
    #[must_use = "unused builder object"]
    pub fn n_darts(mut self, n_darts: usize) -> Self {
        self.n_darts = n_darts;
        self
    }

    /// Set the [`GridDescriptor`] that will be used when building the map.
    #[must_use = "unused builder object"]
    pub fn grid_descriptor(mut self, grid_descriptor: GridDescriptor<T>) -> Self {
        self.grid_descriptor = Some(grid_descriptor);
        self
    }

    /// Set the VTK file that will be used when building the map.
    ///
    /// # Panics
    ///
    /// This function may panic if the file cannot be loaded.
    #[must_use = "unused builder object"]
    pub fn vtk_file(mut self, file_path: impl AsRef<std::path::Path> + std::fmt::Debug) -> Self {
        let vtk_file =
            Vtk::import(file_path).unwrap_or_else(|e| panic!("E: failed to load file: {e:?}"));
        self.vtk_file = Some(vtk_file);
        self
    }

    /// Add the attribute `A` to the attributes the created map will contain.
    ///
    /// # Usage
    ///
    /// Each attribute must be uniquely typed, i.e. a single type or struct cannot be added twice
    /// to the builder / map. This includes type aliases as these are not distinct from the
    /// compiler's perspective.
    ///
    /// If you have multiple attributes that are represented using the same data type, you may want
    /// to look into the **Newtype** pattern
    /// [here](https://rust-unofficial.github.io/patterns/patterns/behavioural/newtype.html)
    /// and [here](https://doc.rust-lang.org/rust-by-example/generics/new_types.html)
    #[must_use = "unused builder object"]
    pub fn add_attribute<A: AttributeBind + 'static>(mut self) -> Self {
        self.attributes.add_storage::<A>(self.n_darts);
        self
    }

    #[allow(clippy::missing_errors_doc)]
    /// Consumes the builder and produce a [`CMap2`] object.
    ///
    /// # Return / Errors
    ///
    /// This method return a `Result` taking the following values:
    /// - `Ok(map: CMap2)` if generation was successful,
    /// - `Err(BuilderError)` otherwise. See [`BuilderError`] for possible failures.
    ///
    /// # Panics
    ///
    /// This method may panic if type casting goes wrong during parameters parsing.
    pub fn build(self) -> Result<CMap2<T>, BuilderError> {
        if let Some(vfile) = self.vtk_file {
            // build from vtk
            // this routine should return a Result instead of the map directly
            return super::io::build_2d_from_vtk(vfile, self.attributes);
        }
        if let Some(gridb) = self.grid_descriptor {
            // build from grid descriptor
            let split = gridb.split_quads;
            return gridb.parse_2d().map(|(origin, ns, lens)| {
                if split {
                    super::grid::build_2d_splitgrid(origin, ns, lens, self.attributes)
                } else {
                    super::grid::build_2d_grid(origin, ns, lens, self.attributes)
                }
            });
        }
        Ok(CMap2::new_with_undefined_attributes(
            self.n_darts,
            self.attributes,
        ))
    }
}

/// # Pre-definite structures
impl<T: CoordsFloat> CMapBuilder<T> {
    /// Create a [`CMapBuilder`] with a predefinite [`GridDescriptor`] value.
    ///
    /// # Arguments
    ///
    /// - `n_square: usize` -- Number of cells along each axis.
    ///
    /// # Return
    ///
    /// This function return a builder structure with pre-definite parameters set to generate
    /// a specific map.
    ///
    /// The map generated by this pre-definite value corresponds to an orthogonal mesh, with an
    /// equal number of cells along each axis:
    ///
    /// ![`CMAP2_GRID`](https://lihpc-computational-geometry.github.io/honeycomb/user-guide/images/bg_grid.svg)
    #[must_use = "unused builder object"]
    pub fn unit_grid(n_square: usize) -> Self {
        GridDescriptor::default()
            .n_cells([n_square; 3])
            .len_per_cell([T::one(); 3])
            .into()
    }

    /// Create a [`CMapBuilder`] with a predefinite [`GridDescriptor`] value.
    ///
    /// # Arguments
    ///
    /// - `n_square: usize` -- Number of cells along each axis.
    ///
    /// # Return
    ///
    /// This function return a builder structure with pre-definite parameters set to generate
    /// a specific map.
    ///
    /// The map generated by this pre-definite value corresponds to an orthogonal mesh, with an
    /// equal number of cells along each axis. Each cell will be split across their diagonal (top
    /// left to bottom right) to form triangles:
    ///
    /// ![`CMAP2_GRID`](https://lihpc-computational-geometry.github.io/honeycomb/user-guide/images/bg_grid_tri.svg)
    #[must_use = "unused builder object"]
    pub fn unit_triangles(n_square: usize) -> Self {
        GridDescriptor::default()
            .n_cells([n_square; 3])
            .len_per_cell([T::one(); 3])
            .split_quads(true)
            .into()
    }
}