diff --git a/crates/fj-core/src/instance.rs b/crates/fj-core/src/instance.rs index e6f653289..242c2f5d7 100644 --- a/crates/fj-core/src/instance.rs +++ b/crates/fj-core/src/instance.rs @@ -7,6 +7,7 @@ use crate::{layers::Layers, validate::ValidationConfig}; /// An instance of the Fornjot core /// /// This is the main entry point to `fj-core`'s API. +#[derive(Default)] pub struct Instance { /// The layers of data that make up the state of a core instance pub layers: Layers, @@ -15,8 +16,7 @@ pub struct Instance { impl Instance { /// Construct an instance of `Instance` pub fn new() -> Self { - let layers = Layers::new(); - Self { layers } + Self::default() } /// Construct an instance of `Instance`, using the provided configuration @@ -25,9 +25,3 @@ impl Instance { Self { layers } } } - -impl Default for Instance { - fn default() -> Self { - Self::new() - } -} diff --git a/crates/fj/src/args.rs b/crates/fj/src/args.rs index 8753f1e0a..28134c6a1 100644 --- a/crates/fj/src/args.rs +++ b/crates/fj/src/args.rs @@ -10,10 +10,10 @@ use fj_math::Scalar; /// interface for viewing and exporting models, and is used for Fornjot's /// example models and the testing infrastructure they are part of. /// -/// You might not want to use this struct directly. [`handle_model`] Provides a -/// more high-level and convenient interface. +/// You might not want to use this struct directly. [`Instance::process_model`] +/// provides a more high-level and convenient interface. /// -/// [`handle_model`]: crate::handle_model() +/// [`Instance::process_model`]: crate::Instance::process_model #[derive(clap::Parser)] pub struct Args { /// Export model to this path diff --git a/crates/fj/src/handle_model.rs b/crates/fj/src/handle_model.rs deleted file mode 100644 index 64a9a00b2..000000000 --- a/crates/fj/src/handle_model.rs +++ /dev/null @@ -1,133 +0,0 @@ -use std::{error::Error as _, fmt}; - -use fj_core::{ - algorithms::{ - approx::{InvalidTolerance, Tolerance}, - bounding_volume::BoundingVolume, - triangulate::Triangulate, - }, - validate::ValidationErrors, - Instance, -}; -use fj_interop::Model; -use fj_math::{Aabb, Point, Scalar}; -use tracing_subscriber::prelude::*; - -use crate::Args; - -/// Export or display a model, according to CLI arguments -/// -/// This function is intended to be called by applications that define a model -/// and want to provide a standardized CLI interface for dealing with that -/// model. -/// -/// This function is used by Fornjot's own testing infrastructure, but is useful -/// beyond that, when using Fornjot directly to define a model. -pub fn handle_model(model: &M, mut core: Instance) -> Result -where - for<'r> (&'r M, Tolerance): Triangulate, - M: BoundingVolume<3>, -{ - tracing_subscriber::registry() - .with(tracing_subscriber::fmt::layer()) - .with(tracing_subscriber::EnvFilter::from_default_env()) - .init(); - - let args = Args::parse(); - - if !args.ignore_validation { - core.layers.validation.take_errors()?; - } - - let aabb = model.aabb().unwrap_or(Aabb { - min: Point::origin(), - max: Point::origin(), - }); - - let tolerance = match args.tolerance { - None => { - // Compute a reasonable default for the tolerance value. To do - // this, we just look at the smallest non-zero extent of the - // bounding box and divide that by some value. - - let mut min_extent = Scalar::MAX; - for extent in aabb.size().components { - if extent > Scalar::ZERO && extent < min_extent { - min_extent = extent; - } - } - - let tolerance = min_extent / Scalar::from_f64(1000.); - Tolerance::from_scalar(tolerance)? - } - Some(user_defined_tolerance) => user_defined_tolerance, - }; - - let mesh = (model, tolerance).triangulate(); - - if let Some(path) = args.export { - crate::export::export(&mesh, &path)?; - return Ok(()); - } - - let model = Model { mesh, aabb }; - - crate::window::display(model, false)?; - - Ok(()) -} - -/// Return value of [`handle_model`] -pub type Result = std::result::Result<(), Error>; - -/// Error returned by [`handle_model`] -#[derive(thiserror::Error)] -pub enum Error { - /// Failed to set up logger - #[error("Failed to set up logger")] - Tracing(#[from] tracing::subscriber::SetGlobalDefaultError), - - /// Error displaying model - #[error("Error displaying model")] - Display(#[from] crate::window::Error), - - /// Error exporting model - #[error("Error exporting model")] - Export(#[from] crate::export::Error), - - /// Invalid tolerance - #[error(transparent)] - Tolerance(#[from] InvalidTolerance), - - /// Unhandled validation errors - #[error(transparent)] - Validation(#[from] ValidationErrors), -} - -impl fmt::Debug for Error { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - // When returning an error from Rust's `main` function, the runtime uses - // the error's `Debug` implementation to display it, not the `Display` - // one. This is unfortunate, and forces us to override `Debug` here. - - // We should be able to replace this with `Report`, once it is stable: - // https://doc.rust-lang.org/std/error/struct.Report.html - - write!(f, "{self}")?; - - let mut source = self.source(); - - if source.is_some() { - write!(f, "\n\nCaused by:")?; - } - - let mut i = 0; - while let Some(s) = source { - write!(f, "\n {i}: {s}")?; - source = s.source(); - i += 1; - } - - Ok(()) - } -} diff --git a/crates/fj/src/instance.rs b/crates/fj/src/instance.rs new file mode 100644 index 000000000..6a07f2256 --- /dev/null +++ b/crates/fj/src/instance.rs @@ -0,0 +1,154 @@ +use std::{error::Error as _, fmt}; + +use fj_core::{ + algorithms::{ + approx::{InvalidTolerance, Tolerance}, + bounding_volume::BoundingVolume, + triangulate::Triangulate, + }, + validate::{ValidationConfig, ValidationErrors}, +}; +use fj_interop::Model; +use fj_math::{Aabb, Point, Scalar}; +use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; + +use crate::Args; + +/// An instance of Fornjot +/// +/// This is the main entry point into the Fornjot API +#[derive(Default)] +pub struct Instance { + /// The instance of the Fornjot core + pub core: fj_core::Instance, +} + +impl Instance { + /// Construct an instance of `Instance` + pub fn new() -> Self { + Self::default() + } + + /// Construct an instance of `Instance`, using the provided configuration + pub fn with_validation_config(config: ValidationConfig) -> Self { + let core = fj_core::Instance::with_validation_config(config); + Self { core } + } + + /// Export or display a model, according to CLI arguments + /// + /// This function is intended to be called by applications that define a + /// model and want to provide a standardized CLI interface for dealing with + /// that model. + /// + /// This function is used by Fornjot's own testing infrastructure, but is + /// useful beyond that, when using Fornjot directly to define a model. + pub fn process_model(&mut self, model: &M) -> Result + where + for<'r> (&'r M, Tolerance): Triangulate, + M: BoundingVolume<3>, + { + tracing_subscriber::registry() + .with(tracing_subscriber::fmt::layer()) + .with(tracing_subscriber::EnvFilter::from_default_env()) + .init(); + + let args = Args::parse(); + + if !args.ignore_validation { + self.core.layers.validation.take_errors()?; + } + + let aabb = model.aabb().unwrap_or(Aabb { + min: Point::origin(), + max: Point::origin(), + }); + + let tolerance = match args.tolerance { + None => { + // Compute a reasonable default for the tolerance value. To do + // this, we just look at the smallest non-zero extent of the + // bounding box and divide that by some value. + + let mut min_extent = Scalar::MAX; + for extent in aabb.size().components { + if extent > Scalar::ZERO && extent < min_extent { + min_extent = extent; + } + } + + let tolerance = min_extent / Scalar::from_f64(1000.); + Tolerance::from_scalar(tolerance)? + } + Some(user_defined_tolerance) => user_defined_tolerance, + }; + + let mesh = (model, tolerance).triangulate(); + + if let Some(path) = args.export { + crate::export::export(&mesh, &path)?; + return Ok(()); + } + + let model = Model { mesh, aabb }; + + crate::window::display(model, false)?; + + Ok(()) + } +} + +/// Return value of [`Instance::process_model`] +pub type Result = std::result::Result<(), Error>; + +/// Error returned by [`Instance::process_model`] +#[derive(thiserror::Error)] +pub enum Error { + /// Failed to set up logger + #[error("Failed to set up logger")] + Tracing(#[from] tracing::subscriber::SetGlobalDefaultError), + + /// Error displaying model + #[error("Error displaying model")] + Display(#[from] crate::window::Error), + + /// Error exporting model + #[error("Error exporting model")] + Export(#[from] crate::export::Error), + + /// Invalid tolerance + #[error(transparent)] + Tolerance(#[from] InvalidTolerance), + + /// Unhandled validation errors + #[error(transparent)] + Validation(#[from] ValidationErrors), +} + +impl fmt::Debug for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + // When returning an error from Rust's `main` function, the runtime uses + // the error's `Debug` implementation to display it, not the `Display` + // one. This is unfortunate, and forces us to override `Debug` here. + + // We should be able to replace this with `Report`, once it is stable: + // https://doc.rust-lang.org/std/error/struct.Report.html + + write!(f, "{self}")?; + + let mut source = self.source(); + + if source.is_some() { + write!(f, "\n\nCaused by:")?; + } + + let mut i = 0; + while let Some(s) = source { + write!(f, "\n {i}: {s}")?; + source = s.source(); + i += 1; + } + + Ok(()) + } +} diff --git a/crates/fj/src/lib.rs b/crates/fj/src/lib.rs index c7c190041..dc25e3fe4 100644 --- a/crates/fj/src/lib.rs +++ b/crates/fj/src/lib.rs @@ -10,11 +10,11 @@ //! [Fornjot]: https://www.fornjot.app/ mod args; -mod handle_model; +mod instance; pub use self::{ args::Args, - handle_model::{handle_model, Error, Result}, + instance::{Error, Instance, Result}, }; pub use fj_core as core; diff --git a/models/all/src/main.rs b/models/all/src/main.rs index 767196187..ddd48b428 100644 --- a/models/all/src/main.rs +++ b/models/all/src/main.rs @@ -1,8 +1,6 @@ -use fj::handle_model; - fn main() -> fj::Result { - let mut core = fj::core::Instance::new(); - let model = all::model(&mut core); - handle_model(&model, core)?; + let mut fj = fj::Instance::new(); + let model = all::model(&mut fj.core); + fj.process_model(&model)?; Ok(()) } diff --git a/models/color/src/main.rs b/models/color/src/main.rs index 909d527de..681932ca2 100644 --- a/models/color/src/main.rs +++ b/models/color/src/main.rs @@ -1,8 +1,6 @@ -use fj::handle_model; - fn main() -> fj::Result { - let mut core = fj::core::Instance::new(); - let model = color::model(&mut core); - handle_model(&model, core)?; + let mut fj = fj::Instance::new(); + let model = color::model(&mut fj.core); + fj.process_model(&model)?; Ok(()) } diff --git a/models/cuboid/src/main.rs b/models/cuboid/src/main.rs index 290a2fbd6..3fc949f16 100644 --- a/models/cuboid/src/main.rs +++ b/models/cuboid/src/main.rs @@ -1,8 +1,6 @@ -use fj::handle_model; - fn main() -> fj::Result { - let mut core = fj::core::Instance::new(); - let model = cuboid::model([3., 2., 1.], &mut core); - handle_model(&model, core)?; + let mut fj = fj::Instance::new(); + let model = cuboid::model([3., 2., 1.], &mut fj.core); + fj.process_model(&model)?; Ok(()) } diff --git a/models/holes/src/main.rs b/models/holes/src/main.rs index 192b65397..336c30c2a 100644 --- a/models/holes/src/main.rs +++ b/models/holes/src/main.rs @@ -1,8 +1,6 @@ -use fj::handle_model; - fn main() -> fj::Result { - let mut core = fj::core::Instance::new(); - let model = holes::model(0.25, &mut core); - handle_model(&model, core)?; + let mut fj = fj::Instance::new(); + let model = holes::model(0.25, &mut fj.core); + fj.process_model(&model)?; Ok(()) } diff --git a/models/spacer/src/main.rs b/models/spacer/src/main.rs index a6d19176e..078a7a764 100644 --- a/models/spacer/src/main.rs +++ b/models/spacer/src/main.rs @@ -1,8 +1,6 @@ -use fj::handle_model; - fn main() -> fj::Result { - let mut core = fj::core::Instance::new(); - let model = spacer::model(1., 0.5, 1., &mut core); - handle_model(&model, core)?; + let mut fj = fj::Instance::new(); + let model = spacer::model(1., 0.5, 1., &mut fj.core); + fj.process_model(&model)?; Ok(()) } diff --git a/models/split/src/main.rs b/models/split/src/main.rs index 7817bdbda..56c34e9d7 100644 --- a/models/split/src/main.rs +++ b/models/split/src/main.rs @@ -1,8 +1,6 @@ -use fj::handle_model; - fn main() -> fj::Result { - let mut core = fj::core::Instance::new(); - let model = split::model(1.0, 0.2, &mut core); - handle_model(&model, core)?; + let mut fj = fj::Instance::new(); + let model = split::model(1.0, 0.2, &mut fj.core); + fj.process_model(&model)?; Ok(()) } diff --git a/models/star/src/main.rs b/models/star/src/main.rs index cc8a8b211..4e9baff2e 100644 --- a/models/star/src/main.rs +++ b/models/star/src/main.rs @@ -1,8 +1,6 @@ -use fj::handle_model; - fn main() -> fj::Result { - let mut core = fj::core::Instance::new(); - let model = star::model(5, 1., 2., 1., &mut core); - handle_model(&model, core)?; + let mut fj = fj::Instance::new(); + let model = star::model(5, 1., 2., 1., &mut fj.core); + fj.process_model(&model)?; Ok(()) } diff --git a/models/vertices-indices/src/main.rs b/models/vertices-indices/src/main.rs index 84d0b4bc0..c6eb5944d 100644 --- a/models/vertices-indices/src/main.rs +++ b/models/vertices-indices/src/main.rs @@ -1,8 +1,6 @@ -use fj::handle_model; - fn main() -> fj::Result { - let mut core = fj::core::Instance::new(); - let model = vertices_indices::model(&mut core); - handle_model(&model, core)?; + let mut fj = fj::Instance::new(); + let model = vertices_indices::model(&mut fj.core); + fj.process_model(&model)?; Ok(()) }