mecha10-sim 0.8.0

Mecha10 simulation support — scene generation, asset management
Documentation
//! Simulation generation module
//!
//! Automatically generates simulation scenes from mecha10.json configuration.
//!
//! # Architecture
//!
//! The simulation module is organized into:
//! - `catalog`: Environment catalog management
//! - `environment_config`: `environment.json` parsing/validation (`features.objects[]`,
//!   including the LAB-2025 config-driven image billboard objects)
//! - `generator`: Scene generation logic
//! - `preset_config`: `preset.json` parsing/validation (LAB-3629 -- environment-namespaced
//!   content sets that layer onto an environment at runtime, reusing `environment_config`'s
//!   `SceneObject` types)
//! - `profile`: Robot profile extraction
//! - `robot_generator`: Robot scene generation
//! - `selector`: Environment selection logic
//! - `validator`: Configuration and catalog validation
//! - `lister`: Environment listing

#![allow(dead_code)]

pub mod catalog;
pub mod environment_config;
pub mod generator;
pub mod lister;
pub mod preset_config;
pub mod profile;
pub mod robot_generator;
pub mod selector;
pub mod validator;

pub use catalog::EnvironmentCatalog;
pub use environment_config::{
    AmbientLight, CubeObject, DirectionalLight, DistanceUnit, EnvironmentConfig, EnvironmentDimensions,
    EnvironmentFeatures, FloorFeature, GoalsFeature, ImageObject, LightingFeature, NpcsFeature, ObstaclesFeature,
    SceneObject, SpawnPointsFeature, WallsFeature,
};
#[allow(unused_imports)]
pub use lister::list_environments;
pub use preset_config::PresetConfig;
pub use profile::RobotProfile;
pub use robot_generator::RobotGenerator;
pub use selector::EnvironmentSelector;
#[allow(unused_imports)]
pub use validator::{validate_catalog, validate_config};

use anyhow::{Context, Result};
use std::path::Path;

/// Read `path` as UTF-8 and parse it as JSON, with a consistent
/// read-failed/parse-failed error message shape.
///
/// Shared by [`profile::RobotProfile::from_config_file`], [`environment_config::EnvironmentConfig::load`],
/// and [`catalog::EnvironmentCatalog::load`], which otherwise each hand-rolled the same
/// read-then-parse-with-context boilerplate for their own JSON file.
pub(crate) fn load_json<T: serde::de::DeserializeOwned>(path: &Path, description: &str) -> Result<T> {
    let content =
        std::fs::read_to_string(path).with_context(|| format!("Failed to read {description}: {}", path.display()))?;

    serde_json::from_str(&content).with_context(|| format!("Failed to parse {description}: {}", path.display()))
}

/// 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());

    Ok(())
}