From 1dd5dc6e25ef8c10a23904578dc8d80d8f62ff86 Mon Sep 17 00:00:00 2001 From: Rainer Kartmann <rainer.kartmann@kit.edu> Date: Wed, 25 Aug 2021 15:12:05 +0200 Subject: [PATCH] Implement adding vertices and creating new graphs; remove robot pose from graph aron --- .../GraphImportExport/GraphImportExport.cpp | 3 +- source/armarx/navigation/graph/Graph.cpp | 34 +- source/armarx/navigation/graph/Graph.h | 11 + source/armarx/navigation/graph/aron/Graph.xml | 5 +- .../LocationGraphEditor/CMakeLists.txt | 2 + .../LocationGraphEditorWidget.ui | 28 +- .../LocationGraphEditorWidgetController.cpp | 579 +++++++++++------- .../LocationGraphEditorWidgetController.h | 104 ++-- .../widgets/EdgeTableWidget.cpp | 13 +- .../widgets/NewEntityIdDialog.cpp | 129 ++++ .../widgets/NewEntityIdDialog.h | 64 ++ .../widgets/VertexTableWidget.cpp | 58 +- .../widgets/VertexTableWidget.h | 10 +- .../LocationGraphEditor/widgets/utils.cpp | 1 + .../LocationGraphEditor/widgets/utils.h | 12 +- 15 files changed, 748 insertions(+), 305 deletions(-) create mode 100644 source/armarx/navigation/gui-plugins/LocationGraphEditor/widgets/NewEntityIdDialog.cpp create mode 100644 source/armarx/navigation/gui-plugins/LocationGraphEditor/widgets/NewEntityIdDialog.h diff --git a/source/armarx/navigation/components/GraphImportExport/GraphImportExport.cpp b/source/armarx/navigation/components/GraphImportExport/GraphImportExport.cpp index 96a69c23..36936fee 100644 --- a/source/armarx/navigation/components/GraphImportExport/GraphImportExport.cpp +++ b/source/armarx/navigation/components/GraphImportExport/GraphImportExport.cpp @@ -26,7 +26,6 @@ #include <armarx/navigation/location/aron/Location.aron.generated.h> #include <armarx/navigation/location/constants.h> -#include <armarx/navigation/graph/Writer.h> #include <armarx/navigation/graph/aron/Graph.aron.generated.h> #include <armarx/navigation/graph/constants.h> @@ -373,7 +372,7 @@ namespace armarx::nav nav::graph::Graph::Vertex& vertex = vertexMap.emplace(name, graph.addVertex(nextVertexID)).first->second; vertex.attrib().aron.vertexID = static_cast<long>(nextVertexID); toAron(vertex.attrib().aron.locationID, getLocationProviderSegmentID().withEntityName(name)); - vertex.attrib().aron.globalRobotPose = globalNodePose->toEigen(); + vertex.attrib().setPose(globalNodePose->toEigen()); nextVertexID++; } diff --git a/source/armarx/navigation/graph/Graph.cpp b/source/armarx/navigation/graph/Graph.cpp index 48c0f4dd..f34cb169 100644 --- a/source/armarx/navigation/graph/Graph.cpp +++ b/source/armarx/navigation/graph/Graph.cpp @@ -22,8 +22,14 @@ #include "Graph.h" +#include <armarx/navigation/location/aron/Location.aron.generated.h> + #include <ArmarXCore/core/exceptions/local/ExpressionException.h> +#include <RobotAPI/libraries/armem/core/aron_conversions.h> +#include <RobotAPI/libraries/armem/core/wm/memory_definitions.h> + + namespace armarx::nav::graph { @@ -33,14 +39,20 @@ namespace armarx::nav::graph return aron.locationID.providerSegmentName + "/" + aron.locationID.entityName; } + bool VertexAttribs::hasPose() const + { + return _pose.has_value(); + } + Eigen::Matrix4f VertexAttribs::getPose() const { - return aron.globalRobotPose; + ARMARX_CHECK(_pose.has_value()); + return _pose.value(); } void VertexAttribs::setPose(const Eigen::Matrix4f& pose) { - this->aron.globalRobotPose = pose; + this->_pose = pose; } } @@ -87,4 +99,22 @@ namespace armarx::nav ARMARX_CHECK_EQUAL(bo.numEdges(), dto.edges.size()); } + + void graph::resolveLocations(Graph& bo, armem::wm::Memory& locationMemory) + { + for (graph::Graph::Vertex vertex : bo.vertices()) + { + armem::MemoryID locationID; + fromAron(vertex.attrib().aron.locationID, locationID); + + armem::wm::EntityInstance* instance = locationMemory.findLatestInstance(locationID); + if (instance) + { + nav::loc::arondto::Location dto; + dto.fromAron(instance->data()); + vertex.attrib().setPose(dto.globalRobotPose); + } + } + } + } diff --git a/source/armarx/navigation/graph/Graph.h b/source/armarx/navigation/graph/Graph.h index d895dd63..aa216d69 100644 --- a/source/armarx/navigation/graph/Graph.h +++ b/source/armarx/navigation/graph/Graph.h @@ -24,6 +24,8 @@ #include <armarx/navigation/graph/aron/Graph.aron.generated.h> +#include <RobotAPI/libraries/armem/core/forward_declarations.h> + #include <SemanticObjectRelations/RelationGraph/RelationGraph.h> @@ -36,13 +38,21 @@ namespace armarx::nav::graph std::string getName() const; + bool hasPose() const; Eigen::Matrix4f getPose() const; void setPose(const Eigen::Matrix4f& pose); + + private: + + std::optional<Eigen::Matrix4f> _pose; + }; + struct EdgeAttribs { armarx::nav::graph::arondto::Edge aron; }; + struct GraphAttribs { }; @@ -52,5 +62,6 @@ namespace armarx::nav::graph void toAron(arondto::Graph& dto, const Graph& bo); void fromAron(const arondto::Graph& dto, Graph& bo); + void resolveLocations(Graph& bo, armem::wm::Memory& locationMemory); } diff --git a/source/armarx/navigation/graph/aron/Graph.xml b/source/armarx/navigation/graph/aron/Graph.xml index 18e1c847..fe09dcc0 100644 --- a/source/armarx/navigation/graph/aron/Graph.xml +++ b/source/armarx/navigation/graph/aron/Graph.xml @@ -19,9 +19,10 @@ <armarx::armem::arondto::MemoryID /> </ObjectChild> - <ObjectChild key='globalRobotPose'> + <!-- Only stored in Location --> + <!--ObjectChild key='globalRobotPose'> <Pose /> - </ObjectChild> + </ObjectChild--> </Object> diff --git a/source/armarx/navigation/gui-plugins/LocationGraphEditor/CMakeLists.txt b/source/armarx/navigation/gui-plugins/LocationGraphEditor/CMakeLists.txt index 0820ee1b..e345f39d 100644 --- a/source/armarx/navigation/gui-plugins/LocationGraphEditor/CMakeLists.txt +++ b/source/armarx/navigation/gui-plugins/LocationGraphEditor/CMakeLists.txt @@ -20,6 +20,7 @@ set(SOURCES widgets/default_colors.cpp widgets/utils.cpp widgets/EdgeTableWidget.cpp + widgets/NewEntityIdDialog.cpp widgets/VertexDataWidget.cpp widgets/VertexTableWidget.cpp @@ -37,6 +38,7 @@ set(HEADERS widgets/default_colors.h widgets/utils.h widgets/EdgeTableWidget.h + widgets/NewEntityIdDialog.h widgets/VertexDataWidget.h widgets/VertexTableWidget.h diff --git a/source/armarx/navigation/gui-plugins/LocationGraphEditor/LocationGraphEditorWidget.ui b/source/armarx/navigation/gui-plugins/LocationGraphEditor/LocationGraphEditorWidget.ui index 161687fa..88fa0f4c 100644 --- a/source/armarx/navigation/gui-plugins/LocationGraphEditor/LocationGraphEditorWidget.ui +++ b/source/armarx/navigation/gui-plugins/LocationGraphEditor/LocationGraphEditorWidget.ui @@ -15,18 +15,28 @@ </property> <layout class="QVBoxLayout" name="verticalLayout_2"> <item> - <widget class="QGroupBox" name="sceneGroupBox"> + <widget class="QGroupBox" name="graphGroupBox"> <property name="title"> <string>Navigation Graphs from Navigation Graph Segment</string> </property> <layout class="QHBoxLayout" name="horizontalLayout_4"> <item> - <widget class="QPushButton" name="refreshGraphsButton"> + <widget class="QPushButton" name="createGraphButton"> <property name="toolTip"> <string>Reloads the list of scenes from the memory</string> </property> <property name="text"> - <string>Refresh List</string> + <string>Create New Graph</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="queryGraphsButton"> + <property name="toolTip"> + <string>Reloads the list of scenes from the memory</string> + </property> + <property name="text"> + <string>Query</string> </property> </widget> </item> @@ -110,6 +120,14 @@ </layout> </widget> </item> + <item> + <widget class="QGroupBox" name="robotVisuGroupBox"> + <property name="title"> + <string>Robot Visu</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_5"/> + </widget> + </item> </layout> </widget> <widget class="QWidget" name="verticalLayout_13Widget"> @@ -117,7 +135,7 @@ <item> <widget class="QGroupBox" name="locationsTableGroupBox"> <property name="title"> - <string>Locations (Graph Vertices)</string> + <string>Locations (right click for options)</string> </property> <layout class="QVBoxLayout" name="verticalLayout_7"> <property name="leftMargin"> @@ -142,7 +160,7 @@ <item> <widget class="QGroupBox" name="edgesTableGroupBox"> <property name="title"> - <string>Graph Edges</string> + <string>Graph Edges (right click for options)</string> </property> <layout class="QVBoxLayout" name="verticalLayout_8"> <property name="leftMargin"> diff --git a/source/armarx/navigation/gui-plugins/LocationGraphEditor/LocationGraphEditorWidgetController.cpp b/source/armarx/navigation/gui-plugins/LocationGraphEditor/LocationGraphEditorWidgetController.cpp index 3f4a7113..abe9388f 100644 --- a/source/armarx/navigation/gui-plugins/LocationGraphEditor/LocationGraphEditorWidgetController.cpp +++ b/source/armarx/navigation/gui-plugins/LocationGraphEditor/LocationGraphEditorWidgetController.cpp @@ -23,8 +23,9 @@ #include <VirtualRobot/VirtualRobot.h> #include "LocationGraphEditorWidgetController.h" -#include "Visu.h" +#include "widgets/utils.h" #include "widgets/EdgeTableWidget.h" +#include "widgets/NewEntityIdDialog.h" #include "widgets/VertexDataWidget.h" #include "widgets/VertexTableWidget.h" #include "widgets/graph_scene/Scene.h" @@ -42,8 +43,7 @@ #include <RobotAPI/components/ArViz/Client/Client.h> #include <ArmarXCore/core/exceptions/local/ExpressionException.h> - -#include <SimoxUtility/color/Color.h> +#include <ArmarXCore/core/logging/Logging.h> // Qt headers #include <QDialog> @@ -67,30 +67,6 @@ static const QString SETTING_LAST_SCENE = "lastScene"; namespace armarx::nav::locgrapheditor { - - /** - * @brief Returns the name used on the debug layer. - * @param edge The edge. - * @return The name used on the debug layer. - */ - std::string iceName(const GuiGraph::Edge& edge) - { - std::stringstream ss; - ss << "edge " << std::to_string(edge.sourceObjectID()) << " -> " << std::to_string(edge.targetObjectID()); - return ss.str(); - } - - /** - * @brief iceName Returns the name used on the debug layer. - * @param vertexName The vertex. - * @return The name used on the debug layer. - */ - std::string iceName(const GuiGraph::Vertex& vertex) - { - return std::to_string(vertex.objectID()); - } - - QString LocationGraphEditorWidgetController::GetWidgetName() { return "Navigation.LocationGraphEditor"; @@ -110,31 +86,49 @@ namespace armarx::nav::locgrapheditor widget.loadGraphButton->setEnabled(false); - view.edgeTable = new EdgeTableWidget(); - view.vertexTable = new VertexTableWidget(); view.vertexData = new VertexDataWidget(); view.vertexData->setEnabled(false); // Enable on first selection of vertex. - widget.edgesTableGroupBox->layout()->addWidget(view.edgeTable); - widget.locationsTableGroupBox->layout()->addWidget(view.vertexTable); + view.vertexTable = new VertexTableWidget(); + view.edgeTable = new EdgeTableWidget(); + widget.locationDataGroupBox->layout()->addWidget(view.vertexData); + if (QBoxLayout* layout = dynamic_cast<QBoxLayout*>(widget.locationDataGroupBox->layout())) + { + layout->addStretch(); + } + + widget.locationsTableGroupBox->layout()->addWidget(view.vertexTable); + widget.edgesTableGroupBox->layout()->addWidget(view.edgeTable); view.graph = new GraphSceneWidget(); widget.graphSceneLayout->addWidget(view.graph); - // Widgets -> This - - // Memory Access connect(this, &This::connected, this, &This::queryGraphs); - connect(this, &This::memoryDataChanged, this, &This::updateGraphList); - connect(widget.refreshGraphsButton, &QPushButton::pressed, this, &This::updateGraphList); - connect(widget.loadGraphButton, &QPushButton::pressed, this, &This::loadGraph); - connect(widget.commitGraphButton, &QPushButton::pressed, this, &This::commit); + connect(widget.createGraphButton, &QPushButton::pressed, + this, &This::createGraphDialog); + + connect(widget.queryGraphsButton, &QPushButton::pressed, + this, &This::queryGraphs); + connect(this, &This::locationMemoryChanged, + this, &This::memoryChanged); + connect(this, &This::graphMemoryChanged, + this, &This::memoryChanged); - // Update views - connect(this, &This::graphChanged, this, &This::updateGraphView); + connect(this, &This::graphMemoryChanged, + this, &This::updateGraphList); + + connect(widget.loadGraphButton, &QPushButton::pressed, + this, &This::loadGraph); + connect(widget.commitGraphButton, &QPushButton::pressed, + this, &This::commit); + + + // View updates + connect(this, &This::graphChanged, + this, &This::updateGraphView); connect(view.vertexData, &VertexDataWidget::vertexDataChanged, this, &This::updateGraphView); @@ -151,8 +145,15 @@ namespace armarx::nav::locgrapheditor connect(view.edgeTable, &EdgeTableWidget::itemSelectionChanged, this, &This::updateEdgeHighlighting); + // Graph modification + + connect(view.vertexTable, &VertexTableWidget::newVertexRequested, + this, &This::createVertexDialog); connect(view.vertexTable, &VertexTableWidget::newEdgesRequested, this, &This::addEdges); + connect(view.vertexTable, &VertexTableWidget::edgeRemovalRequested, + this, &This::removeEdgesOfVertex); + connect(view.edgeTable, &EdgeTableWidget::edgeRemovalRequested, this, &This::removeEdges); } @@ -166,18 +167,18 @@ namespace armarx::nav::locgrapheditor QPointer<QDialog> LocationGraphEditorWidgetController::getConfigDialog(QWidget* parent) { - if (not dialog) + if (not configDialog) { - dialog = new SimpleConfigDialog(parent); - dialog->addProxyFinder<armem::mns::MemoryNameSystemInterfacePrx>("MemoryNameSystem", "Memory Name System", remote.memoryNameSystemName); + configDialog = new SimpleConfigDialog(parent); + configDialog->addProxyFinder<armem::mns::MemoryNameSystemInterfacePrx>("MemoryNameSystem", "Memory Name System", remote.memoryNameSystemName); } - return qobject_cast<SimpleConfigDialog*>(dialog); + return qobject_cast<SimpleConfigDialog*>(configDialog); } void LocationGraphEditorWidgetController::configured() { - remote.memoryNameSystemName = dialog->getProxyName("MemoryNameSystem"); + remote.memoryNameSystemName = configDialog->getProxyName("MemoryNameSystem"); } @@ -217,8 +218,8 @@ namespace armarx::nav::locgrapheditor { std::stringstream ss; ss << "Navigation Graphs (Entities from Core Segment " << nav::graph::coreSegmentID << ")"; - widget.sceneGroupBox->setTitle(QString::fromStdString(ss.str())); - widget.sceneGroupBox->setEnabled(true); + widget.graphGroupBox->setTitle(QString::fromStdString(ss.str())); + widget.graphGroupBox->setEnabled(true); } emit connected(); @@ -240,18 +241,49 @@ namespace armarx::nav::locgrapheditor } - void LocationGraphEditorWidgetController::queryGraphs() + void LocationGraphEditorWidgetController::queryMemory() { - armem::client::QueryResult result = remote.graphReader.getLatestSnapshotsIn(nav::graph::coreSegmentID); + armem::client::QueryResult locResult = queryLocations(); + armem::client::QueryResult graphResult = queryGraphs(); + + if (not (locResult.success and graphResult.success)) + { + QStringList status; + std::stringstream ss; + if (not locResult.success) + { + status.append(QString::fromStdString(locResult.errorMessage)); + } + if (not graphResult.success) + { + status.append(QString::fromStdString(graphResult.errorMessage)); + } + widget.statusLabel->setText(status.join("\n")); + } + } + + + armem::client::QueryResult LocationGraphEditorWidgetController::queryLocations() + { + armem::client::QueryResult result = remote.locationReader.getLatestSnapshotsIn(nav::loc::coreSegmentID); if (result.success) { - model.memory = std::move(result.memory); - emit memoryDataChanged(); + model.locationsMemory = std::move(result.memory); + emit locationMemoryChanged(); } - else + return result; + } + + + armem::client::QueryResult LocationGraphEditorWidgetController::queryGraphs() + { + armem::client::QueryResult result = remote.graphReader.getLatestSnapshotsIn(nav::graph::coreSegmentID); + if (result.success) { - widget.statusLabel->setText(QString::fromStdString(result.errorMessage)); + model.graphMemory = std::move(result.memory); + emit graphMemoryChanged(); } + return result; } @@ -262,7 +294,7 @@ namespace armarx::nav::locgrapheditor int i = 0; int previousIndex = -1; // To keep selection. - model.memory.forEachEntity([&](const armem::wm::Entity& entity) + model.graphMemory.forEachEntity([&](const armem::wm::Entity& entity) { bool hasChanged = (entity.id() == model.graphEntityID ? model.graph.hasChanged() @@ -289,34 +321,9 @@ namespace armarx::nav::locgrapheditor { if (model.graph.hasChanged()) { - QMessageBox msgBox; - msgBox.setText("The current graph and/or locations have uncommitted changes. "); - msgBox.setInformativeText("Do you want to discard them?"); - QStringList detailLines; - if (model.graph.attrib().edgesChanged) - { - detailLines.append("Graph edges have changed."); - } - for (auto vertex : model.graph.vertices()) - { - if (vertex.attrib().changed) - { - detailLines.append("Location " + QString::fromStdString(vertex.attrib().getName()) + " has changed."); - } - } - msgBox.setDetailedText(detailLines.join("\n")); - msgBox.setStandardButtons(QMessageBox::Discard | QMessageBox::Cancel); - msgBox.setDefaultButton(QMessageBox::Cancel); - - int ret = msgBox.exec(); - switch (ret) + if (not loadGraphDialog()) { - case QMessageBox::Discard: - // Ok go. - break; - case QMessageBox::Cancel: - // Abort loading. - return; + return; } } @@ -324,9 +331,9 @@ namespace armarx::nav::locgrapheditor widget.graphsComboBox->currentData().toString().toStdString()); // Refresh local memory. - queryGraphs(); + queryMemory(); - const armem::wm::EntityInstance* instance = model.memory.findLatestInstance(entityID); + const armem::wm::EntityInstance* instance = model.graphMemory.findLatestInstance(entityID); if (instance) { widget.statusLabel->setText(QString::fromStdString("Loaded snapshot " + instance->id().getEntitySnapshotID().str())); @@ -341,9 +348,32 @@ namespace armarx::nav::locgrapheditor nav::graph::Graph nav; { nav::graph::arondto::Graph dto; + ARMARX_CHECK_NOT_NULL(instance->data()); dto.fromAron(instance->data()); fromAron(dto, nav); } + // Resolve locations and remove vertices which could not be resolved. + { + resolveLocations(nav, model.locationsMemory); + std::vector<nav::graph::Graph::Vertex> remove; + for (nav::graph::Graph::Vertex vertex : nav.vertices()) + { + if (not vertex.attrib().hasPose()) + { + remove.push_back(vertex); + } + } + if (not remove.empty()) + { + for (nav::graph::Graph::Vertex vertex : remove) + { + nav.removeVertex(vertex.objectID()); + } + std::stringstream ss; + ss << "Dropped " << remove.size() << " locations which could not be resolved."; + widget.statusLabel->setText(QString::fromStdString(ss.str())); + } + } model.graphEntityID = entityID; ARMARX_VERBOSE << "Loading graph " << nav.str(); @@ -351,6 +381,42 @@ namespace armarx::nav::locgrapheditor } + + bool LocationGraphEditorWidgetController::loadGraphDialog() + { + ARMARX_CHECK(model.graph.hasChanged()); + + QMessageBox msgBox; + msgBox.setText("The current graph and/or locations have uncommitted changes. "); + msgBox.setInformativeText("Do you want to discard them?"); + QStringList detailLines; + if (model.graph.attrib().edgesChanged) + { + detailLines.append("Graph edges have changed."); + } + for (auto vertex : model.graph.vertices()) + { + if (vertex.attrib().changed) + { + detailLines.append("Location " + QString::fromStdString(vertex.attrib().getName()) + " has changed."); + } + } + msgBox.setDetailedText(detailLines.join("\n")); + msgBox.setStandardButtons(QMessageBox::Discard | QMessageBox::Cancel); + msgBox.setDefaultButton(QMessageBox::Cancel); + + switch (msgBox.exec()) + { + case QMessageBox::Discard: + return true; // Ok go. + case QMessageBox::Cancel: + return false; // Abort loading. + default: + return false; // Something went wrong. + } + } + + void LocationGraphEditorWidgetController::commit() { @@ -460,7 +526,7 @@ namespace armarx::nav::locgrapheditor } - void LocationGraphEditorWidgetController::setGraph(graph::Graph& nav) + void LocationGraphEditorWidgetController::setGraph(const graph::Graph& nav) { // Build the gui graph (model). { @@ -471,11 +537,14 @@ namespace armarx::nav::locgrapheditor for (auto vertex : nav.vertices()) { - addVertex(vertex); + ARMARX_CHECK(vertex.attrib().hasPose()); + addVertex(vertex.objectID(), { vertex.attrib() }); } for (auto edge : nav.edges()) { - addEdge(edge); + addEdge(model.graph.vertex(edge.sourceObjectID()), + model.graph.vertex(edge.targetObjectID()), + { edge.attrib() }); } } @@ -484,25 +553,19 @@ namespace armarx::nav::locgrapheditor } - GuiGraph::Vertex - LocationGraphEditorWidgetController::addVertex(graph::Graph::ConstVertex vertex) + GuiGraph::Vertex LocationGraphEditorWidgetController::addVertex( + semrel::ShapeID vertexID, + const VertexData& defaultAttribs) { - ARMARX_CHECK(not model.graph.hasVertex(vertex.objectID())); + ARMARX_CHECK(not model.graph.hasVertex(vertexID)); - VertexData attrib { vertex.attrib() }; - attrib.graphicsItem = view.graph->scene()->addVertex(vertex); - attrib.tableWidgetItem = view.vertexTable->addVertex(vertex); - return model.graph.addVertex(vertex.objectID(), attrib); - } + GuiGraph::Vertex vertex = model.graph.addVertex(vertexID, defaultAttribs); + vertex.attrib().graphicsItem = view.graph->scene()->addVertex(vertex); + vertex.attrib().tableWidgetItem = view.vertexTable->addVertex(); + emit graphChanged(); - GuiGraph::Edge - LocationGraphEditorWidgetController::addEdge(graph::Graph::ConstEdge edge) - { - auto source = model.graph.vertex(edge.sourceObjectID()); - auto target = model.graph.vertex(edge.targetObjectID()); - - return addEdge(source, target, { edge.attrib() }); + return vertex; } @@ -520,68 +583,10 @@ namespace armarx::nav::locgrapheditor GuiGraph::Edge edge = model.graph.addEdge(source, target, defaultAttribs); edge.attrib().tableWidgetItem = view.edgeTable->addEdge(edge); edge.attrib().graphicsItem = view.graph->scene()->addEdge(edge); - return edge; - } - - - void LocationGraphEditorWidgetController::addEdges( - QList<QPair<QTableWidgetItem*, QTableWidgetItem*>> vertexItems) - { - if (/* DISABLES CODE */ (false)) - { - std::stringstream ss; - ss << "Adding edges ..."; - for (const auto& [source, target] : vertexItems) - { - ss << "\n- " << source->text() << " to " << target->text(); - } - ARMARX_IMPORTANT << ss.str(); - } - - const auto itemToVertexMap = model.graph.getTableItemToVertexMap(); - for (const auto& [sourceItem, targetItem] : vertexItems) - { - GuiGraph::Vertex source = itemToVertexMap.at(sourceItem); - GuiGraph::Vertex target = itemToVertexMap.at(targetItem); - if (not model.graph.hasEdge(source, target)) - { - addEdge(source, target, {}); - } - } - - model.graph.attrib().edgesChanged = true; emit graphChanged(); - } - - - void LocationGraphEditorWidgetController::removeEdges( - QList<QTableWidgetItem*> edgeItems) - { - if (/* DISABLES CODE */ (false)) - { - std::stringstream ss; - ss << "Remoiving edges ..."; - for (const auto& edgeItem : edgeItems) - { - ss << "\n- " << edgeItem->text(); - } - ARMARX_IMPORTANT << ss.str(); - } - { - QSignalBlocker blocker(this); - - const auto itemToEdgeMap = model.graph.getTableItemToEdgeMap(); - for (const auto& edgeItem : edgeItems) - { - GuiGraph::Edge edge = itemToEdgeMap.at(edgeItem); - removeEdge(edge); - } - } - model.graph.attrib().edgesChanged = true; - - emit graphChanged(); + return edge; } @@ -606,8 +611,7 @@ namespace armarx::nav::locgrapheditor } - void - LocationGraphEditorWidgetController::updateVertexView(GuiGraph::Vertex vertex) + void LocationGraphEditorWidgetController::updateVertexView(GuiGraph::Vertex vertex) { view.graph->scene()->updateVertex(vertex); view.vertexTable->updateVertex(vertex); @@ -653,14 +657,68 @@ namespace armarx::nav::locgrapheditor { if (remote.arviz) { - viz::Layer layer = remote.arviz->layer(widget.graphsComboBox->currentText().toStdString()); - GraphVisu visu; - visu.draw(layer, model.graph); + const QString layerName = getGraphDisplayName(model.graphEntityID, false); + viz::Layer layer = remote.arviz->layer(layerName.toStdString()); + model.visu.draw(layer, model.graph); remote.arviz->commit(layer); } } + template <class T> + static + void updateElementHighlighting( + QList<QTableWidgetItem*> selectedItems, + std::map<QTableWidgetItem*, T>&& itemToElementMap) + { + for (QTableWidgetItem* selected : selectedItems) + { + if (auto it = itemToElementMap.find(selected); it != itemToElementMap.end()) + { + it->second.attrib().highlighted = true; + itemToElementMap.erase(it); + } + } + for (auto& [_, unselected] : itemToElementMap) + { + unselected.attrib().highlighted = false; + } + } + + + void LocationGraphEditorWidgetController::updateVertexHighlighting() + { + updateElementHighlighting(view.vertexTable->selectedVertexItems(), model.graph.getTableItemToVertexMap()); + emit graphChanged(); + } + + + void LocationGraphEditorWidgetController::updateEdgeHighlighting() + { + updateElementHighlighting(view.edgeTable->selectedEdgeItems(), model.graph.getTableItemToEdgeMap()); + emit graphChanged(); + } + + + void LocationGraphEditorWidgetController::selectVertex(QTableWidgetItem* vertexItem) + { + if (vertexItem == nullptr) + { + view.vertexData->clearVertex(); + } + if (auto vertex = model.graph.getVertexFromTableItem(vertexItem)) + { + selectVertex(vertex.value()); + } + } + + + void LocationGraphEditorWidgetController::selectVertex(GuiGraph::Vertex vertex) + { + view.vertexData->setVertex(vertex); + } + + void LocationGraphEditorWidgetController::clearGraph() { { @@ -681,10 +739,13 @@ namespace armarx::nav::locgrapheditor { // Remove in reverse order to be more array-friendly. std::vector<GuiGraph::Edge> edges { model.graph.edges().begin(), model.graph.edges().end() }; - for (auto it = edges.rbegin(); it != edges.rend(); ++it) { - GuiGraph::Edge edge = *it; - removeEdge(edge); + QSignalBlocker blocker(this); + for (auto it = edges.rbegin(); it != edges.rend(); ++it) + { + GuiGraph::Edge edge = *it; + removeEdge(edge); + } } ARMARX_CHECK_EQUAL(view.edgeTable->rowCount(), 0); @@ -701,10 +762,13 @@ namespace armarx::nav::locgrapheditor // Remove in reverse order to be more array-friendly. std::vector<GuiGraph::Vertex> vertices { model.graph.vertices().begin(), model.graph.vertices().end() }; - for (auto it = vertices.rbegin(); it != vertices.rend(); ++it) { - GuiGraph::Vertex vertex = *it; - removeVertex(vertex); + QSignalBlocker blocker(this); + for (auto it = vertices.rbegin(); it != vertices.rend(); ++it) + { + GuiGraph::Vertex vertex = *it; + removeVertex(vertex); + } } ARMARX_CHECK_EQUAL(view.vertexTable->rowCount(), 0); @@ -720,30 +784,35 @@ namespace armarx::nav::locgrapheditor << "Cannot remove edge that does not exist."; // Remove view elements + { + QSignalBlocker blocker(view.edgeTable); + view.edgeTable->removeEdge(edge); + } view.graph->scene()->removeEdge(edge.attrib().graphicsItem); - const int rows = view.edgeTable->rowCount(); - view.edgeTable->removeEdge(edge); - ARMARX_CHECK_EQUAL(view.edgeTable->rowCount(), rows - 1); - // Remove from model model.graph.removeEdge(edge); ARMARX_CHECK(not model.graph.hasEdge(edge.sourceDescriptor(), edge.targetDescriptor())) << edge.sourceDescriptor() << " -> " << edge.targetDescriptor(); + + emit graphChanged(); } void LocationGraphEditorWidgetController::removeVertex(GuiGraph::Vertex& vertex) { ARMARX_CHECK_EQUAL(vertex.inDegree(), 0) - << "A vertex may not have any edges before being removed. " << vertex.attrib().getName(); + << "A vertex must not have any edges before being removed. " << vertex.attrib().getName(); ARMARX_CHECK_EQUAL(vertex.outDegree(), 0) - << "A vertex may not have any edges before being removed. " << vertex.attrib().getName(); + << "A vertex must not have any edges before being removed. " << vertex.attrib().getName(); // Remove view elements + { + QSignalBlocker blocker(view.vertexTable); + view.vertexTable->removeVertex(vertex); + } view.graph->scene()->removeVertex(vertex.attrib().graphicsItem); - view.vertexTable->removeVertex(vertex); if (view.vertexData->vertex().has_value() and view.vertexData->vertex().value() == vertex) { view.vertexData->clearVertex(); @@ -751,81 +820,159 @@ namespace armarx::nav::locgrapheditor // Remove from model model.graph.removeVertex(vertex); + + emit graphChanged(); } - void LocationGraphEditorWidgetController::resetHighlighting() + void LocationGraphEditorWidgetController::createVertexDialog() { - for (auto vertex : model.graph.vertices()) + NewEntityIdDialog dialog(nav::loc::coreSegmentID, nullptr); + switch (dialog.exec()) { - if (vertex.attrib().highlighted) - { - vertex.attrib().highlighted = false; - } + case QDialog::Accepted: + // Ok go. + break; + case QDialog::Rejected: + // Abort creation. + return; } - for (auto edge : model.graph.edges()) + + // Find free vertex ID + semrel::ShapeID vertexID { 0 }; + while (model.graph.hasVertex(vertexID)) { - if (edge.attrib().highlighted) - { - edge.attrib().highlighted = false; - } + ++vertexID; } - emit graphChanged(); + // Initiaize attributes + VertexData attribs; + toAron(attribs.aron.locationID, dialog.entityID()); + attribs.setPose(Eigen::Matrix4f::Identity()); + attribs.changed = true; + + addVertex(vertexID, attribs); } - template <class T> - static - void updateElementHighlighting( - QList<QTableWidgetItem*> selectedItems, - std::map<QTableWidgetItem*, T>&& itemToElementMap) + void LocationGraphEditorWidgetController::addEdges( + QList<QPair<QTableWidgetItem*, QTableWidgetItem*>> vertexItems) { - for (QTableWidgetItem* selected : selectedItems) + const auto itemToVertexMap = model.graph.getTableItemToVertexMap(); { - if (auto it = itemToElementMap.find(selected); it != itemToElementMap.end()) + QSignalBlocker blocker(this); + + for (const auto& [sourceItem, targetItem] : vertexItems) { - it->second.attrib().highlighted = true; - itemToElementMap.erase(it); + GuiGraph::Vertex source = itemToVertexMap.at(sourceItem); + GuiGraph::Vertex target = itemToVertexMap.at(targetItem); + if (not model.graph.hasEdge(source, target)) + { + addEdge(source, target, {}); + } } } - for (auto& [_, unselected] : itemToElementMap) - { - unselected.attrib().highlighted = false; - } + + model.graph.attrib().edgesChanged = true; + emit graphChanged(); } - void LocationGraphEditorWidgetController::updateVertexHighlighting() + void LocationGraphEditorWidgetController::removeEdges( + QList<QTableWidgetItem*> edgeItems) { - updateElementHighlighting(view.vertexTable->selectedVertexItems(), model.graph.getTableItemToVertexMap()); + const auto itemToEdgeMap = model.graph.getTableItemToEdgeMap(); + { + QSignalBlocker blocker(this); + + for (const auto& edgeItem : edgeItems) + { + GuiGraph::Edge edge = itemToEdgeMap.at(edgeItem); + removeEdge(edge); + } + } + + model.graph.attrib().edgesChanged = true; emit graphChanged(); } - void LocationGraphEditorWidgetController::updateEdgeHighlighting() + void LocationGraphEditorWidgetController::removeEdgesOfVertex( + QList<QTableWidgetItem*> vertexItems, + utils::EdgeDirection direction) { - updateElementHighlighting(view.edgeTable->selectedEdgeItems(), model.graph.getTableItemToEdgeMap()); + auto removeEdges = [this](auto&& range) + { + for (auto edge : range) + { + removeEdge(edge); + } + }; + { + QSignalBlocker blocker(this); + + const auto itemToVertexMap = model.graph.getTableItemToVertexMap(); + for (const auto& vertexItem : vertexItems) + { + GuiGraph::Vertex vertex = itemToVertexMap.at(vertexItem); + switch (direction) + { + case utils::EdgeDirection::To: + removeEdges(vertex.inEdges()); + break; + + case utils::EdgeDirection::From: + removeEdges(vertex.outEdges()); + break; + + case utils::EdgeDirection::Bidirectional: + removeEdges(vertex.inEdges()); + removeEdges(vertex.outEdges()); + break; + } + } + } + + model.graph.attrib().edgesChanged = true; emit graphChanged(); } - void LocationGraphEditorWidgetController::selectVertex(QTableWidgetItem* vertexItem) + void LocationGraphEditorWidgetController::createGraphDialog() { - if (vertexItem == nullptr) + NewEntityIdDialog dialog(nav::graph::coreSegmentID, nullptr); + switch (dialog.exec()) { - view.vertexData->clearVertex(); + case QDialog::Accepted: + break; + case QDialog::Rejected: + return; } - if (auto vertex = model.graph.getVertexFromTableItem(vertexItem)) + + // Find free vertex ID + semrel::ShapeID vertexID { 0 }; + while (model.graph.hasVertex(vertexID)) { - selectVertex(vertex.value()); + ++vertexID; } - } + const armem::MemoryID entityID = dialog.entityID(); - void LocationGraphEditorWidgetController::selectVertex(GuiGraph::Vertex vertex) - { - view.vertexData->setVertex(vertex); + const bool hasChanged = true; + QString text = getGraphDisplayName(entityID, hasChanged); + QString data = QString::fromStdString(entityID.str()); + widget.graphsComboBox->addItem(text, data); + widget.graphsComboBox->setCurrentIndex(widget.graphsComboBox->count() - 1); + + { + QSignalBlocker blocker(this); + clearEdges(); + // clearVertices(); + } + model.graph.attrib().edgesChanged = true; + model.graphEntityID = entityID; + + emit graphChanged(); } diff --git a/source/armarx/navigation/gui-plugins/LocationGraphEditor/LocationGraphEditorWidgetController.h b/source/armarx/navigation/gui-plugins/LocationGraphEditor/LocationGraphEditorWidgetController.h index e4de7136..32ae0ccd 100644 --- a/source/armarx/navigation/gui-plugins/LocationGraphEditor/LocationGraphEditorWidgetController.h +++ b/source/armarx/navigation/gui-plugins/LocationGraphEditor/LocationGraphEditorWidgetController.h @@ -21,8 +21,8 @@ */ #pragma once -#include "FunctionalEventFilter.h" #include "GuiGraph.h" +#include "Visu.h" #include "widgets/graph_scene.h" #include <armarx/navigation/graph/Graph.h> @@ -52,6 +52,10 @@ namespace armarx::viz { class Client; } +namespace armarx::nav::locgrapheditor::utils +{ + enum class EdgeDirection; +} namespace armarx::nav::locgrapheditor { class EdgeTableWidget; @@ -127,59 +131,73 @@ namespace armarx::nav::locgrapheditor signals: void connected(); - void memoryDataChanged(); + + void locationMemoryChanged(); + void graphMemoryChanged(); + void memoryChanged(); + void graphChanged(); private slots: - // Model + // Load + void queryMemory(); + armem::client::QueryResult queryLocations(); + armem::client::QueryResult queryGraphs(); - void queryGraphs(); - void updateGraphList(); void loadGraph(); + bool loadGraphDialog(); - void setGraph(graph::Graph& nav); - GuiGraph::Vertex addVertex(graph::Graph::ConstVertex vertex); - GuiGraph::Edge addEdge(graph::Graph::ConstEdge edge); - GuiGraph::Edge addEdge(GuiGraph::ConstVertex source, GuiGraph::ConstVertex target, - const EdgeData& defaultAttribs); - - void clearGraph(); - void clearEdges(); - void clearVertices(); - - void removeEdge(GuiGraph::Edge& edge); - void removeVertex(GuiGraph::Vertex& vertex); - - void addEdges(QList<QPair<QTableWidgetItem*, QTableWidgetItem*>> vertexItems); - void removeEdges(QList<QTableWidgetItem*> edgeItems); - - + // Commit void commit(); armem::CommitResult commitLocations(); armem::EntityUpdateResult commitGraph(); - // View & Tables + // View & Tables + void updateGraphList(); void updateGraphView(); void updateVertexView(GuiGraph::Vertex vertex); void updateEdgeView(GuiGraph::Edge edge); void updateArViz(); - // Selection - + // Selection & Highlighting void selectVertex(QTableWidgetItem* vertexItem); void selectVertex(GuiGraph::Vertex vertex); - - void resetHighlighting(); void updateVertexHighlighting(); void updateEdgeHighlighting(); + // Graph modification triggered by user interaction + void createVertexDialog(); + + void addEdges(QList<QPair<QTableWidgetItem*, QTableWidgetItem*>> vertexItems); + void removeEdges(QList<QTableWidgetItem*> edgeItems); + void removeEdgesOfVertex(QList<QTableWidgetItem*> vertexItems, utils::EdgeDirection direction); + + void createGraphDialog(); + + private: + void setGraph(const graph::Graph& nav); + + GuiGraph::Vertex + addVertex(semrel::ShapeID vertexID, const VertexData& defaultAttribs); + + GuiGraph::Edge + addEdge(GuiGraph::ConstVertex source, GuiGraph::ConstVertex target, const EdgeData& defaultAttribs); + + void removeVertex(GuiGraph::Vertex& vertex); + void removeEdge(GuiGraph::Edge& edge); + + void clearGraph(); + void clearEdges(); + void clearVertices(); + + QString getGraphDisplayName(const armem::MemoryID& entityID, bool changed = false) const; @@ -187,7 +205,7 @@ namespace armarx::nav::locgrapheditor /// Widget Form Ui::LocationGraphEditorWidget widget; - QPointer<SimpleConfigDialog> dialog; + QPointer<SimpleConfigDialog> configDialog; struct Remote @@ -210,9 +228,13 @@ namespace armarx::nav::locgrapheditor struct Model { - armem::wm::Memory memory; + armem::wm::Memory locationsMemory; + + armem::wm::Memory graphMemory; armem::MemoryID graphEntityID; GuiGraph graph; + + GraphVisu visu; }; Model model; @@ -228,30 +250,6 @@ namespace armarx::nav::locgrapheditor View view; - // Non-refactored - -#if 0 - private slots: - - void tableWidgetVerticesCustomContextMenu(QPoint pos); - void tableWidgetEdgesCustomContextMenu(QPoint pos); - - - private slots: - - /// Add kitchen graph (H2T Armar3a robot kitchen) - void addKitchenGraph(); - - bool addNewEdge(const std::string& from, const std::string& to); - void addNewEdgeBoth(); - void addNewEdgeStartEnd(); - void addNewEdgeEndStart(); - - void addNewGraphVertex(); - void editGraphVertex(); -#endif - - private: QSettings settings; diff --git a/source/armarx/navigation/gui-plugins/LocationGraphEditor/widgets/EdgeTableWidget.cpp b/source/armarx/navigation/gui-plugins/LocationGraphEditor/widgets/EdgeTableWidget.cpp index adecbca4..93d87392 100644 --- a/source/armarx/navigation/gui-plugins/LocationGraphEditor/widgets/EdgeTableWidget.cpp +++ b/source/armarx/navigation/gui-plugins/LocationGraphEditor/widgets/EdgeTableWidget.cpp @@ -100,9 +100,15 @@ namespace armarx::nav::locgrapheditor } - void EdgeTableWidget::removeEdge(GuiGraph::Edge& edge) + void + EdgeTableWidget::removeEdge(GuiGraph::Edge& edge) { - this->removeRow(row(edge.attrib().tableWidgetItem)); + if (currentItem() == edge.attrib().tableWidgetItem) + { + setCurrentItem(nullptr); + } + + removeRow(row(edge.attrib().tableWidgetItem)); edge.attrib().tableWidgetItem = nullptr; } @@ -114,7 +120,8 @@ namespace armarx::nav::locgrapheditor } - void EdgeTableWidget::makeContextMenu(QPoint pos) + void + EdgeTableWidget::makeContextMenu(QPoint pos) { QList<QTableWidgetItem*> items = selectedEdgeItems(); diff --git a/source/armarx/navigation/gui-plugins/LocationGraphEditor/widgets/NewEntityIdDialog.cpp b/source/armarx/navigation/gui-plugins/LocationGraphEditor/widgets/NewEntityIdDialog.cpp new file mode 100644 index 00000000..381556b4 --- /dev/null +++ b/source/armarx/navigation/gui-plugins/LocationGraphEditor/widgets/NewEntityIdDialog.cpp @@ -0,0 +1,129 @@ +/* + * This file is part of ArmarX. + * + * ArmarX is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * ArmarX is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * @author Rainer Kartmann ( rainer dot kartmann at kit dot edu ) + * @date 2021 + * @copyright http://www.gnu.org/licenses/gpl-2.0.txt + * GNU General Public License + */ + +#include "NewEntityIdDialog.h" + +#include <RobotAPI/libraries/armem/core/MemoryID.h> + +#include <ArmarXCore/core/exceptions/local/ExpressionException.h> + +#include <QDialogButtonBox> +#include <QGridLayout> +#include <QLineEdit> +#include <QLabel> +#include <QPushButton> +#include <QVBoxLayout> + + +namespace armarx::nav::locgrapheditor +{ + + NewEntityIdDialog::NewEntityIdDialog(const armem::MemoryID& coreSegmentID, QWidget* parent) : + QDialog(parent), + coreSegmentID(std::make_unique<armem::MemoryID>(coreSegmentID)) + { + _providerSegmentName = new QLineEdit(this); + _entityName = new QLineEdit(this); + + _buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok + | QDialogButtonBox::Cancel, this); + + QLabel* instruction = new QLabel("Enter provider segment name and entity name for new entity:"); + QFont font = instruction->font(); + font.setBold(true); + instruction->setFont(font); + + + QGridLayout* grid = new QGridLayout(); + int col = 0; + + grid->addWidget(new QLabel("Core Segment ID"), 0, col); + grid->addWidget(new QLabel(QString::fromStdString(coreSegmentID.str()) + "/"), 1, col); + ++col; + + grid->addWidget(new QLabel("Provider Segment Name"), 0, col); + grid->addWidget(_providerSegmentName, 1, col); + ++col; + + grid->addWidget(new QLabel("/"), 1, col); + ++col; + + grid->addWidget(new QLabel("Entity Name"), 0, col); + grid->addWidget(_entityName, 1, col); + ++col; + + + QVBoxLayout* layout = new QVBoxLayout(); + setLayout(layout); + layout->addWidget(instruction); + layout->addLayout(grid); + layout->addWidget(_buttonBox); + + + connect(_buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); + connect(_buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); + + auto enableOkIfReady = [this]() + { + _buttonBox->button(QDialogButtonBox::Ok) + ->setEnabled(_providerSegmentName->text().size() > 0 and _entityName->text().size() > 0); + }; + connect(_providerSegmentName, &QLineEdit::textChanged, this, enableOkIfReady); + connect(_entityName, &QLineEdit::textChanged, this, enableOkIfReady); + + enableOkIfReady(); + } + + + NewEntityIdDialog::~NewEntityIdDialog() + { + } + + + QString NewEntityIdDialog::providerSegmentName() const + { + return _providerSegmentName->text(); + } + + + QString NewEntityIdDialog::entityName() const + { + return _entityName->text(); + } + + + armem::MemoryID NewEntityIdDialog::entityIDWithoutCoreSegmentID() const + { + armem::MemoryID id; + id.providerSegmentName = providerSegmentName().toStdString(); + id.entityName = entityName().toStdString(); + return id; + } + + armem::MemoryID NewEntityIdDialog::entityID() const + { + armem::MemoryID entityID = entityIDWithoutCoreSegmentID(); + ARMARX_CHECK_NOT_NULL(coreSegmentID); + entityID.setCoreSegmentID(*coreSegmentID); + return entityID; + } + +} diff --git a/source/armarx/navigation/gui-plugins/LocationGraphEditor/widgets/NewEntityIdDialog.h b/source/armarx/navigation/gui-plugins/LocationGraphEditor/widgets/NewEntityIdDialog.h new file mode 100644 index 00000000..18659cda --- /dev/null +++ b/source/armarx/navigation/gui-plugins/LocationGraphEditor/widgets/NewEntityIdDialog.h @@ -0,0 +1,64 @@ +/* + * This file is part of ArmarX. + * + * ArmarX is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * ArmarX is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * @author Rainer Kartmann ( rainer dot kartmann at kit dot edu ) + * @date 2021 + * @copyright http://www.gnu.org/licenses/gpl-2.0.txt + * GNU General Public License + */ + +#pragma once + +#include <RobotAPI/libraries/armem/core/forward_declarations.h> + +#include <QDialog> + +#include <memory> + + +class QLineEdit; +class QDialogButtonBox; + + +namespace armarx::nav::locgrapheditor +{ + class NewEntityIdDialog : public QDialog + { + public: + + NewEntityIdDialog(const armem::MemoryID& coreSegmentID, QWidget* parent = nullptr); + virtual ~NewEntityIdDialog() override; + + QString providerSegmentName() const; + QString entityName() const; + + + armem::MemoryID entityIDWithoutCoreSegmentID() const; + armem::MemoryID entityID() const; + + + private: + + QLineEdit* _providerSegmentName = nullptr; + QLineEdit* _entityName = nullptr; + + QDialogButtonBox* _buttonBox = nullptr; + + + std::unique_ptr<armem::MemoryID> coreSegmentID; + + }; + +} diff --git a/source/armarx/navigation/gui-plugins/LocationGraphEditor/widgets/VertexTableWidget.cpp b/source/armarx/navigation/gui-plugins/LocationGraphEditor/widgets/VertexTableWidget.cpp index 908ea14f..b1a5ceed 100644 --- a/source/armarx/navigation/gui-plugins/LocationGraphEditor/widgets/VertexTableWidget.cpp +++ b/source/armarx/navigation/gui-plugins/LocationGraphEditor/widgets/VertexTableWidget.cpp @@ -57,10 +57,8 @@ namespace armarx::nav::locgrapheditor QTableWidgetItem* - VertexTableWidget::addVertex(graph::Graph::ConstVertex vertex) + VertexTableWidget::addVertex() { - (void) vertex; - // We need to disable sorting to prevent the new row from being moved away. setSortingEnabled(false); @@ -142,12 +140,8 @@ namespace armarx::nav::locgrapheditor setCurrentItem(nullptr); } - const int numRows = rowCount(); - int row = this->row(vertex.attrib().tableWidgetItem); - removeRow(row); + removeRow(row(vertex.attrib().tableWidgetItem)); vertex.attrib().tableWidgetItem = nullptr; - - ARMARX_CHECK_EQUAL(rowCount(), numRows - 1); } @@ -158,20 +152,22 @@ namespace armarx::nav::locgrapheditor } - QString VertexTableWidget::_nameOf(QTableWidgetItem* item) + QString + VertexTableWidget::_nameOf(QTableWidgetItem* item) { return item->data(Qt::UserRole).toString(); } - void VertexTableWidget::makeContextMenu(QPoint pos) + void + VertexTableWidget::makeContextMenu(QPoint pos) { QList<QTableWidgetItem*> items = selectedVertexItems(); QMenu menu; if (items.size() == 0) { - QAction* action = menu.addAction("No locations selected"); + QAction* action = menu.addSection("No locations selected"); action->setEnabled(false); } @@ -229,7 +225,7 @@ namespace armarx::nav::locgrapheditor using ListOfEdges = QList<QPair<Item*, Item*>>; using AppendFunc = std::function<void(ListOfEdges& edges, Item* selected, Item* action)>; - auto addBulkActions = [this, &items](QMenu* submenu, AppendFunc appendFunc) + auto addBulkAddEdgeActions = [this, &items](QMenu* submenu, AppendFunc appendFunc) { if (items.size() == rowCount()) { @@ -242,8 +238,8 @@ namespace armarx::nav::locgrapheditor if (items.count(action) == 0) // Do no generate self-edges { QAction* a = submenu->addAction(_nameOf(action)); - connect(a, &QAction::triggered, - [this, &items, action, appendFunc]() + connect(a, &QAction::triggered, this, + [this, items, action, appendFunc]() { QList<QPair<QTableWidgetItem*, QTableWidgetItem*>> edges; for (auto* selected : items) @@ -256,24 +252,52 @@ namespace armarx::nav::locgrapheditor } }; - addBulkActions(menu.addMenu("Add " + edges + " " + desc + " " + utils::arrowRight + " ..."), + addBulkAddEdgeActions(menu.addMenu("Add " + edges + " " + desc + " " + utils::arrowRight + " ..."), [](ListOfEdges& edges, Item* selected, Item* action) { edges.append({selected, action}); }); - addBulkActions(menu.addMenu("Add " + edges + " " + desc + " " + utils::arrowLeft + " ..."), + addBulkAddEdgeActions(menu.addMenu("Add " + edges + " " + desc + " " + utils::arrowLeft + " ..."), [](ListOfEdges& edges, Item* selected, Item* action) { edges.append({action, selected}); }); - addBulkActions(menu.addMenu("Add " + edges + " " + desc + " " + utils::arrowBoth + " ..."), + addBulkAddEdgeActions(menu.addMenu("Add " + edges + " " + desc + " " + utils::arrowBoth + " ..."), [](ListOfEdges& edges, Item* selected, Item* action) { edges.append({selected, action}); edges.append({action, selected}); }); + + + auto connectBulkRemoveEdgeAction = [this, &items]( + QAction* action, utils::EdgeDirection edgeDirection) + { + connect(action, &QAction::triggered, this, [this, items, edgeDirection]() + { + emit edgeRemovalRequested(items, edgeDirection); + }); + }; + + connectBulkRemoveEdgeAction( + menu.addAction("Remove all edges " + utils::arrowRight + " " + desc), + utils::EdgeDirection::To); + connectBulkRemoveEdgeAction( + menu.addAction("Remove all edges " + utils::arrowLeft + " " + desc), + utils::EdgeDirection::From); + connectBulkRemoveEdgeAction( + menu.addAction("Remove all edges " + utils::arrowBoth + " " + desc), + utils::EdgeDirection::Bidirectional); } + + menu.addSection("Manage Locations"); + connect(menu.addAction("Create new location ..."), &QAction::triggered, this, + [this]() + { + emit newVertexRequested(); + }); + menu.exec(mapToGlobal(pos)); } diff --git a/source/armarx/navigation/gui-plugins/LocationGraphEditor/widgets/VertexTableWidget.h b/source/armarx/navigation/gui-plugins/LocationGraphEditor/widgets/VertexTableWidget.h index e782867f..9224c249 100644 --- a/source/armarx/navigation/gui-plugins/LocationGraphEditor/widgets/VertexTableWidget.h +++ b/source/armarx/navigation/gui-plugins/LocationGraphEditor/widgets/VertexTableWidget.h @@ -30,9 +30,12 @@ #include <QTableWidget> +namespace armarx::nav::locgrapheditor::utils +{ + enum class EdgeDirection; +} namespace armarx::nav::locgrapheditor { - class VertexTableWidget : public QTableWidget { Q_OBJECT @@ -44,7 +47,7 @@ namespace armarx::nav::locgrapheditor VertexTableWidget(); - QTableWidgetItem* addVertex(graph::Graph::ConstVertex vertex); + QTableWidgetItem* addVertex(); void updateVertex(GuiGraph::Vertex vertex); @@ -56,7 +59,10 @@ namespace armarx::nav::locgrapheditor signals: + void newVertexRequested(); + void newEdgesRequested(QList<QPair<QTableWidgetItem*, QTableWidgetItem*>> edges); + void edgeRemovalRequested(QList<QTableWidgetItem*> vertexItems, utils::EdgeDirection direction); public slots: diff --git a/source/armarx/navigation/gui-plugins/LocationGraphEditor/widgets/utils.cpp b/source/armarx/navigation/gui-plugins/LocationGraphEditor/widgets/utils.cpp index 1a101fff..80b75d16 100644 --- a/source/armarx/navigation/gui-plugins/LocationGraphEditor/widgets/utils.cpp +++ b/source/armarx/navigation/gui-plugins/LocationGraphEditor/widgets/utils.cpp @@ -37,6 +37,7 @@ namespace armarx::nav::locgrapheditor const QString utils::arrowBoth = QStringLiteral("\u21C4"); + QList<QTableWidgetItem*> utils::getSelectedItemsOfColumn(QTableWidget* widget, int column) { diff --git a/source/armarx/navigation/gui-plugins/LocationGraphEditor/widgets/utils.h b/source/armarx/navigation/gui-plugins/LocationGraphEditor/widgets/utils.h index ad866a66..bd1358b3 100644 --- a/source/armarx/navigation/gui-plugins/LocationGraphEditor/widgets/utils.h +++ b/source/armarx/navigation/gui-plugins/LocationGraphEditor/widgets/utils.h @@ -22,6 +22,7 @@ #pragma once #include <QList> +#include <QString> class QTableWidget; class QTableWidgetItem; @@ -30,9 +31,6 @@ class QTableWidgetItem; namespace armarx::nav::locgrapheditor::utils { - QList<QTableWidgetItem*> getSelectedItemsOfColumn(QTableWidget* widget, int column); - - /// <- extern const QString arrowLeft; /// -> @@ -41,4 +39,12 @@ namespace armarx::nav::locgrapheditor::utils extern const QString arrowBoth; + enum class EdgeDirection + { + From, To, Bidirectional + }; + + + QList<QTableWidgetItem*> getSelectedItemsOfColumn(QTableWidget* widget, int column); + } -- GitLab