41#include <QSignalBlocker>
42#include <qboxlayout.h>
43#include <qhashfunctions.h>
46#include <qmessagebox.h>
48#include <qobjectdefs.h>
62#include <RobotAPI/interface/armem/mns/MemoryNameSystemInterface.h>
72#include <RobotAPI/libraries/armem_locations/aron/Location.aron.generated.h>
77#include <armarx/navigation/components/navigation_memory/ComponentInterface.h>
80#include <armarx/navigation/core/aron/Graph.aron.generated.h>
86#include <armarx/navigation/gui-plugins/LocationGraphEditor/ui_LocationGraphEditorWidget.h>
96#include <SemanticObjectRelations/Shapes/Shape.h>
99static const QString SETTING_LAST_SCENE =
"lastScene";
107 return "Navigation.LocationGraphEditor";
113 return QIcon{
"://icons/location_graph_editor.svg"};
122 widget.loadGraphButton->setEnabled(
false);
125 view.vertexData->setEnabled(
false);
126 widget.locationDataGroupBox->layout()->addWidget(view.vertexData);
127 if (QBoxLayout* layout =
dynamic_cast<QBoxLayout*
>(widget.locationDataGroupBox->layout()))
129 layout->addStretch();
133 widget.locationsTableGroupBox->layout()->addWidget(view.vertexTable);
136 widget.edgesTableGroupBox->layout()->addWidget(view.edgeTable);
137 widget.edgesTableGroupBox->hide();
140 widget.robotVisuGroupBox->layout()->addWidget(view.robotVisu);
143 widget.objectPoseClientGroupBox->layout()->addWidget(view.objectPoses);
149 connect(widget.createGraphButton, &QPushButton::pressed,
this, &This::createGraphDialog);
151 connect(widget.queryGraphsButton, &QPushButton::pressed,
this, &This::queryGraphs);
158 connect(widget.loadGraphButton, &QPushButton::pressed,
this, &This::loadGraph);
159 connect(widget.commitGraphButton, &QPushButton::pressed,
this, &This::commit);
163 connect(view.vertexData,
166 &This::updateGraphView);
168 connect(view.robotVisu,
171 [
this]() { view.vertexData->setRobotConnection(&view.robotVisu->connection()); });
174 connect(view.objectPoses,
178 { view.vertexData->setObjectPoseConnection(&view.objectPoses->connection()); });
181 connect(view.vertexTable,
182 &VertexTableWidget::currentItemChanged,
183 [
this](QTableWidgetItem* current, QTableWidgetItem* previous)
186 this->selectVertex(current);
189 connect(view.vertexTable,
190 &VertexTableWidget::itemSelectionChanged,
192 &This::updateVertexHighlighting);
193 connect(view.edgeTable,
194 &EdgeTableWidget::itemSelectionChanged,
196 &This::updateEdgeHighlighting);
199 connect(view.vertexTable,
202 &This::createVertexDialog);
204 connect(view.vertexTable,
207 &This::removeEdgesOfVertex);
212 connect(widget.exportButton, &QPushButton::clicked,
this, &This::exportLocationGraph);
216 WidgetController::exportLocationGraph()
220 auto navigationMemoryPrx =
221 memory::NavigationMemoryInterfacePrx::uncheckedCast(remote.locationReader.
readingPrx);
222 if (navigationMemoryPrx)
224 const ::armarx::PackagePath packagePath(
225 widget.packageNameEdit->text().toStdString(),
226 widget.packageDirectoryEdit->text().toStdString());
228 navigationMemoryPrx->storeLocationGraph(packagePath.serialize());
229 labelText =
"Exported locations and graph.";
233 labelText =
"Export failed: The connected navigation memory does not implement the "
234 "NavigationMemoryInterface.";
237 widget.exportLabel->setText(labelText);
248 if (not configDialog)
251 configDialog->addProxyFinder<armem::mns::MemoryNameSystemInterfacePrx>(
253 "Memory Name System",
254 remote.memoryNameSystemName);
256 return qobject_cast<SimpleConfigDialog*>(configDialog);
262 remote.memoryNameSystemName = configDialog->getProxyName(
"MemoryNameSystem");
268 remote.memoryNameSystemName =
270 ->value(
"memoryNameSystemName", QString::fromStdString(remote.memoryNameSystemName))
278 settings->setValue(
"memoryNameSystemName",
279 QString::fromStdString(remote.memoryNameSystemName));
285 lastSelectedSceneName =
286 settings.value(SETTING_LAST_SCENE, lastSelectedSceneName).toString();
292 settings.setValue(SETTING_LAST_SCENE, lastSelectedSceneName);
304 remote.connect(*
this);
306 std::stringstream ss;
307 ss <<
"Navigation Graphs (Entities from Core Segment "
309 widget.graphGroupBox->setTitle(QString::fromStdString(ss.str()));
310 widget.graphGroupBox->setEnabled(
true);
317 WidgetController::Remote::connect(
Component& parent)
320 parent.
getProxy<armem::mns::MemoryNameSystemInterfacePrx>(memoryNameSystemName);
321 memoryNameSystem.
initialize(mnsProxy, &parent);
333 WidgetController::queryMemory()
341 std::stringstream ss;
350 widget.statusLabel->setText(
status.join(
"\n"));
354 armem::client::QueryResult
355 WidgetController::queryLocations()
358 armem::client::QueryResult result =
365 model.locationsMemory = result.
memory;
375 armem::client::QueryResult
376 WidgetController::queryGraphs()
378 armem::client::QueryResult result =
382 model.graphMemory = std::move(result.
memory);
389 WidgetController::updateGraphList()
391 QString previousText = widget.graphsComboBox->currentText();
392 widget.graphsComboBox->clear();
395 int previousIndex = -1;
396 model.graphMemory.forEachEntity(
397 [&](
const armem::wm::Entity& entity)
400 (entity.
id() == model.graphEntityID ? model.graph.hasChanged() :
false);
401 QString text = getGraphDisplayName(entity.
id(), hasChanged);
402 QString
id = QString::fromStdString(entity.
id().
str());
404 widget.graphsComboBox->addItem(text,
id);
405 if (previousIndex < 0 and text == previousText)
411 if (previousIndex >= 0)
413 widget.graphsComboBox->setCurrentIndex(previousIndex);
415 widget.loadGraphButton->setEnabled(widget.graphsComboBox->count() > 0);
419 WidgetController::loadGraph()
421 if (model.graph.hasChanged())
423 if (not loadGraphDialog())
430 widget.graphsComboBox->currentData().toString().toStdString());
435 const armem::wm::EntityInstance* instance = model.graphMemory.findLatestInstance(entityID);
436 if (instance !=
nullptr)
438 widget.statusLabel->setText(QString::fromStdString(
443 std::stringstream ss;
444 ss <<
"No latest instance of entity " << entityID <<
" in memory.";
445 widget.statusLabel->setText(QString::fromStdString(ss.str()));
449 navigation::core::Graph nav;
451 if (instance !=
nullptr)
454 navigation::core::arondto::Graph dto;
456 dto.fromAron(instance->
data());
462 std::vector<semrel::ShapeID> remove;
463 for (navigation::core::Graph::Vertex vertex : nav.vertices())
465 if (not vertex.attrib().hasPose())
467 remove.push_back(vertex.objectID());
470 if (not remove.empty())
472 for (semrel::ShapeID vertexID : remove)
474 nav.removeVertex(nav.vertex(vertexID));
476 std::stringstream ss;
477 ss <<
"Dropped " << remove.size() <<
" locations which could not be resolved.";
478 widget.statusLabel->setText(QString::fromStdString(ss.str()));
484 setGraph(entityID, nav);
488 WidgetController::loadGraphDialog()
493 msgBox.setText(
"The current graph and/or locations have uncommitted changes. ");
494 msgBox.setInformativeText(
"Do you want to discard them?");
495 QStringList detailLines;
496 if (model.graph.attrib().edgesChanged)
498 detailLines.append(
"Graph edges have changed.");
500 for (
auto vertex : model.graph.vertices())
502 if (vertex.attrib().changed)
504 detailLines.append(
"Location " +
505 QString::fromStdString(vertex.attrib().getFullName()) +
509 msgBox.setDetailedText(detailLines.join(
"\n"));
510 msgBox.setStandardButtons(QMessageBox::Discard | QMessageBox::Cancel);
511 msgBox.setDefaultButton(QMessageBox::Cancel);
513 switch (msgBox.exec())
515 case QMessageBox::Discard:
517 case QMessageBox::Cancel:
525 WidgetController::commit()
527 armem::CommitResult locResults = commitLocations();
528 armem::EntityUpdateResult graphResult = commitGraph();
531 std::stringstream ss;
534 ss <<
"Committed " << locResults.
results.size() <<
" location snapshot";
535 if (locResults.
results.size() != 1)
541 ss <<
" and 1 graph snapshot " << graphResult.
snapshotID;
545 ss <<
" but failed to commit graph: \n" << graphResult.
errorMessage;
550 int numLocs =
static_cast<int>(locResults.
results.size());
552 for (
const auto& r : locResults.
results)
554 numSuccess += int(r.success);
556 int numFailed = numLocs - numSuccess;
560 ss <<
"Committed 1 graph snapshot " << graphResult.
snapshotID;
561 ss <<
" and " << numSuccess <<
" locations, but failed to commit " << numFailed
567 ss <<
"Failed to commit graph and " << numFailed <<
" of " << numLocs
574 widget.statusLabel->setText(QString::fromStdString(ss.str()));
582 WidgetController::commitLocations()
584 armem::Commit commit;
586 for (
auto vertex : model.graph.vertices())
588 if (vertex.attrib().changed)
590 armem::EntityUpdate&
update = commit.add();
594 navigation::location::arondto::Location dto;
595 toAron(dto.framedPose, vertex.attrib().getPose());
596 update.instancesData = {dto.toAron()};
600 armem::CommitResult result = remote.locationWriter.commit(commit);
601 auto it = result.
results.begin();
602 for (
auto vertex : model.graph.vertices())
604 if (vertex.attrib().changed)
610 vertex.attrib().changed =
false;
618 armem::EntityUpdateResult
619 WidgetController::commitGraph()
621 navigation::core::arondto::Graph dto;
623 navigation::core::Graph nav =
fromGuiGraph(model.graph);
627 armem::EntityUpdate
update;
628 update.entityID = model.graphEntityID;
630 update.instancesData = {dto.toAron()};
632 armem::EntityUpdateResult result = remote.graphWriter.commit(update);
636 model.graph.attrib().edgesChanged =
false;
641 template <
class GraphT>
645 while (
graph.hasVertex(vertexID))
655 model.graphEntityID = entityID;
659 QSignalBlocker blocker(
this);
662 model.graph.attrib().edgesChanged =
false;
664 std::set<armem::MemoryID> coveredLocationIDs;
665 for (
auto vertex :
nav.vertices())
668 addVertex(vertex.objectID(), {vertex.attrib()});
672 if (coveredLocationIDs.count(
id) > 0)
678 coveredLocationIDs.insert(
id);
684 semrel::ShapeID vertexID{0};
685 model.locationsMemory.forEachInstance(
686 [&](
const armem::wm::EntityInstance& instance)
688 if (coveredLocationIDs.count(instance.
id().
getEntityID()) == 0)
691 addVertex(vertexID, instance);
697 for (
auto edge : nav.edges())
699 addEdge(model.graph.vertex(edge.sourceObjectID()),
700 model.graph.vertex(edge.targetObjectID()),
710 WidgetController::setEmptyGraph(
const armem::MemoryID& entityID)
712 model.graphEntityID = entityID;
715 QSignalBlocker blocker(
this);
718 semrel::ShapeID
id{0};
720 model.locationsMemory.forEachInstance(
721 [
this, &
id](
const armem::wm::EntityInstance& instance)
723 addVertex(
id, instance);
729 model.graph.attrib().edgesChanged =
true;
735 WidgetController::addVertex(semrel::ShapeID vertexID,
const VertexData& defaultAttribs)
739 GuiGraph::Vertex vertex = model.graph.addVertex(vertexID, defaultAttribs);
740 vertex.attrib().tableWidgetItem = view.vertexTable->addVertex();
748 WidgetController::addVertex(semrel::ShapeID vertexID,
749 const armem::wm::EntityInstance& locationInstance)
751 navigation::location::arondto::Location location;
752 location.fromAron(locationInstance.
data());
757 fromAron(location.framedPose, pose);
758 attrib.setPose(pose);
759 return addVertex(vertexID, attrib);
763 WidgetController::addEdge(GuiGraph::ConstVertex source,
764 GuiGraph::ConstVertex target,
765 const EdgeData& defaultAttribs)
768 <<
"Edge must not exist before being added: " <<
QUOTED(
source.attrib().getFullName())
769 <<
" -> '" <<
target.attrib().getFullName() <<
"'";
771 GuiGraph::Edge edge = model.graph.addEdge(source, target, defaultAttribs);
772 edge.attrib().tableWidgetItem = view.edgeTable->addEdge(edge);
780 WidgetController::updateGraphView()
783 for (
auto vertex : model.graph.vertices())
785 updateVertexView(vertex);
788 for (
auto edge : model.graph.edges())
790 updateEdgeView(edge);
794 widget.graphsComboBox->findData(QString::fromStdString(model.graphEntityID.str()));
797 widget.graphsComboBox->setItemText(
799 getGraphDisplayName(model.graphEntityID, model.graph.hasChanged()));
806 WidgetController::updateVertexView(GuiGraph::Vertex vertex)
808 view.vertexTable->updateVertex(vertex);
813 std::set<semrel::ShapeID> highlightedVertices;
814 for (
auto v : model.graph.vertices())
816 if (
v.attrib().highlighted)
818 highlightedVertices.insert(
v.objectID());
822 for (
auto edge : model.graph.edges())
824 bool verticesHighlighted = highlightedVertices.count(edge.sourceObjectID()) and
825 highlightedVertices.count(edge.targetObjectID());
829 if (edge.attrib().highlighted != verticesHighlighted)
831 edge.attrib().highlighted = verticesHighlighted;
838 WidgetController::updateEdgeView(GuiGraph::Edge edge)
840 view.edgeTable->updateEdge(edge);
844 WidgetController::updateArViz()
849 std::vector<ObjectInfo> objectInfo;
850 graph::visu::ObjectParserInfo info{objectMap, objectInfo};
853 if (view.objectPoses->isConnected())
855 objectMap = view.objectPoses->connection().getObjectPoseMap();
856 objectInfo = view.objectPoses->connection().getObjectInfo();
859 std::vector<viz::Layer> layers;
861 viz::Layer& vertices = layers.emplace_back(remote.arviz->layer(
"Locations"));
862 applyVisu(vertices, model.visu.vertex, model.graph.vertices(), info);
865 viz::Layer& edges = layers.emplace_back(remote.arviz->layer(
"Graph Edges"));
866 applyVisu(edges, model.visu.edge, model.graph.edges(), info);
869 viz::Layer& robot = layers.emplace_back(remote.arviz->layer(
"Robot"));
870 if (view.vertexData->vertex().has_value() and view.robotVisu->isEnabled())
875 view.vertexData->vertex()->attrib().getPose());
876 if (res.pose.has_value())
879 view.robotVisu->connection().vizRobot(
"robot").pose(res.pose.value()));
883 remote.arviz->commit(layers);
889 updateElementHighlighting(QList<QTableWidgetItem*> selectedItems,
890 std::map<QTableWidgetItem*, T>&& itemToElementMap)
892 for (QTableWidgetItem* selected : selectedItems)
894 if (
auto it = itemToElementMap.find(selected); it != itemToElementMap.end())
896 it->second.attrib().highlighted =
true;
897 itemToElementMap.erase(it);
900 for (
auto& [_, unselected] : itemToElementMap)
902 unselected.attrib().highlighted =
false;
907 WidgetController::updateVertexHighlighting()
909 updateElementHighlighting(view.vertexTable->selectedVertexItems(),
910 model.graph.getTableItemToVertexMap());
915 WidgetController::updateEdgeHighlighting()
917 updateElementHighlighting(view.edgeTable->selectedEdgeItems(),
918 model.graph.getTableItemToEdgeMap());
923 WidgetController::selectVertex(QTableWidgetItem* vertexItem)
925 if (vertexItem ==
nullptr)
927 view.vertexData->clearVertex();
929 if (
auto vertex = model.graph.getVertexFromTableItem(vertexItem))
931 selectVertex(vertex.value());
936 WidgetController::selectVertex(GuiGraph::Vertex vertex)
938 view.vertexData->setVertex(vertex);
942 WidgetController::clearGraph()
945 QSignalBlocker blocker(
this);
958 WidgetController::clearEdges()
961 std::vector<GuiGraph::Edge> edges{model.graph.edges().begin(), model.graph.edges().end()};
963 QSignalBlocker blocker(
this);
964 for (
auto it = edges.rbegin(); it != edges.rend(); ++it)
966 GuiGraph::Edge edge = *it;
978 WidgetController::clearVertices()
981 <<
"The graph may not have any edges when clearing the vertices.";
984 std::vector<GuiGraph::Vertex> vertices{model.graph.vertices().begin(),
985 model.graph.vertices().end()};
987 QSignalBlocker blocker(
this);
988 for (
auto it = vertices.rbegin(); it != vertices.rend(); ++it)
990 GuiGraph::Vertex vertex = *it;
991 removeVertex(vertex);
1002 WidgetController::removeEdge(GuiGraph::Edge& edge)
1004 ARMARX_CHECK(model.graph.hasEdge(edge.sourceDescriptor(), edge.targetDescriptor()))
1005 <<
"Cannot remove edge that does not exist.";
1009 QSignalBlocker blocker(view.edgeTable);
1010 view.edgeTable->removeEdge(edge);
1014 model.graph.removeEdge(edge);
1016 ARMARX_CHECK(not model.graph.hasEdge(edge.sourceDescriptor(), edge.targetDescriptor()))
1017 << edge.sourceDescriptor() <<
" -> " << edge.targetDescriptor();
1023 WidgetController::removeVertex(GuiGraph::Vertex& vertex)
1026 <<
"A vertex must not have any edges before being removed. "
1027 << vertex.attrib().getFullName();
1029 <<
"A vertex must not have any edges before being removed. "
1030 << vertex.attrib().getFullName();
1034 QSignalBlocker blocker(view.vertexTable);
1035 view.vertexTable->removeVertex(vertex);
1037 if (view.vertexData->vertex().has_value() and view.vertexData->vertex().value() == vertex)
1039 view.vertexData->clearVertex();
1043 model.graph.removeVertex(vertex);
1049 WidgetController::createVertexDialog()
1055 const auto locationQueryResult = queryLocations();
1057 std::vector<std::string> availableProviders =
1059 .getProviderSegmentNames();
1063 NewEntityIdDialog dialog(providerId, availableProviders,
nullptr);
1064 switch (dialog.exec())
1066 case QDialog::Accepted:
1069 case QDialog::Rejected:
1079 toAron(attribs.aron.locationID, dialog.entityID());
1081 attribs.changed =
true;
1083 addVertex(vertexID, attribs);
1087 WidgetController::addEdges(QList<QPair<QTableWidgetItem*, QTableWidgetItem*>> vertexItems)
1089 const auto itemToVertexMap = model.graph.getTableItemToVertexMap();
1091 QSignalBlocker blocker(
this);
1093 for (
const auto& [sourceItem, targetItem] : vertexItems)
1095 GuiGraph::Vertex
source = itemToVertexMap.at(sourceItem);
1096 GuiGraph::Vertex
target = itemToVertexMap.at(targetItem);
1097 if (not model.graph.hasEdge(source, target))
1099 addEdge(source, target, {});
1104 model.graph.attrib().edgesChanged =
true;
1109 WidgetController::removeEdges(QList<QTableWidgetItem*> edgeItems)
1111 const auto itemToEdgeMap = model.graph.getTableItemToEdgeMap();
1113 QSignalBlocker blocker(
this);
1115 for (
const auto& edgeItem : edgeItems)
1117 GuiGraph::Edge edge = itemToEdgeMap.at(edgeItem);
1122 model.graph.attrib().edgesChanged =
true;
1127 WidgetController::removeEdgesOfVertex(QList<QTableWidgetItem*> vertexItems,
1130 auto removeEdges = [
this](
auto&& range)
1132 for (
auto edge : range)
1138 QSignalBlocker blocker(
this);
1140 const auto itemToVertexMap = model.graph.getTableItemToVertexMap();
1141 for (
const auto& vertexItem : vertexItems)
1143 GuiGraph::Vertex vertex = itemToVertexMap.at(vertexItem);
1147 removeEdges(vertex.inEdges());
1151 removeEdges(vertex.outEdges());
1155 removeEdges(vertex.inEdges());
1156 removeEdges(vertex.outEdges());
1162 model.graph.attrib().edgesChanged =
true;
1167 WidgetController::createGraphDialog()
1170 switch (dialog.exec())
1172 case QDialog::Accepted:
1174 case QDialog::Rejected:
1178 const armem::MemoryID entityID = dialog.entityID();
1180 const bool hasChanged =
true;
1181 QString text = getGraphDisplayName(entityID, hasChanged);
1182 QString
data = QString::fromStdString(entityID.
str());
1183 widget.graphsComboBox->addItem(text, data);
1184 widget.graphsComboBox->setCurrentIndex(widget.graphsComboBox->count() - 1);
1186 setEmptyGraph(entityID);
1190 WidgetController::getGraphDisplayName(
const armem::MemoryID& entityID,
bool changed)
const
Baseclass for all ArmarX ManagedIceObjects requiring properties.
bool usingProxy(const std::string &name, const std::string &endpoints="")
Registers a proxy for retrieval after initialization and adds it to the dependency list.
Ice::ObjectPrx getProxy(long timeoutMs=0, bool waitForScheduler=true) const
Returns the proxy of this object (optionally it waits for the proxy)
A config-dialog containing one (or multiple) proxy finders.
MemoryID getEntitySnapshotID() const
std::string str(bool escapeDelimiters=true) const
Get a string representation of this memory ID.
static MemoryID fromString(const std::string &string)
Alias for constructor from string.
std::string providerSegmentName
MemoryID getEntityID() const
const DataT & data() const
void initialize(mns::MemoryNameSystemInterfacePrx mns, ManagedIceObject *component=nullptr)
Writer useWriter(const MemoryID &memoryID)
Use a memory server and get a writer for it.
Reader useReader(const MemoryID &memoryID)
Use a memory server and get a reader for it.
server::ReadingMemoryInterfacePrx readingPrx
void edgeRemovalRequested(QList< QTableWidgetItem * > vertexItems, utils::EdgeDirection direction)
void newEdgesRequested(QList< QPair< QTableWidgetItem *, QTableWidgetItem * > > edges)
void newVertexRequested()
static Client createForGuiPlugin(armarx::Component &component, std::string const &topicName="ArVizTopic", std::string const &storageName="ArVizStorage")
#define ARMARX_CHECK(expression)
Shortcut for ARMARX_CHECK_EXPRESSION.
#define ARMARX_CHECK_NOT_NULL(ptr)
This macro evaluates whether ptr is not null and if it turns out to be false it will throw an Express...
#define ARMARX_CHECK_EQUAL(lhs, rhs)
This macro evaluates whether lhs is equal (==) rhs and if it turns out to be false it will throw an E...
#define ARMARX_INFO
The normal logging level.
#define ARMARX_WARNING
The logging level for unexpected behaviour, but not a serious problem.
#define ARMARX_VERBOSE
The logging level for verbose information.
std::string const GlobalFrame
Variable of the global coordinate system.
const VariantTypeId FramedPose
bool update(mongocxx::collection &coll, const nlohmann::json &query, const nlohmann::json &update)
void fromAron(const T &dto, T &bo)
double v(double t, double v0, double a0, double j)
void resolveLocation(Graph::Vertex &vertex, const aron::data::DictPtr &locationData)
void resolveLocations(Graph &graph, const MemoryContainerT &locationContainer)
This file is part of ArmarX.
const armem::MemoryID coreSegmentID
const armem::MemoryID coreSegmentID
navigation::core::Graph fromGuiGraph(const GuiGraph &gui)
semrel::ShapeID findNextFreeVertexID(const GraphT &graph, semrel::ShapeID vertexID)
void applyVisu(viz::Layer &layer, const std::optional< VisuT > &visu, RangeT &&range, graph::visu::ObjectParserInfo info)
std::map< ObjectID, ObjectPose > ObjectPoseMap
void toAron(arondto::PackagePath &dto, const PackageFileLocation &bo)
void fromAron(const arondto::PackagePath &dto, PackageFileLocation &bo)
Vertex source(const detail::edge_base< Directed, Vertex > &e, const PCG &)
Vertex target(const detail::edge_base< Directed, Vertex > &e, const PCG &)
std::vector< std::string > allErrorMessages() const
std::vector< EntityUpdateResult > results
wm::Memory memory
The slice of the memory that matched the query.
void add(ElementT const &element)