Skip to content

Commit

Permalink
Proof of concept for headless SDF exporting (#215)
Browse files Browse the repository at this point in the history
Signed-off-by: Luca Della Vedova <[email protected]>
  • Loading branch information
luca-della-vedova authored Jun 21, 2024
1 parent 74d861b commit e764be6
Show file tree
Hide file tree
Showing 31 changed files with 2,547 additions and 316 deletions.
410 changes: 246 additions & 164 deletions Cargo.lock

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion rmf_site_editor/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ bevy_mod_raycast = "0.16"
bevy_mod_outline = "0.6"
# PR merged after 0.10 but not released yet, bump to 0.10.1 once merged
bevy_infinite_grid = { git = "https://github.com/ForesightMiningSoftwareCorporation/bevy_infinite_grid", rev = "86018dd" }
bevy_gltf_export = { git = "https://github.com/luca-della-vedova/bevy_gltf_export", branch = "luca/transform_api"}
bevy_polyline = "0.8.1"
bevy_stl = "0.12"
bevy_obj = { version = "0.12.1", features = ["scene"] }
Expand All @@ -43,8 +44,9 @@ tracing = "0.1.37"
tracing-subscriber = "0.3.1"
rfd = "0.12"
urdf-rs = "0.7"
yaserde = "0.7"
utm = "0.1.6"
sdformat_rs = { git = "https://github.com/open-rmf/sdf_rust_experimental", rev = "a5daef0"}
sdformat_rs = { git = "https://github.com/open-rmf/sdf_rust_experimental", rev = "9fc35f2"}
gz-fuel = { git = "https://github.com/open-rmf/gz-fuel-rs", branch = "luca/ehttp" }
pathdiff = "*"
tera = "1.19.1"
Expand Down
4 changes: 3 additions & 1 deletion rmf_site_editor/examples/extending_menu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,5 +84,7 @@ impl Plugin for MyMenuPlugin {

/// Lets embed site editor in our application with our own plugin
fn main() {
App::new().add_plugins((SiteEditor, MyMenuPlugin)).run();
App::new()
.add_plugins((SiteEditor::default(), MyMenuPlugin))
.run();
}
11 changes: 8 additions & 3 deletions rmf_site_editor/src/interaction/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,9 @@ use bevy_polyline::PolylinePlugin;
pub struct SiteRaycastSet;

#[derive(Default)]
pub struct InteractionPlugin;
pub struct InteractionPlugin {
pub headless: bool,
}

#[derive(Debug, Clone, Copy, Default, Hash, PartialEq, Eq, States)]
pub enum InteractionState {
Expand Down Expand Up @@ -171,8 +173,10 @@ impl Plugin for InteractionPlugin {
CategoryVisibilityPlugin::<WallMarker>::visible(true),
CategoryVisibilityPlugin::<WorkcellVisualizationMarker>::visible(true),
))
.add_plugins((CameraControlsPlugin, ModelPreviewPlugin))
.add_systems(
.add_plugins((CameraControlsPlugin, ModelPreviewPlugin));

if !self.headless {
app.add_systems(
Update,
(
make_lift_doormat_gizmo,
Expand Down Expand Up @@ -255,6 +259,7 @@ impl Plugin for InteractionPlugin {
.run_if(in_state(InteractionState::Enable)),
)
.add_systems(First, (update_picked, update_interaction_mode));
}
}
}

Expand Down
11 changes: 9 additions & 2 deletions rmf_site_editor/src/keyboard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use crate::{
site::{AlignSiteDrawings, Delete},
CreateNewWorkspace, CurrentWorkspace, LoadWorkspace, SaveWorkspace,
};
use bevy::prelude::*;
use bevy::{prelude::*, window::PrimaryWindow};
use bevy_egui::EguiContexts;

#[derive(Debug, Clone, Copy, Resource)]
Expand Down Expand Up @@ -55,8 +55,15 @@ fn handle_keyboard_input(
mut debug_mode: ResMut<DebugMode>,
mut align_site: EventWriter<AlignSiteDrawings>,
current_workspace: Res<CurrentWorkspace>,
primary_windows: Query<Entity, With<PrimaryWindow>>,
) {
let egui_context = egui_context.ctx_mut();
let Some(egui_context) = primary_windows
.get_single()
.ok()
.and_then(|w| egui_context.try_ctx_for_window_mut(w))
else {
return;
};
let ui_has_focus = egui_context.wants_pointer_input()
|| egui_context.wants_keyboard_input()
|| egui_context.is_pointer_over_area();
Expand Down
89 changes: 68 additions & 21 deletions rmf_site_editor/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use bevy::{log::LogPlugin, pbr::DirectionalLightShadowMap, prelude::*};
use bevy::{app::ScheduleRunnerPlugin, log::LogPlugin, pbr::DirectionalLightShadowMap, prelude::*};
use bevy_egui::EguiPlugin;
use main_menu::MainMenuPlugin;
// use warehouse_generator::WarehouseGeneratorPlugin;
Expand Down Expand Up @@ -56,7 +56,7 @@ use wireframe::*;
use aabb::AabbUpdatePlugin;
use animate::AnimationPlugin;
use interaction::InteractionPlugin;
use site::{OSMViewPlugin, SitePlugin};
use site::{OSMViewPlugin, SiteFileMenuPlugin, SitePlugin};
use site_asset_io::SiteAssetIoPlugin;

pub mod osm_slippy_map;
Expand All @@ -75,6 +75,9 @@ pub struct CommandLineArgs {
/// Name of a Site (.site.ron) file to import on top of the base FILENAME.
#[cfg_attr(not(target_arch = "wasm32"), arg(short, long))]
pub import: Option<String>,
/// Run in headless mode and export the loaded site to the requested path.
#[cfg_attr(not(target_arch = "wasm32"), arg(long))]
pub headless_export: Option<String>,
}

#[derive(Clone, Default, Eq, PartialEq, Debug, Hash, States)]
Expand Down Expand Up @@ -117,6 +120,7 @@ pub fn run_js() {

pub fn run(command_line_args: Vec<String>) {
let mut app = App::new();
let mut headless_export = None;

#[cfg(not(target_arch = "wasm32"))]
{
Expand All @@ -127,34 +131,59 @@ pub fn run(command_line_args: Vec<String>) {
command_line_args.import.map(Into::into),
));
}
headless_export = command_line_args.headless_export;
}

app.add_plugins(SiteEditor);
app.add_plugins(SiteEditor { headless_export });
app.run();
}

pub struct SiteEditor;
#[derive(Default)]
pub struct SiteEditor {
/// Contains Some(path) if the site editor is running in headless mode exporting its site.
pub headless_export: Option<String>,
}

impl Plugin for SiteEditor {
fn build(&self, app: &mut App) {
let mut plugins = DefaultPlugins.build();
let headless = {
#[cfg(not(target_arch = "wasm32"))]
{
self.headless_export.is_some()
}
#[cfg(target_arch = "wasm32")]
{
false
}
};
plugins = if headless {
plugins
.set(WindowPlugin {
primary_window: None,
exit_condition: bevy::window::ExitCondition::DontExit,
close_when_requested: false,
})
.disable::<bevy::winit::WinitPlugin>()
} else {
plugins.set(WindowPlugin {
primary_window: Some(Window {
title: "RMF Site Editor".to_owned(),
#[cfg(not(target_arch = "wasm32"))]
resolution: (1600., 900.).into(),
#[cfg(target_arch = "wasm32")]
canvas: Some(String::from("#rmf_site_editor_canvas")),
#[cfg(target_arch = "wasm32")]
fit_canvas_to_parent: true,
..default()
}),
..default()
})
};
app.add_plugins((
SiteAssetIoPlugin,
DefaultPlugins
.build()
plugins
.disable::<LogPlugin>()
.set(WindowPlugin {
primary_window: Some(Window {
title: "RMF Site Editor".to_owned(),
#[cfg(not(target_arch = "wasm32"))]
resolution: (1600., 900.).into(),
#[cfg(target_arch = "wasm32")]
canvas: Some(String::from("#rmf_site_editor_canvas")),
#[cfg(target_arch = "wasm32")]
fit_canvas_to_parent: true,
..default()
}),
..default()
})
.set(ImagePlugin {
default_sampler: SamplerDescriptor {
address_mode_u: AddressMode::Repeat,
Expand All @@ -174,6 +203,7 @@ impl Plugin for SiteEditor {
..default()
}),
));

app.insert_resource(DirectionalLightShadowMap { size: 2048 })
.add_state::<AppState>()
.add_plugins((
Expand All @@ -185,8 +215,12 @@ impl Plugin for SiteEditor {
MainMenuPlugin,
WorkcellEditorPlugin,
SitePlugin,
InteractionPlugin,
StandardUiLayout,
InteractionPlugin {
headless: self.headless_export.is_some(),
},
StandardUiLayout {
headless: self.headless_export.is_some(),
},
AnimationPlugin,
OccupancyPlugin,
WorkspacePlugin,
Expand All @@ -197,10 +231,23 @@ impl Plugin for SiteEditor {
IssuePlugin,
OSMViewPlugin,
SiteWireframePlugin,
SiteFileMenuPlugin,
));

// Ref https://github.com/bevyengine/bevy/issues/10877. The default behavior causes issues
// with events being accumulated when not read (i.e. scrolling mouse wheel on a UI widget).
app.world
.remove_resource::<bevy::ecs::event::EventUpdateSignal>();

if let Some(path) = &self.headless_export {
// We really don't need a high update rate here since we are IO bound, set a low rate
// to save CPU.
// TODO(luca) this still seems to take quite some time, check where the bottleneck is.
app.add_plugins(ScheduleRunnerPlugin::run_loop(
std::time::Duration::from_secs_f64(1.0 / 10.0),
));
app.insert_resource(site::HeadlessSdfExportState::new(path));
app.add_systems(Last, site::headless_sdf_export);
}
}
}
13 changes: 11 additions & 2 deletions rmf_site_editor/src/main_menu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

use super::demo_world::*;
use crate::{AppState, LoadWorkspace, WorkspaceData};
use bevy::{app::AppExit, prelude::*, tasks::Task};
use bevy::{app::AppExit, prelude::*, tasks::Task, window::PrimaryWindow};
use bevy_egui::{egui, EguiContexts};
use std::path::PathBuf;

Expand All @@ -44,6 +44,7 @@ fn egui_ui(
mut _load_workspace: EventWriter<LoadWorkspace>,
mut _app_state: ResMut<State<AppState>>,
autoload: Option<ResMut<Autoload>>,
primary_windows: Query<Entity, With<PrimaryWindow>>,
) {
if let Some(mut autoload) = autoload {
#[cfg(not(target_arch = "wasm32"))]
Expand All @@ -56,12 +57,20 @@ fn egui_ui(
return;
}

let Some(ctx) = primary_windows
.get_single()
.ok()
.and_then(|w| egui_context.try_ctx_for_window_mut(w))
else {
return;
};

egui::Window::new("Welcome!")
.collapsible(false)
.resizable(false)
.title_bar(false)
.anchor(egui::Align2::CENTER_CENTER, egui::vec2(0., 0.))
.show(egui_context.ctx_mut(), |ui| {
.show(ctx, |ui| {
ui.heading("Welcome to The RMF Site Editor!");
ui.add_space(10.);

Expand Down
13 changes: 13 additions & 0 deletions rmf_site_editor/src/site/door.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,19 @@ impl DoorBodyType {
| DoorBodyType::DoubleSliding { left, right } => vec![*left, *right],
}
}

pub fn links(&self) -> Vec<&str> {
match self {
DoorBodyType::SingleSwing { .. }
| DoorBodyType::SingleSliding { .. }
| DoorBodyType::Model { .. } => {
vec!["body"]
}
DoorBodyType::DoubleSwing { .. } | DoorBodyType::DoubleSliding { .. } => {
vec!["left", "right"]
}
}
}
}

#[derive(Debug, Clone, Copy, Component)]
Expand Down
80 changes: 80 additions & 0 deletions rmf_site_editor/src/site/file_menu.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* Copyright (C) 2023 Open Source Robotics Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

use crate::menu_bar::{FileMenu, MenuEvent, MenuItem, MenuVisualizationStates};
use crate::{AppState, ExportFormat, SaveWorkspace, SaveWorkspaceDestination};
use bevy::prelude::*;
use std::collections::HashSet;

/// Keeps track of which entity is associated to the export sdf button.
#[derive(Resource)]
pub struct ExportSdfMenu {
export_sdf: Entity,
}

impl ExportSdfMenu {
pub fn get(&self) -> Entity {
self.export_sdf
}
}

impl FromWorld for ExportSdfMenu {
fn from_world(world: &mut World) -> Self {
let site_states = HashSet::from([
AppState::SiteEditor,
AppState::SiteVisualizer,
AppState::SiteDrawingEditor,
]);
let file_header = world.resource::<FileMenu>().get();
let export_sdf = world
.spawn((
MenuItem::Text("Export Sdf".to_string()),
MenuVisualizationStates(site_states),
))
.set_parent(file_header)
.id();

ExportSdfMenu { export_sdf }
}
}

pub fn handle_export_sdf_menu_events(
mut menu_events: EventReader<MenuEvent>,
sdf_menu: Res<ExportSdfMenu>,
mut save_events: EventWriter<SaveWorkspace>,
) {
for event in menu_events.read() {
if event.clicked() && event.source() == sdf_menu.get() {
save_events.send(SaveWorkspace {
destination: SaveWorkspaceDestination::Dialog,
format: ExportFormat::Sdf,
});
}
}
}

#[derive(Default)]
pub struct SiteFileMenuPlugin;

impl Plugin for SiteFileMenuPlugin {
fn build(&self, app: &mut App) {
app.init_resource::<ExportSdfMenu>().add_systems(
Update,
handle_export_sdf_menu_events.run_if(AppState::in_site_mode()),
);
}
}
2 changes: 1 addition & 1 deletion rmf_site_editor/src/site/floor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ pub const FLOOR_LAYER_START: f32 = DRAWING_LAYER_START + 0.001;

#[derive(Debug, Clone, Copy, Component)]
pub struct FloorSegments {
mesh: Entity,
pub mesh: Entity,
}

fn make_fallback_floor_mesh(p: Vec3) -> Mesh {
Expand Down
Loading

0 comments on commit e764be6

Please sign in to comment.