diff --git a/Cargo.lock b/Cargo.lock index b39cb3b8..d57927c1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -213,6 +213,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + [[package]] name = "android_log-sys" version = "0.3.1" @@ -1394,6 +1400,15 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + [[package]] name = "block-sys" version = "0.1.0-beta.1" @@ -1429,6 +1444,16 @@ dependencies = [ "tracing", ] +[[package]] +name = "bstr" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "542f33a8835a0884b006a0c3df3dadd99c0c3f296ed26c2fdc8028e01ad6230c" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "bumpalo" version = "3.14.0" @@ -1530,6 +1555,40 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" +[[package]] +name = "chrono" +version = "0.4.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "num-traits", + "windows-targets 0.48.5", +] + +[[package]] +name = "chrono-tz" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e23185c0e21df6ed832a12e2bda87c7d1def6842881fb634a8511ced741b0d76" +dependencies = [ + "chrono", + "chrono-tz-build", + "phf", +] + +[[package]] +name = "chrono-tz-build" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "433e39f13c9a060046954e0592a8d0a4bcb1040125cbf91cb8ee58964cfb350f" +dependencies = [ + "parse-zoneinfo", + "phf", + "phf_codegen", +] + [[package]] name = "cipher" version = "0.2.5" @@ -1706,7 +1765,7 @@ dependencies = [ "hmac", "percent-encoding", "rand 0.8.5", - "sha2", + "sha2 0.9.9", "time", "version_check", ] @@ -1845,6 +1904,16 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + [[package]] name = "crypto-mac" version = "0.10.1" @@ -1918,6 +1987,12 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" +[[package]] +name = "deunicode" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a1abaf4d861455be59f64fd2b55606cb151fce304ede7165f410243ce96bde6" + [[package]] name = "digest" version = "0.9.0" @@ -1927,6 +2002,16 @@ dependencies = [ "generic-array", ] +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer 0.10.4", + "crypto-common", +] + [[package]] name = "dirs" version = "4.0.0" @@ -2531,6 +2616,30 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +[[package]] +name = "globset" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "759c97c1e17c55525b57192c06a267cda0ac5210b222d6b82189a2338fa1c13d" +dependencies = [ + "aho-corasick", + "bstr", + "fnv", + "log", + "regex", +] + +[[package]] +name = "globwalk" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93e3af942408868f6934a7b85134a3230832b9977cf66125df2f9edcfce4ddcc" +dependencies = [ + "bitflags 1.3.2", + "ignore", + "walkdir", +] + [[package]] name = "gloo-timers" version = "0.2.6" @@ -2786,7 +2895,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51ab2f639c231793c5f6114bdb9bbe50a7dbbfcd7c7c6bd8475dec2d991e964f" dependencies = [ - "digest", + "digest 0.9.0", "hmac", ] @@ -2797,7 +2906,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1441c6b1e930e2817404b5046f1f989899143a12bf92de603b69f4e0aee1e15" dependencies = [ "crypto-mac", - "digest", + "digest 0.9.0", ] [[package]] @@ -2861,6 +2970,38 @@ dependencies = [ "url", ] +[[package]] +name = "humansize" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7" +dependencies = [ + "libm", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20" +dependencies = [ + "android_system_properties", + "core-foundation-sys 0.8.4", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "idna" version = "0.4.0" @@ -2871,6 +3012,23 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "ignore" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbe7873dab538a9a44ad79ede1faf5f30d49f9a5c883ddbab48bce81b64b7492" +dependencies = [ + "globset", + "lazy_static", + "log", + "memchr", + "regex", + "same-file", + "thread_local", + "walkdir", + "winapi-util", +] + [[package]] name = "image" version = "0.24.7" @@ -3902,6 +4060,15 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "parse-zoneinfo" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c705f256449c60da65e11ff6626e0c16a0a0b96aaa348de61376b249bc340f41" +dependencies = [ + "regex", +] + [[package]] name = "paste" version = "1.0.14" @@ -3926,6 +4093,51 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" +[[package]] +name = "pest" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae9cee2a55a544be8b89dc6848072af97a20f2422603c10865be2a42b580fff5" +dependencies = [ + "memchr", + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81d78524685f5ef2a3b3bd1cafbc9fcabb036253d9b1463e726a91cd16e2dfc2" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68bd1206e71118b5356dae5ddc61c8b11e28b09ef6a31acbd15ea48a28e0c227" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.37", +] + +[[package]] +name = "pest_meta" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c747191d4ad9e4a4ab9c8798f1e82a39affe7ef9648390b7e5548d18e099de6" +dependencies = [ + "once_cell", + "pest", + "sha2 0.10.8", +] + [[package]] name = "petgraph" version = "0.6.4" @@ -3936,6 +4148,44 @@ dependencies = [ "indexmap 2.0.2", ] +[[package]] +name = "phf" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_codegen" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +dependencies = [ + "phf_shared", + "rand 0.8.5", +] + +[[package]] +name = "phf_shared" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project" version = "1.1.3" @@ -4316,6 +4566,7 @@ dependencies = [ "serde_yaml", "smallvec", "surf", + "tera", "thiserror", "thread_local", "tracing", @@ -4590,13 +4841,24 @@ version = "0.9.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" dependencies = [ - "block-buffer", + "block-buffer 0.9.0", "cfg-if", "cpufeatures", - "digest", + "digest 0.9.0", "opaque-debug", ] +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.7", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -4631,6 +4893,12 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + [[package]] name = "slab" version = "0.4.9" @@ -4649,6 +4917,16 @@ dependencies = [ "version_check", ] +[[package]] +name = "slug" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bd94acec9c8da640005f8e135a39fc0372e74535e6b368b7a04b875f784c8c4" +dependencies = [ + "deunicode", + "wasm-bindgen", +] + [[package]] name = "sluice" version = "0.5.5" @@ -4906,6 +5184,28 @@ version = "0.12.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d0e916b1148c8e263850e1ebcbd046f333e0683c724876bb0da63ea4373dc8a" +[[package]] +name = "tera" +version = "1.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "970dff17c11e884a4a09bc76e3a17ef71e01bb13447a11e85226e254fe6d10b8" +dependencies = [ + "chrono", + "chrono-tz", + "globwalk", + "humansize", + "lazy_static", + "percent-encoding", + "pest", + "pest_derive", + "rand 0.8.5", + "regex", + "serde", + "serde_json", + "slug", + "unic-segment", +] + [[package]] name = "termcolor" version = "1.3.0" @@ -5190,6 +5490,62 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "ucd-trie" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" + +[[package]] +name = "unic-char-property" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" +dependencies = [ + "unic-char-range", +] + +[[package]] +name = "unic-char-range" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" + +[[package]] +name = "unic-common" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" + +[[package]] +name = "unic-segment" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4ed5d26be57f84f176157270c112ef57b86debac9cd21daaabbe56db0f88f23" +dependencies = [ + "unic-ucd-segment", +] + +[[package]] +name = "unic-ucd-segment" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2079c122a62205b421f499da10f3ee0f7697f012f55b675e002483c73ea34700" +dependencies = [ + "unic-char-property", + "unic-char-range", + "unic-ucd-version", +] + +[[package]] +name = "unic-ucd-version" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" +dependencies = [ + "unic-common", +] + [[package]] name = "unicase" version = "2.7.0" diff --git a/rmf_site_editor/Cargo.toml b/rmf_site_editor/Cargo.toml index 5bfeb813..388cc91e 100644 --- a/rmf_site_editor/Cargo.toml +++ b/rmf_site_editor/Cargo.toml @@ -48,6 +48,7 @@ utm = "0.1.6" sdformat_rs = { git = "https://github.com/open-rmf/sdf_rust_experimental", rev = "f86344f"} gz-fuel = { git = "https://github.com/open-rmf/gz-fuel-rs", branch = "first_implementation" } pathdiff = "*" +tera = "1.19.1" # only enable the 'dynamic_linking' feature if we're not building for web or windows # TODO(luca) it seems this can be enabled on windows, check diff --git a/rmf_site_editor/src/lib.rs b/rmf_site_editor/src/lib.rs index 03e4bcaa..3c3365b6 100644 --- a/rmf_site_editor/src/lib.rs +++ b/rmf_site_editor/src/lib.rs @@ -19,7 +19,6 @@ pub mod save; use save::*; pub mod widgets; use widgets::{menu_bar::MenuPluginManager, *}; - pub mod occupancy; use occupancy::OccupancyPlugin; pub mod issue; diff --git a/rmf_site_editor/src/workcell/mod.rs b/rmf_site_editor/src/workcell/mod.rs index b6f56fa5..74f354d1 100644 --- a/rmf_site_editor/src/workcell/mod.rs +++ b/rmf_site_editor/src/workcell/mod.rs @@ -33,6 +33,8 @@ pub use model::*; pub mod save; pub use save::*; +pub mod urdf_package_exporter; + pub mod workcell; pub use workcell::*; diff --git a/rmf_site_editor/src/workcell/save.rs b/rmf_site_editor/src/workcell/save.rs index 77ae9f87..afd6e118 100644 --- a/rmf_site_editor/src/workcell/save.rs +++ b/rmf_site_editor/src/workcell/save.rs @@ -17,9 +17,10 @@ use bevy::ecs::system::SystemState; use bevy::prelude::*; -use std::path::PathBuf; +use std::path::{Path as SysPath, PathBuf}; use crate::site::{CollisionMeshMarker, Pending, VisualMeshMarker}; +use crate::workcell::urdf_package_exporter::{generate_package, PackageContext, Person}; use crate::ExportFormat; use thiserror::Error as ThisError; @@ -293,19 +294,6 @@ pub fn save_workcell(world: &mut World) { .drain() .collect(); for save_event in save_events { - let path = save_event.to_file; - info!( - "Saving to {}", - path.to_str().unwrap_or("") - ); - let f = match std::fs::File::create(path) { - Ok(f) => f, - Err(err) => { - error!("Unable to save file: {err}"); - continue; - } - }; - let workcell = match generate_workcell(world, save_event.root) { Ok(root) => root, Err(err) => { @@ -314,23 +302,71 @@ pub fn save_workcell(world: &mut World) { } }; + let path = save_event.to_file; match save_event.format { - ExportFormat::Default => match workcell.to_writer(f) { - Ok(()) => { - info!("Save successful"); + ExportFormat::Default => { + info!( + "Saving to {}", + path.to_str().unwrap_or("") + ); + let f = match std::fs::File::create(path) { + Ok(f) => f, + Err(err) => { + error!("Unable to save file: {err}"); + continue; + } + }; + match workcell.to_writer(f) { + Ok(()) => { + info!("Save successful"); + } + Err(err) => { + error!("Save failed: {err}"); + } } - Err(err) => { - error!("Save failed: {err}"); - } - }, - ExportFormat::Urdf => match workcell.to_urdf_writer(f) { - Ok(()) => { - info!("Save successful"); - } - Err(err) => { - error!("Save failed: {err}"); - } - }, + } + ExportFormat::Urdf => { + match export_package(&path, &workcell) { + Ok(()) => { + info!("Successfully exported package"); + } + Err(err) => { + error!("Failed to export package: {err}"); + } + }; + } } } } + +fn export_package(path: &SysPath, workcell: &Workcell) -> Result<(), Box> { + let output_directory = path + .parent() + .ok_or("Not able to get parent")? + .to_str() + .ok_or("Invalid output directory")? + .to_string(); + let package_name = path + .file_stem() + .ok_or("Not able to get file_stem")? + .to_str() + .ok_or("Invalid file name")? + .to_string(); + + let package_context = PackageContext { + license: "TODO".to_string(), + maintainers: vec![Person { + name: "TODO".to_string(), + email: "todo@todo.com".to_string(), + }], + project_name: package_name, + fixed_frame: "world".to_string(), + dependencies: vec![], + project_description: "TODO".to_string(), + project_version: "0.0.1".to_string(), + urdf_file_name: "robot.urdf".to_string(), + }; + + generate_package(workcell, &package_context, &output_directory)?; + Ok(()) +} diff --git a/rmf_site_editor/src/workcell/urdf_package_exporter/generate_package.rs b/rmf_site_editor/src/workcell/urdf_package_exporter/generate_package.rs new file mode 100644 index 00000000..ba92e219 --- /dev/null +++ b/rmf_site_editor/src/workcell/urdf_package_exporter/generate_package.rs @@ -0,0 +1,242 @@ +use crate::site_asset_io::cache_path; +use crate::workcell::urdf_package_exporter::template; +use rmf_site_format::{AssetSource, Geometry, Workcell}; +use std::error::Error; +use std::io::{Error as IoError, ErrorKind as IoErrorKind}; +use std::path::{Path, PathBuf}; + +pub fn generate_package( + workcell: &Workcell, + package_context: &template::PackageContext, + output_directory: &String, +) -> Result<(), Box> { + let new_package_name = &package_context.project_name; + + let mesh_directory_name = "meshes".to_string(); + let launch_directory_name = "launch".to_string(); + let urdf_directory_name = "urdf".to_string(); + let rviz_directory_name = "rviz".to_string(); + + // Create paths + let output_directory_path = std::path::Path::new(&output_directory); + let output_package_path = output_directory_path.join(&new_package_name); + let meshes_directory_path = output_package_path.join(&mesh_directory_name); + let launch_directory_path = output_package_path.join(&launch_directory_name); + let urdf_directory_path = output_package_path.join(&urdf_directory_name); + let rviz_directory_path = output_package_path.join(&rviz_directory_name); + + // Create directories + if output_package_path.exists() { + std::fs::remove_dir_all(&output_directory_path)?; + } + for directory_path in [ + &output_package_path, + &meshes_directory_path, + &launch_directory_path, + &urdf_directory_path, + &rviz_directory_path, + ] { + std::fs::create_dir_all(directory_path)?; + } + + // Create the package + write_urdf_and_copy_mesh_files( + &workcell, + &mesh_directory_name, + &meshes_directory_path, + &new_package_name, + &urdf_directory_path, + )?; + generate_templates( + package_context, + &output_package_path, + &launch_directory_path, + &rviz_directory_path, + )?; + + Ok(()) +} + +fn write_urdf_and_copy_mesh_files( + workcell: &Workcell, + mesh_directory_name: &str, + meshes_directory_path: &std::path::Path, + new_package_name: &str, + urdf_directory_path: &std::path::Path, +) -> Result<(), Box> { + let asset_paths = get_mesh_paths(&workcell)?; + copy_files(&asset_paths, &meshes_directory_path)?; + + let new_workcell = convert_to_package_paths(&workcell, new_package_name, mesh_directory_name)?; + + let urdf_robot = new_workcell.to_urdf()?; + let urdf_file_path = urdf_directory_path.join("robot.urdf"); + let urdf_string = urdf_rs::write_to_string(&urdf_robot)?; + std::fs::write(urdf_file_path, urdf_string)?; + + Ok(()) +} + +fn convert_to_package_paths( + workcell: &Workcell, + package_name: &str, + mesh_directory_name: &str, +) -> Result> { + let mut workcell = workcell.clone(); + workcell + .visuals + .iter_mut() + .chain(workcell.collisions.iter_mut()) + .try_for_each(|(id, visual)| { + if let Geometry::Mesh { + source: asset_source, + scale: _, + } = &mut visual.bundle.geometry + { + let path = get_path_to_asset_file(asset_source)?; + + let file_name = PathBuf::from(&path) + .file_name() + .ok_or(IoError::new( + IoErrorKind::InvalidInput, + "Unable to get file name from path", + ))? + .to_str() + .ok_or(IoError::new( + IoErrorKind::InvalidInput, + "Unable to convert file name to str", + ))? + .to_owned(); + + let package_path = + format!("{}/{}/{}", package_name, mesh_directory_name, file_name); + *asset_source = AssetSource::Package(package_path); + } + Result::<(), Box>::Ok(()) + })?; + Ok(workcell) +} + +fn get_mesh_paths(workcell: &Workcell) -> Result, Box> { + let paths = workcell + .visuals + .iter() + .chain(workcell.collisions.iter()) + .filter_map(|(id, visual)| get_path_if_mesh_geometry(&visual.bundle.geometry).ok()?) + .collect(); + Ok(paths) +} + +fn get_path_if_mesh_geometry(geometry: &Geometry) -> Result, Box> { + if let Geometry::Mesh { + source: asset_source, + scale, + } = geometry + { + Ok(Some(get_path_to_asset_file(asset_source)?)) + } else { + Ok(None) + } +} + +fn copy_files(paths: &Vec, output_directory: &Path) -> Result<(), Box> { + for path in paths.iter() { + let file_name = PathBuf::from(path) + .file_name() + .ok_or(IoError::new( + IoErrorKind::InvalidInput, + "Unable to get file name from path", + ))? + .to_owned(); + let new_path = PathBuf::from(output_directory).join(file_name); + match std::fs::copy(path, &new_path) { + Ok(_) => {} + Err(e) => { + println!( + "Error copying file '{}' to '{}': {}", + path, + new_path.display(), + e + ); + } + } + } + Ok(()) +} + +fn get_path_to_asset_file(asset_source: &AssetSource) -> Result> { + match asset_source { + AssetSource::Package(path) => { + Ok((*urdf_rs::utils::expand_package_path(&path, None)).to_owned()) + } + AssetSource::Remote(asset_name) => { + let mut asset_path = cache_path(); + asset_path.push(PathBuf::from(&asset_name)); + Ok(asset_path + .to_str() + .ok_or(IoError::new( + IoErrorKind::InvalidInput, + "Unable to convert asset_path to str", + ))? + .to_string()) + } + AssetSource::Local(path) => Ok(path.clone()), + AssetSource::Search(_) + | AssetSource::OSMTile { + zoom: _, + latitude: _, + longitude: _, + } + | AssetSource::Bundled(_) => Err(IoError::new( + IoErrorKind::Unsupported, + "Not a supported asset type for exporting a workcell to a package", + ))?, + } +} + +fn generate_templates( + package_context: &template::PackageContext, + package_directory: &std::path::Path, + launch_directory: &std::path::Path, + rviz_directory: &std::path::Path, +) -> Result<(), Box> { + let directory = Path::new(file!()) + .parent() + .ok_or_else(|| { + IoError::new( + IoErrorKind::Other, + format!("Could not get directory of {}", file!()), + ) + })? + .to_str() + .ok_or_else(|| { + IoError::new( + IoErrorKind::Other, + format!("Could not convert directory of {} to string", file!()), + ) + })?; + let templates = vec![ + template::Template { + name: "package.xml".to_string(), + path: format!("{}/templates/package.xml.j2", directory), + output_path: package_directory.join("package.xml"), + }, + template::Template { + name: "CMakeLists.txt".to_string(), + path: format!("{}/templates/CMakeLists.txt.j2", directory), + output_path: package_directory.join("CMakeLists.txt"), + }, + template::Template { + name: "urdf.rviz".to_string(), + path: format!("{}/templates/urdf.rviz.j2", directory), + output_path: rviz_directory.join("urdf.rviz"), + }, + template::Template { + name: "display.launch.py".to_string(), + path: format!("{}/templates/display.launch.py.j2", directory), + output_path: launch_directory.join("display.launch.py"), + }, + ]; + template::populate_and_save_templates(templates, package_context)?; + Ok(()) +} diff --git a/rmf_site_editor/src/workcell/urdf_package_exporter/mod.rs b/rmf_site_editor/src/workcell/urdf_package_exporter/mod.rs new file mode 100644 index 00000000..5d3e09b3 --- /dev/null +++ b/rmf_site_editor/src/workcell/urdf_package_exporter/mod.rs @@ -0,0 +1,5 @@ +pub mod generate_package; +pub use generate_package::generate_package; + +pub use template::{PackageContext, Person}; +pub mod template; diff --git a/rmf_site_editor/src/workcell/urdf_package_exporter/template.rs b/rmf_site_editor/src/workcell/urdf_package_exporter/template.rs new file mode 100644 index 00000000..286854ec --- /dev/null +++ b/rmf_site_editor/src/workcell/urdf_package_exporter/template.rs @@ -0,0 +1,43 @@ +use serde::Serialize; +use std::error::Error; +use std::path::PathBuf; +use tera::Tera; + +#[derive(Serialize)] +pub struct PackageContext { + pub project_name: String, + pub project_description: String, + pub project_version: String, + pub license: String, + pub maintainers: Vec, + pub dependencies: Vec, + pub fixed_frame: String, + pub urdf_file_name: String, +} + +#[derive(Serialize)] +pub struct Person { + pub name: String, + pub email: String, +} + +pub struct Template { + pub name: String, + pub path: String, + pub output_path: PathBuf, +} + +pub fn populate_and_save_templates( + templates: Vec