From a335fd280c9168eb324ed09a6894cf30d22fec8b Mon Sep 17 00:00:00 2001 From: chaosprint Date: Fri, 4 Aug 2023 12:54:46 +0200 Subject: [PATCH 1/4] add import for cli assets command --- app/Cargo.toml | 1 - app/src/cli/mod.rs | 26 ++++- app/src/main.rs | 30 +++++- crates/build/Cargo.toml | 1 - crates/build/src/pipelines/importer.rs | 141 +++++++++++++++++++++++++ crates/build/src/pipelines/mod.rs | 3 + guest/rust/Cargo.lock | 7 ++ 7 files changed, 200 insertions(+), 9 deletions(-) create mode 100644 crates/build/src/pipelines/importer.rs diff --git a/app/Cargo.toml b/app/Cargo.toml index 0a1ffa0a58..32cf9aec9f 100644 --- a/app/Cargo.toml +++ b/app/Cargo.toml @@ -57,7 +57,6 @@ tracing-stackdriver = { workspace = true, optional = true } tracing-subscriber = { workspace = true, optional = true } tracing-log = { workspace = true, optional = true } time = { workspace = true } - anyhow = { workspace = true } bincode = { workspace = true } byteorder = { workspace = true } diff --git a/app/src/cli/mod.rs b/app/src/cli/mod.rs index 8b6e43cb70..30cbf4e6e6 100644 --- a/app/src/cli/mod.rs +++ b/app/src/cli/mod.rs @@ -82,15 +82,37 @@ pub enum Commands { Assets { #[command(subcommand)] command: AssetCommand, - path: PathBuf, }, } +#[derive(Args, Clone, Debug)] +pub struct MigrateOptions { + #[arg(index = 1, default_value = "./assets")] + /// The path to the assets folder + pub path: PathBuf, +} + +#[derive(Args, Clone, Debug)] +pub struct ImportOptions { + #[arg(index = 1)] + /// The path to the assets you want to import + pub path: PathBuf, + #[arg(long)] + /// Whether to convert audio files to OGG + pub convert_audio: bool, + /// Whether to generate a collider from the model + #[arg(long)] + pub collider_from_model: bool, +} + #[derive(Subcommand, Clone, Debug)] pub enum AssetCommand { /// Migrate json pipelines to toml #[command(name = "migrate-pipelines-toml")] - MigratePipelinesToml, + MigratePipelinesToml(MigrateOptions), + /// Import new assets with interactive prompts + #[command(name = "import")] + Import(ImportOptions), } #[derive(Subcommand, Clone, Copy, Debug)] diff --git a/app/src/main.rs b/app/src/main.rs index 761a7027a1..389f1a9cd4 100644 --- a/app/src/main.rs +++ b/app/src/main.rs @@ -254,16 +254,36 @@ async fn main() -> anyhow::Result<()> { return Ok(()); } - if let Commands::Assets { command, path } = &cli.command { - let path = ProjectPath::new_local(path.clone())?; - let manifest = load_manifest(&assets, &path).await?; - + if let Commands::Assets { command } = &cli.command { match command { - AssetCommand::MigratePipelinesToml => { + AssetCommand::MigratePipelinesToml(opt) => { + let path = ProjectPath::new_local(opt.path.clone())?; + let manifest = load_manifest(&assets, &path).await?; ambient_build::migrate::toml::process(&manifest, path.fs_path.unwrap()) .await .context("Failed to migrate pipelines")?; } + AssetCommand::Import(opt) => match opt.path.extension() { + Some(ext) => { + if ext == "wav" || ext == "mp3" || ext == "ogg" { + let convert = opt.convert_audio; + ambient_build::pipelines::import_audio(opt.path.clone(), convert) + .context("failed to import audio")?; + } else if ext == "fbx" || ext == "glb" || ext == "gltf" || ext == "obj" { + let collider_from_model = opt.collider_from_model; + ambient_build::pipelines::import_model( + opt.path.clone(), + collider_from_model, + ) + .context("failed to import models")?; + } else if ext == "jpg" || ext == "png" || ext == "gif" || ext == "webp" { + todo!(); + } else { + eprintln!("Unsupported file type"); + } + } + None => eprintln!("Unknown file type"), + }, } return Ok(()); diff --git a/crates/build/Cargo.toml b/crates/build/Cargo.toml index b10ee6aa31..192c7bac83 100644 --- a/crates/build/Cargo.toml +++ b/crates/build/Cargo.toml @@ -26,7 +26,6 @@ ambient_pipeline_types = { path = "../pipeline_types" } ambient_project = { path = "../../shared_crates/project" } ambient_unity_parser = { path = "../../libs/unity_parser" } - walkdir = { workspace = true } futures = { workspace = true } tokio = { workspace = true } diff --git a/crates/build/src/pipelines/importer.rs b/crates/build/src/pipelines/importer.rs new file mode 100644 index 0000000000..a08bfc40de --- /dev/null +++ b/crates/build/src/pipelines/importer.rs @@ -0,0 +1,141 @@ +use anyhow::Context; +use std::io::prelude::*; +use std::path::PathBuf; + +pub fn import_audio(path: PathBuf, convert: bool) -> anyhow::Result<()> { + let current_dir = std::env::current_dir().context("Error getting current directory")?; + let asset_folder_path = current_dir.join("assets"); + let tomlpath = current_dir.join("assets/pipeline.toml"); + println!("Importing audio..."); + println!("Read more about audio usage here: https://ambientrun.github.io/Ambient/reference/audio.html"); + if !std::path::Path::new(&asset_folder_path).exists() { + std::fs::create_dir_all(&asset_folder_path)?; + } + if !std::path::Path::new(&tomlpath).exists() { + std::fs::File::create(&tomlpath)?; + } + + let mut file = std::fs::File::open(&tomlpath)?; + let mut contents = String::new(); + file.read_to_string(&mut contents)?; + + let mut data: toml::Value = match toml::from_str(&contents) { + Ok(v) => v, + Err(_) => toml::Value::Table(toml::map::Map::new()), // if we cannot parse the file, start with a fresh table + }; + + if let toml::Value::Table(table) = &mut data { + let pipelines = match table.get_mut("pipelines") { + Some(toml::Value::Array(arr)) => arr, + _ => { + table.insert("pipelines".to_string(), toml::Value::Array(Vec::new())); + match table.get_mut("pipelines") { + Some(toml::Value::Array(arr)) => arr, + _ => panic!("Unexpected state"), + } + } + }; + + let mut new_pipeline = toml::map::Map::new(); + new_pipeline.insert( + String::from("type"), + toml::Value::String(String::from("Audio")), + ); + new_pipeline.insert(String::from("convert"), toml::Value::Boolean(convert)); + let filename_with_ext = path.file_name().unwrap().to_str().unwrap().to_string(); + new_pipeline.insert( + String::from("sources"), + toml::Value::Array(vec![toml::Value::String(filename_with_ext)]), + ); + + pipelines.push(toml::Value::Table(new_pipeline)); + } else { + panic!("Expected table at the root of the TOML document"); + } + + let mut file = std::fs::OpenOptions::new() + .write(true) + .truncate(true) + .open(tomlpath)?; + write!(file, "{}", toml::to_string(&data)?)?; + + let file_name = path.file_name().unwrap(); // get the file name from the path + let destination = asset_folder_path.join(file_name); + std::fs::copy(path.clone(), destination).context("Error copying audio file")?; + Ok(()) +} + +pub fn import_model(path: PathBuf, collider_from_model: bool) -> anyhow::Result<()> { + let current_dir = std::env::current_dir().context("Error getting current directory")?; + let asset_folder_path = current_dir.join("assets"); + let tomlpath = current_dir.join("assets/pipeline.toml"); + println!("Importing model..."); + println!("Read more about model import here: https://ambientrun.github.io/Ambient/reference/asset_pipeline.html"); + if !std::path::Path::new(&asset_folder_path).exists() { + std::fs::create_dir_all(&asset_folder_path)?; + } + if !std::path::Path::new(&tomlpath).exists() { + std::fs::File::create(&tomlpath)?; + } + + let mut file = std::fs::File::open(&tomlpath)?; + let mut contents = String::new(); + file.read_to_string(&mut contents)?; + + let mut data: toml::Value = match toml::from_str(&contents) { + Ok(v) => v, + Err(_) => toml::Value::Table(toml::map::Map::new()), // if we cannot parse the file, start with a fresh table + }; + + if let toml::Value::Table(table) = &mut data { + if collider_from_model { + let mut collider_pipeline = toml::map::Map::new(); + collider_pipeline.insert( + String::from("type"), + toml::Value::String(String::from("FromModel")), + ); + + table.insert( + "pipelines.collider".to_string(), + toml::Value::Table(collider_pipeline), + ); + } + let pipelines = match table.get_mut("pipelines") { + Some(toml::Value::Array(a)) => a, + _ => { + table.insert("pipelines".to_string(), toml::Value::Array(Vec::new())); + match table.get_mut("pipelines") { + Some(toml::Value::Array(a)) => a, + _ => panic!("Unexpected state"), + } + } + }; + + let mut new_pipeline = toml::map::Map::new(); + new_pipeline.insert( + String::from("type"), + toml::Value::String(String::from("Models")), + ); + + let filename_with_ext = path.file_name().unwrap().to_str().unwrap().to_string(); + new_pipeline.insert( + String::from("sources"), + toml::Value::Array(vec![toml::Value::String(filename_with_ext)]), + ); + + pipelines.push(toml::Value::Table(new_pipeline)); + } + + let mut file = std::fs::OpenOptions::new() + .write(true) + .truncate(true) + .open(tomlpath)?; + let toml_string = toml::to_string(&data)?; + let s = toml_string.replace(r#""pipelines.collider""#, r#"pipelines.collider"#); + write!(file, "{}", s)?; + + let file_name = path.file_name().unwrap(); // get the file name from the path + let destination = asset_folder_path.join(file_name); + std::fs::copy(path.clone(), destination).context("Error copying audio file")?; + Ok(()) +} diff --git a/crates/build/src/pipelines/mod.rs b/crates/build/src/pipelines/mod.rs index f6521312ee..9fd161ab6a 100644 --- a/crates/build/src/pipelines/mod.rs +++ b/crates/build/src/pipelines/mod.rs @@ -14,10 +14,13 @@ use out_asset::{OutAsset, OutAssetContent, OutAssetPreview}; pub mod audio; pub mod context; +pub mod importer; pub mod materials; pub mod models; pub mod out_asset; +pub use importer::*; + pub async fn process_pipeline(pipeline: &Pipeline, ctx: PipelineCtx) -> Vec { log::info!("Processing pipeline: {:?}", ctx.pipeline_path()); let mut assets = match &pipeline.processor { diff --git a/guest/rust/Cargo.lock b/guest/rust/Cargo.lock index 4cf2745a4a..35d374f54d 100644 --- a/guest/rust/Cargo.lock +++ b/guest/rust/Cargo.lock @@ -132,6 +132,13 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "ambient_example_arkanoid" +version = "0.3.0-dev" +dependencies = [ + "ambient_api", +] + [[package]] name = "ambient_example_asset_loading" version = "0.3.0-dev" From 76fd9320b3be169982cd7a34c584a820a0d523b5 Mon Sep 17 00:00:00 2001 From: chaosprint Date: Fri, 4 Aug 2023 13:16:37 +0200 Subject: [PATCH 2/4] add reason why there is a todo for material import --- app/src/main.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main.rs b/app/src/main.rs index 389f1a9cd4..92e1eca3d0 100644 --- a/app/src/main.rs +++ b/app/src/main.rs @@ -277,6 +277,7 @@ async fn main() -> anyhow::Result<()> { ) .context("failed to import models")?; } else if ext == "jpg" || ext == "png" || ext == "gif" || ext == "webp" { + // TODO: import textures API may change, so this is just a placeholder todo!(); } else { eprintln!("Unsupported file type"); From 17ac76863aa96c2d775db75e1915ee30f3e2469d Mon Sep 17 00:00:00 2001 From: chaosprint Date: Fri, 4 Aug 2023 14:30:27 +0200 Subject: [PATCH 3/4] improve code format in asset import cli --- app/src/main.rs | 4 +- crates/build/src/pipelines/importer.rs | 75 +++++++++++++------------- 2 files changed, 38 insertions(+), 41 deletions(-) diff --git a/app/src/main.rs b/app/src/main.rs index 92e1eca3d0..bcf023d202 100644 --- a/app/src/main.rs +++ b/app/src/main.rs @@ -280,10 +280,10 @@ async fn main() -> anyhow::Result<()> { // TODO: import textures API may change, so this is just a placeholder todo!(); } else { - eprintln!("Unsupported file type"); + bail!("Unsupported file type"); } } - None => eprintln!("Unknown file type"), + None => bail!("Unknown file type"), }, } diff --git a/crates/build/src/pipelines/importer.rs b/crates/build/src/pipelines/importer.rs index a08bfc40de..33321bf0c5 100644 --- a/crates/build/src/pipelines/importer.rs +++ b/crates/build/src/pipelines/importer.rs @@ -1,6 +1,9 @@ use anyhow::Context; +use std::fs::File; use std::io::prelude::*; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; +use toml::map::Map; +use toml::Value; pub fn import_audio(path: PathBuf, convert: bool) -> anyhow::Result<()> { let current_dir = std::env::current_dir().context("Error getting current directory")?; @@ -8,47 +11,44 @@ pub fn import_audio(path: PathBuf, convert: bool) -> anyhow::Result<()> { let tomlpath = current_dir.join("assets/pipeline.toml"); println!("Importing audio..."); println!("Read more about audio usage here: https://ambientrun.github.io/Ambient/reference/audio.html"); - if !std::path::Path::new(&asset_folder_path).exists() { + if !Path::new(&asset_folder_path).exists() { std::fs::create_dir_all(&asset_folder_path)?; } - if !std::path::Path::new(&tomlpath).exists() { - std::fs::File::create(&tomlpath)?; + if !Path::new(&tomlpath).exists() { + File::create(&tomlpath)?; } - let mut file = std::fs::File::open(&tomlpath)?; + let mut file = File::open(&tomlpath)?; let mut contents = String::new(); file.read_to_string(&mut contents)?; - let mut data: toml::Value = match toml::from_str(&contents) { + let mut data: Value = match toml::from_str(&contents) { Ok(v) => v, - Err(_) => toml::Value::Table(toml::map::Map::new()), // if we cannot parse the file, start with a fresh table + Err(_) => Value::Table(Map::new()), // if we cannot parse the file, start with a fresh table }; - if let toml::Value::Table(table) = &mut data { + if let Value::Table(table) = &mut data { let pipelines = match table.get_mut("pipelines") { - Some(toml::Value::Array(arr)) => arr, + Some(Value::Array(arr)) => arr, _ => { - table.insert("pipelines".to_string(), toml::Value::Array(Vec::new())); + table.insert("pipelines".to_string(), Value::Array(Vec::new())); match table.get_mut("pipelines") { - Some(toml::Value::Array(arr)) => arr, + Some(Value::Array(arr)) => arr, _ => panic!("Unexpected state"), } } }; - let mut new_pipeline = toml::map::Map::new(); - new_pipeline.insert( - String::from("type"), - toml::Value::String(String::from("Audio")), - ); - new_pipeline.insert(String::from("convert"), toml::Value::Boolean(convert)); + let mut new_pipeline = Map::new(); + new_pipeline.insert(String::from("type"), Value::String(String::from("Audio"))); + new_pipeline.insert(String::from("convert"), Value::Boolean(convert)); let filename_with_ext = path.file_name().unwrap().to_str().unwrap().to_string(); new_pipeline.insert( String::from("sources"), - toml::Value::Array(vec![toml::Value::String(filename_with_ext)]), + Value::Array(vec![Value::String(filename_with_ext)]), ); - pipelines.push(toml::Value::Table(new_pipeline)); + pipelines.push(Value::Table(new_pipeline)); } else { panic!("Expected table at the root of the TOML document"); } @@ -71,59 +71,56 @@ pub fn import_model(path: PathBuf, collider_from_model: bool) -> anyhow::Result< let tomlpath = current_dir.join("assets/pipeline.toml"); println!("Importing model..."); println!("Read more about model import here: https://ambientrun.github.io/Ambient/reference/asset_pipeline.html"); - if !std::path::Path::new(&asset_folder_path).exists() { + if !Path::new(&asset_folder_path).exists() { std::fs::create_dir_all(&asset_folder_path)?; } - if !std::path::Path::new(&tomlpath).exists() { - std::fs::File::create(&tomlpath)?; + if !Path::new(&tomlpath).exists() { + File::create(&tomlpath)?; } - let mut file = std::fs::File::open(&tomlpath)?; + let mut file = File::open(&tomlpath)?; let mut contents = String::new(); file.read_to_string(&mut contents)?; - let mut data: toml::Value = match toml::from_str(&contents) { + let mut data: Value = match toml::from_str(&contents) { Ok(v) => v, - Err(_) => toml::Value::Table(toml::map::Map::new()), // if we cannot parse the file, start with a fresh table + Err(_) => Value::Table(Map::new()), // if we cannot parse the file, start with a fresh table }; - if let toml::Value::Table(table) = &mut data { + if let Value::Table(table) = &mut data { if collider_from_model { - let mut collider_pipeline = toml::map::Map::new(); + let mut collider_pipeline = Map::new(); collider_pipeline.insert( String::from("type"), - toml::Value::String(String::from("FromModel")), + Value::String(String::from("FromModel")), ); table.insert( "pipelines.collider".to_string(), - toml::Value::Table(collider_pipeline), + Value::Table(collider_pipeline), ); } let pipelines = match table.get_mut("pipelines") { - Some(toml::Value::Array(a)) => a, + Some(Value::Array(a)) => a, _ => { - table.insert("pipelines".to_string(), toml::Value::Array(Vec::new())); + table.insert("pipelines".to_string(), Value::Array(Vec::new())); match table.get_mut("pipelines") { - Some(toml::Value::Array(a)) => a, + Some(Value::Array(a)) => a, _ => panic!("Unexpected state"), } } }; - let mut new_pipeline = toml::map::Map::new(); - new_pipeline.insert( - String::from("type"), - toml::Value::String(String::from("Models")), - ); + let mut new_pipeline = Map::new(); + new_pipeline.insert(String::from("type"), Value::String(String::from("Models"))); let filename_with_ext = path.file_name().unwrap().to_str().unwrap().to_string(); new_pipeline.insert( String::from("sources"), - toml::Value::Array(vec![toml::Value::String(filename_with_ext)]), + Value::Array(vec![Value::String(filename_with_ext)]), ); - pipelines.push(toml::Value::Table(new_pipeline)); + pipelines.push(Value::Table(new_pipeline)); } let mut file = std::fs::OpenOptions::new() From 73daa7f0ddfed390c27eb90c593ff93caa1cc8ba Mon Sep 17 00:00:00 2001 From: chaosprint Date: Fri, 4 Aug 2023 14:46:01 +0200 Subject: [PATCH 4/4] write changelog for assets cli --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3391daa7e1..e544743ee8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,7 @@ These PRs are not directly user-facing, but improve the development experience. - **ECS**: `Duration` is now a supported primitive type. - **ECS**: All integer types from 8-bit to 64-bit are now supported as component types, including signed and unsigned variants. Additionally, all signed and unsigned integer vector types are now supported. This includes `U16`, `IVec2`, `UVec3`, etc. - **Docs**: The IDE documentation has been improved, including information on how to set up Emacs for Ambient development (thanks to [@kevzettler](https://github.com/kevzettler) in [#505](https://github.com/AmbientRun/Ambient/pull/505)). +- **Assets**: Now you can use `ambient assets import` to import asset one by one. This will create or modify the `pipeline.toml` file for you. #### Examples