407 lines
13 KiB
C++
407 lines
13 KiB
C++
/*
|
|
* Copyright (C) 2012 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.
|
|
*
|
|
*/
|
|
|
|
#ifdef _WIN32
|
|
// Ensure that Winsock2.h is included before Windows.h, which can get
|
|
// pulled in by anybody (e.g., Boost).
|
|
#include <Winsock2.h>
|
|
#endif
|
|
|
|
#include <fstream>
|
|
|
|
#include <boost/bind.hpp>
|
|
#include <boost/filesystem.hpp>
|
|
#include <boost/lexical_cast.hpp>
|
|
#include <sdf/sdf.hh>
|
|
#include <tinyxml.h>
|
|
|
|
#include "gazebo/common/SystemPaths.hh"
|
|
#include "gazebo/common/Console.hh"
|
|
#include "gazebo/common/ModelDatabase.hh"
|
|
|
|
#include "gazebo/rendering/RenderingIface.hh"
|
|
#include "gazebo/rendering/Scene.hh"
|
|
#include "gazebo/rendering/UserCamera.hh"
|
|
#include "gazebo/rendering/Visual.hh"
|
|
#include "gazebo/gui/GuiIface.hh"
|
|
#include "gazebo/gui/GuiEvents.hh"
|
|
|
|
#include "gazebo/transport/Node.hh"
|
|
#include "gazebo/transport/Publisher.hh"
|
|
|
|
#include "gazebo/gui/InsertModelWidgetPrivate.hh"
|
|
#include "gazebo/gui/InsertModelWidget.hh"
|
|
|
|
using namespace gazebo;
|
|
using namespace gui;
|
|
|
|
/////////////////////////////////////////////////
|
|
InsertModelWidget::InsertModelWidget(QWidget *_parent)
|
|
: QWidget(_parent), dataPtr(new InsertModelWidgetPrivate)
|
|
{
|
|
this->setObjectName("insertModel");
|
|
this->dataPtr->modelDatabaseItem = NULL;
|
|
|
|
QVBoxLayout *mainLayout = new QVBoxLayout;
|
|
this->dataPtr->fileTreeWidget = new QTreeWidget();
|
|
this->dataPtr->fileTreeWidget->setColumnCount(1);
|
|
this->dataPtr->fileTreeWidget->setContextMenuPolicy(Qt::CustomContextMenu);
|
|
this->dataPtr->fileTreeWidget->header()->hide();
|
|
connect(this->dataPtr->fileTreeWidget,
|
|
SIGNAL(itemClicked(QTreeWidgetItem *, int)),
|
|
this, SLOT(OnModelSelection(QTreeWidgetItem *, int)));
|
|
|
|
QFrame *frame = new QFrame;
|
|
QVBoxLayout *frameLayout = new QVBoxLayout;
|
|
frameLayout->addWidget(this->dataPtr->fileTreeWidget, 0);
|
|
frameLayout->setContentsMargins(0, 0, 0, 0);
|
|
frame->setLayout(frameLayout);
|
|
|
|
mainLayout->addWidget(frame);
|
|
this->setLayout(mainLayout);
|
|
this->layout()->setContentsMargins(0, 0, 0, 0);
|
|
// Create a system path watcher
|
|
this->dataPtr->watcher = new QFileSystemWatcher();
|
|
|
|
// Update the list of models on the local system.
|
|
this->UpdateAllLocalPaths();
|
|
|
|
// Create a top-level tree item for the path
|
|
this->dataPtr->modelDatabaseItem =
|
|
new QTreeWidgetItem(static_cast<QTreeWidgetItem*>(0),
|
|
QStringList(QString("Connecting to model database...")));
|
|
this->dataPtr->fileTreeWidget->addTopLevelItem(
|
|
this->dataPtr->modelDatabaseItem);
|
|
|
|
// Also insert additional paths from gui.ini
|
|
std::string additionalPaths =
|
|
gui::getINIProperty<std::string>("model_paths.filenames", "");
|
|
if (!additionalPaths.empty())
|
|
{
|
|
common::SystemPaths::Instance()->AddModelPaths(additionalPaths);
|
|
|
|
// Get each path in the : separated list
|
|
std::string delim(":");
|
|
size_t pos1 = 0;
|
|
size_t pos2 = additionalPaths.find(delim);
|
|
while (pos2 != std::string::npos)
|
|
{
|
|
this->UpdateLocalPath(additionalPaths.substr(pos1, pos2-pos1));
|
|
pos1 = pos2+1;
|
|
pos2 = additionalPaths.find(delim, pos2+1);
|
|
}
|
|
this->UpdateLocalPath(additionalPaths.substr(pos1,
|
|
additionalPaths.size()-pos1));
|
|
}
|
|
|
|
// Connect callbacks now that everything else is initialized
|
|
|
|
// Connect a callback that is triggered whenever a directory is changed.
|
|
connect(this->dataPtr->watcher, SIGNAL(directoryChanged(const QString &)),
|
|
this, SLOT(OnDirectoryChanged(const QString &)));
|
|
|
|
// Connect a callback to trigger when the model paths are updated.
|
|
this->connections.push_back(
|
|
common::SystemPaths::Instance()->updateModelRequest.Connect(
|
|
boost::bind(&InsertModelWidget::OnModelUpdateRequest, this, _1)));
|
|
|
|
/// Non-blocking call to get all the models in the database.
|
|
this->dataPtr->getModelsConnection =
|
|
common::ModelDatabase::Instance()->GetModels(
|
|
boost::bind(&InsertModelWidget::OnModels, this, _1));
|
|
|
|
// Start a timer to check for the results from the ModelDatabase. We need
|
|
// to do this so that the QT elements get added in the main thread.
|
|
QTimer::singleShot(1000, this, SLOT(Update()));
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
InsertModelWidget::~InsertModelWidget()
|
|
{
|
|
delete this->dataPtr->watcher;
|
|
delete this->dataPtr;
|
|
this->dataPtr = NULL;
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
bool InsertModelWidget::LocalPathInFileWidget(const std::string &_path)
|
|
{
|
|
return this->dataPtr->localFilenameCache.find(_path) !=
|
|
this->dataPtr->localFilenameCache.end();
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void InsertModelWidget::Update()
|
|
{
|
|
boost::mutex::scoped_lock lock(this->dataPtr->mutex);
|
|
|
|
// If the model database has call the OnModels callback function, then
|
|
// add all the models from the database.
|
|
if (!this->dataPtr->modelBuffer.empty())
|
|
{
|
|
std::string uri = common::ModelDatabase::Instance()->GetURI();
|
|
this->dataPtr->modelDatabaseItem->setText(0,
|
|
QString("%1").arg(QString::fromStdString(uri)));
|
|
|
|
if (!this->dataPtr->modelBuffer.empty())
|
|
{
|
|
for (std::map<std::string, std::string>::const_iterator iter =
|
|
this->dataPtr->modelBuffer.begin();
|
|
iter != this->dataPtr->modelBuffer.end(); ++iter)
|
|
{
|
|
// Add a child item for the model
|
|
QTreeWidgetItem *childItem = new QTreeWidgetItem(
|
|
this->dataPtr->modelDatabaseItem,
|
|
QStringList(QString("%1").arg(
|
|
QString::fromStdString(iter->second))));
|
|
childItem->setData(0, Qt::UserRole, QVariant(iter->first.c_str()));
|
|
this->dataPtr->fileTreeWidget->addTopLevelItem(childItem);
|
|
}
|
|
}
|
|
|
|
this->dataPtr->modelBuffer.clear();
|
|
this->dataPtr->getModelsConnection.reset();
|
|
}
|
|
else
|
|
QTimer::singleShot(1000, this, SLOT(Update()));
|
|
}
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////
|
|
void InsertModelWidget::OnModels(
|
|
const std::map<std::string, std::string> &_models)
|
|
{
|
|
boost::mutex::scoped_lock lock(this->dataPtr->mutex);
|
|
this->dataPtr->modelBuffer = _models;
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void InsertModelWidget::OnModelSelection(QTreeWidgetItem *_item,
|
|
int /*_column*/)
|
|
{
|
|
if (_item)
|
|
{
|
|
std::string path, filename;
|
|
|
|
if (_item->parent())
|
|
path = _item->parent()->text(0).toStdString() + "/";
|
|
|
|
path = _item->data(0, Qt::UserRole).toString().toStdString();
|
|
|
|
if (!path.empty())
|
|
{
|
|
QApplication::setOverrideCursor(Qt::BusyCursor);
|
|
filename = common::ModelDatabase::Instance()->GetModelFile(path);
|
|
gui::Events::createEntity("model", filename);
|
|
|
|
{
|
|
boost::mutex::scoped_lock lock(this->dataPtr->mutex);
|
|
this->dataPtr->fileTreeWidget->clearSelection();
|
|
}
|
|
|
|
QApplication::setOverrideCursor(Qt::ArrowCursor);
|
|
}
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void InsertModelWidget::UpdateLocalPath(const std::string &_path)
|
|
{
|
|
if (_path.empty())
|
|
return;
|
|
|
|
boost::filesystem::path dir(_path);
|
|
bool pathExists = this->IsPathAccessible(dir);
|
|
|
|
QString qpath = QString::fromStdString(_path);
|
|
QTreeWidgetItem *topItem = NULL;
|
|
|
|
QList<QTreeWidgetItem *> matchList =
|
|
this->dataPtr->fileTreeWidget->findItems(qpath, Qt::MatchExactly);
|
|
|
|
// Create a top-level tree item for the path
|
|
if (matchList.empty())
|
|
{
|
|
topItem = new QTreeWidgetItem(
|
|
static_cast<QTreeWidgetItem*>(0), QStringList(qpath));
|
|
this->dataPtr->fileTreeWidget->addTopLevelItem(topItem);
|
|
this->dataPtr->localFilenameCache.insert(_path);
|
|
|
|
// Add the new path to the directory watcher
|
|
if (pathExists)
|
|
{
|
|
this->dataPtr->watcher->addPath(qpath);
|
|
}
|
|
}
|
|
else
|
|
topItem = matchList.first();
|
|
|
|
// Remove current items.
|
|
topItem->takeChildren();
|
|
|
|
if (pathExists && boost::filesystem::is_directory(dir))
|
|
{
|
|
std::vector<boost::filesystem::path> paths;
|
|
|
|
// Get all the paths in alphabetical order
|
|
try
|
|
{
|
|
std::copy(boost::filesystem::directory_iterator(dir),
|
|
boost::filesystem::directory_iterator(),
|
|
std::back_inserter(paths));
|
|
}
|
|
catch(boost::filesystem::filesystem_error & e)
|
|
{
|
|
gzerr << "Not loading models in: " << _path << " ("
|
|
<< e.what() << ")" << std::endl;
|
|
return;
|
|
}
|
|
|
|
std::sort(paths.begin(), paths.end());
|
|
|
|
// Iterate over all the models in the current gazebo path
|
|
for (std::vector<boost::filesystem::path>::iterator dIter = paths.begin();
|
|
dIter != paths.end(); ++dIter)
|
|
{
|
|
std::string modelName;
|
|
boost::filesystem::path fullPath = _path / dIter->filename();
|
|
boost::filesystem::path manifest = fullPath;
|
|
|
|
if (!boost::filesystem::is_directory(fullPath))
|
|
{
|
|
if (dIter->filename() != "database.config")
|
|
{
|
|
gzlog << "Invalid filename or directory[" << fullPath
|
|
<< "] in GAZEBO_MODEL_PATH. It's not a good idea to put extra "
|
|
<< "files in a GAZEBO_MODEL_PATH because the file structure may"
|
|
<< " be modified by Gazebo.\n";
|
|
}
|
|
continue;
|
|
}
|
|
|
|
manifest /= GZ_MODEL_MANIFEST_FILENAME;
|
|
|
|
// Check if the manifest does not exists
|
|
if (!this->IsPathAccessible(manifest))
|
|
{
|
|
gzerr << "Missing " << GZ_MODEL_MANIFEST_FILENAME << " for model "
|
|
<< (*dIter) << "\n";
|
|
|
|
manifest = manifest / "manifest.xml";
|
|
}
|
|
|
|
if (!this->IsPathAccessible(manifest) || manifest == fullPath)
|
|
{
|
|
gzlog << "model.config file is missing in directory["
|
|
<< fullPath << "]\n";
|
|
continue;
|
|
}
|
|
|
|
TiXmlDocument xmlDoc;
|
|
if (xmlDoc.LoadFile(manifest.string()))
|
|
{
|
|
TiXmlElement *modelXML = xmlDoc.FirstChildElement("model");
|
|
if (!modelXML || !modelXML->FirstChildElement("name"))
|
|
gzerr << "No model name in manifest[" << manifest << "]\n";
|
|
else
|
|
modelName = modelXML->FirstChildElement("name")->GetText();
|
|
// Add a child item for the model
|
|
QTreeWidgetItem *childItem = new QTreeWidgetItem(topItem,
|
|
QStringList(QString::fromStdString(modelName)));
|
|
|
|
childItem->setData(0, Qt::UserRole,
|
|
QVariant((std::string("file://") + fullPath.string()).c_str()));
|
|
|
|
this->dataPtr->fileTreeWidget->addTopLevelItem(childItem);
|
|
this->dataPtr->localFilenameCache.insert(fullPath.string());
|
|
}
|
|
}
|
|
}
|
|
|
|
// Make all top-level items expanded. Trying to reduce mouse clicks.
|
|
this->dataPtr->fileTreeWidget->expandItem(topItem);
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void InsertModelWidget::UpdateAllLocalPaths()
|
|
{
|
|
std::list<std::string> gazeboPaths =
|
|
common::SystemPaths::Instance()->GetModelPaths();
|
|
|
|
// Iterate over all the gazebo paths
|
|
for (std::list<std::string>::iterator iter = gazeboPaths.begin();
|
|
iter != gazeboPaths.end(); ++iter)
|
|
{
|
|
// This is the full model path
|
|
this->UpdateLocalPath((*iter));
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void InsertModelWidget::OnDirectoryChanged(const QString &_path)
|
|
{
|
|
boost::mutex::scoped_lock lock(this->dataPtr->mutex);
|
|
this->UpdateLocalPath(_path.toStdString());
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
void InsertModelWidget::OnModelUpdateRequest(const std::string &_path)
|
|
{
|
|
boost::mutex::scoped_lock lock(this->dataPtr->mutex);
|
|
this->UpdateLocalPath(_path);
|
|
}
|
|
|
|
/////////////////////////////////////////////////
|
|
bool InsertModelWidget::IsPathAccessible(const boost::filesystem::path &_path)
|
|
{
|
|
try
|
|
{
|
|
if (!boost::filesystem::exists(_path))
|
|
return false;
|
|
|
|
if (boost::filesystem::is_directory(_path))
|
|
{
|
|
// Try to retrieve a pointer to the first entry in this directory.
|
|
// If permission denied to the directory, will throw filesystem_error
|
|
boost::filesystem::directory_iterator iter(_path);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
std::ifstream ifs(_path.string().c_str(), std::ifstream::in);
|
|
if (ifs.fail() || ifs.bad())
|
|
{
|
|
gzerr << "File unreadable: " << _path << std::endl;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
catch(boost::filesystem::filesystem_error & e)
|
|
{
|
|
gzerr << "Permission denied for directory: " << _path << std::endl;
|
|
}
|
|
catch(std::exception & e)
|
|
{
|
|
gzerr << "Unexpected error while accessing to: " << _path << "."
|
|
<< "Error reported: " << e.what() << std::endl;
|
|
}
|
|
|
|
return false;
|
|
}
|