Streamline ModelBaker initialization and URLs

This commit is contained in:
sabrina-shanman 2019-02-13 17:35:43 -08:00
parent f9e5ee0185
commit e4d6d5af89
10 changed files with 201 additions and 174 deletions

View file

@ -40,8 +40,8 @@
void FBXBaker::bake() {
qDebug() << "FBXBaker" << _modelURL << "bake starting";
// setup the output folder for the results of this bake
setupOutputFolder();
// Setup the output folders for the results of this bake
initializeOutputDirs();
if (shouldStop()) {
return;
@ -78,26 +78,6 @@ void FBXBaker::bakeSourceCopy() {
checkIfTexturesFinished();
}
void FBXBaker::setupOutputFolder() {
// make sure there isn't already an output directory using the same name
if (QDir(_bakedOutputDir).exists()) {
qWarning() << "Output path" << _bakedOutputDir << "already exists. Continuing.";
} else {
qCDebug(model_baking) << "Creating FBX output folder" << _bakedOutputDir;
// attempt to make the output folder
if (!QDir().mkpath(_bakedOutputDir)) {
handleError("Failed to create FBX output folder " + _bakedOutputDir);
return;
}
// attempt to make the output folder
if (!QDir().mkpath(_originalOutputDir)) {
handleError("Failed to create FBX output folder " + _originalOutputDir);
return;
}
}
}
void FBXBaker::loadSourceFBX() {
// check if the FBX is local or first needs to be downloaded
if (_modelURL.isLocalFile()) {

View file

@ -44,8 +44,6 @@ private slots:
void handleFBXNetworkReply();
private:
void setupOutputFolder();
void loadSourceFBX();
void importScene();

View file

@ -64,6 +64,29 @@ ModelBaker::~ModelBaker() {
}
}
void ModelBaker::initializeOutputDirs() {
// Attempt to make the output folders
// Warn if there is an output directory using the same name
if (QDir(_bakedOutputDir).exists()) {
qWarning() << "Output path" << _bakedOutputDir << "already exists. Continuing.";
} else {
qCDebug(model_baking) << "Creating baked output folder" << _bakedOutputDir;
if (!QDir().mkpath(_bakedOutputDir)) {
handleError("Failed to create baked output folder " + _bakedOutputDir);
}
}
if (QDir(_originalOutputDir).exists()) {
qWarning() << "Output path" << _originalOutputDir << "already exists. Continuing.";
} else {
qCDebug(model_baking) << "Creating original output folder" << _originalOutputDir;
if (!QDir().mkpath(_originalOutputDir)) {
handleError("Failed to create original output folder " + _originalOutputDir);
}
}
}
void ModelBaker::abort() {
Baker::abort();

View file

@ -31,6 +31,8 @@ using TextureBakerThreadGetter = std::function<QThread*()>;
using GetMaterialIDCallback = std::function <int(int)>;
static const QString BAKED_FBX_EXTENSION = ".baked.fbx";
static const QString BAKEABLE_MODEL_FBX_EXTENSION { ".fbx" };
static const QString BAKEABLE_MODEL_OBJ_EXTENSION { ".obj" };
class ModelBaker : public Baker {
Q_OBJECT
@ -40,6 +42,8 @@ public:
const QString& bakedOutputDirectory, const QString& originalOutputDirectory = "");
virtual ~ModelBaker();
void initializeOutputDirs();
bool compressMesh(HFMMesh& mesh, bool hasDeformers, FBXNode& dracoMeshNode, GetMaterialIDCallback materialIDCallback = nullptr);
QString compressTexture(QString textureFileName, image::TextureUsage::Type = image::TextureUsage::Type::DEFAULT_TEXTURE);
virtual void setWasAborted(bool wasAborted) override;

View file

@ -38,6 +38,9 @@ const QByteArray MESH = "Mesh";
void OBJBaker::bake() {
qDebug() << "OBJBaker" << _modelURL << "bake starting";
// Setup the output folders for the results of this bake
initializeOutputDirs();
// trigger bakeOBJ once OBJ is loaded
connect(this, &OBJBaker::OBJLoaded, this, &OBJBaker::bakeOBJ);
@ -46,16 +49,6 @@ void OBJBaker::bake() {
}
void OBJBaker::loadOBJ() {
if (!QDir().mkpath(_bakedOutputDir)) {
handleError("Failed to create baked OBJ output folder " + _bakedOutputDir);
return;
}
if (!QDir().mkpath(_originalOutputDir)) {
handleError("Failed to create original OBJ output folder " + _originalOutputDir);
return;
}
// check if the OBJ is local or it needs to be downloaded
if (_modelURL.isLocalFile()) {
// loading the local OBJ

View file

@ -0,0 +1,76 @@
//
// BakerLibrary.cpp
// libraries/baking/src/baking
//
// Created by Sabrina Shanman on 2019/02/14.
// Copyright 2019 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 "BakerLibrary.h"
#include "../FBXBaker.h"
#include "../OBJBaker.h"
QUrl getBakeableModelURL(const QUrl& url, bool shouldRebakeOriginals) {
// Check if the file pointed to by this URL is a bakeable model, by comparing extensions
auto modelFileName = url.fileName();
bool isBakedModel = modelFileName.endsWith(BAKED_FBX_EXTENSION, Qt::CaseInsensitive);
bool isBakeableFBX = modelFileName.endsWith(BAKEABLE_MODEL_FBX_EXTENSION, Qt::CaseInsensitive);
bool isBakeableOBJ = modelFileName.endsWith(BAKEABLE_MODEL_OBJ_EXTENSION, Qt::CaseInsensitive);
bool isBakeable = isBakeableFBX || isBakeableOBJ;
if (isBakeable || (shouldRebakeOriginals && isBakedModel)) {
if (isBakedModel) {
// Grab a URL to the original, that we assume is stored a directory up, in the "original" folder
// with just the fbx extension
qDebug() << "Inferring original URL for baked model URL" << url;
auto originalFileName = modelFileName;
originalFileName.replace(".baked", "");
qDebug() << "Original model URL must be present at" << url;
return url.resolved("../original/" + originalFileName);
} else {
// Grab a clean version of the URL without a query or fragment
return url.adjusted(QUrl::RemoveQuery | QUrl::RemoveFragment);
}
}
qWarning() << "Unknown model type: " << modelFileName;
return QUrl();
}
std::unique_ptr<ModelBaker> getModelBaker(const QUrl& bakeableModelURL, TextureBakerThreadGetter inputTextureThreadGetter, const QString& contentOutputPath) {
auto filename = bakeableModelURL.fileName();
// Output in a sub-folder with the name of the model, potentially suffixed by a number to make it unique
auto baseName = filename.left(filename.lastIndexOf('.'));
auto subDirName = "/" + baseName;
int i = 1;
while (QDir(contentOutputPath + subDirName).exists()) {
subDirName = "/" + baseName + "-" + QString::number(i++);
}
QString bakedOutputDirectory = contentOutputPath + subDirName + "/baked";
QString originalOutputDirectory = contentOutputPath + subDirName + "/original";
std::unique_ptr<ModelBaker> baker;
if (filename.endsWith(BAKEABLE_MODEL_FBX_EXTENSION, Qt::CaseInsensitive)) {
baker = std::make_unique<FBXBaker>(bakeableModelURL, inputTextureThreadGetter, bakedOutputDirectory, originalOutputDirectory);
} else if (filename.endsWith(BAKEABLE_MODEL_OBJ_EXTENSION, Qt::CaseInsensitive)) {
baker = std::make_unique<OBJBaker>(bakeableModelURL, inputTextureThreadGetter, bakedOutputDirectory, originalOutputDirectory);
} else {
qDebug() << "Could not create ModelBaker for url" << bakeableModelURL;
}
if (baker) {
QDir(contentOutputPath).mkpath(subDirName);
}
return baker;
}

View file

@ -0,0 +1,28 @@
//
// ModelBaker.h
// libraries/baking/src/baking
//
// Created by Sabrina Shanman on 2019/02/14.
// Copyright 2019 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_BakerLibrary_h
#define hifi_BakerLibrary_h
#include <QUrl>
#include "../ModelBaker.h"
// Returns either the given model URL, or, if the model is baked and shouldRebakeOriginals is true,
// the guessed location of the original model
// Returns an empty URL if no bakeable URL found
QUrl getBakeableModelURL(const QUrl& url, bool shouldRebakeOriginals);
// Assuming the URL is valid, gets the appropriate baker for the given URL, and creates the base directory where the baker's output will later be stored
// Returns an empty pointer if a baker could not be created
std::unique_ptr<ModelBaker> getModelBaker(const QUrl& bakeableModelURL, TextureBakerThreadGetter inputTextureThreadGetter, const QString& contentOutputPath);
#endif hifi_BakerLibrary_h

View file

@ -20,7 +20,7 @@
#include "OvenCLIApplication.h"
#include "ModelBakingLoggingCategory.h"
#include "FBXBaker.h"
#include "baking/BakerLibrary.h"
#include "JSBaker.h"
#include "TextureBaker.h"
@ -41,7 +41,7 @@ void BakerCLI::bakeFile(QUrl inputUrl, const QString& outputPath, const QString&
static const QString SCRIPT_EXTENSION { "js" };
// check what kind of baker we should be creating
bool isFBX = type == MODEL_EXTENSION;
bool isModel = type == MODEL_EXTENSION;
bool isScript = type == SCRIPT_EXTENSION;
// If the type doesn't match the above, we assume we have a texture, and the type specified is the
@ -54,13 +54,17 @@ void BakerCLI::bakeFile(QUrl inputUrl, const QString& outputPath, const QString&
_outputPath = outputPath;
// create our appropiate baker
if (isFBX) {
_baker = std::unique_ptr<Baker> {
new FBXBaker(inputUrl,
[]() -> QThread* { return Oven::instance().getNextWorkerThread(); },
outputPath)
};
_baker->moveToThread(Oven::instance().getNextWorkerThread());
if (isModel) {
QUrl bakeableModelURL = getBakeableModelURL(inputUrl, false);
if (!bakeableModelURL.isEmpty()) {
auto getWorkerThreadCallback = []() -> QThread* {
return Oven::instance().getNextWorkerThread();
};
_baker = getModelBaker(bakeableModelURL, getWorkerThreadCallback, outputPath);
if (_baker) {
_baker->moveToThread(Oven::instance().getNextWorkerThread());
}
}
} else if (isScript) {
_baker = std::unique_ptr<Baker> { new JSBaker(inputUrl, outputPath) };
_baker->moveToThread(Oven::instance().getNextWorkerThread());

View file

@ -20,8 +20,7 @@
#include "Gzip.h"
#include "Oven.h"
#include "FBXBaker.h"
#include "OBJBaker.h"
#include "baking/BakerLibrary.h"
DomainBaker::DomainBaker(const QUrl& localModelFileURL, const QString& domainName,
const QString& baseOutputPath, const QUrl& destinationPath,
@ -163,82 +162,37 @@ void DomainBaker::enumerateEntities() {
// check if this is an entity with a model URL or is a skybox texture
if (entity.contains(ENTITY_MODEL_URL_KEY)) {
// grab a QUrl for the model URL
QUrl modelURL { entity[ENTITY_MODEL_URL_KEY].toString() };
QUrl bakeableModelURL = getBakeableModelURL(entity[ENTITY_MODEL_URL_KEY].toString(), _shouldRebakeOriginals);
// check if the file pointed to by this URL is a bakeable model, by comparing extensions
auto modelFileName = modelURL.fileName();
static const QString BAKEABLE_MODEL_FBX_EXTENSION { ".fbx" };
static const QString BAKEABLE_MODEL_OBJ_EXTENSION { ".obj" };
static const QString BAKED_MODEL_EXTENSION = ".baked.fbx";
bool isBakedModel = modelFileName.endsWith(BAKED_MODEL_EXTENSION, Qt::CaseInsensitive);
bool isBakeableFBX = modelFileName.endsWith(BAKEABLE_MODEL_FBX_EXTENSION, Qt::CaseInsensitive);
bool isBakeableOBJ = modelFileName.endsWith(BAKEABLE_MODEL_OBJ_EXTENSION, Qt::CaseInsensitive);
bool isBakeable = isBakeableFBX || isBakeableOBJ;
if (isBakeable || (_shouldRebakeOriginals && isBakedModel)) {
if (isBakedModel) {
// grab a URL to the original, that we assume is stored a directory up, in the "original" folder
// with just the fbx extension
qDebug() << "Re-baking original for" << modelURL;
auto originalFileName = modelFileName;
originalFileName.replace(".baked", "");
modelURL = modelURL.resolved("../original/" + originalFileName);
qDebug() << "Original must be present at" << modelURL;
} else {
// grab a clean version of the URL without a query or fragment
modelURL = modelURL.adjusted(QUrl::RemoveQuery | QUrl::RemoveFragment);
}
if (!bakeableModelURL.isEmpty()) {
// setup a ModelBaker for this URL, as long as we don't already have one
if (!_modelBakers.contains(modelURL)) {
auto filename = modelURL.fileName();
auto baseName = filename.left(filename.lastIndexOf('.'));
auto subDirName = "/" + baseName;
int i = 1;
while (QDir(_contentOutputPath + subDirName).exists()) {
subDirName = "/" + baseName + "-" + QString::number(i++);
if (!_modelBakers.contains(bakeableModelURL)) {
auto getWorkerThreadCallback = []() -> QThread* {
return Oven::instance().getNextWorkerThread();
};
QSharedPointer<ModelBaker> baker = QSharedPointer<ModelBaker>(getModelBaker(bakeableModelURL, getWorkerThreadCallback, _contentOutputPath).release(), &ModelBaker::deleteLater);
if (baker) {
// make sure our handler is called when the baker is done
connect(baker.data(), &Baker::finished, this, &DomainBaker::handleFinishedModelBaker);
// insert it into our bakers hash so we hold a strong pointer to it
_modelBakers.insert(bakeableModelURL, baker);
// move the baker to the baker thread
// and kickoff the bake
baker->moveToThread(Oven::instance().getNextWorkerThread());
QMetaObject::invokeMethod(baker.data(), "bake");
// keep track of the total number of baking entities
++_totalNumberOfSubBakes;
// add this QJsonValueRef to our multi hash so that we can easily re-write
// the model URL to the baked version once the baker is complete
_entitiesNeedingRewrite.insert(bakeableModelURL, *it);
}
QSharedPointer<ModelBaker> baker;
if (isBakeableFBX) {
baker = {
new FBXBaker(modelURL, []() -> QThread* {
return Oven::instance().getNextWorkerThread();
}, _contentOutputPath + subDirName + "/baked", _contentOutputPath + subDirName + "/original"),
&FBXBaker::deleteLater
};
} else {
baker = {
new OBJBaker(modelURL, []() -> QThread* {
return Oven::instance().getNextWorkerThread();
}, _contentOutputPath + subDirName + "/baked", _contentOutputPath + subDirName + "/original"),
&OBJBaker::deleteLater
};
}
// make sure our handler is called when the baker is done
connect(baker.data(), &Baker::finished, this, &DomainBaker::handleFinishedModelBaker);
// insert it into our bakers hash so we hold a strong pointer to it
_modelBakers.insert(modelURL, baker);
// move the baker to the baker thread
// and kickoff the bake
baker->moveToThread(Oven::instance().getNextWorkerThread());
QMetaObject::invokeMethod(baker.data(), "bake");
// keep track of the total number of baking entities
++_totalNumberOfSubBakes;
}
// add this QJsonValueRef to our multi hash so that we can easily re-write
// the model URL to the baked version once the baker is complete
_entitiesNeedingRewrite.insert(modelURL, *it);
}
} else {
// // We check now to see if we have either a texture for a skybox or a keylight, or both.

View file

@ -26,8 +26,7 @@
#include "../Oven.h"
#include "../OvenGUIApplication.h"
#include "OvenMainWindow.h"
#include "FBXBaker.h"
#include "OBJBaker.h"
#include "baking/BakerLibrary.h"
static const auto EXPORT_DIR_SETTING_KEY = "model_export_directory";
@ -172,74 +171,42 @@ void ModelBakeWidget::bakeButtonClicked() {
// construct a URL from the path in the model file text box
QUrl modelToBakeURL(fileURLString);
// if the URL doesn't have a scheme, assume it is a local file
if (modelToBakeURL.scheme() != "http" && modelToBakeURL.scheme() != "https" && modelToBakeURL.scheme() != "ftp") {
qDebug() << modelToBakeURL.toString();
qDebug() << modelToBakeURL.scheme();
modelToBakeURL = QUrl::fromLocalFile(fileURLString);
qDebug() << "New url: " << modelToBakeURL;
}
auto modelName = modelToBakeURL.fileName().left(modelToBakeURL.fileName().lastIndexOf('.'));
// make sure we have a valid output directory
QDir outputDirectory(_outputDirLineEdit->text());
QString subFolderName = modelName + "/";
// output in a sub-folder with the name of the fbx, potentially suffixed by a number to make it unique
int iteration = 0;
while (outputDirectory.exists(subFolderName)) {
subFolderName = modelName + "-" + QString::number(++iteration) + "/";
}
outputDirectory.mkpath(subFolderName);
if (!outputDirectory.exists()) {
QMessageBox::warning(this, "Unable to create directory", "Unable to create output directory. Please create it manually or choose a different directory.");
return;
}
outputDirectory.cd(subFolderName);
QUrl bakeableModelURL = getBakeableModelURL(QUrl(modelToBakeURL), false);
QDir bakedOutputDirectory = outputDirectory.absoluteFilePath("baked");
QDir originalOutputDirectory = outputDirectory.absoluteFilePath("original");
if (!bakeableModelURL.isEmpty()) {
auto getWorkerThreadCallback = []() -> QThread* {
return Oven::instance().getNextWorkerThread();
};
bakedOutputDirectory.mkdir(".");
originalOutputDirectory.mkdir(".");
std::unique_ptr<Baker> baker = getModelBaker(bakeableModelURL, getWorkerThreadCallback, outputDirectory.path());
if (baker) {
// everything seems to be in place, kick off a bake for this model now
std::unique_ptr<Baker> baker;
auto getWorkerThreadCallback = []() -> QThread* {
return Oven::instance().getNextWorkerThread();
};
// everything seems to be in place, kick off a bake for this model now
if (modelToBakeURL.fileName().endsWith(".fbx")) {
baker.reset(new FBXBaker(modelToBakeURL, getWorkerThreadCallback, bakedOutputDirectory.absolutePath(),
originalOutputDirectory.absolutePath()));
} else if (modelToBakeURL.fileName().endsWith(".obj")) {
baker.reset(new OBJBaker(modelToBakeURL, getWorkerThreadCallback, bakedOutputDirectory.absolutePath(),
originalOutputDirectory.absolutePath()));
} else {
qWarning() << "Unknown model type: " << modelToBakeURL.fileName();
continue;
// move the baker to the FBX baker thread
baker->moveToThread(Oven::instance().getNextWorkerThread());
// invoke the bake method on the baker thread
QMetaObject::invokeMethod(baker.get(), "bake");
// make sure we hear about the results of this baker when it is done
connect(baker.get(), &Baker::finished, this, &ModelBakeWidget::handleFinishedBaker);
// add a pending row to the results window to show that this bake is in process
auto resultsWindow = OvenGUIApplication::instance()->getMainWindow()->showResultsWindow();
auto resultsRow = resultsWindow->addPendingResultRow(modelToBakeURL.fileName(), outputDirectory);
// keep a unique_ptr to this baker
// and remember the row that represents it in the results table
_bakers.emplace_back(std::move(baker), resultsRow);
}
}
// move the baker to the FBX baker thread
baker->moveToThread(Oven::instance().getNextWorkerThread());
// invoke the bake method on the baker thread
QMetaObject::invokeMethod(baker.get(), "bake");
// make sure we hear about the results of this baker when it is done
connect(baker.get(), &Baker::finished, this, &ModelBakeWidget::handleFinishedBaker);
// add a pending row to the results window to show that this bake is in process
auto resultsWindow = OvenGUIApplication::instance()->getMainWindow()->showResultsWindow();
auto resultsRow = resultsWindow->addPendingResultRow(modelToBakeURL.fileName(), outputDirectory);
// keep a unique_ptr to this baker
// and remember the row that represents it in the results table
_bakers.emplace_back(std::move(baker), resultsRow);
}
}