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