use clap::{Args, Parser, Subcommand};
use cumulus3d_cli::declare::{Plan, Recon};
use cumulus3d_cli::interop::{self, InteropCmd};
use cumulus3d_cli::stream::{run_stream, StreamConfig};
use std::path::PathBuf;
#[derive(Parser, Debug)]
#[command(name = "cumulus3d", version, about = "한 프로세스 점진 재구성 + 단계별 하위 명령")]
struct Cli {
#[command(subcommand)]
cmd: Cmd,
}
#[derive(Subcommand, Debug)]
enum Cmd {
Stream(Box<StreamArgs>),
Run(RunArgs),
Plan(PlanArgs),
#[command(flatten)]
Interop(InteropCmd),
}
#[derive(Args, Debug)]
struct StreamArgs {
#[arg(long)]
src: PathBuf,
#[arg(long)]
out: PathBuf,
#[arg(long, default_value_t = 12)]
span: usize,
#[arg(long, default_value_t = 2)]
overlap: usize,
#[arg(long, default_value_t = 3)]
stride: usize,
#[arg(long)]
incremental_triangulation: bool,
#[arg(long)]
depth_cache: bool,
#[arg(long)]
fixed_enu_origin: bool,
#[arg(long, default_value = "cuda")]
pm_backend: String,
#[arg(long, default_value = "fast")]
mvs_profile: String,
#[arg(long, default_value = "cpu")]
sift_backend: String,
#[arg(long, default_value = "cpu")]
match_backend: String,
#[arg(long)]
gpu: bool,
#[arg(long, default_value_t = 0)]
threads: usize,
#[arg(long)]
max_positions: Option<usize>,
#[arg(long)]
save_models: bool,
#[arg(long)]
serialize_dense: bool,
#[arg(long, default_value_t = 960)]
dense_max_image_size: i64,
#[arg(long, default_value_t = 10)]
number_views: usize,
#[arg(long, default_value_t = 8192)]
max_num_features: usize,
#[arg(long, default_value_t = 3200)]
sift_max_image_size: usize,
#[arg(long)]
quiet: bool,
#[arg(long)]
no_dense: bool,
#[arg(long)]
stable_display: bool,
#[arg(long, default_value_t = 0.01)]
settle_tol: f64,
#[arg(long, default_value_t = 3)]
settle_max_zone: usize,
#[arg(long)]
intrinsics_prior: Option<PathBuf>,
#[arg(long, default_value_t = 0.005)]
intrinsics_prior_sigma: f64,
#[arg(long, default_value_t = 0.01)]
intrinsics_prior_dist_sigma: f64,
#[arg(long)]
seed: Option<u64>,
#[arg(long, default_value_t = 6, value_parser = parse_live_stride)]
live_stride: usize,
#[arg(long)]
max_levels: Option<usize>,
#[arg(long)]
fusion_smooth_iters: Option<usize>,
#[arg(long)]
fusion_smooth_radius: Option<f64>,
#[arg(long)]
fusion_smooth_range: Option<f64>,
#[arg(long)]
fusion_smooth_normal: Option<f64>,
#[arg(long)]
visibility_views: Option<usize>,
#[arg(long)]
visibility_tau: Option<f64>,
#[arg(long)]
visibility_min_violations: Option<usize>,
}
#[derive(Args, Debug)]
struct RunArgs {
plan: PathBuf,
#[arg(long)]
check: bool,
}
#[derive(Args, Debug)]
struct PlanArgs {
#[arg(long)]
print_default: bool,
#[arg(long)]
check: Option<PathBuf>,
}
fn parse_live_stride(s: &str) -> Result<usize, String> {
match s.parse::<usize>() {
Ok(n) if n >= 1 => Ok(n),
_ => Err("must be an integer >= 1".into()),
}
}
fn check_plan(path: &PathBuf) -> Result<Recon, String> {
Recon::from_plan(Plan::load(path)?).build().map_err(|e| e.to_string())
}
fn main() {
let cli = Cli::parse();
let r = match cli.cmd {
Cmd::Stream(a) => {
let a = *a;
let mut c = StreamConfig::new(a.src, a.out);
c.span = a.span;
c.overlap = a.overlap;
c.stride = a.stride;
c.incremental_triangulation = a.incremental_triangulation;
c.depth_cache = a.depth_cache;
c.fixed_enu_origin = a.fixed_enu_origin;
c.mvs_profile = a.mvs_profile;
c.pm_backend = a.pm_backend;
c.sift_backend = a.sift_backend;
c.match_backend = a.match_backend;
if a.gpu {
c.pm_backend = "cuda".into();
c.sift_backend = "cuda".into();
c.match_backend = "cuda".into();
}
c.threads = a.threads;
c.max_positions = a.max_positions;
c.save_models = a.save_models;
c.serialize_dense = a.serialize_dense;
c.dense_max_image_size = a.dense_max_image_size;
c.number_views = a.number_views;
c.max_num_features = a.max_num_features;
c.sift_max_image_size = a.sift_max_image_size;
c.echo = !a.quiet;
c.no_dense = a.no_dense;
c.stable_display = a.stable_display;
c.settle_tol = a.settle_tol;
c.settle_max_zone = a.settle_max_zone;
c.intrinsics_prior = a.intrinsics_prior;
c.intrinsics_prior_sigma = a.intrinsics_prior_sigma;
c.intrinsics_prior_dist_sigma = a.intrinsics_prior_dist_sigma;
c.seed = a.seed;
c.max_levels = a.max_levels;
c.live_stride = a.live_stride;
c.fusion.smooth_iters = a.fusion_smooth_iters;
c.fusion.smooth_radius = a.fusion_smooth_radius;
c.fusion.smooth_range = a.fusion_smooth_range;
c.fusion.smooth_normal = a.fusion_smooth_normal;
c.fusion.visibility_views = a.visibility_views;
c.fusion.visibility_tau = a.visibility_tau;
c.fusion.visibility_min_violations = a.visibility_min_violations;
run_stream(c)
}
Cmd::Run(a) => check_plan(&a.plan).and_then(|r| if a.check { Ok(()) } else { r.run().map(|_| ()) }),
Cmd::Plan(a) => {
if a.print_default {
print!("{}", Plan::default().to_toml_annotated());
Ok(())
} else if let Some(p) = a.check {
check_plan(&p).map(|r| println!("계획 검사 통과: 위치 {}", r.positions()))
} else {
Err("--print-default 또는 --check <plan.toml> 중 하나가 필요함".into())
}
}
Cmd::Interop(c) => interop::run(c),
};
if let Err(e) = r {
eprintln!("오류: {e}");
std::process::exit(1);
}
}