automesh 0.4.9

Automatic mesh generation.
use super::{
    ErrorWrapper,
    io::{read_mesh, write_mesh},
    metrics::write_metrics,
    remesh::{MeshRemeshSubcommand, apply_remesh_subcommand},
};
use clap::{Subcommand, ValueEnum};
use conspire::geometry::mesh::{Mesh, Smoothing, Weighting};
use std::time::Instant;

pub const TAUBIN_DEFAULT_ITERS: usize = 20;
pub const TAUBIN_DEFAULT_BAND: f64 = 0.1;
pub const TAUBIN_DEFAULT_SCALE: f64 = 0.6307;

/// Parsed by clap, so a misspelled method fails before any work starts.
#[derive(Clone, Copy, Debug, ValueEnum)]
#[value(rename_all = "PascalCase")]
pub enum SmoothingMethod {
    #[value(alias = "Laplacian")]
    Laplace,
    Taubin,
}

#[derive(Subcommand, Debug)]
pub enum MeshSmoothCommands {
    /// Applies smoothing to the mesh before output
    Smooth {
        #[command(subcommand)]
        remeshing: Option<MeshRemeshSubcommand>,

        /// Number of smoothing iterations
        #[arg(default_value_t = TAUBIN_DEFAULT_ITERS, long, short = 'n', value_name = "NUM")]
        iterations: usize,

        /// Smoothing method
        #[arg(
            default_value_t = SmoothingMethod::Taubin,
            ignore_case = true,
            long,
            short,
            value_enum,
            value_name = "NAME"
        )]
        method: SmoothingMethod,

        /// Pass-band frequency (for Taubin only)
        #[arg(default_value_t = TAUBIN_DEFAULT_BAND, long, short = 'k', value_name = "FREQ")]
        pass_band: f64,

        /// Scaling parameter for all smoothing methods
        #[arg(default_value_t = TAUBIN_DEFAULT_SCALE, long, short, value_name = "SCALE")]
        scale: f64,

        /// Enables hierarchical smoothing
        #[arg(action, long, short = 'b')]
        hierarchical: bool,
    },
}

#[derive(clap::Args)]
pub struct SmoothArgs {
    #[command(subcommand)]
    pub remeshing: Option<MeshRemeshSubcommand>,

    /// Mesh input file (exo | inp | off | stl | vtu)
    #[arg(long, short, value_name = "FILE")]
    pub input: String,

    /// Smoothed mesh output file (exo | inp | mesh | off | stl | vtu)
    #[arg(long, short, value_name = "FILE")]
    pub output: String,

    /// Number of smoothing iterations
    #[arg(default_value_t = TAUBIN_DEFAULT_ITERS, long, short = 'n', value_name = "NUM")]
    pub iterations: usize,

    /// Smoothing method
    #[arg(
        default_value_t = SmoothingMethod::Taubin,
        ignore_case = true,
        long,
        short,
        value_enum,
        value_name = "NAME"
    )]
    pub method: SmoothingMethod,

    /// Pass-band frequency (for Taubin only)
    #[arg(default_value_t = TAUBIN_DEFAULT_BAND, long, short = 'k', value_name = "FREQ")]
    pub pass_band: f64,

    /// Scaling parameter for all smoothing methods
    #[arg(default_value_t = TAUBIN_DEFAULT_SCALE, long, short, value_name = "SCALE")]
    pub scale: f64,

    /// Enables hierarchical smoothing
    #[arg(action, long, short = 'b')]
    pub hierarchical: bool,

    /// Quality metrics output file (csv | npy)
    #[arg(long, value_name = "FILE")]
    pub metrics: Option<String>,
}

pub fn smooth(args: SmoothArgs, quiet: bool) -> Result<(), ErrorWrapper> {
    let mut mesh = read_mesh(&args.input, quiet)?;
    apply_smoothing_method(
        &mut mesh,
        args.iterations,
        args.method,
        args.pass_band,
        args.scale,
        args.hierarchical,
        quiet,
    )?;
    if let Some(subcommand) = args.remeshing {
        mesh = apply_remesh_subcommand(mesh, subcommand, quiet)?;
    }
    if let Some(file) = args.metrics {
        write_metrics(&mesh, &file, quiet)?;
    }
    write_mesh(&args.output, mesh, quiet)
}

pub fn apply_smoothing_method(
    mesh: &mut Mesh<3>,
    iterations: usize,
    method: SmoothingMethod,
    pass_band: f64,
    scale: f64,
    hierarchical: bool,
    quiet: bool,
) -> Result<(), ErrorWrapper> {
    let time = Instant::now();
    let smoothing = match method {
        SmoothingMethod::Laplace => {
            crate::echo!(
                quiet,
                "   \x1b[1;96mSmoothing\x1b[0m with {iterations} iterations of Laplace"
            );
            Smoothing::Laplace {
                iterations,
                scale,
                weighting: Weighting::Uniform,
                preserve_boundary: hierarchical,
                preserve_interfaces: hierarchical,
            }
        }
        SmoothingMethod::Taubin => {
            crate::echo!(
                quiet,
                "   \x1b[1;96mSmoothing\x1b[0m with {iterations} iterations of Taubin"
            );
            Smoothing::Taubin {
                iterations,
                pass_band,
                scale,
                weighting: Weighting::Uniform,
                preserve_boundary: hierarchical,
                preserve_interfaces: hierarchical,
            }
        }
    };
    mesh.smooth(smoothing)?;
    crate::echo!(quiet, "        \x1b[1;92mDone\x1b[0m {:?}", time.elapsed());
    Ok(())
}