use crate::allocator::GetPeakAllocatedMemory;
use crate::{convert, logging, reconstruct};
use anyhow::Context;
use clap::Parser;
use log::info;
crate::register_counting_allocator!(GLOBAL_ALLOCATOR, enable = false);
static HELP_TEMPLATE: &str = "{before-help}{name} (v{version}) - {author-with-newline}{about-with-newline}\n{usage-heading} {usage}\n\n{all-args}{after-help}";
#[derive(Clone, Debug, clap::Parser)]
#[command(
name = "splashsurf",
author = "Fabian Löschner <loeschner@cs.rwth-aachen.de>",
about = "Surface reconstruction for particle data from SPH simulations (https://github.com/InteractiveComputerGraphics/splashsurf)",
version,
propagate_version = true,
help_template = HELP_TEMPLATE,
)]
struct CommandlineArgs {
#[arg(long, short = 'q', global = true)]
quiet: bool,
#[arg(short, action = clap::ArgAction::Count, global = true)]
verbosity: u8,
#[command(subcommand)]
subcommand: Subcommand,
}
#[derive(Clone, Debug, clap::Parser)]
enum Subcommand {
#[command(help_template = HELP_TEMPLATE)]
Reconstruct(reconstruct::ReconstructSubcommandArgs),
#[command(help_template = HELP_TEMPLATE)]
Convert(convert::ConvertSubcommandArgs),
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, clap::ValueEnum)]
pub(crate) enum Switch {
Off,
On,
}
impl Switch {
pub(crate) fn into_bool(self) -> bool {
match self {
Switch::Off => false,
Switch::On => true,
}
}
}
pub fn run_splashsurf<I, T>(args: I) -> Result<(), anyhow::Error>
where
I: IntoIterator<Item = T>,
T: Into<std::ffi::OsString> + Clone,
{
run_splashsurf_impl(args).inspect_err(logging::log_error)
}
fn run_splashsurf_impl<I, T>(args: I) -> Result<(), anyhow::Error>
where
I: IntoIterator<Item = T>,
T: Into<std::ffi::OsString> + Clone,
{
let cmd_args = CommandlineArgs::parse_from(args);
let verbosity = VerbosityLevel::from(cmd_args.verbosity);
let is_quiet = cmd_args.quiet;
logging::initialize_logging(verbosity, is_quiet).context("Failed to initialize logging")?;
logging::log_program_info();
let result = match &cmd_args.subcommand {
Subcommand::Reconstruct(cmd_args) => reconstruct::reconstruct_subcommand(cmd_args),
Subcommand::Convert(cmd_args) => convert::convert_subcommand(cmd_args),
};
info!("Timings:");
splashsurf_lib::profiling::write_to_string()
.unwrap()
.split("\n")
.filter(|l| !l.is_empty())
.for_each(|l| info!("{}", l));
if let Some(peak_allocation_bytes) = GLOBAL_ALLOCATOR.get_peak_allocated_memory() {
info!(
"Peak memory usage: {} bytes ({:.2}MB)",
peak_allocation_bytes,
peak_allocation_bytes as f64 * 1e-6
);
}
info!(
"Finished at {}.",
chrono::Local::now().to_rfc3339_opts(chrono::SecondsFormat::Micros, false)
);
result
}
#[derive(Copy, Clone, Debug)]
pub(crate) enum VerbosityLevel {
None,
Verbose,
VeryVerbose,
VeryVeryVerbose,
}
impl From<u8> for VerbosityLevel {
fn from(value: u8) -> Self {
match value {
0 => VerbosityLevel::None,
1 => VerbosityLevel::Verbose,
2 => VerbosityLevel::VeryVerbose,
3 => VerbosityLevel::VeryVeryVerbose,
_ => VerbosityLevel::VeryVeryVerbose,
}
}
}
impl VerbosityLevel {
pub fn into_filter(self) -> Option<log::LevelFilter> {
match self {
VerbosityLevel::None => None,
VerbosityLevel::Verbose => Some(log::LevelFilter::Info),
VerbosityLevel::VeryVerbose => Some(log::LevelFilter::Debug),
VerbosityLevel::VeryVeryVerbose => Some(log::LevelFilter::Trace),
}
}
}
#[cfg(test)]
mod cli_args_tests {
use super::*;
#[test]
fn verify_main_cli() {
use clap::CommandFactory;
CommandlineArgs::command().debug_assert()
}
#[test]
fn verify_reconstruct_cli() {
use clap::CommandFactory;
reconstruct::ReconstructSubcommandArgs::command().debug_assert()
}
#[test]
fn verify_convert_cli() {
use clap::CommandFactory;
convert::ConvertSubcommandArgs::command().debug_assert()
}
#[test]
fn test_main_cli() {
use clap::Parser;
assert_eq!(
CommandlineArgs::try_parse_from(["splashsurf", "--help",])
.expect_err("this command is supposed to fail")
.kind(),
clap::error::ErrorKind::DisplayHelp
);
assert_eq!(
CommandlineArgs::try_parse_from(["splashsurf", "reconstruct", "--help",])
.expect_err("this command is supposed to fail")
.kind(),
clap::error::ErrorKind::DisplayHelp
);
assert_eq!(
CommandlineArgs::try_parse_from(["splashsurf", "convert", "--help",])
.expect_err("this command is supposed to fail")
.kind(),
clap::error::ErrorKind::DisplayHelp
);
if let Subcommand::Reconstruct(rec_args) = CommandlineArgs::try_parse_from([
"splashsurf",
"reconstruct",
"test.vtk",
"--particle-radius=0.05",
"--smoothing-length=3.0",
"--cube-size=0.75",
])
.expect("this command is supposed to work")
.subcommand
{
assert_eq!(
rec_args.input_file_or_sequence,
std::path::PathBuf::from("test.vtk")
);
};
if let Subcommand::Reconstruct(rec_args) = CommandlineArgs::try_parse_from([
"splashsurf",
"reconstruct",
"test.vtk",
"--particle-radius=0.05",
"--smoothing-length=3.0",
"--cube-size=0.75",
"--normals=on",
])
.expect("this command is supposed to work")
.subcommand
{
assert_eq!(rec_args.normals, Switch::On);
};
if let Subcommand::Reconstruct(rec_args) = CommandlineArgs::try_parse_from([
"splashsurf",
"reconstruct",
"test.vtk",
"--particle-radius=0.05",
"--smoothing-length=3.0",
"--cube-size=0.75",
"--normals=off",
])
.expect("this command is supposed to work")
.subcommand
{
assert_eq!(rec_args.normals, Switch::Off);
};
if let Subcommand::Reconstruct(rec_args) = CommandlineArgs::try_parse_from([
"splashsurf",
"reconstruct",
"test.vtk",
"--particle-radius=0.05",
"--smoothing-length=3.0",
"--cube-size=0.75",
"--particle-aabb-min",
"-1.0",
"1.0",
"-1.0",
"--particle-aabb-max",
"-2.0",
"2.0",
"-2.0",
])
.expect("this command is supposed to work")
.subcommand
{
assert_eq!(rec_args.particle_aabb_min, Some(vec![-1.0, 1.0, -1.0]));
assert_eq!(rec_args.particle_aabb_max, Some(vec![-2.0, 2.0, -2.0]));
};
assert_eq!(
CommandlineArgs::try_parse_from([
"splashsurf",
"reconstruct",
"test.vtk",
"--particle-radius=0.05",
"--smoothing-length=3.0",
"--cube-size=0.75",
"--particle-aabb-min",
"-1.0",
"1.0",
"-1.0",
"2.0",
"--particle-aabb-max",
"-2.0",
"2.0",
"-2.0",
])
.expect_err("this command is supposed to fail")
.kind(),
clap::error::ErrorKind::UnknownArgument
);
}
}