mecha10-cli 0.1.47

Mecha10 CLI tool
Documentation
//! Simulation generation module
//!
//! Automatically generates Godot simulation scenes from mecha10.json configuration.
//!
//! # Architecture
//!
//! The simulation module is organized into:
//! - `catalog`: Environment catalog management
//! - `generator`: Scene generation logic
//! - `profile`: Robot profile extraction
//! - `robot_generator`: Robot scene generation
//! - `scene`: Scene template management
//! - `selector`: Environment selection logic
//! - `validator`: Configuration and catalog validation
//! - `runner`: Simulation execution
//! - `lister`: Environment listing

#![allow(dead_code)]

use crate::paths;

pub mod catalog;
pub mod generator;
pub mod lister;
pub mod profile;
pub mod robot_generator;
pub mod runner;
pub mod scene;
pub mod selector;
pub mod validator;

pub use catalog::EnvironmentCatalog;
#[allow(unused_imports)]
pub use lister::list_environments;
pub use profile::RobotProfile;
pub use robot_generator::RobotGenerator;
#[allow(unused_imports)]
pub use runner::run_simulation;
pub use selector::EnvironmentSelector;
#[allow(unused_imports)]
pub use validator::{validate_catalog, validate_config};

use anyhow::Result;
use std::path::Path;
use std::process::Command;

/// Validate Godot installation
fn validate_godot_installation() -> Result<()> {
    // Try common Godot executable locations
    let godot_paths = if cfg!(target_os = "macos") {
        vec![
            "/Applications/Godot.app/Contents/MacOS/Godot",
            "/usr/local/bin/godot",
            "/opt/homebrew/bin/godot",
        ]
    } else if cfg!(target_os = "linux") {
        vec!["/usr/bin/godot", "/usr/local/bin/godot", "/snap/bin/godot"]
    } else {
        vec!["godot.exe", "C:\\Program Files\\Godot\\godot.exe"]
    };

    // Also check PATH
    let mut found = false;
    let mut godot_version = String::new();
    let mut godot_path = String::new();

    // First try 'godot' command from PATH
    if let Ok(output) = Command::new("godot").arg("--version").output() {
        if output.status.success() {
            found = true;
            godot_version = String::from_utf8_lossy(&output.stdout).trim().to_string();
            godot_path = "godot (in PATH)".to_string();
        }
    }

    // If not found in PATH, try specific locations
    if !found {
        for path in &godot_paths {
            if std::path::Path::new(path).exists() {
                if let Ok(output) = Command::new(path).arg("--version").output() {
                    if output.status.success() {
                        found = true;
                        godot_version = String::from_utf8_lossy(&output.stdout).trim().to_string();
                        godot_path = path.to_string();
                        break;
                    }
                }
            }
        }
    }

    if !found {
        anyhow::bail!(
            "❌ Godot not found\n\n\
            Godot 4.x is required to generate and run simulations.\n\n\
            Installation instructions:\n\
            • macOS: brew install godot or download from https://godotengine.org/download/macos\n\
            • Linux: sudo apt install godot or download from https://godotengine.org/download/linux\n\
            • Windows: Download from https://godotengine.org/download/windows\n\n\
            After installation, ensure 'godot' is in your PATH or installed to a standard location."
        );
    }

    // Validate version (should be 4.x)
    if !godot_version.starts_with("4.") {
        println!("⚠️  Warning: Godot {} detected at {}", godot_version, godot_path);
        println!("   mecha10 is designed for Godot 4.x. Some features may not work correctly.");
        println!();
    } else {
        println!("✓ Godot {} detected at {}", godot_version, godot_path);
        println!();
    }

    Ok(())
}

/// Generate simulation scenes from mecha10.json
pub fn generate_simulation(config_path: &Path, max_envs: usize, min_score: i32) -> Result<()> {
    println!("🤖 Mecha10 Simulation Generator\n");

    // Validate Godot installation first
    validate_godot_installation()?;

    // Load robot profile
    let profile = RobotProfile::from_config_file(config_path)?;
    println!("📋 Robot Configuration:");
    println!("  Platform: {}", profile.platform);
    println!("  Sensors: {}", profile.sensors.join(", "));
    println!(
        "  Task Nodes: {}",
        profile
            .task_nodes
            .iter()
            .map(|n| n.name.as_str())
            .collect::<Vec<_>>()
            .join(", ")
    );
    println!();

    // Step 1: Generate robot scene
    println!("🤖 Step 1/3: Generating robot scene...");
    let robot_output = std::path::PathBuf::from(paths::project::SIMULATION_GODOT_DIR).join("robot.tscn");
    let robot_generator = RobotGenerator::from_config_file(config_path)?;
    robot_generator.generate(&robot_output)?;
    println!("  ✓ Generated: {}", robot_output.display());
    println!();

    // Step 2: Select and validate environments
    println!("🎯 Step 2/3: Selecting environments...");
    let selector = EnvironmentSelector::new()?;
    let matches = selector.select_environments(&profile, max_envs)?;

    let filtered_matches: Vec<_> = matches.into_iter().filter(|m| m.score >= min_score).collect();

    if filtered_matches.is_empty() {
        println!("❌ No matching environments found (min score: {})", min_score);
        println!("\nTry:");
        println!("  - Lowering --min-score");
        println!("  - Adding more sensors");
        return Ok(());
    }

    println!("  Available environments:");
    for env_match in &filtered_matches {
        println!("{} (score: {})", env_match.environment.name, env_match.score);
    }
    println!();

    // Step 3: Validate catalog
    println!("✅ Step 3/3: Validating environment catalog...");
    let catalog_path = std::path::PathBuf::from(paths::framework::ROBOT_TASKS_CATALOG);
    let _catalog = EnvironmentCatalog::load(&catalog_path)?;
    let base_path = std::path::PathBuf::from(paths::framework::ROBOT_TASKS_DIR);

    let mut available_count = 0;
    for env_match in &filtered_matches {
        let env_path = base_path.join(&env_match.environment.path);
        if env_path.exists() {
            available_count += 1;
            println!("{} (ready)", env_match.environment.id);
        } else {
            println!("{} (not implemented)", env_match.environment.id);
        }
    }
    println!();

    // Summary and next steps
    println!("✅ Generation complete!\n");
    println!("📊 Summary:");
    println!("  Robot scene: {}", robot_output.display());
    println!(
        "  Available environments: {}/{}",
        available_count,
        filtered_matches.len()
    );
    println!();

    if available_count > 0 {
        let best_env = &filtered_matches[0].environment;
        println!("🚀 Next steps:");
        println!();
        println!("  Run simulation:");
        println!(
            "    mecha10 sim run --robot rover-robot --env {} --headless",
            best_env.id
        );
        println!();
        println!("  List all environments:");
        println!("    mecha10 sim list-envs");
        println!();
        println!("  Open in Godot:");
        println!(
            "    godot --path packages/simulation/godot-project -- --env={} --robot=rover-robot",
            best_env.id
        );
        println!();
    }

    Ok(())
}

/// Generate standalone robot scene from mecha10.json
pub fn generate_robot(config_path: &Path, output_path: &Path) -> Result<()> {
    println!("🤖 Mecha10 Robot Generator\n");

    let generator = RobotGenerator::from_config_file(config_path)?;
    generator.generate(output_path)?;

    println!("\n✅ Robot generation complete!");
    println!("\nOutput: {}", output_path.display());
    println!("\nNext steps:");
    println!("  1. Open in Godot: godot {}", output_path.display());
    println!("  2. Test with environment: godot --headless --robot=<name> --env=corridor_navigation");

    Ok(())
}