From 982e04286e84b06bf08519d080f5333018ed0280 Mon Sep 17 00:00:00 2001 From: gferret <gatien.ferret@kitware.com> Date: Mon, 22 Aug 2022 14:55:10 +0200 Subject: [PATCH] [feat] Add in the UI of the ensor list tool to live transform data Add sliders for translations and rotation updates, linked to lidar proxies to help manipulate the sensor data --- .../lqLoadLidarStateReaction.cxx | 75 +-- .../lqLoadLidarStateReaction.h | 13 - .../SensorWidget/lqSensorReaderWidget.cxx | 5 +- .../SensorWidget/lqSensorReaderWidget.h | 5 + .../SensorWidget/lqSensorWidget.cxx | 362 ++++++++++++++ .../SensorWidget/lqSensorWidget.h | 72 +++ .../SensorWidget/lqSensorWidget.ui | 461 +++++++++++++++++- ApplicationComponents/lqHelper.cxx | 69 +++ ApplicationComponents/lqHelper.h | 13 + 9 files changed, 975 insertions(+), 100 deletions(-) diff --git a/ApplicationComponents/SaveAndLoadLidarState/lqLoadLidarStateReaction.cxx b/ApplicationComponents/SaveAndLoadLidarState/lqLoadLidarStateReaction.cxx index 50752f3b8..6e6b9c3a7 100644 --- a/ApplicationComponents/SaveAndLoadLidarState/lqLoadLidarStateReaction.cxx +++ b/ApplicationComponents/SaveAndLoadLidarState/lqLoadLidarStateReaction.cxx @@ -110,8 +110,8 @@ void lqLoadLidarStateReaction::LoadLidarState(vtkSMProxy * lidarCurrentProxy) QMessageBox::information(nullptr, tr(""), tr(message.c_str()) ); } else - { - lqLoadLidarStateReaction::UpdateProxyProperty(lidarProxy, propertyName, currentProp.values); + { + UpdateProxyProperty(lidarProxy, propertyName, currentProp.values); } } } @@ -162,74 +162,3 @@ void lqLoadLidarStateReaction::ParseJsonContent(Json::Value contents, std::strin } } } - -//----------------------------------------------------------------------------- -int lqLoadLidarStateReaction::UpdateProxyProperty(vtkSMProxy * proxy, const std::string &propNameToFind, - const std::vector<std::string> &values) -{ - // If there is no value to set, the request is skipped - if(values.empty()) - { - std::string message = "Property " + propNameToFind + " couldn't be applied"; - QMessageBox::information(nullptr, QObject::tr(""), QObject::tr(message.c_str()) ); - return 0; - } - - vtkSMProperty* prop = GetPropertyFromProxy(proxy, propNameToFind); - - if(!prop) - { - return 0; - } - - std::vector<double> propertyAsDoubleArray = vtkSMPropertyHelper(prop).GetDoubleArray(); - if(propertyAsDoubleArray.size() > 1) - { - if(propertyAsDoubleArray.size() != values.size()) - { - std::cout << "Values to applied and base property does not have the same size" << std::endl; - } - std::vector<double> d; - for(unsigned int j = 0; j < values.size(); j++) - { - d.push_back(std::stod(values[j])); - } - vtkSMPropertyHelper(prop).Set(d.data(), d.size()); - proxy->UpdateProperty(propNameToFind.c_str()); - return 1; - } - else - { - // If the property is a valid variant we display it to the user - vtkVariant propertyAsVariant = vtkSMPropertyHelper(prop).GetAsVariant(0); - if(propertyAsVariant.IsValid()) - { - if(propertyAsVariant.IsInt()) - { - vtkSMBooleanDomain * boolDomain = vtkSMBooleanDomain::SafeDownCast(prop->FindDomain("vtkSMBooleanDomain")); - if(boolDomain && (values[0].compare("false") == 0 || values[0].compare("true") == 0)) - { - int value = (values[0].compare("false") == 0) ? 0 : 1; - vtkSMPropertyHelper(prop).Set(value); - } - else - { - vtkSMPropertyHelper(prop).Set(std::stoi(values[0])); - } - } - else if(propertyAsVariant.IsNumeric()) - { - vtkSMPropertyHelper(prop).Set(std::stof(values[0])); - - } - else if(propertyAsVariant.IsString()) - { - vtkSMPropertyHelper(prop).Set(values[0].c_str()); - } - proxy->UpdateProperty(propNameToFind.c_str()); - return 1; - } - } - return 0; -} - diff --git a/ApplicationComponents/SaveAndLoadLidarState/lqLoadLidarStateReaction.h b/ApplicationComponents/SaveAndLoadLidarState/lqLoadLidarStateReaction.h index ba5bc7299..03afbca23 100644 --- a/ApplicationComponents/SaveAndLoadLidarState/lqLoadLidarStateReaction.h +++ b/ApplicationComponents/SaveAndLoadLidarState/lqLoadLidarStateReaction.h @@ -27,19 +27,6 @@ public: static void LoadLidarState(vtkSMProxy * lidarCurrentProxy); - /** - * @brief UpdateProxyProperty set the values to the property "propNameToFind" of the proxy - * If the property is not found in the proxy, a message is displayed but nothing is done. - * This function is useful, if the property type is unknown. - * If it is known, you should directly use vtkSMPropertyHelper to set the property - * @param proxy proxy where to search the property - * @param propNameToFind name of the property - * @param values properties values to set - * @return 1 if the property has been well set, 0 otherwise - */ - static int UpdateProxyProperty(vtkSMProxy * proxy, const std::string & propNameToFind, - const std::vector<std::string> & values); - public slots: /** * Called when the action is triggered. diff --git a/ApplicationComponents/SensorWidget/lqSensorReaderWidget.cxx b/ApplicationComponents/SensorWidget/lqSensorReaderWidget.cxx index 960609d7c..0ce8dc8a2 100644 --- a/ApplicationComponents/SensorWidget/lqSensorReaderWidget.cxx +++ b/ApplicationComponents/SensorWidget/lqSensorReaderWidget.cxx @@ -7,6 +7,9 @@ #include <vtkSMPropertyHelper.h> #include <vtkSMProxy.h> +#include <iostream> +#include <fstream> + //----------------------------------------------------------------------------- lqSensorReaderWidget::lqSensorReaderWidget(QWidget *parent) : lqSensorWidget(parent) @@ -14,7 +17,7 @@ lqSensorReaderWidget::lqSensorReaderWidget(QWidget *parent) : // In reading mode, we only read the packet received on the right lidarPort. // This is why we want to expose it in reader mode too this->UI->lidarPort->setVisible(true); -} + } //----------------------------------------------------------------------------- void lqSensorReaderWidget::onUpdateUI() diff --git a/ApplicationComponents/SensorWidget/lqSensorReaderWidget.h b/ApplicationComponents/SensorWidget/lqSensorReaderWidget.h index 4c160a2c3..044cecc6a 100644 --- a/ApplicationComponents/SensorWidget/lqSensorReaderWidget.h +++ b/ApplicationComponents/SensorWidget/lqSensorReaderWidget.h @@ -9,6 +9,8 @@ class pqPipelineSource; + + /** * @brief The lqSensorWidget enable to handle some function of a Lidar with buttons. * @@ -25,6 +27,9 @@ class LQAPPLICATIONCOMPONENTS_EXPORT lqSensorReaderWidget : public lqSensorWidge QString GetExplanationOnUI() override; public slots: + /*! + * @brief Function slot used to update the text of the UI depending on whether we are in reader or stream mode + */ void onUpdateUI() override; private: diff --git a/ApplicationComponents/SensorWidget/lqSensorWidget.cxx b/ApplicationComponents/SensorWidget/lqSensorWidget.cxx index b2bb69472..ff92c6c49 100644 --- a/ApplicationComponents/SensorWidget/lqSensorWidget.cxx +++ b/ApplicationComponents/SensorWidget/lqSensorWidget.cxx @@ -1,4 +1,5 @@ #include "lqSensorWidget.h" +#include "lqHelper.h" #include "ui_lqSensorWidget.h" #include "lqLoadLidarStateReaction.h" @@ -13,6 +14,7 @@ #include <vtkSMProperty.h> #include <vtkSMPropertyHelper.h> #include <vtkSMProxy.h> +#include <vtkSMSourceProxy.h> #include "vtkLidarStream.h" @@ -44,6 +46,108 @@ lqSensorWidget::lqSensorWidget(QWidget *parent) : this->setFocusPolicy(Qt::StrongFocus); this->SourceToDisplay = nullptr; + + + this->UI->enableLiveDataTransformCheckBox->setVisible(true); + + // set up arrays to point on sliders and spinboxes corresponding to each transform param + this->slider_Array[TRANSFORMVALUE_INDEX::POS_X] = this->UI->TxSlider; + this->slider_Array[TRANSFORMVALUE_INDEX::POS_Y] = this->UI->TySlider; + this->slider_Array[TRANSFORMVALUE_INDEX::POS_Z] = this->UI->TzSlider; + this->slider_Array[TRANSFORMVALUE_INDEX::ROT_ROLL] = this->UI->RollSlider; + this->slider_Array[TRANSFORMVALUE_INDEX::ROT_PITCH] = this->UI->PitchSlider; + this->slider_Array[TRANSFORMVALUE_INDEX::ROT_YAW] = this->UI->YawSlider; + + this->spinbox_Array[TRANSFORMVALUE_INDEX::POS_X] = this->UI->TxSpinBox; + this->spinbox_Array[TRANSFORMVALUE_INDEX::POS_Y] = this->UI->TySpinBox; + this->spinbox_Array[TRANSFORMVALUE_INDEX::POS_Z] = this->UI->TzSpinBox; + this->spinbox_Array[TRANSFORMVALUE_INDEX::ROT_ROLL] = this->UI->RollSpinBox; + this->spinbox_Array[TRANSFORMVALUE_INDEX::ROT_PITCH] = this->UI->PitchSpinBox; + this->spinbox_Array[TRANSFORMVALUE_INDEX::ROT_YAW] = this->UI->YawSpinBox; + + // Hide UI that shall not be shown if not in live transform mode + this->onEnableLiveTransformToggle(); + + // init internal values from UI defaults + for (unsigned int idxValue = 0; idxValue < TRANSFORMVALUE_INDEX::TRANSFORM_SIZE; idxValue++) + { + min_Transform[idxValue] = this->spinbox_Array[idxValue]->minimum(); + max_Transform[idxValue] = this->spinbox_Array[idxValue]->maximum(); + curr_Transform[idxValue] = this->spinbox_Array[idxValue]->value(); + } + + connect(this->UI->enableLiveDataTransformCheckBox, &QCheckBox::stateChanged, this, + [this] { lqSensorWidget::onEnableLiveTransformToggle(); }); + + // Connect Slider movement to transform updating function + connect(this->UI->TxSlider, &QSlider::sliderMoved, this, + [this] { lqSensorWidget::onSliderUpdate(TRANSFORMVALUE_INDEX::POS_X); }); + connect(this->UI->TySlider, &QSlider::sliderMoved, this, + [this] { lqSensorWidget::onSliderUpdate(TRANSFORMVALUE_INDEX::POS_Y); }); + connect(this->UI->TzSlider, &QSlider::sliderMoved, this, + [this] { lqSensorWidget::onSliderUpdate(TRANSFORMVALUE_INDEX::POS_Z); }); + connect(this->UI->RollSlider, &QSlider::sliderMoved, this, + [this] { lqSensorWidget::onSliderUpdate(TRANSFORMVALUE_INDEX::ROT_ROLL); }); + connect(this->UI->PitchSlider, &QSlider::sliderMoved, this, + [this] { lqSensorWidget::onSliderUpdate(TRANSFORMVALUE_INDEX::ROT_PITCH); }); + connect(this->UI->YawSlider, &QSlider::sliderMoved, this, + [this] { lqSensorWidget::onSliderUpdate(TRANSFORMVALUE_INDEX::ROT_YAW); }); + + // connect Slider Release signal to make sur last value of the slider is recorded + connect(this->UI->TxSlider, &QSlider::sliderReleased, this, + [this] { lqSensorWidget::onSliderUpdate(TRANSFORMVALUE_INDEX::POS_X); }); + connect(this->UI->TySlider, &QSlider::sliderReleased, this, + [this] { lqSensorWidget::onSliderUpdate(TRANSFORMVALUE_INDEX::POS_Y); }); + connect(this->UI->TzSlider, &QSlider::sliderReleased, this, + [this] { lqSensorWidget::onSliderUpdate(TRANSFORMVALUE_INDEX::POS_Z); }); + connect(this->UI->RollSlider, &QSlider::sliderReleased, this, + [this] { lqSensorWidget::onSliderUpdate(TRANSFORMVALUE_INDEX::ROT_ROLL); }); + connect(this->UI->PitchSlider, &QSlider::sliderReleased, this, + [this] { lqSensorWidget::onSliderUpdate(TRANSFORMVALUE_INDEX::ROT_PITCH); }); + connect(this->UI->YawSlider, &QSlider::sliderReleased, this, + [this] { lqSensorWidget::onSliderUpdate(TRANSFORMVALUE_INDEX::ROT_YAW); }); + + // connect value spinboxes to corresponding function + connect(this->UI->TxSpinBox, &QDoubleSpinBox::editingFinished, this, + [this] { lqSensorWidget::onValueSpinBoxUpdate(TRANSFORMVALUE_INDEX::POS_X); }); + connect(this->UI->TySpinBox, &QDoubleSpinBox::editingFinished, this, + [this] { lqSensorWidget::onValueSpinBoxUpdate(TRANSFORMVALUE_INDEX::POS_Y); }); + connect(this->UI->TzSpinBox, &QDoubleSpinBox::editingFinished, this, + [this] { lqSensorWidget::onValueSpinBoxUpdate(TRANSFORMVALUE_INDEX::POS_Z); }); + connect(this->UI->RollSpinBox, &QDoubleSpinBox::editingFinished, this, + [this] { lqSensorWidget::onValueSpinBoxUpdate(TRANSFORMVALUE_INDEX::ROT_ROLL); }); + connect(this->UI->PitchSpinBox, &QDoubleSpinBox::editingFinished, this, + [this] { lqSensorWidget::onValueSpinBoxUpdate(TRANSFORMVALUE_INDEX::ROT_PITCH); }); + connect(this->UI->YawSpinBox, &QDoubleSpinBox::editingFinished, this, + [this] { lqSensorWidget::onValueSpinBoxUpdate(TRANSFORMVALUE_INDEX::ROT_YAW); }); + + // connect min max spin boxes to corresponding function + connect(this->UI->MinTranslationValueSpinBox, &QDoubleSpinBox::editingFinished, this, + [this] { lqSensorWidget::onMinMaxValueSpinBoxUpdate(TRANSFORMVALUE_INDEX::POS_X); }); + connect(this->UI->MinTranslationValueSpinBox, &QDoubleSpinBox::editingFinished, this, + [this] { lqSensorWidget::onMinMaxValueSpinBoxUpdate(TRANSFORMVALUE_INDEX::POS_Y); }); + connect(this->UI->MinTranslationValueSpinBox, &QDoubleSpinBox::editingFinished, this, + [this] { lqSensorWidget::onMinMaxValueSpinBoxUpdate(TRANSFORMVALUE_INDEX::POS_Z); }); + connect(this->UI->MinRotationValueSpinBox, &QDoubleSpinBox::editingFinished, this, + [this] { lqSensorWidget::onMinMaxValueSpinBoxUpdate(TRANSFORMVALUE_INDEX::ROT_ROLL); }); + connect(this->UI->MinRotationValueSpinBox, &QDoubleSpinBox::editingFinished, this, + [this] { lqSensorWidget::onMinMaxValueSpinBoxUpdate(TRANSFORMVALUE_INDEX::ROT_PITCH); }); + connect(this->UI->MinRotationValueSpinBox, &QDoubleSpinBox::editingFinished, this, + [this] { lqSensorWidget::onMinMaxValueSpinBoxUpdate(TRANSFORMVALUE_INDEX::ROT_YAW); }); + + // connect min max spin boxes to corresponding function + connect(this->UI->MaxTranslationValueSpinBox, &QDoubleSpinBox::editingFinished, this, + [this] { lqSensorWidget::onMinMaxValueSpinBoxUpdate(TRANSFORMVALUE_INDEX::POS_X); }); + connect(this->UI->MaxTranslationValueSpinBox, &QDoubleSpinBox::editingFinished, this, + [this] { lqSensorWidget::onMinMaxValueSpinBoxUpdate(TRANSFORMVALUE_INDEX::POS_Y); }); + connect(this->UI->MaxTranslationValueSpinBox, &QDoubleSpinBox::editingFinished, this, + [this] { lqSensorWidget::onMinMaxValueSpinBoxUpdate(TRANSFORMVALUE_INDEX::POS_Z); }); + connect(this->UI->MaxRotationValueSpinBox, &QDoubleSpinBox::editingFinished, this, + [this] { lqSensorWidget::onMinMaxValueSpinBoxUpdate(TRANSFORMVALUE_INDEX::ROT_ROLL); }); + connect(this->UI->MaxRotationValueSpinBox, &QDoubleSpinBox::editingFinished, this, + [this] { lqSensorWidget::onMinMaxValueSpinBoxUpdate(TRANSFORMVALUE_INDEX::ROT_PITCH); }); + connect(this->UI->MaxRotationValueSpinBox, &QDoubleSpinBox::editingFinished, this, + [this] { lqSensorWidget::onMinMaxValueSpinBoxUpdate(TRANSFORMVALUE_INDEX::ROT_YAW); }); } //----------------------------------------------------------------------------- @@ -151,6 +255,9 @@ void lqSensorWidget::onCalibrate() { this->CalibrationFunction(this->LidarSource, this->PositionOrientationSource); } + // update internal transform based on updated proxy transform + ReadValueFromProxy(); + } //----------------------------------------------------------------------------- @@ -250,3 +357,258 @@ void lqSensorWidget::focusInEvent(QFocusEvent*) { emit selected(this); } + +void lqSensorWidget::onEnableLiveTransformToggle() +{ + // Get enable flag for the check box + bool isLiveTransformVisible = this->UI->enableLiveDataTransformCheckBox->isChecked(); + + // set / unset visibility of the tools to move the lidar data + this->UI->TranslationLabel->setVisible(isLiveTransformVisible); + this->UI->labelMinMaxTranslation->setVisible(isLiveTransformVisible); + this->UI->MinTranslationValueSpinBox->setVisible(isLiveTransformVisible); + this->UI->MaxTranslationValueSpinBox->setVisible(isLiveTransformVisible); + this->UI->TxSlider->setVisible(isLiveTransformVisible); + this->UI->TxSpinBox->setVisible(isLiveTransformVisible); + this->UI->TySlider->setVisible(isLiveTransformVisible); + this->UI->TySpinBox->setVisible(isLiveTransformVisible); + this->UI->TzSlider->setVisible(isLiveTransformVisible); + this->UI->TzSpinBox->setVisible(isLiveTransformVisible); + this->UI->RotationLabel->setVisible(isLiveTransformVisible); + this->UI->labelMinMaxRotation->setVisible(isLiveTransformVisible); + this->UI->MinRotationValueSpinBox->setVisible(isLiveTransformVisible); + this->UI->MaxRotationValueSpinBox->setVisible(isLiveTransformVisible); + this->UI->RollSlider->setVisible(isLiveTransformVisible); + this->UI->RollSpinBox->setVisible(isLiveTransformVisible); + this->UI->PitchSlider->setVisible(isLiveTransformVisible); + this->UI->PitchSpinBox->setVisible(isLiveTransformVisible); + this->UI->YawSlider->setVisible(isLiveTransformVisible); + this->UI->YawSpinBox->setVisible(isLiveTransformVisible); + + ReadValueFromProxy(); +} + +void lqSensorWidget::onSliderUpdate(unsigned int idxValue) +{ + // get value from slider + // update MinMax values + if (idxValue <= POS_Z) + { + min_Transform[idxValue] = this->UI->MinTranslationValueSpinBox->value(); + max_Transform[idxValue] = this->UI->MaxTranslationValueSpinBox->value(); + } + else + { + min_Transform[idxValue] = this->UI->MinRotationValueSpinBox->value(); + max_Transform[idxValue] = this->UI->MaxRotationValueSpinBox->value(); + } + + // compute value represented by the slider position + double sliderRange = std::max( + static_cast<double>(slider_Array[idxValue]->maximum() - slider_Array[idxValue]->minimum()), + 1.0); + double value = + (static_cast<double>((slider_Array[idxValue]->value() - slider_Array[idxValue]->minimum())) / + sliderRange) * + (max_Transform[idxValue] - min_Transform[idxValue]) + + min_Transform[idxValue]; + + // update internal value + curr_Transform[idxValue] = value; + + // update corresponding spinbox + this->spinbox_Array[idxValue]->setValue(value); + + // call update transform + onUpdateTransform(); +} + +void lqSensorWidget::onValueSpinBoxUpdate(unsigned int idxValue) +{ + // update internal value + curr_Transform[idxValue] = this->spinbox_Array[idxValue]->value(); + // update slider position based on scaling + int sliderPosition = slider_Array[idxValue]->minimum() + + ((slider_Array[idxValue]->maximum() - slider_Array[idxValue]->minimum()) * + (curr_Transform[idxValue] - min_Transform[idxValue]) / + (max_Transform[idxValue] - min_Transform[idxValue])); + this->slider_Array[idxValue]->setValue(sliderPosition); + + // call update transform + onUpdateTransform(); +} + +void lqSensorWidget::onMinMaxValueSpinBoxUpdate(unsigned int idxValue) +{ + // update MinMax values ( assumption is that first values are translation then rotation, in + // X,Y,Z,Roll,Pitch,Yaw order) + if (idxValue <= POS_Z) + { + min_Transform[idxValue] = this->UI->MinTranslationValueSpinBox->value(); + max_Transform[idxValue] = this->UI->MaxTranslationValueSpinBox->value(); + } + else + { + min_Transform[idxValue] = this->UI->MinRotationValueSpinBox->value(); + max_Transform[idxValue] = this->UI->MaxRotationValueSpinBox->value(); + } + // Saturate internal values + if (min_Transform[idxValue] > curr_Transform[idxValue]) + { + curr_Transform[idxValue] = min_Transform[idxValue]; + } + if (max_Transform[idxValue] < curr_Transform[idxValue]) + { + curr_Transform[idxValue] = max_Transform[idxValue]; + } + + // update widgets + UpdateSpinBoxAndSliderFromInternalValues(idxValue); + + // call update transform + onUpdateTransform(); +} + +void lqSensorWidget::UpdateSpinBoxAndSliderFromInternalValues(unsigned int idxValue) +{ + // update spinbox min and max + this->spinbox_Array[idxValue]->setMinimum(min_Transform[idxValue]); + this->spinbox_Array[idxValue]->setMaximum(max_Transform[idxValue]); + + // update spinbox / slider position based on scaling + this->spinbox_Array[idxValue]->setValue(curr_Transform[idxValue]); + + int sliderPosition = slider_Array[idxValue]->minimum() + + ((slider_Array[idxValue]->maximum() - slider_Array[idxValue]->minimum()) * + (curr_Transform[idxValue] - min_Transform[idxValue]) / + (max_Transform[idxValue] - min_Transform[idxValue])); + this->slider_Array[idxValue]->setValue(sliderPosition); +} + +void lqSensorWidget::ReadValueFromProxy() +{ + // Get lidar proxy + if (this->LidarSource) + { + vtkSMProxy* lidarProxy = this->LidarSource->getProxy(); + + // names of the lidarreader proxy + std::string propertyNameTranslation = "Position"; + std::string propertyNameRotation = "Rotation"; + std::string TransformproxyName = "Transform2"; + + if (lidarProxy) + { + // Get transform proxy + vtkSMProxy* TransformProxy = SearchProxyByName(lidarProxy, TransformproxyName); + // Get translation property + vtkSMProperty* propTranslation = + GetPropertyFromProxy(TransformProxy, propertyNameTranslation); + std::vector<double> TranslationVector = vtkSMPropertyHelper(propTranslation).GetDoubleArray(); + + // Get Translation parameters + double Tx = TranslationVector[0]; + double Ty = TranslationVector[1]; + double Tz = TranslationVector[2]; + + // Get translation property + vtkSMProperty* propRotation = GetPropertyFromProxy(TransformProxy, propertyNameRotation); + std::vector<double> RotationVector = vtkSMPropertyHelper(propRotation).GetDoubleArray(); + // Get Rotation parameters + double Roll = RotationVector[0]; + double Pitch = RotationVector[1]; + double Yaw = RotationVector[2]; + + // Concatenate them in a vector + double TransformParams[TRANSFORMVALUE_INDEX::TRANSFORM_SIZE] = { Tx, Ty, Tz, Roll, Pitch, + Yaw }; + + // update internal values + // Get current min and max for rotation and translation + double minMinTranslation = this->UI->MinTranslationValueSpinBox->value(); + double maxMaxTranslation = this->UI->MaxTranslationValueSpinBox->value(); + double minMinRotation = this->UI->MinRotationValueSpinBox->value(); + double maxMaxRotation = this->UI->MaxRotationValueSpinBox->value(); + + // Loop over parameters to update the current values with what is found on the proxy + for (unsigned int idxValue = 0; idxValue < TRANSFORMVALUE_INDEX::TRANSFORM_SIZE; idxValue++) + { + // update current value + curr_Transform[idxValue] = TransformParams[idxValue]; + + // update min and max if necessary + min_Transform[idxValue] = std::min(min_Transform[idxValue], curr_Transform[idxValue]); + max_Transform[idxValue] = std::max(max_Transform[idxValue], curr_Transform[idxValue]); + this->spinbox_Array[idxValue]->setMinimum(min_Transform[idxValue]); + this->spinbox_Array[idxValue]->setMaximum(max_Transform[idxValue]); + + // collect overall minimum and maximum to be able to update the min and max spinboxes + if (idxValue <= POS_Z) + { + minMinTranslation = std::min(minMinTranslation, min_Transform[idxValue]); + maxMaxTranslation = std::max(maxMaxTranslation, max_Transform[idxValue]); + } + else + { + minMinRotation = std::min(minMinRotation, min_Transform[idxValue]); + maxMaxRotation = std::max(maxMaxRotation, max_Transform[idxValue]); + } + // update spinbox and slider + UpdateSpinBoxAndSliderFromInternalValues(idxValue); + + } // End for loop on transforms + + // set min and max spinboxes with the minimum found + this->UI->MinTranslationValueSpinBox->setValue(minMinTranslation); + this->UI->MaxTranslationValueSpinBox->setValue(maxMaxTranslation); + this->UI->MinRotationValueSpinBox->setValue(minMinRotation); + this->UI->MaxRotationValueSpinBox->setValue(maxMaxRotation); + + } // end if lidarProxy + } // end if LidarSource +} + +void lqSensorWidget::onUpdateTransform() +{ + vtkSMProxy* lidarProxy = this->LidarSource->getProxy(); + + // names of the lidarreader proxy + std::string propertyNameTranslation = "Position"; + std::string propertyNameRotation = "Rotation"; + std::string TransformproxyName = "Transform2"; + + if (lidarProxy) + { + // Recover translation from sliders + double Tx = curr_Transform[TRANSFORMVALUE_INDEX::POS_X]; + double Ty = curr_Transform[TRANSFORMVALUE_INDEX::POS_Y]; + double Tz = curr_Transform[TRANSFORMVALUE_INDEX::POS_Z]; + std::vector<std::string> TranslationValues = { std::to_string(Tx), std::to_string(Ty), + std::to_string(Tz) }; + + // Recover Rotation from sliders + double Roll = curr_Transform[TRANSFORMVALUE_INDEX::ROT_ROLL]; + double Pitch = curr_Transform[TRANSFORMVALUE_INDEX::ROT_PITCH]; + double Yaw = curr_Transform[TRANSFORMVALUE_INDEX::ROT_YAW]; + std::vector<std::string> RotationValues = { std::to_string(Roll), std::to_string(Pitch), + std::to_string(Yaw) }; + + // Get transform proxy + vtkSMProxy* TransformProxy = SearchProxyByName(lidarProxy, TransformproxyName); + + // update proxy properties + vtkSMProperty* prop = GetPropertyFromProxy(TransformProxy, propertyNameTranslation); + UpdateProxyProperty(TransformProxy, propertyNameTranslation, TranslationValues); + + prop = GetPropertyFromProxy(TransformProxy, propertyNameRotation); + UpdateProxyProperty(TransformProxy, propertyNameRotation, RotationValues); + + lidarProxy->UpdateSelfAndAllInputs(); + vtkSMSourceProxy* sourcelidarProxy = vtkSMSourceProxy::SafeDownCast(lidarProxy); + if (sourcelidarProxy) + { + sourcelidarProxy->UpdatePipelineInformation(); + } + pqApplicationCore::instance()->render(); + } +} diff --git a/ApplicationComponents/SensorWidget/lqSensorWidget.h b/ApplicationComponents/SensorWidget/lqSensorWidget.h index 82d9ba194..3d1dbddfa 100644 --- a/ApplicationComponents/SensorWidget/lqSensorWidget.h +++ b/ApplicationComponents/SensorWidget/lqSensorWidget.h @@ -4,6 +4,8 @@ #include "lqapplicationcomponents_export.h" #include <QWidget> +#include <QSlider> +#include <QDoubleSpinBox> #include <functional> class pqPipelineSource; @@ -23,6 +25,19 @@ class LQAPPLICATIONCOMPONENTS_EXPORT lqSensorWidget : public QWidget { Q_OBJECT + // method to identify the normal of the plane containing the turn + enum TRANSFORMVALUE_INDEX + { + POS_X = 0, + POS_Y = 1, + POS_Z = 2, + ROT_ROLL = 3, + ROT_PITCH = 4, + ROT_YAW = 5, + TRANSFORM_SIZE = 6 + }; + + public: explicit lqSensorWidget(QWidget *parent = 0); ~lqSensorWidget(); @@ -46,6 +61,12 @@ class LQAPPLICATIONCOMPONENTS_EXPORT lqSensorWidget : public QWidget virtual QString GetExplanationOnUI() = 0; + /*! + * @brief Function used to get the transfrom data from a proxy and update the internal data / + * spinbox and sliders of this class accordingly + */ + void ReadValueFromProxy(); + public slots: virtual void onUpdateUI(); void onCalibrate(); @@ -54,6 +75,36 @@ class LQAPPLICATIONCOMPONENTS_EXPORT lqSensorWidget : public QWidget void onSaveLidarState(); void onLoadLidarState(); + protected slots: + /*! + * @brief Function slot used to update the transform of the lidarsource based on the data + * entered in the UI + */ + void onUpdateTransform(); + /*! + * @brief Function slot used to react to an update of a spinbox of a value + * This will store the data entered in internal variables, update the associated slider + * accordingly and update the transform + */ + void onValueSpinBoxUpdate(unsigned int idxValue); + /*! + * @brief Function slot used to react to an update of a slider of a value. This will update the + * internal variables for this and update the transform + */ + void onSliderUpdate(unsigned int idxValue); + /*! + * @brief Function slot used to react of an update of the min / max spinboxes for translations + * or rotations This will saturate the values used in the transform and update spinboxes / + * sliders accordingly + */ + void onMinMaxValueSpinBoxUpdate(unsigned int idxValue); + /*! + * @brief Function slot used to react on the toggling of the check box to enable or not the live + * transform This will hide / show the whole interface and read the data from the proxy to feed + * to the interface + */ + void onEnableLiveTransformToggle(); + signals: void selected(lqSensorWidget*); @@ -69,6 +120,27 @@ protected: bool IsClosing; Ui::lqSensorWidget* UI; std::function<void(pqPipelineSource* &, pqPipelineSource* &)> CalibrationFunction; + + + /*! + * @brief common function to update internal QT widgets to internal values + */ + void UpdateSpinBoxAndSliderFromInternalValues(unsigned int idxValue); + + // maximum value for the sliders scaling + double min_Transform[TRANSFORMVALUE_INDEX::TRANSFORM_SIZE]; // in order : X,Y,Z,Roll,Pitch,Yaw + + // minimum value for the slider / spinbox scaling + double max_Transform[TRANSFORMVALUE_INDEX::TRANSFORM_SIZE]; // in order : X,Y,Z,Roll,Pitch,Yaw + + // current value for each transform + double curr_Transform[TRANSFORMVALUE_INDEX::TRANSFORM_SIZE]; // in order : X,Y,Z,Roll,Pitch,Yaw + + // current value for each transform + QSlider* slider_Array[TRANSFORMVALUE_INDEX::TRANSFORM_SIZE]; // in order : X,Y,Z,Roll,Pitch,Yaw + + // current value for each transform + QDoubleSpinBox* spinbox_Array[TRANSFORMVALUE_INDEX::TRANSFORM_SIZE]; // in order : X,Y,Z,Roll,Pitch,Yaw }; #endif // LQSENSORWIDGET_H diff --git a/ApplicationComponents/SensorWidget/lqSensorWidget.ui b/ApplicationComponents/SensorWidget/lqSensorWidget.ui index 5d43b31e5..c2be0832c 100644 --- a/ApplicationComponents/SensorWidget/lqSensorWidget.ui +++ b/ApplicationComponents/SensorWidget/lqSensorWidget.ui @@ -6,12 +6,12 @@ <rect> <x>0</x> <y>0</y> - <width>330</width> - <height>150</height> + <width>334</width> + <height>300</height> </rect> </property> <property name="sizePolicy"> - <sizepolicy hsizetype="Expanding" vsizetype="Fixed"> + <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> <horstretch>0</horstretch> <verstretch>0</verstretch> </sizepolicy> @@ -19,13 +19,13 @@ <property name="minimumSize"> <size> <width>330</width> - <height>150</height> + <height>225</height> </size> </property> <property name="maximumSize"> <size> <width>16777215</width> - <height>300</height> + <height>500</height> </size> </property> <property name="windowTitle"> @@ -323,6 +323,441 @@ </item> </layout> </item> + <item> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_7"> + <item> + <widget class="QCheckBox" name="enableLiveDataTransformCheckBox"> + <property name="layoutDirection"> + <enum>Qt::RightToLeft</enum> + </property> + <property name="text"> + <string>Enable Live Data Transform</string> + </property> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_12"> + <item> + <widget class="QLabel" name="TranslationLabel"> + <property name="toolTip"> + <string><html><head/><body><p>Translate the data in X,Y, and Z directions within boundaries defined by Min and Max value. You may use the sliders of the boxes below to change the values.</p></body></html></string> + </property> + <property name="text"> + <string>Translation [X;Y;Z]</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_17"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::MinimumExpanding</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QLabel" name="labelMinMaxTranslation"> + <property name="text"> + <string>[Min;Max]</string> + </property> + </widget> + </item> + <item> + <widget class="QDoubleSpinBox" name="MinTranslationValueSpinBox"> + <property name="minimum"> + <double>-2000.000000000000000</double> + </property> + <property name="maximum"> + <double>2000.000000000000000</double> + </property> + <property name="value"> + <double>-5.000000000000000</double> + </property> + </widget> + </item> + <item> + <widget class="QDoubleSpinBox" name="MaxTranslationValueSpinBox"> + <property name="minimum"> + <double>-2000.000000000000000</double> + </property> + <property name="maximum"> + <double>2000.000000000000000</double> + </property> + <property name="value"> + <double>5.000000000000000</double> + </property> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="LayoutTranslationSliders"> + <item> + <widget class="QSlider" name="TxSlider"> + <property name="minimum"> + <number>-50</number> + </property> + <property name="maximum"> + <number>50</number> + </property> + <property name="value"> + <number>0</number> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_8"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QDoubleSpinBox" name="TxSpinBox"> + <property name="minimum"> + <double>-5.000000000000000</double> + </property> + <property name="maximum"> + <double>5.000000000000000</double> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_5"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>10</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QSlider" name="TySlider"> + <property name="minimum"> + <number>-50</number> + </property> + <property name="maximum"> + <number>50</number> + </property> + <property name="value"> + <number>0</number> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_9"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QDoubleSpinBox" name="TySpinBox"> + <property name="minimum"> + <double>-5.000000000000000</double> + </property> + <property name="maximum"> + <double>5.000000000000000</double> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_4"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>10</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QSlider" name="TzSlider"> + <property name="minimum"> + <number>-50</number> + </property> + <property name="maximum"> + <number>50</number> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_10"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QDoubleSpinBox" name="TzSpinBox"> + <property name="minimum"> + <double>-5.000000000000000</double> + </property> + <property name="maximum"> + <double>5.000000000000000</double> + </property> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_9"> + <item> + <widget class="QLabel" name="RotationLabel"> + <property name="toolTip"> + <string><html><head/><body><p>Rotate the data according to base axis with values defined between min and max values</p><p>Roll corresponds to rotation around the X axis</p><p>Pitch corresponds to rotation around the Y axis</p><p>Yaw corresponds to rotation around the Z axis </p></body></html></string> + </property> + <property name="text"> + <string>Rotation [Roll;Pitch;Yaw]</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_16"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeType"> + <enum>QSizePolicy::MinimumExpanding</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QLabel" name="labelMinMaxRotation"> + <property name="text"> + <string>[Min;Max]</string> + </property> + </widget> + </item> + <item> + <widget class="QDoubleSpinBox" name="MinRotationValueSpinBox"> + <property name="maximum"> + <double>360.000000000000000</double> + </property> + <property name="singleStep"> + <double>1.000000000000000</double> + </property> + </widget> + </item> + <item> + <widget class="QDoubleSpinBox" name="MaxRotationValueSpinBox"> + <property name="maximum"> + <double>360.000000000000000</double> + </property> + <property name="singleStep"> + <double>1.000000000000000</double> + </property> + <property name="value"> + <double>360.000000000000000</double> + </property> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="LayoutRotationSliders"> + <item> + <widget class="QSlider" name="RollSlider"> + <property name="minimum"> + <number>-500</number> + </property> + <property name="maximum"> + <number>500</number> + </property> + <property name="value"> + <number>-500</number> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_11"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QDoubleSpinBox" name="RollSpinBox"> + <property name="minimum"> + <double>0.000000000000000</double> + </property> + <property name="maximum"> + <double>360.000000000000000</double> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_6"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>10</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QSlider" name="PitchSlider"> + <property name="minimum"> + <number>-500</number> + </property> + <property name="maximum"> + <number>500</number> + </property> + <property name="value"> + <number>-500</number> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_12"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QDoubleSpinBox" name="PitchSpinBox"> + <property name="minimum"> + <double>0.000000000000000</double> + </property> + <property name="maximum"> + <double>360.000000000000000</double> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_7"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>10</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QSlider" name="YawSlider"> + <property name="maximumSize"> + <size> + <width>16777215</width> + <height>16777215</height> + </size> + </property> + <property name="minimum"> + <number>-500</number> + </property> + <property name="maximum"> + <number>500</number> + </property> + <property name="value"> + <number>-500</number> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer_13"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QDoubleSpinBox" name="YawSpinBox"> + <property name="maximum"> + <double>360.000000000000000</double> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </item> <item> <spacer name="verticalSpacer"> <property name="orientation"> @@ -418,24 +853,24 @@ </item> <item> <widget class="QPushButton" name="SaveLidarState"> + <property name="toolTip"> + <string>Save Lidar state</string> + </property> <property name="icon"> - <iconset resource="../LVCore/ApplicationComponents/lqResources.qrc"> + <iconset> <normaloff>:/lqResources/Icons/pqSaveState.png</normaloff>:/lqResources/Icons/pqSaveState.png</iconset> </property> - <property name="toolTip"> - <string>Save Lidar state</string> - </property> </widget> </item> <item> <widget class="QPushButton" name="LoadLidarState"> + <property name="toolTip"> + <string>Load Lidar state</string> + </property> <property name="icon"> - <iconset resource="../LVCore/ApplicationComponents/lqResources.qrc"> + <iconset> <normaloff>:/lqResources/Icons/pqLoadState.png</normaloff>:/lqResources/Icons/pqLoadState.png</iconset> </property> - <property name="toolTip"> - <string>Load Lidar state</string> - </property> </widget> </item> <item> diff --git a/ApplicationComponents/lqHelper.cxx b/ApplicationComponents/lqHelper.cxx index 7dfaa4c8f..99b150670 100644 --- a/ApplicationComponents/lqHelper.cxx +++ b/ApplicationComponents/lqHelper.cxx @@ -222,6 +222,75 @@ vtkSMProperty* GetPropertyFromProxy(vtkSMProxy * proxy, const std::string &propN return nullptr; } +//----------------------------------------------------------------------------- +int UpdateProxyProperty(vtkSMProxy* proxy, const std::string& propNameToFind, const std::vector<std::string>& values) +{ + // If there is no value to set, the request is skipped + if (values.empty()) + { + std::string message = "Property " + propNameToFind + " couldn't be applied"; + QMessageBox::information(nullptr, QObject::tr(""), QObject::tr(message.c_str())); + return 0; + } + + vtkSMProperty* prop = GetPropertyFromProxy(proxy, propNameToFind); + + if (!prop) + { + return 0; + } + + std::vector<double> propertyAsDoubleArray = vtkSMPropertyHelper(prop).GetDoubleArray(); + if (propertyAsDoubleArray.size() > 1) + { + if (propertyAsDoubleArray.size() != values.size()) + { + std::cout << "Values to applied and base property does not have the same size" << std::endl; + } + std::vector<double> d; + for (unsigned int j = 0; j < values.size(); j++) + { + d.push_back(std::stod(values[j])); + } + vtkSMPropertyHelper(prop).Set(d.data(), d.size()); + proxy->UpdateProperty(propNameToFind.c_str()); + return 1; + } + else + { + // If the property is a valid variant we display it to the user + vtkVariant propertyAsVariant = vtkSMPropertyHelper(prop).GetAsVariant(0); + if (propertyAsVariant.IsValid()) + { + if (propertyAsVariant.IsInt()) + { + vtkSMBooleanDomain* boolDomain = + vtkSMBooleanDomain::SafeDownCast(prop->FindDomain("vtkSMBooleanDomain")); + if (boolDomain && (values[0].compare("false") == 0 || values[0].compare("true") == 0)) + { + int value = (values[0].compare("false") == 0) ? 0 : 1; + vtkSMPropertyHelper(prop).Set(value); + } + else + { + vtkSMPropertyHelper(prop).Set(std::stoi(values[0])); + } + } + else if (propertyAsVariant.IsNumeric()) + { + vtkSMPropertyHelper(prop).Set(std::stof(values[0])); + } + else if (propertyAsVariant.IsString()) + { + vtkSMPropertyHelper(prop).Set(values[0].c_str()); + } + proxy->UpdateProperty(propNameToFind.c_str()); + return 1; + } + } + return 0; +} + std::string GetGroupName(vtkSMProxy * existingProxy, const std::string & proxyToFindName) { vtkSMSessionProxyManager* pxm = existingProxy->GetSessionProxyManager(); diff --git a/ApplicationComponents/lqHelper.h b/ApplicationComponents/lqHelper.h index ec4affe1e..75ca0325f 100644 --- a/ApplicationComponents/lqHelper.h +++ b/ApplicationComponents/lqHelper.h @@ -140,6 +140,19 @@ std::vector<vtkSMProxy*> GetLidarsProxy(); */ vtkSMProperty* GetPropertyFromProxy(vtkSMProxy * proxy, const std::string &propNameToFind); + + /** + * @brief UpdateProxyProperty set the values to the property "propNameToFind" of the proxy + * If the property is not found in the proxy, a message is displayed but nothing is done. + * This function is useful, if the property type is unknown. + * If it is known, you should directly use vtkSMPropertyHelper to set the property + * @param proxy proxy where to search the property + * @param propNameToFind name of the property + * @param values properties values to set + * @return 1 if the property has been well set, 0 otherwise + */ +int UpdateProxyProperty(vtkSMProxy* proxy, const std::string& propNameToFind, const std::vector<std::string>& values); + /** * @brief GetGroupName Get the name of the first group where appear a proxy * @param existingProxy a proxy of the pipeline, use to get the ProxyDefinitionManager -- GitLab