diff --git a/fj-app/src/args.rs b/fj-app/src/args.rs index 16cada701..aabaa3e60 100644 --- a/fj-app/src/args.rs +++ b/fj-app/src/args.rs @@ -1,5 +1,7 @@ use std::{path::PathBuf, str::FromStr as _}; +use anyhow::anyhow; +use fj_host::Parameters; use fj_kernel::algorithms::Tolerance; use fj_math::Scalar; @@ -15,8 +17,8 @@ pub struct Args { pub export: Option, /// Parameters for the model, each in the form `key=value` - #[clap(short, long)] - pub parameters: Vec, + #[clap(short, long, parse(try_from_str = parse_parameters))] + pub parameters: Option, /// Model deviation tolerance #[clap[short, long, parse(try_from_str = parse_tolerance)]] @@ -33,6 +35,27 @@ impl Args { } } +fn parse_parameters(input: &str) -> anyhow::Result { + let mut parameters = Parameters::empty(); + + for parameter in input.split(',') { + let mut parameter = parameter.splitn(2, '='); + + let key = parameter + .next() + .ok_or_else(|| anyhow!("Expected model parameter key"))? + .to_owned(); + let value = parameter + .next() + .ok_or_else(|| anyhow!("Expected model parameter value"))? + .to_owned(); + + parameters.0.insert(key, value); + } + + Ok(parameters) +} + fn parse_tolerance(input: &str) -> anyhow::Result { let tolerance = f64::from_str(input)?; let tolerance = Scalar::from_f64(tolerance); diff --git a/fj-app/src/main.rs b/fj-app/src/main.rs index 6589f1553..b44061526 100644 --- a/fj-app/src/main.rs +++ b/fj-app/src/main.rs @@ -6,9 +6,10 @@ mod input; mod window; use std::path::PathBuf; -use std::{collections::HashMap, time::Instant}; +use std::time::Instant; -use fj_host::Model; +use anyhow::anyhow; +use fj_host::{Model, Parameters}; use fj_interop::{debug::DebugInfo, mesh::Mesh}; use fj_kernel::algorithms::{triangulate, Tolerance}; use fj_math::{Aabb, Point, Scalar}; @@ -48,35 +49,16 @@ fn main() -> anyhow::Result<()> { let config = Config::load()?; let mut path = config.default_path.unwrap_or_else(|| PathBuf::from("")); - match args.model.or(config.default_model) { - Some(model) => { - path.push(model); - } - None => { - anyhow::bail!( - "No model specified, and no default model configured.\n\ + let model = args.model.or(config.default_model).ok_or_else(|| { + anyhow!( + "No model specified, and no default model configured.\n\ Specify a model by passing `--model path/to/model`." - ); - } - } + ) + })?; + path.push(model); let model = Model::from_path(path, config.target_dir)?; - - let mut parameters = HashMap::new(); - for parameter in args.parameters { - let mut parameter = parameter.splitn(2, '='); - - let key = parameter - .next() - .expect("model parameter: key not found") - .to_owned(); - let value = parameter - .next() - .expect("model parameter: value not found") - .to_owned(); - - parameters.insert(key, value); - } + let parameters = args.parameters.unwrap_or_else(Parameters::empty); let shape_processor = ShapeProcessor { tolerance: args.tolerance, diff --git a/fj-host/src/lib.rs b/fj-host/src/lib.rs index 9689a68cb..64ba006e7 100644 --- a/fj-host/src/lib.rs +++ b/fj-host/src/lib.rs @@ -74,7 +74,7 @@ impl Model { /// model for changes, reloading it continually. pub fn load_once( &self, - arguments: &HashMap, + arguments: &Parameters, ) -> Result { let manifest_path = self.manifest_path.display().to_string(); @@ -120,7 +120,7 @@ impl Model { /// be queried for changes to the model. pub fn load_and_watch( self, - parameters: HashMap, + parameters: Parameters, ) -> Result { let (tx, rx) = mpsc::sync_channel(0); let tx2 = tx.clone(); @@ -202,7 +202,7 @@ pub struct Watcher { _watcher: Box, channel: mpsc::Receiver<()>, model: Model, - parameters: HashMap, + parameters: Parameters, } impl Watcher { @@ -243,6 +243,16 @@ impl Watcher { } } +/// Parameters that are passed to a model +pub struct Parameters(pub HashMap); + +impl Parameters { + /// Construct an empty instance of `Parameters` + pub fn empty() -> Self { + Self(HashMap::new()) + } +} + /// An error that can occur when loading or reloading a model #[derive(Debug, Error)] pub enum Error { @@ -263,5 +273,4 @@ pub enum Error { Notify(#[from] notify::Error), } -type ModelFn = - unsafe extern "C" fn(args: &HashMap) -> fj::Shape; +type ModelFn = unsafe extern "C" fn(args: &Parameters) -> fj::Shape;