diff --git a/tools/oven/src/DomainBaker.cpp b/tools/oven/src/DomainBaker.cpp new file mode 100644 index 0000000000..dbd080d5a7 --- /dev/null +++ b/tools/oven/src/DomainBaker.cpp @@ -0,0 +1,111 @@ +// +// DomainBaker.cpp +// tools/oven/src +// +// Created by Stephen Birarda on 4/12/17. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include +#include +#include + +#include +#include + +#include "Gzip.h" + +#include "DomainBaker.h" + +DomainBaker::DomainBaker(const QUrl& localModelFileURL, QString baseOutputPath) : + _localEntitiesFileURL(localModelFileURL), + _baseOutputPath(baseOutputPath) +{ + +} + +void DomainBaker::start() { + loadLocalFile(); + enumerateEntities(); +} + +void DomainBaker::loadLocalFile() { + // load up the local entities file + QFile modelsFile { _localEntitiesFileURL.toLocalFile() }; + + if (!modelsFile.open(QIODevice::ReadOnly)) { + // add an error to our list to specify that the file could not be read + + // return to stop processing + return; + } + + // grab a byte array from the file + auto fileContents = modelsFile.readAll(); + + // check if we need to inflate a gzipped models file or if this was already decompressed + static const QString GZIPPED_ENTITIES_FILE_SUFFIX = "gz"; + if (QFileInfo(_localEntitiesFileURL.toLocalFile()).suffix() == "gz") { + // this was a gzipped models file that we need to decompress + QByteArray uncompressedContents; + gunzip(fileContents, uncompressedContents); + fileContents = uncompressedContents; + } + + // read the file contents to a JSON document + auto jsonDocument = QJsonDocument::fromJson(fileContents); + + // grab the entities object from the root JSON object + _entities = jsonDocument.object()["Entities"].toArray(); + + if (_entities.isEmpty()) { + // add an error to our list stating that the models file was empty + + // return to stop processing + return; + } +} + +void DomainBaker::enumerateEntities() { + qDebug() << "Enumerating" << _entities.size() << "entities from domain"; + + foreach(QJsonValue entityValue, _entities) { + // make sure this is a JSON object + if (entityValue.isObject()) { + auto entity = entityValue.toObject(); + + // check if this is an entity with a model URL + static const QString ENTITY_MODEL_URL_KEY = "modelURL"; + if (entity.contains(ENTITY_MODEL_URL_KEY)) { + // grab a QUrl for the model URL + auto modelURL = QUrl(entity[ENTITY_MODEL_URL_KEY].toString()); + + // check if the file pointed to by this URL is a bakeable model, by comparing extensions + auto modelFileName = modelURL.fileName(); + + static const QStringList BAKEABLE_MODEL_EXTENSIONS { ".fbx" }; + auto completeLowerExtension = modelFileName.mid(modelFileName.indexOf('.')).toLower(); + + if (BAKEABLE_MODEL_EXTENSIONS.contains(completeLowerExtension)) { + // grab a clean version of the URL without a query or fragment + modelURL.setFragment(""); + modelURL.setQuery(""); + + // setup an FBXBaker for this URL, as long as we don't already have one + if (!_bakers.contains(modelURL)) { + QSharedPointer baker { new FBXBaker(modelURL, _baseOutputPath) }; + + // start the baker + baker->start(); + + // insert it into our bakers hash so we hold a strong pointer to it + _bakers.insert(modelURL, baker); + } + } + } + } + } +} diff --git a/tools/oven/src/DomainBaker.h b/tools/oven/src/DomainBaker.h new file mode 100644 index 0000000000..50a2a12759 --- /dev/null +++ b/tools/oven/src/DomainBaker.h @@ -0,0 +1,44 @@ +// +// DomainBaker.h +// tools/oven/src +// +// Created by Stephen Birarda on 4/12/17. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_DomainBaker_h +#define hifi_DomainBaker_h + +#include +#include +#include + +#include + +class DomainBaker : public QObject { + Q_OBJECT +public: + DomainBaker(const QUrl& localEntitiesFileURL, QString baseOutputPath); + +public slots: + void start(); + +signals: + void finished(); + +private: + void loadLocalFile(); + void enumerateEntities(); + + QUrl _localEntitiesFileURL; + QString _baseOutputPath; + QJsonArray _entities; + + + QHash> _bakers; +}; + +#endif // hifi_DomainBaker_h diff --git a/tools/oven/src/ui/DomainBakeWidget.cpp b/tools/oven/src/ui/DomainBakeWidget.cpp new file mode 100644 index 0000000000..d5d5901ffa --- /dev/null +++ b/tools/oven/src/ui/DomainBakeWidget.cpp @@ -0,0 +1,177 @@ +// +// DomainBakeWidget.cpp +// tools/oven/src/ui +// +// Created by Stephen Birarda on 4/12/17. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "DomainBakeWidget.h" + +static const QString EXPORT_DIR_SETTING_KEY = "domain_export_directory"; +static const QString BROWSE_START_DIR_SETTING_KEY = "domain_search_directory"; + +DomainBakeWidget::DomainBakeWidget(QWidget* parent, Qt::WindowFlags flags) : + QWidget(parent, flags), + _exportDirectory(EXPORT_DIR_SETTING_KEY), + _browseStartDirectory(BROWSE_START_DIR_SETTING_KEY) +{ + setupUI(); +} + +void DomainBakeWidget::setupUI() { + // setup a grid layout to hold everything + QGridLayout* gridLayout = new QGridLayout; + + int rowIndex = 0; + + // setup a section to choose the file being baked + QLabel* entitiesFileLabel = new QLabel("Entities File"); + + _entitiesFileLineEdit = new QLineEdit; + _entitiesFileLineEdit->setPlaceholderText("File or URL"); + + QPushButton* chooseFileButton = new QPushButton("Browse..."); + connect(chooseFileButton, &QPushButton::clicked, this, &DomainBakeWidget::chooseFileButtonClicked); + + // add the components for the entities file picker to the layout + gridLayout->addWidget(entitiesFileLabel, rowIndex, 0); + gridLayout->addWidget(_entitiesFileLineEdit, rowIndex, 1, 1, 3); + gridLayout->addWidget(chooseFileButton, rowIndex, 4); + + // start a new row for next component + ++rowIndex; + + // setup a section to choose the output directory + QLabel* outputDirectoryLabel = new QLabel("Output Directory"); + + _outputDirLineEdit = new QLineEdit; + + // set the current export directory to whatever was last used + _outputDirLineEdit->setText(_exportDirectory.get()); + + // whenever the output directory line edit changes, update the value in settings + connect(_outputDirLineEdit, &QLineEdit::textChanged, this, &DomainBakeWidget::outputDirectoryChanged); + + QPushButton* chooseOutputDirectoryButton = new QPushButton("Browse..."); + connect(chooseOutputDirectoryButton, &QPushButton::clicked, this, &DomainBakeWidget::chooseOutputDirButtonClicked); + + // add the components for the output directory picker to the layout + gridLayout->addWidget(outputDirectoryLabel, rowIndex, 0); + gridLayout->addWidget(_outputDirLineEdit, rowIndex, 1, 1, 3); + gridLayout->addWidget(chooseOutputDirectoryButton, rowIndex, 4); + + // start a new row for the next component + ++rowIndex; + + // add a horizontal line to split the bake/cancel buttons off + QFrame* lineFrame = new QFrame; + lineFrame->setFrameShape(QFrame::HLine); + lineFrame->setFrameShadow(QFrame::Sunken); + gridLayout->addWidget(lineFrame, rowIndex, 0, 1, -1); + + // start a new row for the next component + ++rowIndex; + + // add a button that will kickoff the bake + QPushButton* bakeButton = new QPushButton("Bake"); + connect(bakeButton, &QPushButton::clicked, this, &DomainBakeWidget::bakeButtonClicked); + gridLayout->addWidget(bakeButton, rowIndex, 3); + + // add a cancel button to go back to the modes page + QPushButton* cancelButton = new QPushButton("Cancel"); + connect(cancelButton, &QPushButton::clicked, this, &DomainBakeWidget::cancelButtonClicked); + gridLayout->addWidget(cancelButton, rowIndex, 4); + + setLayout(gridLayout); +} + +void DomainBakeWidget::chooseFileButtonClicked() { + // pop a file dialog so the user can select the entities file + + // if we have picked an FBX before, start in the folder that matches the last path + // otherwise start in the home directory + auto startDir = _browseStartDirectory.get(); + if (startDir.isEmpty()) { + startDir = QDir::homePath(); + } + + auto selectedFile = QFileDialog::getOpenFileName(this, "Choose Entities File", startDir); + + if (!selectedFile.isEmpty()) { + // set the contents of the entities file text box to be the path to the selected file + _entitiesFileLineEdit->setText(selectedFile); + + auto directoryOfEntitiesFile = QFileInfo(selectedFile).absolutePath(); + + // save the directory containing this entities file so we can default to it next time we show the file dialog + _browseStartDirectory.set(directoryOfEntitiesFile); + + // if our output directory is not yet set, set it to the directory of this entities file + _outputDirLineEdit->setText(directoryOfEntitiesFile); + } +} + +void DomainBakeWidget::chooseOutputDirButtonClicked() { + // pop a file dialog so the user can select the output directory + + // if we have a previously selected output directory, use that as the initial path in the choose dialog + // otherwise use the user's home directory + auto startDir = _exportDirectory.get(); + if (startDir.isEmpty()) { + startDir = QDir::homePath(); + } + + auto selectedDir = QFileDialog::getExistingDirectory(this, "Choose Output Directory", startDir); + + if (!selectedDir.isEmpty()) { + // set the contents of the output directory text box to be the path to the directory + _outputDirLineEdit->setText(selectedDir); + } +} + +void DomainBakeWidget::outputDirectoryChanged(const QString& newDirectory) { + // update the export directory setting so we can re-use it next time + _exportDirectory.set(newDirectory); +} + +void DomainBakeWidget::bakeButtonClicked() { + // make sure we have a valid output directory + QDir outputDirectory(_outputDirLineEdit->text()); + + if (!outputDirectory.exists()) { + return; + } + + // make sure we have a non empty URL to an entities file to bake + if (!_entitiesFileLineEdit->text().isEmpty()) { + // everything seems to be in place, kick off a bake for this entities file now + auto fileToBakeURL = QUrl::fromLocalFile(_entitiesFileLineEdit->text()); + _baker = std::unique_ptr { new DomainBaker(fileToBakeURL, outputDirectory.absolutePath()) }; + _baker->start(); + + return; + } +} + +void DomainBakeWidget::cancelButtonClicked() { + // the user wants to go back to the mode selection screen + // remove ourselves from the stacked widget and call delete later so we'll be cleaned up + auto stackedWidget = qobject_cast(parentWidget()); + stackedWidget->removeWidget(this); + + this->deleteLater(); +} diff --git a/tools/oven/src/ui/DomainBakeWidget.h b/tools/oven/src/ui/DomainBakeWidget.h new file mode 100644 index 0000000000..485f80683c --- /dev/null +++ b/tools/oven/src/ui/DomainBakeWidget.h @@ -0,0 +1,49 @@ +// +// DomainBakeWidget.h +// tools/oven/src/ui +// +// Created by Stephen Birarda on 4/12/17. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_DomainBakeWidget_h +#define hifi_DomainBakeWidget_h + +#include + +#include + +#include "../DomainBaker.h" + +class QLineEdit; + +class DomainBakeWidget : public QWidget { + Q_OBJECT + +public: + DomainBakeWidget(QWidget* parent = nullptr, Qt::WindowFlags flags = Qt::WindowFlags()); + +private slots: + void chooseFileButtonClicked(); + void chooseOutputDirButtonClicked(); + void bakeButtonClicked(); + void cancelButtonClicked(); + + void outputDirectoryChanged(const QString& newDirectory); + +private: + void setupUI(); + + std::unique_ptr _baker; + + QLineEdit* _entitiesFileLineEdit; + QLineEdit* _outputDirLineEdit; + + Setting::Handle _exportDirectory; + Setting::Handle _browseStartDirectory; +}; + +#endif // hifi_ModelBakeWidget_h diff --git a/tools/oven/src/ui/ModelBakeWidget.h b/tools/oven/src/ui/ModelBakeWidget.h index 5d20b3fb53..354ad9f311 100644 --- a/tools/oven/src/ui/ModelBakeWidget.h +++ b/tools/oven/src/ui/ModelBakeWidget.h @@ -42,8 +42,6 @@ private: QLineEdit* _modelLineEdit; QLineEdit* _outputDirLineEdit; - QUrl modelToBakeURL; - Setting::Handle _exportDirectory; Setting::Handle _modelStartDirectory; }; diff --git a/tools/oven/src/ui/ModesWidget.cpp b/tools/oven/src/ui/ModesWidget.cpp index 1df2c9fe4f..867f89b4c4 100644 --- a/tools/oven/src/ui/ModesWidget.cpp +++ b/tools/oven/src/ui/ModesWidget.cpp @@ -13,6 +13,7 @@ #include #include +#include "DomainBakeWidget.h" #include "ModelBakeWidget.h" #include "ModesWidget.h" @@ -34,6 +35,7 @@ void ModesWidget::setupUI() { // add a button for domain baking QPushButton* domainButton = new QPushButton("Bake Domain"); + connect(domainButton, &QPushButton::clicked, this, &ModesWidget::showDomainBakingWidget); horizontalLayout->addWidget(domainButton); // add a button for texture baking @@ -49,3 +51,10 @@ void ModesWidget::showModelBakingWidget() { // add a new widget for making baking to the stack, and switch to it stackedWidget->setCurrentIndex(stackedWidget->addWidget(new ModelBakeWidget)); } + +void ModesWidget::showDomainBakingWidget() { + auto stackedWidget = qobject_cast(parentWidget()); + + // add a new widget for making baking to the stack, and switch to it + stackedWidget->setCurrentIndex(stackedWidget->addWidget(new DomainBakeWidget)); +} diff --git a/tools/oven/src/ui/ModesWidget.h b/tools/oven/src/ui/ModesWidget.h index bde2898e0c..e7e239d63e 100644 --- a/tools/oven/src/ui/ModesWidget.h +++ b/tools/oven/src/ui/ModesWidget.h @@ -21,6 +21,7 @@ public: private slots: void showModelBakingWidget(); + void showDomainBakingWidget(); private: void setupUI();