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()
}
}