560 lines
16 KiB
C++
560 lines
16 KiB
C++
/*
|
|
* Copyright (C) 2015 Open Source Robotics Foundation
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*
|
|
*/
|
|
|
|
#include "gazebo/msgs/msgs.hh"
|
|
#include "gazebo/common/Console.hh"
|
|
#include "gazebo/rendering/Material.hh"
|
|
#include "gazebo/gui/ConfigWidget.hh"
|
|
#include "gazebo/gui/model/VisualConfig.hh"
|
|
|
|
using namespace gazebo;
|
|
using namespace gui;
|
|
|
|
/////////////////////////////////////////////////
|
|
VisualConfig::VisualConfig()
|
|
{
|
|
this->setObjectName("VisualConfig");
|
|
|
|
// Layout for list
|
|
this->listLayout = new QVBoxLayout();
|
|
this->listLayout->setContentsMargins(0, 0, 0, 0);
|
|
this->listLayout->setAlignment(Qt::AlignTop);
|
|
|
|
// Widget for list, which will be scrollable
|
|
QWidget *listWidget = new QWidget();
|
|
listWidget->setLayout(this->listLayout);
|
|
listWidget->setStyleSheet("QWidget{background-color: #808080}");
|
|
|
|
// Scroll area for list
|
|
QScrollArea *scrollArea = new QScrollArea;
|
|
scrollArea->setWidget(listWidget);
|
|
scrollArea->setWidgetResizable(true);
|
|
|
|
// Add Visual button
|
|
QPushButton *addVisualButton = new QPushButton(tr("+ &Another Visual"));
|
|
addVisualButton->setMaximumWidth(200);
|
|
addVisualButton->setDefault(false);
|
|
addVisualButton->setAutoDefault(false);
|
|
connect(addVisualButton, SIGNAL(clicked()), this, SLOT(OnAddVisual()));
|
|
|
|
// Main layout
|
|
QVBoxLayout *mainLayout = new QVBoxLayout;
|
|
mainLayout->addWidget(scrollArea);
|
|
mainLayout->addWidget(addVisualButton);
|
|
mainLayout->setContentsMargins(0, 0, 0, 0);
|
|
this->setLayout(mainLayout);
|
|
|
|
this->counter = 0;
|
|
this->mapperRemove = new QSignalMapper(this);
|
|
|
|
this->connect(this->mapperRemove, SIGNAL(mapped(int)),
|
|
this, SLOT(OnRemoveVisual(int)));
|
|
|
|
this->mapperShow = new QSignalMapper(this);
|
|
this->connect(this->mapperShow, SIGNAL(mapped(int)),
|
|
this, SLOT(OnShowVisual(int)));
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
VisualConfig::~VisualConfig()
|
|
{
|
|
while (!this->configs.empty())
|
|
{
|
|
auto config = this->configs.begin();
|
|
delete config->second;
|
|
this->configs.erase(config);
|
|
}
|
|
while (!this->deletedConfigs.empty())
|
|
{
|
|
auto config = this->deletedConfigs.begin();
|
|
delete config->second;
|
|
this->deletedConfigs.erase(config);
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void VisualConfig::Init()
|
|
{
|
|
// Keep original data in case user cancels
|
|
for (auto &it : this->configs)
|
|
{
|
|
VisualConfigData *configData = it.second;
|
|
configData->originalDataMsg.CopyFrom(*this->GetData(configData->name));
|
|
}
|
|
|
|
// Clear lists
|
|
for (auto &it : this->deletedConfigs)
|
|
delete it.second->widget;
|
|
this->deletedConfigs.clear();
|
|
|
|
this->addedConfigs.clear();
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void VisualConfig::OnAddVisual()
|
|
{
|
|
std::stringstream visualIndex;
|
|
visualIndex << "visual_" << this->counter;
|
|
this->AddVisual(visualIndex.str());
|
|
emit VisualAdded(visualIndex.str());
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
unsigned int VisualConfig::GetVisualCount() const
|
|
{
|
|
return this->configs.size();
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void VisualConfig::Reset()
|
|
{
|
|
for (auto &it : this->configs)
|
|
{
|
|
this->listLayout->removeWidget(it.second->widget);
|
|
delete it.second;
|
|
}
|
|
|
|
this->configs.clear();
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void VisualConfig::AddVisual(const std::string &_name,
|
|
const msgs::Visual *_visualMsg)
|
|
{
|
|
// Header button
|
|
QRadioButton *headerButton = new QRadioButton();
|
|
headerButton->setChecked(false);
|
|
headerButton->setFocusPolicy(Qt::NoFocus);
|
|
headerButton->setText(QString(_name.c_str()));
|
|
headerButton->setStyleSheet(
|
|
"QRadioButton {\
|
|
color: #d0d0d0;\
|
|
}\
|
|
QRadioButton::indicator::unchecked {\
|
|
image: url(:/images/right_arrow.png);\
|
|
}\
|
|
QRadioButton::indicator::checked {\
|
|
image: url(:/images/down_arrow.png);\
|
|
}");
|
|
|
|
// Show button
|
|
auto showVisualButton = new QToolButton(this);
|
|
showVisualButton->setObjectName(
|
|
"showVisualButton_" + QString(_name.c_str()));
|
|
showVisualButton->setFixedSize(QSize(30, 30));
|
|
showVisualButton->setToolTip("Show/hide " + QString(_name.c_str()));
|
|
showVisualButton->setIcon(QPixmap(":/images/eye.png"));
|
|
showVisualButton->setToolButtonStyle(Qt::ToolButtonIconOnly);
|
|
showVisualButton->setIconSize(QSize(16, 16));
|
|
showVisualButton->setCheckable(true);
|
|
this->connect(showVisualButton, SIGNAL(clicked()), this->mapperShow,
|
|
SLOT(map()));
|
|
this->mapperShow->setMapping(showVisualButton, this->counter);
|
|
|
|
// Remove button
|
|
QToolButton *removeVisualButton = new QToolButton(this);
|
|
removeVisualButton->setObjectName(
|
|
"removeVisualButton_" + QString::number(this->counter));
|
|
removeVisualButton->setFixedSize(QSize(30, 30));
|
|
removeVisualButton->setToolTip("Remove " + QString(_name.c_str()));
|
|
removeVisualButton->setIcon(QPixmap(":/images/trashcan.png"));
|
|
removeVisualButton->setToolButtonStyle(Qt::ToolButtonIconOnly);
|
|
removeVisualButton->setIconSize(QSize(16, 16));
|
|
removeVisualButton->setCheckable(false);
|
|
connect(removeVisualButton, SIGNAL(clicked()), this->mapperRemove,
|
|
SLOT(map()));
|
|
this->mapperRemove->setMapping(removeVisualButton, this->counter);
|
|
|
|
// Header Layout
|
|
QHBoxLayout *headerLayout = new QHBoxLayout;
|
|
headerLayout->setContentsMargins(0, 0, 0, 0);
|
|
headerLayout->addWidget(headerButton);
|
|
headerLayout->addWidget(showVisualButton);
|
|
headerLayout->addWidget(removeVisualButton);
|
|
|
|
// Header widget
|
|
QWidget *headerWidget = new QWidget;
|
|
headerWidget->setLayout(headerLayout);
|
|
|
|
// ConfigWidget
|
|
msgs::Visual msgToLoad;
|
|
if (_visualMsg)
|
|
msgToLoad = *_visualMsg;
|
|
|
|
// set default values
|
|
// TODO: auto-fill them with SDF defaults
|
|
msgs::Material *matMsg = msgToLoad.mutable_material();
|
|
if (!matMsg->has_lighting())
|
|
matMsg->set_lighting(true);
|
|
|
|
ConfigWidget *configWidget = new ConfigWidget;
|
|
configWidget->Load(&msgToLoad);
|
|
configWidget->hide();
|
|
|
|
configWidget->SetWidgetVisible("id", false);
|
|
configWidget->SetWidgetVisible("name", false);
|
|
configWidget->SetWidgetVisible("parent_name", false);
|
|
configWidget->SetWidgetVisible("parent_id", false);
|
|
configWidget->SetWidgetVisible("delete_me", false);
|
|
configWidget->SetWidgetVisible("is_static", false);
|
|
configWidget->SetWidgetVisible("visible", false);
|
|
configWidget->SetWidgetVisible("scale", false);
|
|
configWidget->SetWidgetVisible("plugin", false);
|
|
configWidget->SetWidgetVisible("type", false);
|
|
configWidget->SetWidgetReadOnly("id", true);
|
|
configWidget->SetWidgetReadOnly("name", true);
|
|
configWidget->SetWidgetReadOnly("parent_name", true);
|
|
configWidget->SetWidgetReadOnly("parent_id", true);
|
|
configWidget->SetWidgetReadOnly("delete_me", true);
|
|
configWidget->SetWidgetReadOnly("is_static", true);
|
|
configWidget->SetWidgetReadOnly("visible", true);
|
|
configWidget->SetWidgetReadOnly("scale", true);
|
|
configWidget->SetWidgetReadOnly("plugin", true);
|
|
configWidget->SetWidgetReadOnly("type", true);
|
|
|
|
// Connect config widget signals
|
|
connect(configWidget, SIGNAL(PoseValueChanged(const QString &,
|
|
const ignition::math::Pose3d &)), this,
|
|
SLOT(OnPoseChanged(const QString &, const ignition::math::Pose3d &)));
|
|
|
|
connect(configWidget, SIGNAL(GeometryValueChanged(const std::string &,
|
|
const std::string &, const ignition::math::Vector3d &,
|
|
const std::string &)), this, SLOT(OnGeometryChanged(const std::string &,
|
|
const std::string &, const ignition::math::Vector3d &,
|
|
const std::string &)));
|
|
|
|
connect(configWidget, SIGNAL(ColorValueChanged(const QString &,
|
|
const gazebo::common::Color &)), this,
|
|
SLOT(OnColorChanged(const QString &, const gazebo::common::Color &)));
|
|
|
|
connect(configWidget, SIGNAL(DoubleValueChanged(const QString &,
|
|
const double)), this,
|
|
SLOT(OnDoubleChanged(const QString &, const double)));
|
|
|
|
connect(configWidget, SIGNAL(StringValueChanged(const QString &,
|
|
const std::string &)), this,
|
|
SLOT(OnStringChanged(const QString &, const std::string &)));
|
|
|
|
// Item layout
|
|
QVBoxLayout *itemLayout = new QVBoxLayout();
|
|
itemLayout->addWidget(headerWidget);
|
|
itemLayout->addWidget(configWidget);
|
|
|
|
// Put the layout in a widget which can be added/deleted
|
|
QWidget *item = new QWidget();
|
|
item->setLayout(itemLayout);
|
|
|
|
// Add to the list
|
|
this->listLayout->addWidget(item);
|
|
|
|
// Fill ConfigData
|
|
VisualConfigData *configData = new VisualConfigData;
|
|
configData->configWidget = configWidget;
|
|
configData->id = this->counter;
|
|
configData->widget = item;
|
|
configData->name = _name;
|
|
connect(headerButton, SIGNAL(toggled(bool)), configData,
|
|
SLOT(OnToggleItem(bool)));
|
|
this->configs[this->counter] = configData;
|
|
this->addedConfigs[this->counter] = configData;
|
|
|
|
this->counter++;
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void VisualConfig::UpdateVisual(const std::string &_name,
|
|
ConstVisualPtr _visualMsg)
|
|
{
|
|
for (auto &it : this->configs)
|
|
{
|
|
if (it.second->name == _name)
|
|
{
|
|
VisualConfigData *configData = it.second;
|
|
configData->configWidget->UpdateFromMsg(_visualMsg.get());
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void VisualConfig::OnRemoveVisual(int _id)
|
|
{
|
|
auto it = this->configs.find(_id);
|
|
if (it == this->configs.end())
|
|
{
|
|
gzerr << "Visual not found " << std::endl;
|
|
return;
|
|
}
|
|
|
|
VisualConfigData *configData = this->configs[_id];
|
|
|
|
// Ask for confirmation
|
|
std::string msg;
|
|
|
|
if (this->configs.size() == 1)
|
|
{
|
|
msg = "Are you sure you want to remove " +
|
|
configData->name + "?\n\n" +
|
|
"This is the only visual. \n" +
|
|
"Without visuals, this link won't be visible.\n";
|
|
}
|
|
else
|
|
{
|
|
msg = "Are you sure you want to remove " +
|
|
configData->name + "?\n";
|
|
}
|
|
|
|
QMessageBox msgBox(QMessageBox::Warning, QString("Remove visual?"),
|
|
QString(msg.c_str()));
|
|
msgBox.setWindowFlags(Qt::Window | Qt::WindowTitleHint |
|
|
Qt::WindowStaysOnTopHint | Qt::CustomizeWindowHint);
|
|
|
|
QPushButton *cancelButton =
|
|
msgBox.addButton("Cancel", QMessageBox::RejectRole);
|
|
QPushButton *removeButton = msgBox.addButton("Remove",
|
|
QMessageBox::AcceptRole);
|
|
msgBox.setDefaultButton(removeButton);
|
|
msgBox.setEscapeButton(cancelButton);
|
|
msgBox.exec();
|
|
if (msgBox.clickedButton() && msgBox.clickedButton() != removeButton)
|
|
return;
|
|
|
|
// Remove
|
|
this->listLayout->removeWidget(configData->widget);
|
|
configData->widget->hide();
|
|
|
|
emit VisualRemoved(configData->name);
|
|
// Add to delete list only if this existed from the beginning
|
|
auto itAdded = this->addedConfigs.find(_id);
|
|
if (itAdded == this->addedConfigs.end())
|
|
{
|
|
this->deletedConfigs[_id] = configData;
|
|
this->configs.erase(it);
|
|
}
|
|
else
|
|
{
|
|
this->addedConfigs.erase(itAdded);
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void VisualConfig::SetShowVisual(const bool _show,
|
|
const std::string &_name)
|
|
{
|
|
auto button = this->findChild<QToolButton *>("showVisualButton_" +
|
|
QString(_name.c_str()));
|
|
|
|
if (button)
|
|
button->setChecked(_show);
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void VisualConfig::OnShowVisual(const int _id)
|
|
{
|
|
auto it = this->configs.find(_id);
|
|
if (it == this->configs.end())
|
|
{
|
|
gzerr << "Visual not found " << std::endl;
|
|
return;
|
|
}
|
|
|
|
auto button = qobject_cast<QToolButton *>(this->mapperShow->mapping(_id));
|
|
if (!button)
|
|
{
|
|
gzerr << "Couldn't find button with ID [" << _id << "]" << std::endl;
|
|
return;
|
|
}
|
|
|
|
bool showVis = button->isChecked();
|
|
|
|
auto configData = this->configs[_id];
|
|
this->ShowVisual(showVis, configData->name);
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
msgs::Visual *VisualConfig::GetData(const std::string &_name) const
|
|
{
|
|
for (auto const &it : this->configs)
|
|
{
|
|
if (it.second->name == _name)
|
|
return dynamic_cast<msgs::Visual *>(it.second->configWidget->Msg());
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void VisualConfig::SetGeometry(const std::string &_name,
|
|
const math::Vector3 &_size, const std::string &_uri)
|
|
{
|
|
for (auto &it : this->configs)
|
|
{
|
|
if (it.second->name == _name)
|
|
{
|
|
ignition::math::Vector3d dimensions;
|
|
std::string uri;
|
|
std::string type = it.second->configWidget->GeometryWidgetValue(
|
|
"geometry", dimensions, uri);
|
|
it.second->configWidget->SetGeometryWidgetValue("geometry", type,
|
|
_size.Ign(), _uri);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void VisualConfig::Geometry(const std::string &_name,
|
|
ignition::math::Vector3d &_size, std::string &_uri)
|
|
{
|
|
for (auto &it : this->configs)
|
|
{
|
|
if (it.second->name == _name)
|
|
{
|
|
it.second->configWidget->GeometryWidgetValue("geometry", _size, _uri);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void VisualConfig::SetMaterial(const std::string &_name,
|
|
const std::string &_materialName, const common::Color &_ambient,
|
|
const common::Color &_diffuse, const common::Color &_specular,
|
|
const common::Color &_emissive)
|
|
{
|
|
for (auto &it : this->configs)
|
|
{
|
|
if (it.second->name == _name)
|
|
{
|
|
it.second->configWidget->SetStringWidgetValue("material::script::name",
|
|
_materialName);
|
|
it.second->configWidget->SetColorWidgetValue("material::ambient",
|
|
_ambient);
|
|
it.second->configWidget->SetColorWidgetValue("material::diffuse",
|
|
_diffuse);
|
|
it.second->configWidget->SetColorWidgetValue("material::specular",
|
|
_specular);
|
|
it.second->configWidget->SetColorWidgetValue("material::emissive",
|
|
_emissive);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
const std::map<int, VisualConfigData *> &VisualConfig::ConfigData() const
|
|
{
|
|
return this->configs;
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void VisualConfig::OnPoseChanged(const QString &/*_name*/,
|
|
const ignition::math::Pose3d &/*_value*/)
|
|
{
|
|
emit Applied();
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void VisualConfig::OnGeometryChanged(const std::string &/*_name*/,
|
|
const std::string &/*_value*/,
|
|
const ignition::math::Vector3d &/*dimensions*/,
|
|
const std::string &/*_uri*/)
|
|
{
|
|
emit Applied();
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void VisualConfig::OnColorChanged(const QString &/*_name*/,
|
|
const gazebo::common::Color &/*_value*/)
|
|
{
|
|
emit Applied();
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void VisualConfig::OnDoubleChanged(const QString &_name,
|
|
const double /*_value*/)
|
|
{
|
|
// Only transparency affects the visualization
|
|
if (_name == "transparency")
|
|
emit Applied();
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void VisualConfig::OnStringChanged(const QString &_name,
|
|
const std::string &/*_value*/)
|
|
{
|
|
// Only material script affects the visualization
|
|
if (_name == "material::script::name")
|
|
emit Applied();
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void VisualConfig::RestoreOriginalData()
|
|
{
|
|
// Remove added configs
|
|
for (auto &it : this->addedConfigs)
|
|
{
|
|
auto configData = it.second;
|
|
|
|
this->listLayout->removeWidget(configData->widget);
|
|
delete configData->widget;
|
|
|
|
emit VisualRemoved(configData->name);
|
|
|
|
this->configs.erase(it.first);
|
|
}
|
|
this->addedConfigs.clear();
|
|
|
|
// Restore previously existing configs
|
|
for (auto &it : this->configs)
|
|
{
|
|
it.second->RestoreOriginalData();
|
|
}
|
|
|
|
// Reinsert deleted configs
|
|
for (auto &it : this->deletedConfigs)
|
|
{
|
|
this->listLayout->addWidget(it.second->widget);
|
|
it.second->widget->show();
|
|
|
|
this->configs[it.first] = it.second;
|
|
emit VisualAdded(it.second->name);
|
|
}
|
|
this->deletedConfigs.clear();
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void VisualConfigData::OnToggleItem(bool _checked)
|
|
{
|
|
if (_checked)
|
|
this->configWidget->show();
|
|
else
|
|
this->configWidget->hide();
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void VisualConfigData::RestoreOriginalData()
|
|
{
|
|
msgs::VisualPtr visualPtr;
|
|
visualPtr.reset(new msgs::Visual);
|
|
visualPtr->CopyFrom(this->originalDataMsg);
|
|
|
|
// Update default widgets
|
|
this->configWidget->blockSignals(true);
|
|
this->configWidget->UpdateFromMsg(visualPtr.get());
|
|
this->configWidget->blockSignals(false);
|
|
}
|