mod sensors;
use anyhow::{Context, Result};
use std::fs;
use std::path::Path;
use super::profile::RobotProfile;
use super::selector::EnvironmentSelector;
pub struct SimulationGenerator {
selector: EnvironmentSelector,
}
impl SimulationGenerator {
pub fn new() -> Result<Self> {
let selector = EnvironmentSelector::new()?;
Ok(Self { selector })
}
pub fn generate_with_auto_selection(
&self,
config_path: &Path,
output_path: &Path,
max_envs: usize,
min_score: i32,
) -> Result<()> {
println!("🤖 Mecha10 Simulation Generator\n");
println!("📋 Analyzing robot configuration...");
let profile = RobotProfile::from_config_file(config_path)?;
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!();
println!("🎯 Auto-selecting environments...");
let matches = self.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");
println!(" - Checking required_sensors in catalog");
return Ok(());
}
for env_match in &filtered_matches {
println!(" ✓ {} (score: {})", env_match.environment.name, env_match.score);
}
println!();
println!("🔨 Generating robot scenes...");
fs::create_dir_all(output_path)?;
for env_match in &filtered_matches {
let env_output = output_path.join(&env_match.environment.id);
self.generate_scene_for_environment(config_path, &env_output, &env_match.environment)?;
println!(" ✓ {}/scene.tscn", env_match.environment.id);
}
println!();
println!("✅ Generation complete!");
println!("\nOutput: {}", output_path.display());
println!("\nNext steps:");
println!(
" 1. Open in Godot: godot {}/{}/scene.tscn",
output_path.display(),
filtered_matches[0].environment.id
);
println!(" 2. Test manually with arrow keys");
println!(
" 3. Train with RL: mecha10 rl train --env {}",
filtered_matches[0].environment.id
);
Ok(())
}
pub fn generate_with_environment(&self, config_path: &Path, output_path: &Path, env_id: &str) -> Result<()> {
println!("🤖 Mecha10 Simulation Generator\n");
let env = self
.selector
.catalog()
.find_by_id(env_id)
.with_context(|| format!("Environment not found: {}", env_id))?;
println!("📋 Using environment: {}", env.name);
println!();
println!("🔨 Generating robot scene...");
fs::create_dir_all(output_path)?;
self.generate_scene_for_environment(config_path, output_path, env)?;
println!(" ✓ scene.tscn");
println!();
println!("✅ Generation complete!");
println!("\nOutput: {}/scene.tscn", output_path.display());
Ok(())
}
fn generate_scene_for_environment(
&self,
config_path: &Path,
output_path: &Path,
environment: &super::catalog::Environment,
) -> Result<()> {
fs::create_dir_all(output_path)?;
let profile = RobotProfile::from_config_file(config_path)?;
let mut scene = super::scene::GodotScene::new();
scene.uid = Some(format!("uid://generated_robot_{}", environment.id));
let template_path = self.get_robot_template_path(&profile.platform)?;
let robot_id = scene.add_ext_resource("PackedScene", &template_path);
let robot_node = super::scene::SceneNode::instance("Robot", &robot_id);
scene.add_node(robot_node);
let mut sensor_errors = Vec::new();
let mut sensors_added = 0;
for sensor_config in &profile.sensor_configs {
let result = match sensor_config.sensor_type.as_str() {
"lidar" => sensors::add_lidar_sensor_with_config(&mut scene, sensor_config),
"camera" => sensors::add_camera_sensor_with_config(&mut scene, sensor_config),
"imu" => sensors::add_imu_sensor_with_config(&mut scene, sensor_config),
"motor" => continue, _ => {
println!(
"⚠️ Warning: Unknown sensor type '{}' for sensor '{}'",
sensor_config.sensor_type, sensor_config.name
);
continue;
}
};
match result {
Ok(_) => {
sensors_added += 1;
}
Err(e) => {
let error_msg = format!(
"Failed to add {} sensor '{}': {}",
sensor_config.sensor_type, sensor_config.name, e
);
println!("⚠️ {}", error_msg);
sensor_errors.push(error_msg);
}
}
}
if !sensor_errors.is_empty() {
println!("\n⚠️ Sensor Errors Summary:");
for error in &sensor_errors {
println!(" - {}", error);
}
println!(" Successfully added: {} sensors", sensors_added);
println!(" Failed: {} sensors", sensor_errors.len());
}
if sensors_added == 0 && !profile.sensor_configs.is_empty() {
anyhow::bail!(
"Failed to add any sensors. {} error(s) occurred. Scene generation aborted.",
sensor_errors.len()
);
}
scene.save(&output_path.join("scene.tscn"))?;
Ok(())
}
fn get_robot_template_path(&self, platform: &str) -> Result<String> {
let template_name = match platform {
"rover" | "differential-drive" => "rover_base_rl",
"humanoid" => "humanoid_base_rl",
"arm" => "arm_base_rl",
_ => "rover_base_rl", };
Ok(format!(
"res://packages/godot-components/robot_templates/{}.tscn",
template_name
))
}
}