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