mod cli;
mod disk;
mod interactive;
mod ops;
use std::fs;
use std::path::PathBuf;
use clap::{CommandFactory, Parser, Subcommand};
use clap_complete::Shell;
use cli::config::ConfigArgs;
use cli::filament::FilamentArgs;
use cli::job::{JobInvocation, JobType, OutputFormat};
use cli::printer::PrinterArgs;
#[derive(Parser)]
#[command(
name = "printcalc",
version,
about = "Calculate 3D print job costs from sliced-file output."
)]
struct Cli {
file: Option<PathBuf>,
#[arg(short = 'n', long)]
name: Option<String>,
#[arg(short = 'p', long)]
printer: Option<String>,
#[arg(short = 'f', long)]
filament: Option<String>,
#[arg(long = "filament-map")]
filament_map: Option<String>,
#[arg(long = "default-filament")]
default_filament: Option<String>,
#[arg(short, long)]
output: Option<PathBuf>,
#[arg(short = 't', long = "type", value_enum, default_value = "default")]
job_type: JobType,
#[arg(long = "retier", value_enum)]
retier: Option<JobType>,
#[arg(short = 'P', long)]
plate: Option<String>,
#[arg(short = 'm', long = "labor-minutes")]
labor_minutes: Option<f64>,
#[arg(short = 'r', long = "labor-rate")]
labor_rate: Option<f64>,
#[arg(short = 'q', long)]
quantity: Option<u32>,
#[arg(short = 'e', long = "electricity-cost")]
electricity_cost: Option<f64>,
#[arg(long = "margin", alias = "markup", value_name = "LIST")]
margin: Option<String>,
#[arg(long = "efficiency-factor")]
efficiency_factor: Option<f64>,
#[arg(
short = 'H',
long = "hardware",
visible_alias = "extra",
value_name = "NAME:PRICE[:UNITS]"
)]
hardware: Vec<String>,
#[arg(long = "no-detect")]
no_detect: bool,
#[arg(short = 'F', long, value_enum, default_value = "table")]
format: OutputFormat,
#[arg(long = "no-input", global = true)]
no_input: bool,
#[arg(long = "no-color", global = true)]
no_color: bool,
#[command(subcommand)]
command: Option<Command>,
}
#[derive(Subcommand)]
enum Command {
Printer(PrinterArgs),
Filament(FilamentArgs),
Config(ConfigArgs),
Completions {
#[arg(value_enum)]
shell: Shell,
},
Man,
}
fn main() {
let cli = Cli::parse();
let interactive = !cli.no_input;
let result = match cli.command {
Some(Command::Printer(args)) => cli::printer::run(args.command, interactive),
Some(Command::Filament(args)) => cli::filament::run(args.command, interactive),
Some(Command::Config(args)) => cli::config::run(args),
Some(Command::Completions { shell }) => {
let mut out = Vec::new();
clap_complete::generate(shell, &mut Cli::command(), "printcalc", &mut out);
std::io::Write::write_all(&mut std::io::stdout(), &out).map_err(|e| e.into())
}
Some(Command::Man) => {
let mut out = Vec::new();
clap_mangen::Man::new(Cli::command())
.render(&mut out)
.and_then(|()| std::io::Write::write_all(&mut std::io::stdout(), &out))
.map_err(|e| e.into())
}
None => match cli.file {
Some(file) => {
let full_file:PathBuf = fs::canonicalize(file).expect("unable to get input file path");
cli::job::run(JobInvocation {
file: full_file,
name: cli.name,
printer: cli.printer,
filament: cli.filament,
filament_map: cli.filament_map,
default_filament: cli.default_filament,
output: cli.output,
job_type: cli.job_type,
retier: cli.retier,
plate: cli.plate,
labor_minutes: cli.labor_minutes,
labor_rate: cli.labor_rate,
quantity: cli.quantity,
electricity_cost: cli.electricity_cost,
margin: cli.margin,
efficiency_factor: cli.efficiency_factor,
hardware: cli.hardware,
no_detect: cli.no_detect,
format: cli.format,
color: !cli.no_color,
interactive,
})},
None => {
Err("no file given — pass a sliced file or a job .toml, or run `printcalc printer`/`printcalc filament`".into())
}
},
};
if let Err(e) = result {
eprintln!("error: {e}");
std::process::exit(1);
}
}