Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement a diagnostic tool #154

Merged
merged 86 commits into from
Sep 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
86 commits
Select commit Hold shift + click to select a range
36bb7c4
Cleanup and restore drawing asset source update behavior
luca-della-vedova May 15, 2023
4170acb
Refactor loading drawing to be a component
luca-della-vedova May 15, 2023
d9706d1
Bare drawing editor mode with 2d camera and visibility toggling
luca-della-vedova May 17, 2023
0f0a6d7
Refactor top menu bar code
luca-della-vedova May 17, 2023
2e1994a
Add fiducials, fix glitches with visibility toggling
luca-della-vedova May 18, 2023
88407a5
Make fiducials selectable, cleanup
luca-della-vedova May 18, 2023
872b057
Spawn traffic editor layers
luca-della-vedova May 19, 2023
47e39f4
WIP fiducials and features as children of drawings
luca-della-vedova May 24, 2023
ce8f0cc
Cleanup and fix system ordering
luca-della-vedova May 24, 2023
4911a89
Fiducial and anchor scaling working
luca-della-vedova May 25, 2023
13e9a03
Minor cleanup
luca-della-vedova May 25, 2023
607d0bd
Fix measurement scale
luca-della-vedova May 25, 2023
cdce56b
Implement constraints
luca-della-vedova May 25, 2023
2e3dfa2
Minor fix for layering
luca-della-vedova May 26, 2023
1859621
Add a component for ground truth drawings
luca-della-vedova May 26, 2023
c69c55f
Add widget
luca-della-vedova May 26, 2023
35b7209
Hide anchors on change to drawing editor mode
luca-della-vedova May 26, 2023
40fa108
Fix layering measurements
luca-della-vedova May 26, 2023
7fcd0f3
Assign parent to newly spawned measurements and fiducials
luca-della-vedova May 26, 2023
2cef81f
Add spawning of drawings
luca-della-vedova May 26, 2023
a62d1cd
Fixed hierarchy for newly spawned fiducials
luca-della-vedova May 29, 2023
d52c917
Fixed scale and transfor for new fiducials
luca-della-vedova May 29, 2023
4b1d562
Cleanups, fix measurement parent
luca-della-vedova May 29, 2023
1005d82
Cleanup
luca-della-vedova May 29, 2023
68bd3cb
Improve UX
luca-della-vedova May 29, 2023
7d0d010
Split constraints into site and level
luca-della-vedova May 30, 2023
b3f3978
Add inspector for measurement distance
luca-della-vedova May 30, 2023
3cb3996
Basic implementation of pixels per meter calculation
luca-della-vedova May 30, 2023
b3fb95e
Fix regression for create widget
luca-della-vedova May 30, 2023
1972d2d
Refactor category visibility into generic plugin
luca-della-vedova May 30, 2023
6dafcd9
Fix panic when spawning constraints
luca-della-vedova May 30, 2023
e162a7b
Add layer transparency for drawings
luca-della-vedova May 31, 2023
fd0cb9e
First implementation of layer alignment
luca-della-vedova Jun 1, 2023
d3ffa29
Cleanups to optimizer code
luca-della-vedova Jun 1, 2023
d6f52fa
Cleanup and refactor
luca-della-vedova Jun 1, 2023
83721bd
Cleanup
luca-della-vedova Jun 1, 2023
90727b7
More cleanups in optimization code
luca-della-vedova Jun 1, 2023
154b914
Minor cleanups, add default transparency logic to drawings
luca-della-vedova Jun 1, 2023
985c009
Make layer transparency a resource, different for drawings and floors
luca-della-vedova Jun 1, 2023
05b2510
Remove workaround and fix fiducial scale
luca-della-vedova Jun 5, 2023
68b597f
Revert "Remove workaround and fix fiducial scale"
luca-della-vedova Jun 5, 2023
c1681dc
Force constraints to only be spawned between fiducials
luca-della-vedova Jun 5, 2023
a348592
Prevent user from drawing constraints between fiducials in the same
luca-della-vedova Jun 5, 2023
cc51def
Fix visibility in backout behavior
luca-della-vedova Jun 5, 2023
0c62f02
Fix constraints saving, cleanup drawing logic
luca-della-vedova Jun 6, 2023
0be0e04
Minor cleanup
luca-della-vedova Jun 6, 2023
43eb002
Implement parsing of multilevel fiducials
luca-della-vedova Jun 8, 2023
4a65c71
First draft of multilevel alignment
luca-della-vedova Jun 9, 2023
3ae7e05
Only populate constraints from reference to other floors
luca-della-vedova Jun 9, 2023
e4c616d
Cleanup into systemparams
luca-della-vedova Jun 9, 2023
b2b8b1b
Minor cleanups
luca-della-vedova Jun 9, 2023
8c2f332
Hide drawing anchors when spawning site entities
luca-della-vedova Jun 9, 2023
59d84b9
Convert line stroke transform to 3d
luca-della-vedova Jun 12, 2023
70587a7
Added 3d site preview mode
luca-della-vedova Jun 12, 2023
9e4e03d
Add button to trigger site alignment
luca-della-vedova Jun 12, 2023
0122a71
Cleanup duplicated packages a bit
luca-della-vedova Jun 13, 2023
e807007
Spawn multilevel constraints based on label values
luca-della-vedova Jun 14, 2023
ed4b0d1
Merge branch 'main' into luca/drawing_editor
luca-della-vedova Jul 10, 2023
dc0406e
Remove spawn model widget from drawing editor
luca-della-vedova Jul 10, 2023
46a1dec
Minor cleanups, avoid creating a constraint between fiducials with no
luca-della-vedova Jul 17, 2023
f845135
First implementation of issue system, detects duplicated door names
luca-della-vedova Jul 21, 2023
977d9b3
Minor UI tunings
luca-della-vedova Jul 24, 2023
1c37f00
Add duplicated lift name warnings
luca-della-vedova Jul 24, 2023
13e5df6
Minor cleanup
luca-della-vedova Jul 24, 2023
256d204
Refactor issue adding into trait, add fiducial without label issue
luca-della-vedova Jul 24, 2023
b32990a
Change event to ValidateWorkspace
luca-della-vedova Jul 24, 2023
a9c0fee
Add check for duplicated dock name
luca-della-vedova Jul 24, 2023
52660e5
Add unconnected anchor warning, minor cleanups
luca-della-vedova Jul 25, 2023
cc84c6d
Add icon for issue suppression
luca-della-vedova Jul 25, 2023
452b884
Minor cleanups
luca-della-vedova Jul 25, 2023
488b7c0
Merge branch 'main' into luca/diagnostic_tool
luca-della-vedova Aug 28, 2023
9685ad7
Restore functionality after merge
luca-della-vedova Aug 29, 2023
96ee4b7
Remove unused code
luca-della-vedova Aug 29, 2023
b63839b
Fix serde error
luca-della-vedova Aug 29, 2023
582c495
Merge remote-tracking branch 'origin/main' into luca/diagnostic_tool
luca-della-vedova Aug 29, 2023
46cfa57
Migrate to left panel, fix CI
luca-della-vedova Aug 29, 2023
4e6db8b
Tune UI
luca-della-vedova Aug 29, 2023
08c5890
Add attribution
luca-della-vedova Aug 29, 2023
443c91a
Remove unused code
luca-della-vedova Aug 29, 2023
b8f527f
Move to ChangePlugin for changing component values
luca-della-vedova Aug 30, 2023
564e4d8
Merge remote-tracking branch 'origin/main' into luca/diagnostic_tool
luca-della-vedova Aug 30, 2023
dd5c653
Merge branch 'main' into luca/diagnostic_tool
luca-della-vedova Sep 4, 2023
3fb48ee
Merge branch 'main' into luca/diagnostic_tool
mxgrey Sep 5, 2023
54f9d70
Smaller distance threshold
mxgrey Sep 5, 2023
f092b86
Track entity IDs while loading elements of drawings
mxgrey Sep 5, 2023
d5e6d09
Use General category instead of Level when calculating anchor proximity
mxgrey Sep 5, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions assets/textures/attribution.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
* [`add.png`](https://thenounproject.com/icon/plus-1809810/)
* [`search.png`](https://thenounproject.com/icon/search-3743008/)
* [`merge.png`](https://thenounproject.com/icon/merge-3402180/)
* [`hide.png`](https://thenounproject.com/icon/hide-eye-796162/)
* `trash.png`: @mxgrey
* `selected.png`: @mxgrey
* `alignment.png`: @mxgrey
Binary file added assets/textures/hide.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 12 additions & 0 deletions assets/textures/hide.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
121 changes: 121 additions & 0 deletions rmf_site_editor/src/issue.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/*
* 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::site::ChangePlugin;
use crate::widgets::{
diagnostic_window::DiagnosticWindowState,
menu_bar::{MenuEvent, MenuItem, ToolMenu},
};
use bevy::prelude::*;
use bevy::utils::{HashMap, Uuid};
use rmf_site_format::{FilteredIssueKinds, FilteredIssues, IssueKey};

#[derive(Component, Debug, Clone)]
pub struct Issue {
pub key: IssueKey<Entity>,
/// Short description of the issue
pub brief: String,
/// Hint on how to approach solving the issue
pub hint: String,
}

pub trait RegisterIssueType {
fn add_issue_type(&mut self, type_uuid: &Uuid, name: &str) -> &mut Self;
}

impl RegisterIssueType for App {
fn add_issue_type(&mut self, type_uuid: &Uuid, name: &str) -> &mut Self {
let mut issue_dictionary = self
.world
.get_resource_or_insert_with::<IssueDictionary>(Default::default);
issue_dictionary.insert(type_uuid.clone(), name.into());
self
}
}

/// Used as an event to request validation of a workspace
#[derive(Deref, DerefMut)]
pub struct ValidateWorkspace(pub Entity);

// Maps a uuid to the issue name
#[derive(Default, Resource, Deref, DerefMut)]
pub struct IssueDictionary(HashMap<Uuid, String>);

#[derive(Default)]
pub struct IssuePlugin;

#[derive(Resource)]
pub struct IssueMenu {
diagnostic_tool: Entity,
}

impl FromWorld for IssueMenu {
fn from_world(world: &mut World) -> Self {
// Tools menu
let diagnostic_tool = world
.spawn(MenuItem::Text("Diagnostic Tool".to_string()))
.id();

let tool_header = world.resource::<ToolMenu>().get();
world
.entity_mut(tool_header)
.push_children(&[diagnostic_tool]);

IssueMenu { diagnostic_tool }
}
}

fn handle_diagnostic_window_visibility(
mut menu_events: EventReader<MenuEvent>,
issue_menu: Res<IssueMenu>,
mut diagnostic_window: ResMut<DiagnosticWindowState>,
) {
for event in menu_events.iter() {
if event.clicked() && event.source() == issue_menu.diagnostic_tool {
diagnostic_window.show = true;
}
}
}

impl Plugin for IssuePlugin {
fn build(&self, app: &mut App) {
app.add_event::<ValidateWorkspace>()
.add_plugin(ChangePlugin::<FilteredIssues<Entity>>::default())
.add_plugin(ChangePlugin::<FilteredIssueKinds>::default())
.init_resource::<IssueDictionary>()
.init_resource::<IssueMenu>()
.add_system(handle_diagnostic_window_visibility);
}
}

pub fn clear_old_issues_on_new_validate_event(
mut commands: Commands,
mut validate_events: EventReader<ValidateWorkspace>,
children: Query<&Children>,
issues: Query<Entity, With<Issue>>,
) {
for root in validate_events.iter() {
let Ok(children) = children.get(**root) else {
return;
};
for e in children {
if issues.get(*e).is_ok() {
commands.entity(*e).despawn_recursive();
}
}
}
}
4 changes: 4 additions & 0 deletions rmf_site_editor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ use widgets::{menu_bar::MenuPluginManager, *};

pub mod occupancy;
use occupancy::OccupancyPlugin;
pub mod issue;
use issue::*;

pub mod demo_world;
pub mod log;
Expand Down Expand Up @@ -222,6 +224,8 @@ impl Plugin for SiteEditor {
.add_plugin(AnimationPlugin)
.add_plugin(OccupancyPlugin)
.add_plugin(WorkspacePlugin)
// Note order matters, issue and OSMView plugins must be initialized after the UI
.add_plugin(IssuePlugin)
.add_plugin(OSMViewPlugin);
}
}
74 changes: 71 additions & 3 deletions rmf_site_editor/src/site/anchor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@
*
*/

use crate::site::*;
use bevy::{prelude::*, render::primitives::Sphere};
use rmf_site_format::{Anchor, LevelProperties, LiftCabin};
use crate::{site::*, Issue, ValidateWorkspace};
use bevy::{prelude::*, render::primitives::Sphere, utils::Uuid};
use itertools::Itertools;
use rmf_site_format::{Anchor, LevelElevation, LiftCabin};
use std::collections::HashMap;

#[derive(Bundle, Debug)]
pub struct AnchorBundle {
Expand Down Expand Up @@ -176,3 +178,69 @@ pub fn assign_orphan_anchors_to_parent(
commands.entity(parent).add_child(e_anchor);
}
}

/// Unique UUID to identify issue of anchors being close but not connected
pub const UNCONNECTED_ANCHORS_ISSUE_UUID: Uuid =
Uuid::from_u128(0xe1ef2a60c3bc45829effdf8ca7dd3403u128);

// When triggered by a validation request event, check if there are anchors that are very close to
// each other but not connected
pub fn check_for_close_unconnected_anchors(
mut commands: Commands,
mut validate_events: EventReader<ValidateWorkspace>,
parents: Query<&Parent>,
anchors: AnchorParams,
anchor_entities: Query<Entity, With<Anchor>>,
levels: Query<Entity, With<LevelElevation>>,
dependents: Query<&Dependents>,
) {
const ISSUE_HINT: &str = "Pair of anchors that are very close but not connected was found, \
review if this is intended and, if it is, suppress the issue";
// TODO(luca) make this configurable
const DISTANCE_THRESHOLD: f32 = 0.2;
for root in validate_events.iter() {
// Key is level id, value is vector of (Entity, Global tf's position)
let mut anchor_poses: HashMap<Entity, Vec<(Entity, Vec3)>> = HashMap::new();
for e in &anchor_entities {
if let Some(level) = AncestorIter::new(&parents, e).find(|p| levels.get(*p).is_ok()) {
if AncestorIter::new(&parents, level).any(|p| p == **root) {
// Level that belongs to requested workspace
let poses = anchor_poses.entry(level).or_default();
poses.push((
e,
anchors
.point_in_parent_frame_of(e, Category::General, level)
.expect("Failed fetching anchor pose"),
));
}
}
}
// Now find close unconnected pairs, sadly n^2 problem for anchors, unless we use better
// data structures that sort in space
for values in anchor_poses.values() {
for ((e0, p0), (e1, p1)) in values.iter().tuple_combinations() {
if p0.distance(*p1) < DISTANCE_THRESHOLD {
let mut edge_found = false;
if let (Ok(d0), Ok(d1)) = (dependents.get(*e0), dependents.get(*e1)) {
edge_found = d0.iter().any(|d| d1.contains(d));
}
if !edge_found {
let issue = Issue {
key: IssueKey {
entities: [*e0, *e1].into(),
kind: UNCONNECTED_ANCHORS_ISSUE_UUID,
},
brief: format!(
"Anchors are closer than {} m but unconnected",
DISTANCE_THRESHOLD
),
hint: ISSUE_HINT.to_string(),
};
let id = commands.spawn(issue).id();
commands.entity(**root).add_child(id);
}
}
}
}
}
}
9 changes: 9 additions & 0 deletions rmf_site_editor/src/site/deletion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ use crate::{
Category, CurrentLevel, Dependents, LevelElevation, LevelProperties, NameInSite,
SiteUpdateStage,
},
Issue,
};
use bevy::{ecs::system::SystemParam, prelude::*};
use rmf_site_format::{ConstraintDependents, Edge, MeshConstraint, Path, Point};
Expand Down Expand Up @@ -91,6 +92,7 @@ struct DeletionParams<'w, 's> {
levels: Query<'w, 's, Entity, With<LevelElevation>>,
select: EventWriter<'w, 's, Select>,
log: EventWriter<'w, 's, Log>,
issues: Query<'w, 's, (Entity, &'static mut Issue)>,
}

pub struct DeletionPlugin;
Expand Down Expand Up @@ -225,6 +227,13 @@ fn cautious_delete(element: Entity, params: &mut DeletionParams) {
}
}

for (e, mut issue) in &mut params.issues {
issue.key.entities.remove(&element);
if issue.key.entities.is_empty() {
params.commands.entity(e).despawn_recursive();
}
}

// Fetch the parent and delete this dependent
// TODO(luca) should we add this snippet to the recursive delete also?
if let Ok(parent) = params.parents.get(element) {
Expand Down
41 changes: 41 additions & 0 deletions rmf_site_editor/src/site/door.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,17 @@

use crate::{
interaction::{Hovered, Selectable},
issue::*,
shapes::*,
site::*,
};
use bevy::{
prelude::*,
render::mesh::{Indices, PrimitiveTopology},
utils::Uuid,
};
use rmf_site_format::{Category, DoorType, Edge, DEFAULT_LEVEL_HEIGHT};
use std::collections::{BTreeSet, HashMap};

pub const DOOR_CUE_HEIGHT: f32 = 0.004;
pub const DOOR_STOP_LINE_THICKNESS: f32 = 0.01;
Expand Down Expand Up @@ -471,3 +474,41 @@ pub fn update_door_for_moved_anchors(
}
}
}

/// Unique UUID to identify issue of duplicated door names
pub const DUPLICATED_DOOR_NAME_ISSUE_UUID: Uuid =
Uuid::from_u128(0x73f641f2a08d4ffd90216eb9bacb4743u128);

// When triggered by a validation request event, check if there are duplicated door names and
// generate an issue if that is the case
pub fn check_for_duplicated_door_names(
mut commands: Commands,
mut validate_events: EventReader<ValidateWorkspace>,
parents: Query<&Parent>,
door_names: Query<(Entity, &NameInSite), With<DoorMarker>>,
) {
for root in validate_events.iter() {
let mut names: HashMap<String, BTreeSet<Entity>> = HashMap::new();
for (e, name) in &door_names {
if AncestorIter::new(&parents, e).any(|p| p == **root) {
let entities_with_name = names.entry(name.0.clone()).or_default();
entities_with_name.insert(e);
}
}
for (name, entities) in names.drain() {
if entities.len() > 1 {
let issue = Issue {
key: IssueKey {
entities: entities,
kind: DUPLICATED_DOOR_NAME_ISSUE_UUID,
},
brief: format!("Multiple doors found with the same name {}", name),
hint: "Doors use their names as identifiers with RMF and each door should have a unique \
name, rename the affected doors".to_string()
};
let id = commands.spawn(issue).id();
commands.entity(**root).add_child(id);
}
}
}
}
38 changes: 37 additions & 1 deletion rmf_site_editor/src/site/fiducial.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@

use crate::interaction::VisualCue;
use crate::site::*;
use bevy::prelude::*;
use crate::{Issue, ValidateWorkspace};
use bevy::{prelude::*, utils::Uuid};
use std::collections::HashMap;

#[derive(Component)]
Expand Down Expand Up @@ -290,3 +291,38 @@ pub fn update_fiducial_for_moved_anchors(
}
}
}

/// Unique UUID to identify issue of fiducials without affiliation
pub const FIDUCIAL_WITHOUT_AFFILIATION_ISSUE_UUID: Uuid =
Uuid::from_u128(0x242a655f67cc4d4f9176ed5d64cd87f0u128);

// When triggered by a validation request event, check if there are fiducials without affiliation,
// generate an issue if that is the case
pub fn check_for_fiducials_without_affiliation(
mut commands: Commands,
mut validate_events: EventReader<ValidateWorkspace>,
parents: Query<&Parent>,
fiducial_affiliations: Query<(Entity, &Affiliation<Entity>), With<FiducialMarker>>,
) {
const ISSUE_HINT: &str = "Fiducial affiliations are used by the site editor to map matching \
fiducials between different floors or drawings and calculate their \
relative transform, fiducials without affiliation are ignored";
for root in validate_events.iter() {
for (e, affiliation) in &fiducial_affiliations {
if AncestorIter::new(&parents, e).any(|p| p == **root) {
if affiliation.0.is_none() {
let issue = Issue {
key: IssueKey {
entities: [e].into(),
kind: FIDUCIAL_WITHOUT_AFFILIATION_ISSUE_UUID,
},
brief: format!("Fiducial without affiliation found"),
hint: ISSUE_HINT.to_string(),
};
let id = commands.spawn(issue).id();
commands.entity(**root).add_child(id);
}
}
}
}
}
4 changes: 3 additions & 1 deletion rmf_site_editor/src/site/floor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,9 @@ pub fn update_floors(
.iter()
.flat_map(|members| members.iter().cloned()),
) {
let Ok((segment, path, texture_source)) = floors.get(e) else { continue };
let Ok((segment, path, texture_source)) = floors.get(e) else {
continue;
};
let (base_color_texture, texture) = from_texture_source(texture_source, &textures);
if let Ok(mut mesh) = mesh_handles.get_mut(segment.mesh) {
if let Ok(material) = material_handles.get(segment.mesh) {
Expand Down
Loading