ArVizWidgetController.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 RobotAPI::gui-plugins::ArVizWidgetController
17 * \author Fabian Paus ( fabian dot paus at kit dot edu )
18 * \date 2019
19 * \copyright http://www.gnu.org/licenses/gpl-2.0.txt
20 * GNU General Public License
21 */
22
24
25#include <RobotAPI/gui-plugins/ArViz/ui_ArVizWidget.h>
26
27#include <QFileDialog>
28#include <QLineEdit>
29#include <QMessageBox>
30#include <QTimer>
31
32#include <SimoxUtility/algorithm/string/string_tools.h>
33
35
36namespace armarx
37{
38 struct ArVizWidgetBatchCallback : IceUtil::Shared
39 {
41
42 void
43 onSuccess(viz::data::RecordingBatch const& batch)
44 {
45 this_->onGetBatchAsync(batch);
46 }
47
48 void
49 onFailure(Ice::Exception const& ex)
50 {
51 ARMARX_WARNING << "Failed to get batch async.\nReason:" << ex;
52 }
53 };
54
56 {
57 using This = ArVizWidgetController;
58
59 widget = std::make_unique<Ui::ArVizWidget>();
60 widget->setupUi(getWidget());
61
62 updateTimer = new QTimer(this);
63 connect(updateTimer, &QTimer::timeout, this, QOverload<>::of(&This::onUpdate));
64
65 replayTimer = new QTimer(this);
66 connect(replayTimer, &QTimer::timeout, this, QOverload<>::of(&This::onReplayTimerTick));
67
68 connect(widget->layerTree, &QTreeWidget::itemChanged, this, &This::layerTreeChanged);
69 connect(widget->expandButton, &QPushButton::clicked, this, &This::onExpandAll);
70 connect(widget->collapseButton, &QPushButton::clicked, this, &This::onCollapseAll);
71 connect(widget->filterEdit, &QLineEdit::textChanged, this, &This::onFilterTextChanged);
72 connect(widget->hideAllButton, &QPushButton::clicked, this, &This::onHideAll);
73 connect(widget->showAllButton, &QPushButton::clicked, this, &This::onShowAll);
74 connect(widget->hideFilteredButton, &QPushButton::clicked, this, &This::onHideFiltered);
75 connect(widget->showFilteredButton, &QPushButton::clicked, this, &This::onShowFiltered);
76
77 connect(widget->recordingStartButton, &QPushButton::clicked, this, &This::onStartRecording);
78 connect(widget->recordingStopButton, &QPushButton::clicked, this, &This::onStopRecording);
79
80 connect(widget->refreshRecordingsButton,
81 &QPushButton::clicked,
82 this,
83 &This::onRefreshRecordings);
84 connect(widget->recordingList,
85 &QListWidget::currentItemChanged,
86 this,
87 &This::onRecordingSelectionChanged);
88
89 connect(widget->replayRevisionSpinBox,
90 QOverload<int>::of(&QSpinBox::valueChanged),
91 this,
92 &This::onReplaySpinChanged);
93 connect(widget->replayRevisionSlider,
94 QOverload<int>::of(&QSlider::valueChanged),
95 this,
96 &This::onReplaySliderChanged);
97
98 connect(widget->replayStartButton, &QPushButton::clicked, this, &This::onReplayStart);
99 connect(widget->replayStopButton, &QPushButton::clicked, this, &This::onReplayStop);
100 connect(widget->replayTimedButton, &QPushButton::toggled, this, &This::onReplayTimedStart);
101
102 connect(widget->exportToVRMLButton, &QPushButton::clicked, this, &This::exportToVRML);
103
104 connect(widget->deselectButton, &QPushButton::clicked, this, &This::onDeselectElement);
105 connect(widget->listInteractiveElements,
106 &QListWidget::itemDoubleClicked,
107 this,
108 &This::onInteractiveElementSelected);
109
110 connect(this, &This::connectGui, this, &This::onConnectGui, Qt::QueuedConnection);
111 connect(this, &This::disconnectGui, this, &This::onDisconnectGui, Qt::QueuedConnection);
112
113 // Layer info tree.
114 connect(
115 widget->layerTree, &QTreeWidget::currentItemChanged, this, &This::updateSelectedLayer);
116
117 connect(widget->layerInfoTreeGroupBox,
118 &QGroupBox::toggled,
119 &layerInfoTree,
121 connect(widget->defaultShowLimitSpinBox,
122 qOverload<int>(&QSpinBox::valueChanged),
123 &layerInfoTree,
125 layerInfoTree.setMaxElementCountDefault(widget->defaultShowLimitSpinBox->value());
126
127 layerInfoTree.setWidget(widget->layerInfoTree);
128 layerInfoTree.setEnabled(widget->layerInfoTreeGroupBox->isChecked());
129 layerInfoTree.registerVisualizerCallbacks(visualizer);
130
131 // We need a callback from the visualizer, when the layers have changed
132 // So we can update the tree accordingly
133 visualizer.layersChangedCallback = [this](std::vector<viz::CoinLayerID> const& layers)
134 { layersChanged(layers); };
135
136 // Arviz Profile tree.
137 arvizeProfileLayerWidget = new ArvizProfileManagerWidget();
138 widget->verticalLayout->addWidget(arvizeProfileLayerWidget);
139 connect(arvizeProfileLayerWidget,
141 this,
142 &This::updateLayersFromProfile);
143 connect(arvizeProfileLayerWidget,
145 this,
146 &This::updateLayers);
147
148 replayTimer->start(33);
149 }
150
154
155 void
157 {
158 if (storageName.size() > 0)
159 {
160 usingProxy(storageName);
161 }
162 if (debugObserverName.size() > 0)
163 {
164 usingProxy(debugObserverName);
165 }
166
167 callbackData = new ArVizWidgetBatchCallback();
168 callbackData->this_ = this;
169 callback = viz::newCallback_StorageInterface_getRecordingBatch(
170 callbackData,
173 }
174
175 void
177 {
178 visualizer.clearCache();
179 }
180
181 void
183 {
184 getProxy(storage, storageName);
185 // DebugObserver is optional (check for null on every call)
186 if (!debugObserverName.empty())
187 {
188 getProxy(debugObserver, debugObserverName, false, "", false);
189 }
190
191 lastTiming = visualizer.getTiming();
192 visualizer.startAsync(storage);
193
194 // Changes to UI elements are only allowed in the GUI thread
195 emit connectGui();
196 }
197
198 void
200 {
201 // Changes to UI elements are only allowed in the GUI thread
202 emit disconnectGui();
203 }
204
205 void
206 ArVizWidgetController::updateLayers()
207 {
208 // Update shown/hidden layers
209 layerTreeChanged(nullptr, 0);
210 }
211
212 void
213 ArVizWidgetController::updateLayersFromProfile()
214 {
215 {
216 QSignalBlocker blocker(widget->layerTree);
217
218 // Iterate over all items and activate/deactivate layers accordingly
219 for (int compIndex = 0; compIndex < widget->layerTree->topLevelItemCount(); ++compIndex)
220 {
221 QTreeWidgetItem* componentItem = widget->layerTree->topLevelItem(compIndex);
222 bool parentChecked = true;
223 const std::string component = componentItem->text(0).toStdString();
224 for (int layerIndex = 0; layerIndex < componentItem->childCount(); ++layerIndex)
225 {
226 QTreeWidgetItem* layerItem = componentItem->child(layerIndex);
227 const std::string layer = layerItem->text(0).toStdString();
228 const viz::CoinLayerID layerID(component, layer);
229 const bool visible = not arvizeProfileLayerWidget->isHidden(layerID);
230 layerItem->setCheckState(
231 0, visible ? Qt::CheckState::Checked : Qt::CheckState::Unchecked);
232 parentChecked &= visible;
233 visualizer.showLayer(layerID, visible);
234 }
235 componentItem->setCheckState(
236 0, parentChecked ? Qt::CheckState::Checked : Qt::CheckState::Unchecked);
237 }
238 }
239 }
240
241 void
242 ArVizWidgetController::onConnectGui()
243 {
244 onRefreshRecordings();
245 currentRecordingSelected = false;
246 changeMode(ArVizWidgetMode::Live);
247
248 updateTimer->start(33);
249 }
250
251 void
252 ArVizWidgetController::onDisconnectGui()
253 {
254 visualizer.stop();
255 updateTimer->stop();
257 }
258
259 void
260 ArVizWidgetController::layerTreeChanged(QTreeWidgetItem* item, int /*column*/)
261 {
262 // Iterate over all items and activate/deactivate layers accordingly
263 int componentCount = widget->layerTree->topLevelItemCount();
264 for (int compIndex = 0; compIndex < componentCount; ++compIndex)
265 {
266 QTreeWidgetItem* componentItem = widget->layerTree->topLevelItem(compIndex);
267 std::string component = componentItem->text(0).toStdString();
268 Qt::CheckState componentCheck = componentItem->checkState(0);
269 int layerCount = componentItem->childCount();
270
271 arvizeProfileLayerWidget->update(component, componentCheck == Qt::Checked);
272
273 if (componentItem == item)
274 {
275 // The parent was selected or deselected, so all children should be set accordingly
276 ARMARX_VERBOSE << "Setting all children of " << component << " to "
277 << (componentCheck == Qt::Checked);
278 for (int layerIndex = 0; layerIndex < layerCount; ++layerIndex)
279 {
280 QTreeWidgetItem* layerItem = componentItem->child(layerIndex);
281 if (layerItem->checkState(0) != componentCheck)
282 {
283 layerItem->setCheckState(0, componentCheck);
284 }
285 }
286 return;
287 }
288 for (int layerIndex = 0; layerIndex < layerCount; ++layerIndex)
289 {
290 QTreeWidgetItem* layerItem = componentItem->child(layerIndex);
291 std::string layer = layerItem->text(0).toStdString();
292 bool layerVisible = (layerItem->checkState(0) == Qt::Checked);
293 ARMARX_VERBOSE << "Layer: " << layer << ", Visible: " << layerVisible;
294
295 viz::CoinLayerID layerID(component, layer);
296
297 arvizeProfileLayerWidget->update(layerID, layerVisible);
298
299 visualizer.showLayer(layerID, layerVisible);
300 }
301 }
302 }
303
304 void
305 ArVizWidgetController::layersChanged(std::vector<viz::CoinLayerID> const& layers)
306 {
307 QTreeWidget* tree = widget->layerTree;
308
309 std::map<std::string, QTreeWidgetItem*> currentComponents;
310 std::map<viz::CoinLayerID, QTreeWidgetItem*> currentLayers;
311 int componentCount = widget->layerTree->topLevelItemCount();
312 for (int compIndex = 0; compIndex < componentCount; ++compIndex)
313 {
314 QTreeWidgetItem* componentItem = widget->layerTree->topLevelItem(compIndex);
315 std::string component = componentItem->text(0).toStdString();
316 currentComponents.emplace(component, componentItem);
317
318 int layerCount = componentItem->childCount();
319 for (int layerIndex = 0; layerIndex < layerCount; ++layerIndex)
320 {
321 QTreeWidgetItem* layerItem = componentItem->child(layerIndex);
322 std::string layer = layerItem->text(0).toStdString();
323
324 viz::CoinLayerID layerID(component, layer);
325 currentLayers.emplace(layerID, layerItem);
326 }
327 }
328
329 const QList<QTreeWidgetItem*> selectedItems = widget->layerTree->selectedItems();
330
331 // We need to determine which layers are new and where to append them
332 QTreeWidgetItem* currentItem = nullptr;
333 bool componentWasNew = false;
334 std::string currentComponent;
335 for (auto& entry : layers)
336 {
337 std::string const& component = entry.first;
338 if (component != currentComponent)
339 {
340 auto iter = currentComponents.find(component);
341 if (iter == currentComponents.end())
342 {
343 // Create a new item
344 QSignalBlocker blocker(
345 tree); // otherwise added as unchecked and directly updated
346 currentItem = new QTreeWidgetItem(tree);
347 currentItem->setText(0, QString::fromStdString(component));
348 currentItem->setCheckState(0,
349 arvizeProfileLayerWidget->isHidden(component)
350 ? Qt::Unchecked
351 : Qt::Checked);
352
353 componentWasNew = true;
354 }
355 else
356 {
357 // Item exists already
358 currentItem = iter->second;
359
360 componentWasNew = false;
361 }
362
363 currentComponent = component;
364 }
365
366 auto iter = currentLayers.find(entry);
367 if (iter == currentLayers.end())
368 {
369 // Create a new item
370 std::string const& layer = entry.second;
371 QTreeWidgetItem* layerItem = new QTreeWidgetItem;
372 layerItem->setText(0, QString::fromStdString(layer));
373 layerItem->setCheckState(
374 0, arvizeProfileLayerWidget->isHidden(entry) ? Qt::Unchecked : Qt::Checked);
375
376 if (currentItem)
377 {
378 currentItem->addChild(layerItem);
379
380 if (componentWasNew)
381 {
382 // Initially expand new items
383 tree->expandItem(currentItem);
384 }
385 }
386 else
387 {
388 ARMARX_WARNING << deactivateSpam(10, entry.first + entry.second)
389 << "Invalid component/layer ID: " << entry.first << " / "
390 << entry.second;
391 }
392 }
393 else
394 {
395 // Item exists already ==> make sure that it is not shown if necessary
396 if (arvizeProfileLayerWidget->isHidden(iter->first) or
397 iter->second->checkState(0) == Qt::Unchecked)
398 {
399 visualizer.showLayer(iter->first, false);
400 }
401 }
402 }
403 }
404
405 void
406 ArVizWidgetController::updateSelectedLayer(QTreeWidgetItem* current, QTreeWidgetItem* previous)
407 {
408 (void)previous;
409
410 if (!current->parent())
411 {
412 // A component was selected.
413 return;
414 }
415
416 // A layer was selected.
417 viz::CoinLayerID id(current->parent()->text(0).toStdString(),
418 current->text(0).toStdString());
419
420 viz::CoinLayer* layer = visualizer.layers.findLayer(id);
421 if (layer == nullptr)
422 {
423 ARMARX_WARNING << "Could not find layer (" << id.first << " / " << id.second
424 << ") in Visualizer.";
425 }
426 else
427 {
428 layerInfoTree.setSelectedLayer(id, &visualizer.layers);
429 }
430 }
431
432 void
433 ArVizWidgetController::onCollapseAll(bool)
434 {
435 widget->layerTree->collapseAll();
436 }
437
438 void
439 ArVizWidgetController::onExpandAll(bool)
440 {
441 widget->layerTree->expandAll();
442 }
443
444 void
445 ArVizWidgetController::onHideAll(bool)
446 {
447 showAllLayers(false);
448 }
449
450 void
451 ArVizWidgetController::onShowAll(bool)
452 {
453 showAllLayers(true);
454 }
455
456 void
457 ArVizWidgetController::onHideFiltered(bool)
458 {
459 showFilteredLayers(false);
460 }
461
462 void
463 ArVizWidgetController::onShowFiltered(bool)
464 {
465 showFilteredLayers(true);
466 }
467
468 void
469 ArVizWidgetController::onFilterTextChanged(const QString& filter)
470 {
471 // Now we need to show these matches and hide the other items
472 // Is there a better way? Probably, via QSortFilterProxyModel...
473 QRegExp rx(filter, Qt::CaseInsensitive, QRegExp::Wildcard);
474 for (QTreeWidgetItemIterator iter(widget->layerTree); *iter; ++iter)
475 {
476 QTreeWidgetItem* item = *iter;
477 QString itemText = item->text(0);
478 bool matches = filter.size() == 0 || itemText.contains(rx);
479 item->setHidden(!matches);
480 if (matches && item->parent())
481 {
482 // Make parent visible if a child is visible
483 item->parent()->setHidden(false);
484 }
485 }
486 }
487
488 void
489 ArVizWidgetController::showAllLayers(bool visible)
490 {
491 widget->layerTree->blockSignals(true);
492
493 for (QTreeWidgetItemIterator iter(widget->layerTree); *iter; ++iter)
494 {
495 QTreeWidgetItem* item = *iter;
496 item->setCheckState(0, visible ? Qt::Checked : Qt::Unchecked);
497 }
498
499 widget->layerTree->blockSignals(false);
500
501 updateLayers();
502 }
503
504 void
505 ArVizWidgetController::showFilteredLayers(bool visible)
506 {
507 widget->layerTree->blockSignals(true);
508
509 QString filter = widget->filterEdit->text();
510 QRegExp rx(filter, Qt::CaseInsensitive, QRegExp::Wildcard);
511
512 for (QTreeWidgetItemIterator iter(widget->layerTree); *iter; ++iter)
513 {
514 QTreeWidgetItem* item = *iter;
515 QString itemText = item->text(0);
516 bool matches = filter.size() == 0 || itemText.contains(rx);
517 if (matches)
518 {
519 item->setCheckState(0, visible ? Qt::Checked : Qt::Unchecked);
520 }
521 }
522
523 widget->layerTree->blockSignals(false);
524
525 updateLayers();
526 }
527
528 void
529 ArVizWidgetController::onDeselectElement()
530 {
531 // We just deselect all elements.
532 // Maybe we need to be more specific for strange use cases (?)
533 visualizer.selection->deselectAll();
534 }
535
536 void
537 ArVizWidgetController::onContextMenuClicked()
538 {
539 viz::ElementInteractionData* selected = visualizer.selectedElement;
540 if (selected == nullptr)
541 {
542 ARMARX_WARNING << "Selected element is null, but a context menu option was clicked!";
543 return;
544 }
545
546 QPushButton* clickedButton = static_cast<QPushButton*>(sender());
547
548 QLayout* layout = widget->groupBoxContextMenu->layout();
549 int count = layout->count();
550 for (int i = 0; i < count; ++i)
551 {
552 QPushButton* button = static_cast<QPushButton*>(layout->itemAt(i)->widget());
553 if (button == clickedButton)
554 {
555 viz::data::InteractionFeedback& interaction =
556 visualizer.interactionFeedbackBuffer.emplace_back();
557 interaction.component = selected->layer.first;
558 interaction.layer = selected->layer.second;
559 interaction.element = selected->element;
560 interaction.type = viz::data::InteractionFeedbackType::CONTEXT_MENU_CHOSEN;
561 interaction.chosenContextMenuEntry = i;
562 return;
563 }
564 }
565 }
566
567 void
568 ArVizWidgetController::onInteractiveElementSelected(QListWidgetItem* item)
569 {
570 int index = widget->listInteractiveElements->row(item);
571 if (index < 0)
572 {
573 return;
574 }
575
576 visualizer.selectElement(index);
577 }
578
579 static QString
581 {
582 std::string id = inter.layer.first + "/" + inter.layer.second + "/" + inter.element;
583 return QString::fromStdString(id);
584 }
585
586 void
587 ArVizWidgetController::onUpdate()
588 {
589 visualizer.update();
590
591 // Show the currently selected element
592 QString selectedElementName("<None>");
593 QLayout* contextMenuLayout = widget->groupBoxContextMenu->layout();
594 if (visualizer.selectedElement)
595 {
596 selectedElementName = toQString(*visualizer.selectedElement);
597
598 // Show context menu options if enabled
599 int currentCount = contextMenuLayout->count();
600 viz::data::InteractionDescription const& desc = visualizer.selectedElement->interaction;
601 std::vector<std::string> const& options = desc.contextMenuOptions;
602 int newCount = 0;
603 if (desc.enableFlags & viz::data::InteractionEnableFlags::CONTEXT_MENU)
604 {
605 newCount = (int)options.size();
606 }
607 if (newCount != currentCount)
608 {
609 // Remove all items
610 QLayoutItem* item;
611 while ((item = contextMenuLayout->takeAt(0)) != nullptr)
612 {
613 delete item->widget();
614 delete item;
615 }
616
617 for (std::string const& option : options)
618 {
619 QPushButton* button =
620 new QPushButton(QString::fromStdString(option), widget->groupBoxContextMenu);
621 connect(button,
622 &QPushButton::clicked,
623 this,
624 &ArVizWidgetController::onContextMenuClicked);
625 contextMenuLayout->addWidget(button);
626 }
627 }
628 else
629 {
630 for (int i = 0; i < currentCount; ++i)
631 {
632 QLayoutItem* item = contextMenuLayout->itemAt(i);
633 QPushButton* button = static_cast<QPushButton*>(item->widget());
634 button->setText(QString::fromStdString(options[i]));
635 }
636 }
637 }
638 else
639 {
640 QLayoutItem* item;
641 while ((item = contextMenuLayout->takeAt(0)) != nullptr)
642 {
643 delete item->widget();
644 delete item;
645 }
646 }
647 widget->labelSelectedElement->setText(selectedElementName);
648
649 // Show the interactive elements
650 int currentCount = widget->listInteractiveElements->count();
651 int newCount = (int)visualizer.elementInteractions.size();
652 if (newCount != currentCount)
653 {
654 widget->listInteractiveElements->clear();
655 for (auto& interaction : visualizer.elementInteractions)
656 {
657 QString elementID = toQString(*interaction);
658 widget->listInteractiveElements->addItem(elementID);
659 }
660 }
661
662 onTimingObserverUpdate();
663 }
664
665 void
666 ArVizWidgetController::onTimingObserverUpdate()
667 {
668 viz::CoinVisualizer_UpdateTiming timing = visualizer.getTiming();
669 //if (timing.counter > lastTiming.counter)
670 {
671 if (debugObserver)
672 {
673 timingMap["0. pull (ms)"] = new Variant(timing.pull.toMilliSecondsDouble());
674 timingMap["1. apply (ms)"] =
675 new Variant(timing.applyTotal.total.toMilliSecondsDouble());
676 timingMap["1.1 apply, addLayer (ms)"] =
677 new Variant(timing.applyTotal.addLayer.toMilliSecondsDouble());
678 timingMap["1.2 apply, updateElements (ms)"] =
679 new Variant(timing.applyTotal.updateElements.toMilliSecondsDouble());
680 timingMap["1.3 apply, removeElements (ms)"] =
681 new Variant(timing.applyTotal.removeElements.toMilliSecondsDouble());
682 timingMap["2. layers (ms)"] =
683 new Variant(timing.layersChanged.toMilliSecondsDouble());
684 timingMap["3. wait duration (ms)"] =
685 new Variant(timing.waitDuration.toMilliSecondsDouble());
686 timingMap["4. update toggle"] = new Variant(timing.updateToggle);
687 timingMap["total (ms)"] = new Variant(timing.total.toMilliSecondsDouble());
688
689 timings.push_back(timing.total.toMilliSecondsDouble());
690 int numTimings = 20;
691 if ((int)timings.size() > numTimings)
692 {
693 timings.erase(timings.begin());
694 }
695 double averageTime =
696 std::accumulate(timings.begin(), timings.end(), 0.0) / numTimings;
697 timingMap["total avg (ms)"] = new Variant(averageTime);
698
699 debugObserver->begin_setDebugChannel("ArViz_Timing", timingMap);
700 }
701 }
702 }
703
704 void
705 ArVizWidgetController::onStartRecording()
706 {
707 std::string recordingID = widget->recordingIdTextBox->text().toStdString();
708
709 std::string runningID = storage->startRecording(recordingID);
710
711 changeMode(ArVizWidgetMode::Recording);
712
713 if (runningID.size() > 0)
714 {
715 std::string message = "There is already a recording running with ID '" + runningID +
716 "'. \n" + "You have to stop the running recording first";
717 QMessageBox::information(
718 widget->tabWidget, "Recording running", QString::fromStdString(message));
719
720
721 return;
722 }
723 }
724
725 void
726 ArVizWidgetController::onStopRecording()
727 {
728 storage->stopRecording();
729
730 onRefreshRecordings();
731 changeMode(ArVizWidgetMode::Live);
732 }
733
734 void
735 ArVizWidgetController::onRefreshRecordings()
736 {
737 allRecordings = storage->getAllRecordings().recordings;
738 std::sort(allRecordings.begin(),
739 allRecordings.end(),
740 [](viz::data::Recording const& lhs, viz::data::Recording const& rhs)
741 { return lhs.id < rhs.id; });
742
743 std::string currentText;
744 QListWidgetItem* currentItem = widget->recordingList->currentItem();
745 if (currentItem)
746 {
747 currentText = currentItem->text().toStdString();
748 }
749
750 widget->recordingList->clear();
751 int currentTextIndex = -1;
752 int index = 0;
753 for (auto& recording : allRecordings)
754 {
755 if (recording.id == currentText)
756 {
757 currentTextIndex = index;
758 }
759 widget->recordingList->addItem(QString::fromStdString(recording.id));
760 ++index;
761 }
762
763 if (currentTextIndex > 0)
764 {
765 widget->recordingList->setCurrentRow(currentTextIndex);
766 }
767 }
768
769 void
770 ArVizWidgetController::onRecordingSelectionChanged(QListWidgetItem* current,
771 QListWidgetItem* previous)
772 {
773 if (!current)
774 {
775 return;
776 }
777
778 std::string selectedID = current->text().toStdString();
779 for (viz::data::Recording const& recording : allRecordings)
780 {
781 if (recording.id == selectedID)
782 {
783 selectRecording(recording);
784 break;
785 }
786 }
787 }
788
789 static std::string
790 timestampToString(long timestampInMicroSeconds, bool showMS = false)
791 {
792 IceUtil::Time time = IceUtil::Time::microSeconds(timestampInMicroSeconds);
793 std::string timeString = time.toDateTime();
794 if (!showMS)
795 {
796 timeString = timeString.substr(0, timeString.size() - 4);
797 };
798 return timeString;
799 }
800
801 void
802 ArVizWidgetController::onReplaySpinChanged(int newValue)
803 {
804 widget->replayRevisionSlider->setValue(newValue);
805 }
806
807 void
808 ArVizWidgetController::onReplaySliderChanged(int newValue)
809 {
810 if (currentRevision != newValue)
811 {
812 long timestamp = replayToRevision(newValue);
813 if (timestamp > 0)
814 {
815 currentRevision = newValue;
816 currentTimestamp = timestamp;
817 widget->replayRevisionSpinBox->setValue(newValue);
818
819
820 widget->replayTimestampLabel->setText(
821 QString::fromStdString(timestampToString(timestamp, true)));
822 }
823 else
824 {
825 widget->replayRevisionSlider->setValue(currentRevision);
826 }
827 }
828 }
829
830 void
831 ArVizWidgetController::selectRecording(const viz::data::Recording& recording)
832 {
833 // Update recording description
834 widget->recordingIdLabel->setText(QString::fromStdString(recording.id));
835
836 widget->recordingRevisionLabel->setText(
837 QString::fromStdString(std::to_string(recording.firstRevision) + " - " +
838 std::to_string(recording.lastRevision)));
839
840 std::string firstTimeString = timestampToString(recording.firstTimestampInMicroSeconds);
841 std::string lastTimeString = timestampToString(recording.lastTimestampInMicroSeconds);
842
843 widget->recordingTimestampLabel->setText(
844 QString::fromStdString(firstTimeString + " - " + lastTimeString));
845
846 IceUtil::Time duration = IceUtil::Time::microSeconds(
847 recording.lastTimestampInMicroSeconds - recording.firstTimestampInMicroSeconds);
848 int durationSeconds = duration.toSeconds();
849 widget->recordingDurationLabel->setText(
850 QString::fromStdString(std::to_string(durationSeconds) + " s"));
851
852 widget->recordingBatchesLabel->setText(
853 QString::fromStdString(std::to_string(recording.batchHeaders.size())));
854
855 // Update replay control
856 currentRecording = recording;
857 currentRecordingSelected = true;
858 enableWidgetAccordingToMode();
859 }
860
861 void
862 ArVizWidgetController::onReplayStart(bool)
863 {
864 if (!currentRecordingSelected)
865 {
866 ARMARX_WARNING << "No recording selected, so no replay can be started";
867 return;
868 }
869 {
870 std::unique_lock<std::mutex> lock(recordingBatchCacheMutex);
871 recordingBatchCache.clear();
872 }
873
874 visualizer.stop();
875
877
878 widget->replayRevisionSpinBox->blockSignals(true);
879 widget->replayRevisionSpinBox->setMinimum(currentRecording.firstRevision);
880 widget->replayRevisionSpinBox->setMaximum(currentRecording.lastRevision);
881 widget->replayRevisionSpinBox->setValue(currentRecording.firstRevision);
882 widget->replayRevisionSpinBox->blockSignals(false);
883
884 widget->replayRevisionSlider->blockSignals(true);
885 widget->replayRevisionSlider->setMinimum(currentRecording.firstRevision);
886 widget->replayRevisionSlider->setMaximum(currentRecording.lastRevision);
887 widget->replayRevisionSlider->setValue(currentRecording.firstRevision);
888 widget->replayRevisionSlider->blockSignals(false);
889
890 currentRevision = -1;
891 onReplaySliderChanged(widget->replayRevisionSlider->value());
892 }
893
894 void
895 ArVizWidgetController::onReplayStop(bool)
896 {
897 visualizer.startAsync(storage);
898
899 changeMode(ArVizWidgetMode::Live);
900 }
901
902 long
903 ArVizWidgetController::replayToRevision(long revision)
904 {
906 {
907 ARMARX_WARNING << "Cannot call replayToRevision, when not in replaying mode.\n"
908 << "Actual mode: " << int(mode);
909 return -1;
910 }
911
912 viz::data::RecordingBatchHeader* matchingBatchHeader = nullptr;
913 for (auto& batchHeader : currentRecording.batchHeaders)
914 {
915 if (batchHeader.firstRevision <= revision && revision <= batchHeader.lastRevision)
916 {
917 matchingBatchHeader = &batchHeader;
918 break;
919 }
920 }
921 if (matchingBatchHeader == nullptr)
922 {
923 ARMARX_WARNING << "Could not find batch for revision: " << revision;
924 return -1;
925 }
926
927 viz::data::RecordingBatch const& batch = getRecordingBatch(matchingBatchHeader->index);
928
929 ARMARX_VERBOSE << "Replaying to revision : " << revision
930 << "\nGot batch: " << batch.header.firstRevision << " - "
931 << batch.header.lastRevision << "\nUpdates: " << batch.updates.size()
932 << "\nInitial state: " << batch.initialState.size();
933
934
935 auto revisionLess = [](viz::data::TimestampedLayerUpdate const& lhs,
936 viz::data::TimestampedLayerUpdate const& rhs)
937 { return lhs.revision < rhs.revision; };
938 viz::data::TimestampedLayerUpdate pivot;
939 pivot.revision = revision;
940 auto updateBegin =
941 std::lower_bound(batch.updates.begin(), batch.updates.end(), pivot, revisionLess);
942 auto updateEnd = std::upper_bound(updateBegin, batch.updates.end(), pivot, revisionLess);
943
944 // TODO: Optimize: Only start from the last update position
945 std::map<std::string, viz::data::LayerUpdate const*> updates;
946
947 for (auto& update : batch.initialState)
948 {
949 updates[update.update.name] = &update.update;
950 }
951 for (auto updateIter = batch.updates.begin(); updateIter != updateEnd; ++updateIter)
952 {
953 updates[updateIter->update.name] = &updateIter->update;
954 }
955
956 auto layerIDsBefore = visualizer.getLayerIDs();
957 for (auto& pair : updates)
958 {
959 visualizer.apply(*pair.second);
960 }
961 auto layerIDsAfter = visualizer.getLayerIDs();
962 if (layerIDsAfter != layerIDsBefore)
963 {
964 visualizer.emitLayersChanged(layerIDsAfter);
965 }
966
967 return updateBegin->timestampInMicroseconds;
968 }
969
970 long
971 ArVizWidgetController::getRevisionForTimestamp(long timestamp)
972 {
974 {
975 ARMARX_WARNING << "Cannot call replayToTimestamp, when not in replaying mode.\n"
976 << "Actual mode: " << int(mode);
977 return -1;
978 }
979
980 if (timestamp < currentRecording.firstTimestampInMicroSeconds)
981 {
982 ARMARX_INFO << "Requested timestamp is earlier than recording: "
983 << timestampToString(timestamp);
984 return -1;
985 }
986
987 viz::data::RecordingBatchHeader* matchingBatchHeader = nullptr;
988 for (auto& batchHeader : currentRecording.batchHeaders)
989 {
990 matchingBatchHeader = &batchHeader;
991 if (timestamp <= batchHeader.lastTimestampInMicroSeconds)
992 {
993 break;
994 }
995 }
996
997 viz::data::RecordingBatch const& batch = getRecordingBatch(matchingBatchHeader->index);
998
999 auto timestampLess = [](viz::data::TimestampedLayerUpdate const& lhs,
1000 viz::data::TimestampedLayerUpdate const& rhs)
1001 { return lhs.timestampInMicroseconds < rhs.timestampInMicroseconds; };
1002 viz::data::TimestampedLayerUpdate pivot;
1003 pivot.timestampInMicroseconds = timestamp;
1004 auto updateEnd =
1005 std::lower_bound(batch.updates.begin(), batch.updates.end(), pivot, timestampLess);
1006 if (updateEnd == batch.updates.end())
1007 {
1008 return -2;
1009 }
1010 if (updateEnd != batch.updates.begin())
1011 {
1012 // lower_bound gives the first entry with a later timestamp then the goal
1013 // So we should only apply updates before this point
1014 updateEnd -= 1;
1015 }
1016
1017 long revisionBeforeTimestamp = updateEnd->revision;
1018 return revisionBeforeTimestamp;
1019 }
1020
1021 void
1022 ArVizWidgetController::onReplayTimedStart(bool checked)
1023 {
1024 if (checked)
1025 {
1026
1028 replayCurrentTimestamp = currentTimestamp;
1029 }
1030 else
1031 {
1033 }
1034 }
1035
1036 void
1037 ArVizWidgetController::onReplayTimerTick()
1038 {
1040 {
1041 double replaySpeed = widget->replaySpeedSpinBox->value();
1042
1043 long currentRealTime = IceUtil::Time::now().toMicroSeconds();
1044 long elapsedRealTime = currentRealTime - lastReplayRealTime;
1045
1046 replayCurrentTimestamp += elapsedRealTime * replaySpeed;
1047
1048 long revision = getRevisionForTimestamp(replayCurrentTimestamp);
1049 if (revision == -2)
1050 {
1051 if (widget->replayLoopbackCheckBox->checkState() == Qt::Checked)
1052 {
1053 replayCurrentTimestamp = currentRecording.firstTimestampInMicroSeconds;
1054 }
1055 else
1056 {
1057 revision = currentRecording.lastRevision;
1058 }
1059 }
1060 if (revision >= 0)
1061 {
1062 widget->replayRevisionSlider->setValue(revision);
1063 }
1064 }
1065
1066 lastReplayRealTime = IceUtil::Time::now().toMicroSeconds();
1067 }
1068
1069 void
1070 ArVizWidgetController::changeMode(ArVizWidgetMode newMode)
1071 {
1072 mode = newMode;
1073
1074 enableWidgetAccordingToMode();
1075 }
1076
1077 void
1078 ArVizWidgetController::enableWidgetAccordingToMode()
1079 {
1080 switch (mode)
1081 {
1083 {
1084 widget->recordingStartButton->setDisabled(true);
1085 widget->recordingStopButton->setDisabled(true);
1086 widget->replayStartButton->setDisabled(true);
1087 widget->replayStopButton->setDisabled(true);
1088 widget->replayControlGroupBox->setDisabled(true);
1089 }
1090 break;
1092 {
1093 widget->recordingStartButton->setDisabled(false);
1094 widget->recordingStopButton->setDisabled(true);
1095 widget->replayStartButton->setDisabled(false);
1096 widget->replayStopButton->setDisabled(true);
1097 widget->replayControlGroupBox->setDisabled(true);
1098 widget->recordingList->setDisabled(false);
1099 }
1100 break;
1102 {
1103 widget->recordingStartButton->setDisabled(true);
1104 widget->recordingStopButton->setDisabled(false);
1105 widget->replayStartButton->setDisabled(true);
1106 widget->replayStopButton->setDisabled(true);
1107 widget->replayControlGroupBox->setDisabled(true);
1108 }
1109 break;
1111 {
1112 widget->recordingStartButton->setDisabled(true);
1113 widget->recordingStopButton->setDisabled(true);
1114 widget->replayStartButton->setDisabled(true);
1115 widget->replayStopButton->setDisabled(false);
1116 widget->replayControlGroupBox->setDisabled(false);
1117 widget->replayRevisionSlider->setDisabled(false);
1118 widget->replayRevisionSpinBox->setDisabled(false);
1119 widget->recordingList->setDisabled(true);
1120 }
1121 break;
1123 {
1124 widget->recordingStartButton->setDisabled(true);
1125 widget->recordingStopButton->setDisabled(true);
1126 widget->replayStartButton->setDisabled(true);
1127 widget->replayStopButton->setDisabled(false);
1128 widget->replayControlGroupBox->setDisabled(false);
1129 widget->replayRevisionSlider->setDisabled(true);
1130 widget->replayRevisionSpinBox->setDisabled(true);
1131 widget->recordingList->setDisabled(true);
1132 }
1133 break;
1134 }
1135
1136 if (!currentRecordingSelected)
1137 {
1138 widget->replayStartButton->setDisabled(true);
1139 widget->replayStopButton->setDisabled(true);
1140 }
1141 }
1142
1143 void
1144 ArVizWidgetController::onGetBatchAsync(const viz::data::RecordingBatch& batch)
1145 {
1146 // We received a batch asynchronously ==> Update the cache
1147 ARMARX_INFO << "Received async batch: " << batch.header.index;
1148 std::unique_lock<std::mutex> lock(recordingBatchCacheMutex);
1149
1150 auto& entry = recordingBatchCache[batch.header.index];
1151 entry.data = batch;
1152 entry.lastUsed = IceUtil::Time::now();
1153
1154 limitRecordingBatchCacheSize();
1155
1156 recordingWaitingForBatchIndex = -1;
1157 }
1158
1159 viz::data::RecordingBatch const&
1160 ArVizWidgetController::getRecordingBatch(long index)
1161 {
1163
1164 IceUtil::Time now = IceUtil::Time::now();
1165
1166 std::unique_lock<std::mutex> lock(recordingBatchCacheMutex);
1167
1168 auto iter = recordingBatchCache.find(index);
1169 if (iter != recordingBatchCache.end())
1170 {
1171 // Start prefetching neighbouring batches
1172 bool asyncPrefetchIsRunning = callbackResult && !callbackResult->isCompleted();
1173 if (!asyncPrefetchIsRunning && recordingWaitingForBatchIndex == -1)
1174 {
1175 if (index + 1 < long(currentRecording.batchHeaders.size()) &&
1176 recordingBatchCache.count(index + 1) == 0)
1177 {
1178 // ARMARX_WARNING << "after begin_getRecordingBatch: " << (index + 1)
1179 // << " waiting for " << recordingWaitingForBatchIndex;
1180 callbackResult =
1181 storage->begin_getRecordingBatch(currentRecording.id, index + 1, callback);
1182 recordingWaitingForBatchIndex = index + 1;
1183 ARMARX_INFO << "Now waiting for " << recordingWaitingForBatchIndex;
1184 }
1185 else if (index > 0 && recordingBatchCache.count(index - 1) == 0)
1186 {
1187 // ARMARX_WARNING << "before begin_getRecordingBatch: " << (index - 1)
1188 // << " waiting for " << recordingWaitingForBatchIndex;
1189 callbackResult =
1190 storage->begin_getRecordingBatch(currentRecording.id, index - 1, callback);
1191 recordingWaitingForBatchIndex = index - 1;
1192 }
1193 }
1194
1195 TimestampedRecordingBatch& entry = iter->second;
1196 entry.lastUsed = now;
1197 return entry.data;
1198 }
1199
1200 // Maybe there has already been sent a asynchronous request to get
1201 if (index == recordingWaitingForBatchIndex)
1202 {
1203 lock.unlock();
1204 ARMARX_INFO << "Waiting to receive async batch: " << index;
1205 // Wait until request completes
1206 while (recordingWaitingForBatchIndex != -1)
1207 {
1208 QCoreApplication::processEvents();
1209 }
1210 return getRecordingBatch(index);
1211 }
1212
1213 ARMARX_INFO << "Batch #" << index
1214 << " is not in the cache. Getting synchronously, blocking GUI...";
1215
1216 // Entry is not in the cache, we have to get it from ArVizStorage
1217 auto& newEntry = recordingBatchCache[index];
1218 newEntry.lastUsed = now;
1219 newEntry.data = storage->getRecordingBatch(currentRecording.id, index);
1220
1221 limitRecordingBatchCacheSize();
1222
1223 return newEntry.data;
1224 }
1225
1226 void
1227 ArVizWidgetController::limitRecordingBatchCacheSize()
1228 {
1229 if (recordingBatchCache.size() > recordingBatchCacheMaxSize)
1230 {
1231 // Remove the entry with the oldest last used timestamp
1232 auto oldestIter = recordingBatchCache.begin();
1233 for (auto iter = recordingBatchCache.begin(); iter != recordingBatchCache.end(); ++iter)
1234 {
1235 TimestampedRecordingBatch& entry = iter->second;
1236 if (entry.lastUsed < oldestIter->second.lastUsed)
1237 {
1238 oldestIter = iter;
1239 }
1240 }
1241
1242 recordingBatchCache.erase(oldestIter);
1243 }
1244 }
1245
1246 SoNode*
1248 {
1249 return visualizer.root;
1250 }
1251
1252 static const std::string CONFIG_KEY_STORAGE = "Storage";
1253 static const std::string CONFIG_KEY_DEBUG_OBSERVER = "DebugObserver";
1254
1255 void
1257 {
1258 storageName = settings->value(QString::fromStdString(CONFIG_KEY_STORAGE), "ArVizStorage")
1259 .toString()
1260 .toStdString();
1261 debugObserverName =
1262 settings->value(QString::fromStdString(CONFIG_KEY_DEBUG_OBSERVER), "DebugObserver")
1263 .toString()
1264 .toStdString();
1265 }
1266
1267 void
1269 {
1270 settings->setValue(QString::fromStdString(CONFIG_KEY_STORAGE),
1271 QString::fromStdString(storageName));
1272 settings->setValue(QString::fromStdString(CONFIG_KEY_DEBUG_OBSERVER),
1273 QString::fromStdString(debugObserverName));
1274 }
1275
1276 QPointer<QDialog>
1278 {
1279 if (!configDialog)
1280 {
1281 configDialog = new SimpleConfigDialog(parent);
1282 configDialog->addProxyFinder<armarx::viz::StorageInterfacePrx>(
1283 {CONFIG_KEY_STORAGE, "ArViz Storage", "ArViz*"});
1284 configDialog->addProxyFinder<armarx::DebugObserverInterfacePrx>(
1285 {CONFIG_KEY_DEBUG_OBSERVER, "Debug observer", "DebugObserver"});
1286 }
1287 return qobject_cast<QDialog*>(configDialog);
1288 }
1289
1290 void
1292 {
1293 if (configDialog)
1294 {
1295 storageName = configDialog->getProxyName(CONFIG_KEY_STORAGE);
1296 debugObserverName = configDialog->getProxyName(CONFIG_KEY_DEBUG_OBSERVER);
1297 }
1298 }
1299
1300 void
1301 ArVizWidgetController::exportToVRML()
1302 {
1303
1304 QString fi = QFileDialog::getSaveFileName(
1305 Q_NULLPTR, tr("VRML 2.0 File"), QString(), tr("VRML Files (*.wrl)"));
1306 std::string s = std::string(fi.toLatin1());
1307
1308 if (s.empty())
1309 {
1310 return;
1311 }
1312 if (!simox::alg::ends_with(s, ".wrl"))
1313 {
1314 s += ".wrl";
1315 }
1316
1317 visualizer.exportToVRML(s);
1318 }
1319} // namespace armarx
std::string timestamp()
uint8_t index
#define option(type, fn)
SpamFilterDataPtr deactivateSpam(SpamFilterDataPtr const &spamFilter, float deactivationDurationSec, const std::string &identifier, bool deactivate)
Definition Logging.cpp:75
virtual QPointer< QWidget > getWidget()
getWidget returns a pointer to the a widget of this controller.
bool isHidden(const viz::CoinLayerID &layerID) const
void setEnabled(bool enabled)
Enable or disable the layer info tree.
void setMaxElementCountDefault(int max)
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.
void showLayer(CoinLayerID const &id, bool visible)
#define ARMARX_INFO
The normal logging level.
Definition Logging.h:179
#define ARMARX_WARNING
The logging level for unexpected behaviour, but not a serious problem.
Definition Logging.h:191
#define ARMARX_VERBOSE
The logging level for verbose information.
Definition Logging.h:185
QString toQString(std::string const &string)
bool update(mongocxx::collection &coll, const nlohmann::json &query, const nlohmann::json &update)
Definition mongodb.cpp:68
bool matches(std::string skillName, std::vector< std::string > &searches)
std::pair< std::string, std::string > CoinLayerID
Definition Visualizer.h:20
InteractionDescription interaction()
Definition ElementOps.h:109
This file offers overloads of toIce() and fromIce() functions for STL container types.
::IceInternal::ProxyHandle<::IceProxy::armarx::DebugObserverInterface > DebugObserverInterfacePrx
void onFailure(Ice::Exception const &ex)
void onSuccess(viz::data::RecordingBatch const &batch)
struct ArVizWidgetController * this_
ArVizWidgetController brief one line description.
void onDisconnectComponent() override
Hook for subclass.
void loadSettings(QSettings *settings) override
Implement to load the settings that are part of the GUI configuration.
void saveSettings(QSettings *settings) override
Implement to save the settings as part of the GUI configuration.
SoNode * getScene() override
Reimplementing this function and returning a SoNode* will show this SoNode in the 3DViewerWidget,...
void onGetBatchAsync(viz::data::RecordingBatch const &batch)
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.
virtual ~ArVizWidgetController() override
Controller destructor.
void onExitComponent() override
Hook for subclass.
ArVizWidgetController()
Controller Constructor.
QPointer< QDialog > getConfigDialog(QWidget *parent) override
getConfigDialog returns a pointer to the a configuration widget of this controller.
CoinVisualizer_ApplyTiming applyTotal
Definition Visualizer.h:179
#define ARMARX_TRACE
Definition trace.h:75