WidgetController.cpp
Go to the documentation of this file.
1/*
2 * This file is part of ArmarX.
3 *
4 * ArmarX is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2 as
6 * published by the Free Software Foundation.
7 *
8 * ArmarX is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http:// www.gnu.org/licenses/>.
15 *
16 * \package Navigation::gui-plugins::WidgetController
17 * \author Rainer Kartmann ( rainer dot kartmann at kit dot edu )
18 * \date 2021
19 * \copyright http:// www.gnu.org/licenses/gpl-2.0.txt
20 * GNU General Public License
21 */
22
23#include "WidgetController.h"
24
25#include <map>
26#include <memory>
27#include <set>
28#include <sstream>
29#include <utility>
30#include <vector>
31
32#include <QDialog>
33#include <QHBoxLayout>
34#include <QLabel>
35#include <QLineEdit>
36#include <QMenu>
37#include <QMessageBox>
38#include <QMouseEvent>
39#include <QObject>
40#include <QPushButton>
41#include <QSignalBlocker>
42#include <qboxlayout.h>
43#include <qhashfunctions.h>
44#include <qicon.h>
45#include <qlist.h>
46#include <qmessagebox.h>
47#include <qobject.h>
48#include <qobjectdefs.h>
49#include <qpair.h>
50#include <qpointer.h>
51#include <qsettings.h>
52#include <qwidget.h>
53
57
59
62#include <RobotAPI/interface/armem/mns/MemoryNameSystemInterface.h>
72#include <RobotAPI/libraries/armem_locations/aron/Location.aron.generated.h>
76
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>
88
95#include "widgets/utils.h"
96#include <SemanticObjectRelations/Shapes/Shape.h>
97
98
99static const QString SETTING_LAST_SCENE = "lastScene";
100
102{
103
104 QString
106 {
107 return "Navigation.LocationGraphEditor";
108 }
109
110 QIcon
112 {
113 return QIcon{"://icons/location_graph_editor.svg"};
114 }
115
116 WidgetController::WidgetController() : settings{"KIT", "WidgetController"}
117 {
118 widget.setupUi(getWidget());
119
121
122 widget.loadGraphButton->setEnabled(false);
123
124 view.vertexData = new VertexDataWidget();
125 view.vertexData->setEnabled(false); // Enable on first selection of vertex.
126 widget.locationDataGroupBox->layout()->addWidget(view.vertexData);
127 if (QBoxLayout* layout = dynamic_cast<QBoxLayout*>(widget.locationDataGroupBox->layout()))
128 {
129 layout->addStretch();
130 }
131
132 view.vertexTable = new VertexTableWidget();
133 widget.locationsTableGroupBox->layout()->addWidget(view.vertexTable);
134
135 view.edgeTable = new EdgeTableWidget();
136 widget.edgesTableGroupBox->layout()->addWidget(view.edgeTable);
137 widget.edgesTableGroupBox->hide();
138
139 view.robotVisu = new RobotVisuWidget(*this);
140 widget.robotVisuGroupBox->layout()->addWidget(view.robotVisu);
141
142 view.objectPoses = new ObjectPoseClientWidget(*this);
143 widget.objectPoseClientGroupBox->layout()->addWidget(view.objectPoses);
144
145
146 connect(this, &This::connected, this, &This::queryGraphs);
147 connect(this, &This::connected, this, &This::graphChanged);
148
149 connect(widget.createGraphButton, &QPushButton::pressed, this, &This::createGraphDialog);
150
151 connect(widget.queryGraphsButton, &QPushButton::pressed, this, &This::queryGraphs);
152
153 connect(this, &This::locationMemoryChanged, this, &This::memoryChanged);
154 connect(this, &This::graphMemoryChanged, this, &This::memoryChanged);
155
156 connect(this, &This::graphMemoryChanged, this, &This::updateGraphList);
157
158 connect(widget.loadGraphButton, &QPushButton::pressed, this, &This::loadGraph);
159 connect(widget.commitGraphButton, &QPushButton::pressed, this, &This::commit);
160
161 // View updates
162 connect(this, &This::graphChanged, this, &This::updateGraphView);
163 connect(view.vertexData,
165 this,
166 &This::updateGraphView);
167 connect(view.robotVisu, &RobotVisuWidget::connected, this, &This::updateGraphView);
168 connect(view.robotVisu,
170 this,
171 [this]() { view.vertexData->setRobotConnection(&view.robotVisu->connection()); });
172 connect(view.robotVisu, &RobotVisuWidget::settingsChanged, this, &This::updateGraphView);
173
174 connect(view.objectPoses,
176 this,
177 [this]()
178 { view.vertexData->setObjectPoseConnection(&view.objectPoses->connection()); });
179
180 // Selection
181 connect(view.vertexTable,
182 &VertexTableWidget::currentItemChanged,
183 [this](QTableWidgetItem* current, QTableWidgetItem* previous)
184 {
185 (void)previous;
186 this->selectVertex(current);
187 });
188
189 connect(view.vertexTable,
190 &VertexTableWidget::itemSelectionChanged,
191 this,
192 &This::updateVertexHighlighting);
193 connect(view.edgeTable,
194 &EdgeTableWidget::itemSelectionChanged,
195 this,
196 &This::updateEdgeHighlighting);
197
198 // Graph modification
199 connect(view.vertexTable,
201 this,
202 &This::createVertexDialog);
203 connect(view.vertexTable, &VertexTableWidget::newEdgesRequested, this, &This::addEdges);
204 connect(view.vertexTable,
206 this,
207 &This::removeEdgesOfVertex);
208
209 connect(view.edgeTable, &EdgeTableWidget::edgeRemovalRequested, this, &This::removeEdges);
210
211 // export
212 connect(widget.exportButton, &QPushButton::clicked, this, &This::exportLocationGraph);
213 }
214
215 void
216 WidgetController::exportLocationGraph()
217 {
218 QString labelText;
219
220 auto navigationMemoryPrx =
221 memory::NavigationMemoryInterfacePrx::uncheckedCast(remote.locationReader.readingPrx);
222 if (navigationMemoryPrx)
223 {
224 const ::armarx::PackagePath packagePath(
225 widget.packageNameEdit->text().toStdString(),
226 widget.packageDirectoryEdit->text().toStdString());
227
228 navigationMemoryPrx->storeLocationGraph(packagePath.serialize());
229 labelText = "Exported locations and graph.";
230 }
231 else
232 {
233 labelText = "Export failed: The connected navigation memory does not implement the "
234 "NavigationMemoryInterface.";
235 }
236
237 widget.exportLabel->setText(labelText);
238 }
239
244
245 QPointer<QDialog>
247 {
248 if (not configDialog)
249 {
250 configDialog = new SimpleConfigDialog(parent);
251 configDialog->addProxyFinder<armem::mns::MemoryNameSystemInterfacePrx>(
252 "MemoryNameSystem",
253 "Memory Name System",
254 remote.memoryNameSystemName);
255 }
256 return qobject_cast<SimpleConfigDialog*>(configDialog);
257 }
258
259 void
261 {
262 remote.memoryNameSystemName = configDialog->getProxyName("MemoryNameSystem");
263 }
264
265 void
267 {
268 remote.memoryNameSystemName =
269 settings
270 ->value("memoryNameSystemName", QString::fromStdString(remote.memoryNameSystemName))
271 .toString()
272 .toStdString();
273 }
274
275 void
277 {
278 settings->setValue("memoryNameSystemName",
279 QString::fromStdString(remote.memoryNameSystemName));
280 }
281
282 void
284 {
285 lastSelectedSceneName =
286 settings.value(SETTING_LAST_SCENE, lastSelectedSceneName).toString();
287 }
288
289 void
291 {
292 settings.setValue(SETTING_LAST_SCENE, lastSelectedSceneName);
293 }
294
295 void
297 {
298 usingProxy(remote.memoryNameSystemName);
299 }
300
301 void
303 {
304 remote.connect(*this);
305 {
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);
311 }
312
313 emit connected();
314 }
315
316 void
317 WidgetController::Remote::connect(Component& parent)
318 {
319 auto mnsProxy =
320 parent.getProxy<armem::mns::MemoryNameSystemInterfacePrx>(memoryNameSystemName);
321 memoryNameSystem.initialize(mnsProxy, &parent);
322
323 locationReader = memoryNameSystem.useReader(navigation::location::coreSegmentID);
324 locationWriter = memoryNameSystem.useWriter(navigation::location::coreSegmentID);
325
326 graphReader = memoryNameSystem.useReader(navigation::graph::coreSegmentID);
327 graphWriter = memoryNameSystem.useWriter(navigation::graph::coreSegmentID);
328
329 arviz = std::make_unique<viz::Client>(viz::Client::createForGuiPlugin(parent));
330 }
331
332 void
333 WidgetController::queryMemory()
334 {
335 armem::client::QueryResult locResult = queryLocations();
336 armem::client::QueryResult graphResult = queryGraphs();
337
338 if (not(locResult.success and graphResult.success))
339 {
340 QStringList status;
341 std::stringstream ss;
342 if (not locResult.success)
343 {
344 status.append(QString::fromStdString(locResult.errorMessage));
345 }
346 if (not graphResult.success)
347 {
348 status.append(QString::fromStdString(graphResult.errorMessage));
349 }
350 widget.statusLabel->setText(status.join("\n"));
351 }
352 }
353
354 armem::client::QueryResult
355 WidgetController::queryLocations()
356 {
357 ARMARX_INFO << "Querying locations";
358 armem::client::QueryResult result =
359 remote.locationReader.getLatestSnapshotsIn(navigation::location::coreSegmentID);
360
361 if (result.success)
362 {
363 ARMARX_INFO << "Successfully queried locations.";
364 // keep copy
365 model.locationsMemory = result.memory;
367 }
368 else
369 {
370 ARMARX_WARNING << "Failed to query locations from memory: " << result.errorMessage;
371 }
372 return result;
373 }
374
375 armem::client::QueryResult
376 WidgetController::queryGraphs()
377 {
378 armem::client::QueryResult result =
379 remote.graphReader.getLatestSnapshotsIn(navigation::graph::coreSegmentID);
380 if (result.success)
381 {
382 model.graphMemory = std::move(result.memory);
383 emit graphMemoryChanged();
384 }
385 return result;
386 }
387
388 void
389 WidgetController::updateGraphList()
390 {
391 QString previousText = widget.graphsComboBox->currentText();
392 widget.graphsComboBox->clear();
393
394 int i = 0;
395 int previousIndex = -1; // To keep selection.
396 model.graphMemory.forEachEntity(
397 [&](const armem::wm::Entity& entity)
398 {
399 bool hasChanged =
400 (entity.id() == model.graphEntityID ? model.graph.hasChanged() : false);
401 QString text = getGraphDisplayName(entity.id(), hasChanged);
402 QString id = QString::fromStdString(entity.id().str());
403
404 widget.graphsComboBox->addItem(text, id);
405 if (previousIndex < 0 and text == previousText)
406 {
407 previousIndex = i;
408 }
409 ++i;
410 });
411 if (previousIndex >= 0)
412 {
413 widget.graphsComboBox->setCurrentIndex(previousIndex);
414 }
415 widget.loadGraphButton->setEnabled(widget.graphsComboBox->count() > 0);
416 }
417
418 void
419 WidgetController::loadGraph()
420 {
421 if (model.graph.hasChanged())
422 {
423 if (not loadGraphDialog())
424 {
425 return;
426 }
427 }
428
429 const armem::MemoryID entityID = armem::MemoryID::fromString(
430 widget.graphsComboBox->currentData().toString().toStdString());
431
432 // Refresh local memory.
433 queryMemory();
434
435 const armem::wm::EntityInstance* instance = model.graphMemory.findLatestInstance(entityID);
436 if (instance != nullptr)
437 {
438 widget.statusLabel->setText(QString::fromStdString(
439 "Loaded snapshot " + instance->id().getEntitySnapshotID().str()));
440 }
441 else
442 {
443 std::stringstream ss;
444 ss << "No latest instance of entity " << entityID << " in memory.";
445 widget.statusLabel->setText(QString::fromStdString(ss.str()));
446 return;
447 }
448
449 navigation::core::Graph nav;
450
451 if (instance != nullptr)
452 {
453 {
454 navigation::core::arondto::Graph dto;
455 ARMARX_CHECK_NOT_NULL(instance->data());
456 dto.fromAron(instance->data());
457 fromAron(dto, nav);
458 }
459 // Resolve locations and remove vertices which could not be resolved.
460 {
461 resolveLocations(nav, model.locationsMemory);
462 std::vector<semrel::ShapeID> remove;
463 for (navigation::core::Graph::Vertex vertex : nav.vertices())
464 {
465 if (not vertex.attrib().hasPose())
466 {
467 remove.push_back(vertex.objectID());
468 }
469 }
470 if (not remove.empty())
471 {
472 for (semrel::ShapeID vertexID : remove)
473 {
474 nav.removeVertex(nav.vertex(vertexID));
475 }
476 std::stringstream ss;
477 ss << "Dropped " << remove.size() << " locations which could not be resolved.";
478 widget.statusLabel->setText(QString::fromStdString(ss.str()));
479 }
480 }
481
482 ARMARX_VERBOSE << "Loading graph " << nav.str();
483 }
484 setGraph(entityID, nav);
485 }
486
487 bool
488 WidgetController::loadGraphDialog()
489 {
490 ARMARX_CHECK(model.graph.hasChanged());
491
492 QMessageBox msgBox;
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)
497 {
498 detailLines.append("Graph edges have changed.");
499 }
500 for (auto vertex : model.graph.vertices())
501 {
502 if (vertex.attrib().changed)
503 {
504 detailLines.append("Location " +
505 QString::fromStdString(vertex.attrib().getFullName()) +
506 " has changed.");
507 }
508 }
509 msgBox.setDetailedText(detailLines.join("\n"));
510 msgBox.setStandardButtons(QMessageBox::Discard | QMessageBox::Cancel);
511 msgBox.setDefaultButton(QMessageBox::Cancel);
512
513 switch (msgBox.exec())
514 {
515 case QMessageBox::Discard:
516 return true; // Ok go.
517 case QMessageBox::Cancel:
518 return false; // Abort loading.
519 default:
520 return false; // Something went wrong.
521 }
522 }
523
524 void
525 WidgetController::commit()
526 {
527 armem::CommitResult locResults = commitLocations();
528 armem::EntityUpdateResult graphResult = commitGraph();
529
530 {
531 std::stringstream ss;
532 if (locResults.allSuccess())
533 {
534 ss << "Committed " << locResults.results.size() << " location snapshot";
535 if (locResults.results.size() != 1)
536 {
537 ss << "s"; // plural
538 }
539 if (graphResult.success)
540 {
541 ss << " and 1 graph snapshot " << graphResult.snapshotID;
542 }
543 else
544 {
545 ss << " but failed to commit graph: \n" << graphResult.errorMessage;
546 }
547 }
548 else
549 {
550 int numLocs = static_cast<int>(locResults.results.size());
551 int numSuccess = 0;
552 for (const auto& r : locResults.results)
553 {
554 numSuccess += int(r.success);
555 }
556 int numFailed = numLocs - numSuccess;
557
558 if (graphResult.success)
559 {
560 ss << "Committed 1 graph snapshot " << graphResult.snapshotID;
561 ss << " and " << numSuccess << " locations, but failed to commit " << numFailed
562 << " locations: \n"
563 << locResults.allErrorMessages();
564 }
565 else
566 {
567 ss << "Failed to commit graph and " << numFailed << " of " << numLocs
568 << " locations: \n";
569 ss << graphResult.errorMessage << "\n";
570 ss << locResults.allErrorMessages();
571 }
572 }
573
574 widget.statusLabel->setText(QString::fromStdString(ss.str()));
575 }
576
577 // `changed` flags may have changed
578 emit graphChanged();
579 }
580
581 armem::CommitResult
582 WidgetController::commitLocations()
583 {
584 armem::Commit commit;
585
586 for (auto vertex : model.graph.vertices())
587 {
588 if (vertex.attrib().changed)
589 {
590 armem::EntityUpdate& update = commit.add();
591 fromAron(vertex.attrib().aron.locationID, update.entityID);
592 update.referencedTime = armem::Time::Now();
593
594 navigation::location::arondto::Location dto;
595 toAron(dto.framedPose, vertex.attrib().getPose());
596 update.instancesData = {dto.toAron()};
597 }
598 }
599
600 armem::CommitResult result = remote.locationWriter.commit(commit);
601 auto it = result.results.begin();
602 for (auto vertex : model.graph.vertices())
603 {
604 if (vertex.attrib().changed)
605 {
606 ARMARX_CHECK(it != result.results.end());
607 if (it->success)
608 {
609 // Only clear dirty flag when update was successful.
610 vertex.attrib().changed = false;
611 }
612 ++it;
613 }
614 }
615 return result;
616 }
617
618 armem::EntityUpdateResult
619 WidgetController::commitGraph()
620 {
621 navigation::core::arondto::Graph dto;
622 {
623 navigation::core::Graph nav = fromGuiGraph(model.graph);
624 toAron(dto, nav);
625 }
626
627 armem::EntityUpdate update;
628 update.entityID = model.graphEntityID;
629 update.referencedTime = armem::Time::Now();
630 update.instancesData = {dto.toAron()};
631
632 armem::EntityUpdateResult result = remote.graphWriter.commit(update);
633 if (result.success)
634 {
635 // Clear dirty flag
636 model.graph.attrib().edgesChanged = false;
637 }
638 return result;
639 }
640
641 template <class GraphT>
642 semrel::ShapeID
643 findNextFreeVertexID(const GraphT& graph, semrel::ShapeID vertexID)
644 {
645 while (graph.hasVertex(vertexID))
646 {
647 ++vertexID;
648 }
649 return vertexID;
650 }
651
652 void
653 WidgetController::setGraph(const armem::MemoryID& entityID, const core::Graph& nav)
654 {
655 model.graphEntityID = entityID;
656
657 // Build the gui graph (model).
658 {
659 QSignalBlocker blocker(this);
660
661 clearGraph();
662 model.graph.attrib().edgesChanged = false;
663
664 std::set<armem::MemoryID> coveredLocationIDs;
665 for (auto vertex : nav.vertices())
666 {
667 ARMARX_CHECK(vertex.attrib().hasPose());
668 addVertex(vertex.objectID(), {vertex.attrib()});
669
670 armem::MemoryID id =
671 aron::fromAron<armem::MemoryID>(vertex.attrib().aron.locationID).getEntityID();
672 if (coveredLocationIDs.count(id) > 0)
673 {
674 ARMARX_WARNING << "Two vertices have the same location!";
675 }
676 else
677 {
678 coveredLocationIDs.insert(id);
679 }
680 }
681 // Add locations which have not been part of graph.
682 // ToDo: This should be an explicit step in the GUI.
683 {
684 semrel::ShapeID vertexID{0};
685 model.locationsMemory.forEachInstance(
686 [&](const armem::wm::EntityInstance& instance)
687 {
688 if (coveredLocationIDs.count(instance.id().getEntityID()) == 0)
689 {
690 vertexID = findNextFreeVertexID(model.graph, vertexID);
691 addVertex(vertexID, instance);
692 }
693 });
694 }
695
696
697 for (auto edge : nav.edges())
698 {
699 addEdge(model.graph.vertex(edge.sourceObjectID()),
700 model.graph.vertex(edge.targetObjectID()),
701 {edge.attrib()});
702 }
703 }
704
705 // Trigger a view update.
706 emit graphChanged();
707 }
708
709 void
710 WidgetController::setEmptyGraph(const armem::MemoryID& entityID)
711 {
712 model.graphEntityID = entityID;
713
714 {
715 QSignalBlocker blocker(this);
716 clearGraph();
717
718 semrel::ShapeID id{0};
719 queryLocations();
720 model.locationsMemory.forEachInstance(
721 [this, &id](const armem::wm::EntityInstance& instance)
722 {
723 addVertex(id, instance);
724 ++id;
725 });
726 }
727
728 // Mark graph as changed.
729 model.graph.attrib().edgesChanged = true;
730
731 emit graphChanged();
732 }
733
734 GuiGraph::Vertex
735 WidgetController::addVertex(semrel::ShapeID vertexID, const VertexData& defaultAttribs)
736 {
737 ARMARX_CHECK(not model.graph.hasVertex(vertexID));
738
739 GuiGraph::Vertex vertex = model.graph.addVertex(vertexID, defaultAttribs);
740 vertex.attrib().tableWidgetItem = view.vertexTable->addVertex();
741
742 emit graphChanged();
743
744 return vertex;
745 }
746
747 GuiGraph::Vertex
748 WidgetController::addVertex(semrel::ShapeID vertexID,
749 const armem::wm::EntityInstance& locationInstance)
750 {
751 navigation::location::arondto::Location location;
752 location.fromAron(locationInstance.data());
753
754 VertexData attrib;
755 toAron(attrib.aron.locationID, locationInstance.id().getEntityID());
756 FramedPose pose;
757 fromAron(location.framedPose, pose);
758 attrib.setPose(pose);
759 return addVertex(vertexID, attrib);
760 }
761
762 GuiGraph::Edge
763 WidgetController::addEdge(GuiGraph::ConstVertex source,
764 GuiGraph::ConstVertex target,
765 const EdgeData& defaultAttribs)
766 {
767 ARMARX_CHECK(not model.graph.hasEdge(source.objectID(), target.objectID()))
768 << "Edge must not exist before being added: " << QUOTED(source.attrib().getFullName())
769 << " -> '" << target.attrib().getFullName() << "'";
770
771 GuiGraph::Edge edge = model.graph.addEdge(source, target, defaultAttribs);
772 edge.attrib().tableWidgetItem = view.edgeTable->addEdge(edge);
773
774 emit graphChanged();
775
776 return edge;
777 }
778
779 void
780 WidgetController::updateGraphView()
781 {
782 ARMARX_VERBOSE << "Updating vertices";
783 for (auto vertex : model.graph.vertices())
784 {
785 updateVertexView(vertex);
786 }
787 ARMARX_VERBOSE << "Updating edges";
788 for (auto edge : model.graph.edges())
789 {
790 updateEdgeView(edge);
791 }
792
793 int index =
794 widget.graphsComboBox->findData(QString::fromStdString(model.graphEntityID.str()));
795 if (index >= 0)
796 {
797 widget.graphsComboBox->setItemText(
798 index,
799 getGraphDisplayName(model.graphEntityID, model.graph.hasChanged()));
800 }
801
802 updateArViz();
803 }
804
805 void
806 WidgetController::updateVertexView(GuiGraph::Vertex vertex)
807 {
808 view.vertexTable->updateVertex(vertex);
809
810 if (/* DISABLES CODE */ (false)) // Disable this for the moment.
811 {
812 // Highlight all edges between highlighted vertices
813 std::set<semrel::ShapeID> highlightedVertices;
814 for (auto v : model.graph.vertices())
815 {
816 if (v.attrib().highlighted)
817 {
818 highlightedVertices.insert(v.objectID());
819 }
820 }
821
822 for (auto edge : model.graph.edges())
823 {
824 bool verticesHighlighted = highlightedVertices.count(edge.sourceObjectID()) and
825 highlightedVertices.count(edge.targetObjectID());
826
827 // Already highlighted but vertices not highlighted => to false, update
828 // Not highlighted but vertices highlighted => to true, update
829 if (edge.attrib().highlighted != verticesHighlighted)
830 {
831 edge.attrib().highlighted = verticesHighlighted;
832 }
833 }
834 }
835 }
836
837 void
838 WidgetController::updateEdgeView(GuiGraph::Edge edge)
839 {
840 view.edgeTable->updateEdge(edge);
841 }
842
843 void
844 WidgetController::updateArViz()
845 {
846 if (remote.arviz)
847 {
848 objpose::ObjectPoseMap objectMap;
849 std::vector<ObjectInfo> objectInfo;
850 graph::visu::ObjectParserInfo info{objectMap, objectInfo};
851
852 // if ObjectPoseClient is connected, load objects to resolve graph locations
853 if (view.objectPoses->isConnected())
854 {
855 objectMap = view.objectPoses->connection().getObjectPoseMap();
856 objectInfo = view.objectPoses->connection().getObjectInfo();
857 }
858
859 std::vector<viz::Layer> layers;
860 {
861 viz::Layer& vertices = layers.emplace_back(remote.arviz->layer("Locations"));
862 applyVisu(vertices, model.visu.vertex, model.graph.vertices(), info);
863 }
864 {
865 viz::Layer& edges = layers.emplace_back(remote.arviz->layer("Graph Edges"));
866 applyVisu(edges, model.visu.edge, model.graph.edges(), info);
867 }
868 {
869 viz::Layer& robot = layers.emplace_back(remote.arviz->layer("Robot"));
870 if (view.vertexData->vertex().has_value() and view.robotVisu->isEnabled())
871 {
872 const auto res =
873 core::resolveLocation(objectMap,
874 objectInfo,
875 view.vertexData->vertex()->attrib().getPose());
876 if (res.pose.has_value())
877 {
878 robot.add(
879 view.robotVisu->connection().vizRobot("robot").pose(res.pose.value()));
880 }
881 }
882 }
883 remote.arviz->commit(layers);
884 }
885 }
886
887 template <class T>
888 static void
889 updateElementHighlighting(QList<QTableWidgetItem*> selectedItems,
890 std::map<QTableWidgetItem*, T>&& itemToElementMap)
891 {
892 for (QTableWidgetItem* selected : selectedItems)
893 {
894 if (auto it = itemToElementMap.find(selected); it != itemToElementMap.end())
895 {
896 it->second.attrib().highlighted = true;
897 itemToElementMap.erase(it);
898 }
899 }
900 for (auto& [_, unselected] : itemToElementMap)
901 {
902 unselected.attrib().highlighted = false;
903 }
904 }
905
906 void
907 WidgetController::updateVertexHighlighting()
908 {
909 updateElementHighlighting(view.vertexTable->selectedVertexItems(),
910 model.graph.getTableItemToVertexMap());
911 emit graphChanged();
912 }
913
914 void
915 WidgetController::updateEdgeHighlighting()
916 {
917 updateElementHighlighting(view.edgeTable->selectedEdgeItems(),
918 model.graph.getTableItemToEdgeMap());
919 emit graphChanged();
920 }
921
922 void
923 WidgetController::selectVertex(QTableWidgetItem* vertexItem)
924 {
925 if (vertexItem == nullptr)
926 {
927 view.vertexData->clearVertex();
928 }
929 if (auto vertex = model.graph.getVertexFromTableItem(vertexItem))
930 {
931 selectVertex(vertex.value());
932 }
933 }
934
935 void
936 WidgetController::selectVertex(GuiGraph::Vertex vertex)
937 {
938 view.vertexData->setVertex(vertex);
939 }
940
941 void
942 WidgetController::clearGraph()
943 {
944 {
945 QSignalBlocker blocker(this);
946 clearEdges();
947 clearVertices();
948 }
949
950 // Clear data structure
951 ARMARX_CHECK_EQUAL(model.graph.numEdges(), 0);
952 ARMARX_CHECK_EQUAL(model.graph.numVertices(), 0);
953
954 emit graphChanged();
955 }
956
957 void
958 WidgetController::clearEdges()
959 {
960 // Remove in reverse order to be more array-friendly.
961 std::vector<GuiGraph::Edge> edges{model.graph.edges().begin(), model.graph.edges().end()};
962 {
963 QSignalBlocker blocker(this);
964 for (auto it = edges.rbegin(); it != edges.rend(); ++it)
965 {
966 GuiGraph::Edge edge = *it;
967 removeEdge(edge);
968 }
969 }
970
971 ARMARX_CHECK_EQUAL(view.edgeTable->rowCount(), 0);
972 ARMARX_CHECK_EQUAL(model.graph.numEdges(), 0);
973
974 emit graphChanged();
975 }
976
977 void
978 WidgetController::clearVertices()
979 {
980 ARMARX_CHECK_EQUAL(model.graph.numEdges(), 0)
981 << "The graph may not have any edges when clearing the vertices.";
982
983 // Remove in reverse order to be more array-friendly.
984 std::vector<GuiGraph::Vertex> vertices{model.graph.vertices().begin(),
985 model.graph.vertices().end()};
986 {
987 QSignalBlocker blocker(this);
988 for (auto it = vertices.rbegin(); it != vertices.rend(); ++it)
989 {
990 GuiGraph::Vertex vertex = *it;
991 removeVertex(vertex);
992 }
993 }
994
995 ARMARX_CHECK_EQUAL(view.vertexTable->rowCount(), 0);
996 ARMARX_CHECK_EQUAL(model.graph.numVertices(), 0);
997
998 emit graphChanged();
999 }
1000
1001 void
1002 WidgetController::removeEdge(GuiGraph::Edge& edge)
1003 {
1004 ARMARX_CHECK(model.graph.hasEdge(edge.sourceDescriptor(), edge.targetDescriptor()))
1005 << "Cannot remove edge that does not exist.";
1006
1007 // Remove view elements
1008 {
1009 QSignalBlocker blocker(view.edgeTable);
1010 view.edgeTable->removeEdge(edge);
1011 }
1012
1013 // Remove from model
1014 model.graph.removeEdge(edge);
1015
1016 ARMARX_CHECK(not model.graph.hasEdge(edge.sourceDescriptor(), edge.targetDescriptor()))
1017 << edge.sourceDescriptor() << " -> " << edge.targetDescriptor();
1018
1019 emit graphChanged();
1020 }
1021
1022 void
1023 WidgetController::removeVertex(GuiGraph::Vertex& vertex)
1024 {
1025 ARMARX_CHECK_EQUAL(vertex.inDegree(), 0)
1026 << "A vertex must not have any edges before being removed. "
1027 << vertex.attrib().getFullName();
1028 ARMARX_CHECK_EQUAL(vertex.outDegree(), 0)
1029 << "A vertex must not have any edges before being removed. "
1030 << vertex.attrib().getFullName();
1031
1032 // Remove view elements
1033 {
1034 QSignalBlocker blocker(view.vertexTable);
1035 view.vertexTable->removeVertex(vertex);
1036 }
1037 if (view.vertexData->vertex().has_value() and view.vertexData->vertex().value() == vertex)
1038 {
1039 view.vertexData->clearVertex();
1040 }
1041
1042 // Remove from model
1043 model.graph.removeVertex(vertex);
1044
1045 emit graphChanged();
1046 }
1047
1048 void
1049 WidgetController::createVertexDialog()
1050 {
1051 auto providerId = navigation::location::coreSegmentID;
1052 // TODO read field `providerName` and set it here.
1053 // providerId.providerSegmentName =
1054
1055 const auto locationQueryResult = queryLocations();
1056
1057 std::vector<std::string> availableProviders =
1058 locationQueryResult.memory.getCoreSegment(navigation::location::coreSegmentID)
1059 .getProviderSegmentNames();
1060
1061 ARMARX_INFO << VAROUT(availableProviders);
1062
1063 NewEntityIdDialog dialog(providerId, availableProviders, nullptr);
1064 switch (dialog.exec())
1065 {
1066 case QDialog::Accepted:
1067 // Ok go.
1068 break;
1069 case QDialog::Rejected:
1070 // Abort creation.
1071 return;
1072 }
1073
1074 // Find free vertex ID
1075 const semrel::ShapeID vertexID = findNextFreeVertexID(model.graph, semrel::ShapeID{0});
1076
1077 // Initialize attributes
1078 VertexData attribs;
1079 toAron(attribs.aron.locationID, dialog.entityID());
1080 attribs.setPose(FramedPose(core::Pose::Identity().matrix(), armarx::GlobalFrame, ""));
1081 attribs.changed = true;
1082
1083 addVertex(vertexID, attribs);
1084 }
1085
1086 void
1087 WidgetController::addEdges(QList<QPair<QTableWidgetItem*, QTableWidgetItem*>> vertexItems)
1088 {
1089 const auto itemToVertexMap = model.graph.getTableItemToVertexMap();
1090 {
1091 QSignalBlocker blocker(this);
1092
1093 for (const auto& [sourceItem, targetItem] : vertexItems)
1094 {
1095 GuiGraph::Vertex source = itemToVertexMap.at(sourceItem);
1096 GuiGraph::Vertex target = itemToVertexMap.at(targetItem);
1097 if (not model.graph.hasEdge(source, target))
1098 {
1099 addEdge(source, target, {});
1100 }
1101 }
1102 }
1103
1104 model.graph.attrib().edgesChanged = true;
1105 emit graphChanged();
1106 }
1107
1108 void
1109 WidgetController::removeEdges(QList<QTableWidgetItem*> edgeItems)
1110 {
1111 const auto itemToEdgeMap = model.graph.getTableItemToEdgeMap();
1112 {
1113 QSignalBlocker blocker(this);
1114
1115 for (const auto& edgeItem : edgeItems)
1116 {
1117 GuiGraph::Edge edge = itemToEdgeMap.at(edgeItem);
1118 removeEdge(edge);
1119 }
1120 }
1121
1122 model.graph.attrib().edgesChanged = true;
1123 emit graphChanged();
1124 }
1125
1126 void
1127 WidgetController::removeEdgesOfVertex(QList<QTableWidgetItem*> vertexItems,
1128 utils::EdgeDirection direction)
1129 {
1130 auto removeEdges = [this](auto&& range)
1131 {
1132 for (auto edge : range)
1133 {
1134 removeEdge(edge);
1135 }
1136 };
1137 {
1138 QSignalBlocker blocker(this);
1139
1140 const auto itemToVertexMap = model.graph.getTableItemToVertexMap();
1141 for (const auto& vertexItem : vertexItems)
1142 {
1143 GuiGraph::Vertex vertex = itemToVertexMap.at(vertexItem);
1144 switch (direction)
1145 {
1147 removeEdges(vertex.inEdges());
1148 break;
1149
1151 removeEdges(vertex.outEdges());
1152 break;
1153
1155 removeEdges(vertex.inEdges());
1156 removeEdges(vertex.outEdges());
1157 break;
1158 }
1159 }
1160 }
1161
1162 model.graph.attrib().edgesChanged = true;
1163 emit graphChanged();
1164 }
1165
1166 void
1167 WidgetController::createGraphDialog()
1168 {
1169 NewEntityIdDialog dialog(navigation::graph::coreSegmentID, {}, nullptr);
1170 switch (dialog.exec())
1171 {
1172 case QDialog::Accepted:
1173 break;
1174 case QDialog::Rejected:
1175 return;
1176 }
1177
1178 const armem::MemoryID entityID = dialog.entityID();
1179
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);
1185
1186 setEmptyGraph(entityID);
1187 }
1188
1189 QString
1190 WidgetController::getGraphDisplayName(const armem::MemoryID& entityID, bool changed) const
1191 {
1192 QString name =
1193 QString::fromStdString(entityID.providerSegmentName + "::" + entityID.entityName);
1194 if (changed)
1195 {
1196 name += "*";
1197 }
1198 return name;
1199 }
1200
1201} // namespace armarx::navigation::qt_plugins::location_graph_editor
uint8_t data[1]
uint8_t index
#define VAROUT(x)
#define QUOTED(x)
virtual QPointer< QWidget > getWidget()
getWidget returns a pointer to the a widget of this controller.
Baseclass for all ArmarX ManagedIceObjects requiring properties.
Definition Component.h:94
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
Definition MemoryID.cpp:318
std::string str(bool escapeDelimiters=true) const
Get a string representation of this memory ID.
Definition MemoryID.cpp:102
std::string entityName
Definition MemoryID.h:53
static MemoryID fromString(const std::string &string)
Alias for constructor from string.
Definition MemoryID.cpp:188
std::string providerSegmentName
Definition MemoryID.h:52
MemoryID getEntityID() const
Definition MemoryID.cpp:310
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
Definition Reader.h:227
static DateTime Now()
Definition DateTime.cpp:51
void edgeRemovalRequested(QList< QTableWidgetItem * > edgeItems)
void edgeRemovalRequested(QList< QTableWidgetItem * > vertexItems, utils::EdgeDirection direction)
void newEdgesRequested(QList< QPair< QTableWidgetItem *, QTableWidgetItem * > > edges)
void onInitComponent() override
Pure virtual hook for the subclass.
QPointer< QDialog > getConfigDialog(QWidget *parent=nullptr) override
getConfigDialog returns a pointer to the a configuration widget of this controller.
void loadSettings(QSettings *settings) override
Implement to load the settings that are part of the GUI configuration.
void onConnectComponent() override
Pure virtual hook for the subclass.
void configured() override
This function must be implemented by the user, if he supplies a config dialog.
static Client createForGuiPlugin(armarx::Component &component, std::string const &topicName="ArVizTopic", std::string const &storageName="ArVizStorage")
Definition Client.cpp:62
#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.
Definition Logging.h:181
#define ARMARX_WARNING
The logging level for unexpected behaviour, but not a serious problem.
Definition Logging.h:193
#define ARMARX_VERBOSE
The logging level for verbose information.
Definition Logging.h:187
std::string const GlobalFrame
Variable of the global coordinate system.
Definition FramedPose.h:65
const VariantTypeId FramedPose
Definition FramedPose.h:36
bool update(mongocxx::collection &coll, const nlohmann::json &query, const nlohmann::json &update)
Definition mongodb.cpp:68
void fromAron(const T &dto, T &bo)
double v(double t, double v0, double a0, double j)
Definition CtrlUtil.h:39
void resolveLocation(Graph::Vertex &vertex, const aron::data::DictPtr &locationData)
Definition Graph.cpp:267
void resolveLocations(Graph &graph, const MemoryContainerT &locationContainer)
Definition Graph.h:145
This file is part of ArmarX.
Definition Visu.h:48
const armem::MemoryID coreSegmentID
Definition constants.h:30
const armem::MemoryID coreSegmentID
Definition constants.h:30
navigation::core::Graph fromGuiGraph(const GuiGraph &gui)
Definition GuiGraph.cpp:127
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)
Definition Visu.h:105
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
Definition Commit.cpp:73
std::vector< EntityUpdateResult > results
Definition Commit.h:112
Result of a QueryInput.
Definition Query.h:51
wm::Memory memory
The slice of the memory that matched the query.
Definition Query.h:58
void add(ElementT const &element)
Definition Layer.h:31