25 #include <VirtualRobot/VirtualRobot.h>
38 #include <armarx/navigation/components/navigation_memory/ComponentInterface.h>
40 #include <armarx/navigation/core/aron/Graph.aron.generated.h>
41 #include <armarx/navigation/core/aron/Location.aron.generated.h>
43 #include <armarx/navigation/gui-plugins/LocationGraphEditor/ui_LocationGraphEditorWidget.h>
56 #include <QHBoxLayout>
60 #include <QMessageBox>
61 #include <QMouseEvent>
63 #include <QPushButton>
64 #include <QSignalBlocker>
70 static const QString SETTING_LAST_SCENE =
"lastScene";
78 return "Navigation.LocationGraphEditor";
84 return QIcon{
"://icons/location_graph_editor.svg"};
89 widget.setupUi(getWidget());
91 loadAutomaticSettings();
93 widget.loadGraphButton->setEnabled(
false);
96 view.vertexData->setEnabled(
false);
97 widget.locationDataGroupBox->layout()->addWidget(view.vertexData);
98 if (QBoxLayout* layout =
dynamic_cast<QBoxLayout*
>(widget.locationDataGroupBox->layout()))
100 layout->addStretch();
103 view.vertexTable =
new VertexTableWidget();
104 widget.locationsTableGroupBox->layout()->addWidget(view.vertexTable);
106 view.edgeTable =
new EdgeTableWidget();
107 widget.edgesTableGroupBox->layout()->addWidget(view.edgeTable);
109 view.robotVisu =
new RobotVisuWidget(*
this);
110 widget.robotVisuGroupBox->layout()->addWidget(view.robotVisu);
112 view.objectPoses =
new ObjectPoseClientWidget(*
this);
113 widget.objectPoseClientGroupBox->layout()->addWidget(view.objectPoses);
116 connect(
this, &This::connected,
this, &This::queryGraphs);
117 connect(
this, &This::connected,
this, &This::graphChanged);
119 connect(widget.createGraphButton, &QPushButton::pressed,
this, &This::createGraphDialog);
121 connect(widget.queryGraphsButton, &QPushButton::pressed,
this, &This::queryGraphs);
123 connect(
this, &This::locationMemoryChanged,
this, &This::memoryChanged);
124 connect(
this, &This::graphMemoryChanged,
this, &This::memoryChanged);
126 connect(
this, &This::graphMemoryChanged,
this, &This::updateGraphList);
128 connect(widget.loadGraphButton, &QPushButton::pressed,
this, &This::loadGraph);
129 connect(widget.commitGraphButton, &QPushButton::pressed,
this, &This::commit);
132 connect(
this, &This::graphChanged,
this, &This::updateGraphView);
136 connect(view.robotVisu,
139 [
this]() { view.vertexData->setRobotConnection(&view.robotVisu->connection()); });
142 connect(view.objectPoses,
146 { view.vertexData->setObjectPoseConnection(&view.objectPoses->connection()); });
149 connect(view.vertexTable,
150 &VertexTableWidget::currentItemChanged,
151 [
this](QTableWidgetItem* current, QTableWidgetItem* previous)
154 this->selectVertex(current);
157 connect(view.vertexTable,
158 &VertexTableWidget::itemSelectionChanged,
160 &This::updateVertexHighlighting);
161 connect(view.edgeTable,
162 &EdgeTableWidget::itemSelectionChanged,
164 &This::updateEdgeHighlighting);
167 connect(view.vertexTable,
170 &This::createVertexDialog);
172 connect(view.vertexTable,
175 &This::removeEdgesOfVertex);
180 connect(widget.exportButton, &QPushButton::clicked,
this, &This::exportLocationGraph);
184 WidgetController::exportLocationGraph()
188 auto navigationMemoryPrx =
189 memory::NavigationMemoryInterfacePrx::uncheckedCast(remote.locationReader.readingPrx);
190 if (navigationMemoryPrx)
192 const ::armarx::PackagePath packagePath(
193 widget.packageNameEdit->text().toStdString(),
194 widget.packageDirectoryEdit->text().toStdString());
196 navigationMemoryPrx->storeLocationGraph(packagePath.serialize());
197 labelText =
"Exported locations and graph.";
201 labelText =
"Export failed: The connected navigation memory does not implement the "
202 "NavigationMemoryInterface.";
205 widget.exportLabel->setText(labelText);
216 if (not configDialog)
219 configDialog->addProxyFinder<armem::mns::MemoryNameSystemInterfacePrx>(
220 "MemoryNameSystem",
"Memory Name System", remote.memoryNameSystemName);
222 return qobject_cast<SimpleConfigDialog*>(configDialog);
228 remote.memoryNameSystemName = configDialog->getProxyName(
"MemoryNameSystem");
234 remote.memoryNameSystemName =
236 ->value(
"memoryNameSystemName", QString::fromStdString(remote.memoryNameSystemName))
244 settings->setValue(
"memoryNameSystemName",
245 QString::fromStdString(remote.memoryNameSystemName));
251 lastSelectedSceneName =
252 settings.value(SETTING_LAST_SCENE, lastSelectedSceneName).toString();
258 settings.setValue(SETTING_LAST_SCENE, lastSelectedSceneName);
270 remote.connect(*
this);
272 std::stringstream ss;
273 ss <<
"Navigation Graphs (Entities from Core Segment "
275 widget.graphGroupBox->setTitle(QString::fromStdString(ss.str()));
276 widget.graphGroupBox->setEnabled(
true);
283 WidgetController::Remote::connect(
Component& parent)
286 parent.
getProxy<armem::mns::MemoryNameSystemInterfacePrx>(memoryNameSystemName);
299 WidgetController::queryMemory()
307 std::stringstream ss;
316 widget.statusLabel->setText(
status.join(
"\n"));
320 armem::client::QueryResult
321 WidgetController::queryLocations()
323 armem::client::QueryResult result =
327 model.locationsMemory = std::move(result.memory);
333 armem::client::QueryResult
334 WidgetController::queryGraphs()
336 armem::client::QueryResult result =
340 model.graphMemory = std::move(result.memory);
347 WidgetController::updateGraphList()
349 QString previousText = widget.graphsComboBox->currentText();
350 widget.graphsComboBox->clear();
353 int previousIndex = -1;
354 model.graphMemory.forEachEntity(
355 [&](
const armem::wm::Entity& entity)
358 (entity.id() == model.graphEntityID ? model.graph.hasChanged() :
false);
359 QString text = getGraphDisplayName(entity.id(), hasChanged);
360 QString
id = QString::fromStdString(entity.id().str());
362 widget.graphsComboBox->addItem(text,
id);
363 if (previousIndex < 0 and text == previousText)
369 if (previousIndex >= 0)
371 widget.graphsComboBox->setCurrentIndex(previousIndex);
373 widget.loadGraphButton->setEnabled(widget.graphsComboBox->count() > 0);
377 WidgetController::loadGraph()
379 if (model.graph.hasChanged())
381 if (not loadGraphDialog())
388 widget.graphsComboBox->currentData().toString().toStdString());
396 widget.statusLabel->setText(QString::fromStdString(
397 "Loaded snapshot " + instance->id().getEntitySnapshotID().str()));
401 std::stringstream ss;
402 ss <<
"No latest instance of entity " << entityID <<
" in memory.";
403 widget.statusLabel->setText(QString::fromStdString(ss.str()));
411 dto.fromAron(instance->data());
417 std::vector<semrel::ShapeID> remove;
418 for (navigation::core::Graph::Vertex vertex : nav.vertices())
420 if (not vertex.attrib().hasPose())
422 remove.push_back(vertex.objectID());
425 if (not remove.empty())
427 for (semrel::ShapeID vertexID : remove)
429 nav.removeVertex(nav.vertex(vertexID));
431 std::stringstream ss;
432 ss <<
"Dropped " << remove.size() <<
" locations which could not be resolved.";
433 widget.statusLabel->setText(QString::fromStdString(ss.str()));
438 setGraph(entityID, nav);
442 WidgetController::loadGraphDialog()
447 msgBox.setText(
"The current graph and/or locations have uncommitted changes. ");
448 msgBox.setInformativeText(
"Do you want to discard them?");
449 QStringList detailLines;
450 if (model.graph.attrib().edgesChanged)
452 detailLines.append(
"Graph edges have changed.");
454 for (
auto vertex : model.graph.vertices())
456 if (vertex.attrib().changed)
458 detailLines.append(
"Location " + QString::fromStdString(vertex.attrib().getName()) +
462 msgBox.setDetailedText(detailLines.join(
"\n"));
463 msgBox.setStandardButtons(QMessageBox::Discard | QMessageBox::Cancel);
464 msgBox.setDefaultButton(QMessageBox::Cancel);
466 switch (msgBox.exec())
468 case QMessageBox::Discard:
470 case QMessageBox::Cancel:
478 WidgetController::commit()
484 std::stringstream ss;
487 ss <<
"Committed " << locResults.
results.size() <<
" location snapshot";
488 if (locResults.
results.size() != 1)
494 ss <<
" and 1 graph snapshot " << graphResult.
snapshotID;
498 ss <<
" but failed to commit graph: \n" << graphResult.
errorMessage;
503 int numLocs =
static_cast<int>(locResults.
results.size());
505 for (
const auto& r : locResults.
results)
507 numSuccess += int(r.success);
509 int numFailed = numLocs - numSuccess;
513 ss <<
"Committed 1 graph snapshot " << graphResult.
snapshotID;
514 ss <<
" and " << numSuccess <<
" locations, but failed to commit " << numFailed
520 ss <<
"Failed to commit graph and " << numFailed <<
" of " << numLocs
527 widget.statusLabel->setText(QString::fromStdString(ss.str()));
535 WidgetController::commitLocations()
539 for (
auto vertex : model.graph.vertices())
541 if (vertex.attrib().changed)
547 navigation::location::arondto::Location dto;
548 toAron(dto.framedPose, vertex.attrib().getPose());
549 update.instancesData = {dto.toAron()};
554 auto it = result.
results.begin();
555 for (
auto vertex : model.graph.vertices())
557 if (vertex.attrib().changed)
563 vertex.attrib().changed =
false;
572 WidgetController::commitGraph()
581 update.entityID = model.graphEntityID;
583 update.instancesData = {dto.toAron()};
589 model.graph.attrib().edgesChanged =
false;
594 template <
class GraphT>
598 while (graph.hasVertex(vertexID))
608 model.graphEntityID = entityID;
612 QSignalBlocker blocker(
this);
615 model.graph.attrib().edgesChanged =
false;
617 std::set<armem::MemoryID> coveredLocationIDs;
618 for (
auto vertex : nav.vertices())
621 addVertex(vertex.objectID(), {vertex.attrib()});
624 aron::fromAron<armem::MemoryID>(vertex.attrib().aron.locationID).
getEntityID();
625 if (coveredLocationIDs.count(
id) > 0)
631 coveredLocationIDs.insert(
id);
637 semrel::ShapeID vertexID{0};
638 model.locationsMemory.forEachInstance(
641 if (coveredLocationIDs.count(instance.id().getEntityID()) == 0)
643 vertexID = findNextFreeVertexID(model.graph, vertexID);
644 addVertex(vertexID, instance);
650 for (
auto edge : nav.edges())
652 addEdge(model.graph.vertex(edge.sourceObjectID()),
653 model.graph.vertex(edge.targetObjectID()),
665 model.graphEntityID = entityID;
668 QSignalBlocker blocker(
this);
671 semrel::ShapeID
id{0};
673 model.locationsMemory.forEachInstance(
676 addVertex(
id, instance);
682 model.graph.attrib().edgesChanged =
true;
688 WidgetController::addVertex(semrel::ShapeID vertexID,
const VertexData& defaultAttribs)
692 GuiGraph::Vertex vertex = model.graph.addVertex(vertexID, defaultAttribs);
693 vertex.attrib().tableWidgetItem = view.vertexTable->addVertex();
701 WidgetController::addVertex(semrel::ShapeID vertexID,
704 navigation::location::arondto::Location location;
705 location.fromAron(locationInstance.data());
708 toAron(attrib.aron.locationID, locationInstance.id().getEntityID());
710 fromAron(location.framedPose, pose);
711 attrib.setPose(pose);
712 return addVertex(vertexID, attrib);
716 WidgetController::addEdge(GuiGraph::ConstVertex
source,
717 GuiGraph::ConstVertex
target,
718 const EdgeData& defaultAttribs)
721 <<
"Edge must not exist before being added: '" <<
source.attrib().getName() <<
"' -> '"
722 <<
target.attrib().getName() <<
"'";
724 GuiGraph::Edge edge = model.graph.addEdge(
source,
target, defaultAttribs);
725 edge.attrib().tableWidgetItem = view.edgeTable->addEdge(edge);
733 WidgetController::updateGraphView()
735 for (
auto vertex : model.graph.vertices())
737 updateVertexView(vertex);
739 for (
auto edge : model.graph.edges())
741 updateEdgeView(edge);
745 widget.graphsComboBox->findData(QString::fromStdString(model.graphEntityID.str()));
748 widget.graphsComboBox->setItemText(
749 index, getGraphDisplayName(model.graphEntityID, model.graph.hasChanged()));
756 WidgetController::updateVertexView(GuiGraph::Vertex vertex)
758 view.vertexTable->updateVertex(vertex);
763 std::set<semrel::ShapeID> highlightedVertices;
764 for (
auto v : model.graph.vertices())
766 if (
v.attrib().highlighted)
768 highlightedVertices.insert(
v.objectID());
772 for (
auto edge : model.graph.edges())
774 bool verticesHighlighted = highlightedVertices.count(edge.sourceObjectID()) and
775 highlightedVertices.count(edge.targetObjectID());
779 if (edge.attrib().highlighted != verticesHighlighted)
781 edge.attrib().highlighted = verticesHighlighted;
788 WidgetController::updateEdgeView(GuiGraph::Edge edge)
790 view.edgeTable->updateEdge(edge);
794 WidgetController::updateArViz()
799 std::vector<ObjectInfo> objectInfo;
800 graph::visu::ObjectParserInfo info{objectMap, objectInfo};
803 if (view.objectPoses->isConnected())
805 objectMap = view.objectPoses->connection().getObjectPoseMap();
806 objectInfo = view.objectPoses->connection().getObjectInfo();
809 std::vector<viz::Layer> layers;
811 viz::Layer& vertices = layers.emplace_back(remote.arviz->layer(
"Locations"));
812 applyVisu(vertices, model.visu.vertex, model.graph.vertices(), info);
815 viz::Layer& edges = layers.emplace_back(remote.arviz->layer(
"Graph Edges"));
816 applyVisu(edges, model.visu.edge, model.graph.edges(), info);
819 viz::Layer& robot = layers.emplace_back(remote.arviz->layer(
"Robot"));
820 if (view.vertexData->vertex().has_value() and view.robotVisu->isEnabled())
823 objectMap, objectInfo, view.vertexData->vertex()->attrib().getPose());
824 if (res.pose.has_value())
827 view.robotVisu->connection().vizRobot(
"robot").pose(res.pose.value()));
831 remote.arviz->commit(layers);
837 updateElementHighlighting(QList<QTableWidgetItem*> selectedItems,
838 std::map<QTableWidgetItem*, T>&& itemToElementMap)
840 for (QTableWidgetItem* selected : selectedItems)
842 if (
auto it = itemToElementMap.find(selected); it != itemToElementMap.end())
844 it->second.attrib().highlighted =
true;
845 itemToElementMap.erase(it);
848 for (
auto& [_, unselected] : itemToElementMap)
850 unselected.attrib().highlighted =
false;
855 WidgetController::updateVertexHighlighting()
857 updateElementHighlighting(view.vertexTable->selectedVertexItems(),
858 model.graph.getTableItemToVertexMap());
863 WidgetController::updateEdgeHighlighting()
865 updateElementHighlighting(view.edgeTable->selectedEdgeItems(),
866 model.graph.getTableItemToEdgeMap());
871 WidgetController::selectVertex(QTableWidgetItem* vertexItem)
873 if (vertexItem ==
nullptr)
875 view.vertexData->clearVertex();
877 if (
auto vertex = model.graph.getVertexFromTableItem(vertexItem))
879 selectVertex(vertex.value());
884 WidgetController::selectVertex(GuiGraph::Vertex vertex)
886 view.vertexData->setVertex(vertex);
890 WidgetController::clearGraph()
893 QSignalBlocker blocker(
this);
906 WidgetController::clearEdges()
909 std::vector<GuiGraph::Edge> edges{model.graph.edges().begin(), model.graph.edges().end()};
911 QSignalBlocker blocker(
this);
912 for (
auto it = edges.rbegin(); it != edges.rend(); ++it)
914 GuiGraph::Edge edge = *it;
926 WidgetController::clearVertices()
929 <<
"The graph may not have any edges when clearing the vertices.";
932 std::vector<GuiGraph::Vertex> vertices{model.graph.vertices().begin(),
933 model.graph.vertices().end()};
935 QSignalBlocker blocker(
this);
936 for (
auto it = vertices.rbegin(); it != vertices.rend(); ++it)
938 GuiGraph::Vertex vertex = *it;
939 removeVertex(vertex);
950 WidgetController::removeEdge(GuiGraph::Edge& edge)
952 ARMARX_CHECK(model.graph.hasEdge(edge.sourceDescriptor(), edge.targetDescriptor()))
953 <<
"Cannot remove edge that does not exist.";
957 QSignalBlocker blocker(view.edgeTable);
958 view.edgeTable->removeEdge(edge);
962 model.graph.removeEdge(edge);
964 ARMARX_CHECK(not model.graph.hasEdge(edge.sourceDescriptor(), edge.targetDescriptor()))
965 << edge.sourceDescriptor() <<
" -> " << edge.targetDescriptor();
971 WidgetController::removeVertex(GuiGraph::Vertex& vertex)
974 <<
"A vertex must not have any edges before being removed. "
975 << vertex.attrib().getName();
977 <<
"A vertex must not have any edges before being removed. "
978 << vertex.attrib().getName();
982 QSignalBlocker blocker(view.vertexTable);
983 view.vertexTable->removeVertex(vertex);
985 if (view.vertexData->vertex().has_value() and view.vertexData->vertex().value() == vertex)
987 view.vertexData->clearVertex();
991 model.graph.removeVertex(vertex);
997 WidgetController::createVertexDialog()
1000 switch (dialog.exec())
1002 case QDialog::Accepted:
1005 case QDialog::Rejected:
1015 toAron(attribs.aron.locationID, dialog.entityID());
1017 attribs.changed =
true;
1019 addVertex(vertexID, attribs);
1023 WidgetController::addEdges(QList<QPair<QTableWidgetItem*, QTableWidgetItem*>> vertexItems)
1025 const auto itemToVertexMap = model.graph.getTableItemToVertexMap();
1027 QSignalBlocker blocker(
this);
1029 for (
const auto& [sourceItem, targetItem] : vertexItems)
1031 GuiGraph::Vertex
source = itemToVertexMap.at(sourceItem);
1032 GuiGraph::Vertex
target = itemToVertexMap.at(targetItem);
1040 model.graph.attrib().edgesChanged =
true;
1045 WidgetController::removeEdges(QList<QTableWidgetItem*> edgeItems)
1047 const auto itemToEdgeMap = model.graph.getTableItemToEdgeMap();
1049 QSignalBlocker blocker(
this);
1051 for (
const auto& edgeItem : edgeItems)
1053 GuiGraph::Edge edge = itemToEdgeMap.at(edgeItem);
1058 model.graph.attrib().edgesChanged =
true;
1063 WidgetController::removeEdgesOfVertex(QList<QTableWidgetItem*> vertexItems,
1066 auto removeEdges = [
this](
auto&& range)
1068 for (
auto edge : range)
1074 QSignalBlocker blocker(
this);
1076 const auto itemToVertexMap = model.graph.getTableItemToVertexMap();
1077 for (
const auto& vertexItem : vertexItems)
1079 GuiGraph::Vertex vertex = itemToVertexMap.at(vertexItem);
1083 removeEdges(vertex.inEdges());
1087 removeEdges(vertex.outEdges());
1091 removeEdges(vertex.inEdges());
1092 removeEdges(vertex.outEdges());
1098 model.graph.attrib().edgesChanged =
true;
1103 WidgetController::createGraphDialog()
1106 switch (dialog.exec())
1108 case QDialog::Accepted:
1110 case QDialog::Rejected:
1116 const bool hasChanged =
true;
1117 QString text = getGraphDisplayName(entityID, hasChanged);
1118 QString
data = QString::fromStdString(entityID.
str());
1119 widget.graphsComboBox->addItem(text,
data);
1120 widget.graphsComboBox->setCurrentIndex(widget.graphsComboBox->count() - 1);
1122 setEmptyGraph(entityID);
1126 WidgetController::getGraphDisplayName(
const armem::MemoryID& entityID,
bool changed)
const