mirror of
https://github.com/overte-org/overte.git
synced 2025-04-25 14:54:29 +02:00
Working on FBX uploads (i.e., without FST).
This commit is contained in:
parent
19b76e3331
commit
66e0cbf6ad
4 changed files with 205 additions and 98 deletions
|
@ -10,13 +10,18 @@
|
|||
//
|
||||
|
||||
#include <QDebug>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QFile>
|
||||
#include <QFileDialog>
|
||||
#include <QFormLayout>
|
||||
#include <QGridLayout>
|
||||
#include <QHttpMultiPart>
|
||||
#include <QLineEdit>
|
||||
#include <QMessageBox>
|
||||
#include <QProgressBar>
|
||||
#include <QPushButton>
|
||||
#include <QStandardPaths>
|
||||
#include <QTemporaryFile>
|
||||
#include <QTextStream>
|
||||
#include <QVariant>
|
||||
|
||||
|
@ -32,6 +37,7 @@ static const QString NAME_FIELD = "name";
|
|||
static const QString FILENAME_FIELD = "filename";
|
||||
static const QString TEXDIR_FIELD = "texdir";
|
||||
static const QString LOD_FIELD = "lod";
|
||||
static const QString JOINT_INDEX_FIELD = "jointIndex";
|
||||
|
||||
static const QString S3_URL = "http://highfidelity-public.s3-us-west-1.amazonaws.com";
|
||||
static const QString DATA_SERVER_URL = "https://data-web.highfidelity.io";
|
||||
|
@ -76,7 +82,8 @@ bool ModelUploader::zip() {
|
|||
}
|
||||
|
||||
|
||||
QString filename = QFileDialog::getOpenFileName(NULL, "Select your .fst file ...", lastLocation, "*.fst");
|
||||
QString filename = QFileDialog::getOpenFileName(NULL, "Select your model file ...",
|
||||
lastLocation, "Model files (*.fst *.fbx)");
|
||||
if (filename == "") {
|
||||
// If the user canceled we return.
|
||||
Application::getInstance()->unlockSettings();
|
||||
|
@ -85,14 +92,14 @@ bool ModelUploader::zip() {
|
|||
settings->setValue(SETTING_NAME, filename);
|
||||
Application::getInstance()->unlockSettings();
|
||||
|
||||
bool _nameIsPresent = false;
|
||||
QString texDir;
|
||||
// First we check the FST file (if any)
|
||||
QFile* fst;
|
||||
QVariantHash mapping;
|
||||
QString basePath;
|
||||
QString fbxFile;
|
||||
|
||||
|
||||
// First we check the FST file
|
||||
QFile fst(filename);
|
||||
if (!fst.open(QFile::ReadOnly | QFile::Text)) {
|
||||
if (filename.toLower().endsWith(".fst")) {
|
||||
fst = new QFile(filename, this);
|
||||
if (!fst->open(QFile::ReadOnly | QFile::Text)) {
|
||||
QMessageBox::warning(NULL,
|
||||
QString("ModelUploader::zip()"),
|
||||
QString("Could not open FST file."),
|
||||
|
@ -100,33 +107,10 @@ bool ModelUploader::zip() {
|
|||
qDebug() << "[Warning] " << QString("Could not open FST file.");
|
||||
return false;
|
||||
}
|
||||
qDebug() << "Reading FST file : " << QFileInfo(fst).filePath();
|
||||
|
||||
// Compress and copy the fst
|
||||
if (!addPart(QFileInfo(fst).filePath(), QString("fst"))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Let's read through the FST file
|
||||
QTextStream stream(&fst);
|
||||
QList<QString> line;
|
||||
while (!stream.atEnd()) {
|
||||
line = stream.readLine().split(QRegExp("[ =]"), QString::SkipEmptyParts);
|
||||
if (line.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// according to what is read, we modify the command
|
||||
if (line[0] == NAME_FIELD) {
|
||||
QHttpPart textPart;
|
||||
textPart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data;"
|
||||
" name=\"model_name\"");
|
||||
textPart.setBody(line[1].toUtf8());
|
||||
_dataMultiPart->append(textPart);
|
||||
_url = S3_URL + ((_isHead)? "/models/heads/" : "/models/skeletons/") + line[1].toUtf8() + ".fst";
|
||||
_nameIsPresent = true;
|
||||
} else if (line[0] == FILENAME_FIELD) {
|
||||
fbxFile = QFileInfo(fst).path() + "/" + line[1];
|
||||
qDebug() << "Reading FST file : " << QFileInfo(*fst).filePath();
|
||||
mapping = readMapping(fst->readAll());
|
||||
basePath = QFileInfo(*fst).path();
|
||||
fbxFile = basePath + "/" + mapping.value(FILENAME_FIELD).toString();
|
||||
QFileInfo fbxInfo(fbxFile);
|
||||
if (!fbxInfo.exists() || !fbxInfo.isFile()) { // Check existence
|
||||
QMessageBox::warning(NULL,
|
||||
|
@ -136,12 +120,46 @@ bool ModelUploader::zip() {
|
|||
qDebug() << "[Warning] " << QString("FBX file %1 could not be found.").arg(fbxInfo.fileName());
|
||||
return false;
|
||||
}
|
||||
// Compress and copy
|
||||
if (!addPart(fbxInfo.filePath(), "fbx")) {
|
||||
} else {
|
||||
fst = new QTemporaryFile(this);
|
||||
fbxFile = filename;
|
||||
basePath = QFileInfo(filename).path();
|
||||
}
|
||||
|
||||
// open the fbx file
|
||||
QFile fbx(fbxFile);
|
||||
if (!fbx.open(QIODevice::ReadOnly)) {
|
||||
return false;
|
||||
}
|
||||
} else if (line[0] == TEXDIR_FIELD) { // Check existence
|
||||
texDir = QFileInfo(fst).path() + "/" + line[1];
|
||||
QByteArray fbxContents = fbx.readAll();
|
||||
FBXGeometry geometry = readFBX(fbxContents, QVariantHash());
|
||||
|
||||
ModelPropertiesDialog properties(_isHead, mapping);
|
||||
if (properties.exec() == QDialog::Rejected) {
|
||||
return false;
|
||||
}
|
||||
mapping = properties.getMapping();
|
||||
|
||||
QByteArray nameField = mapping.value(NAME_FIELD).toByteArray();
|
||||
if (!nameField.isEmpty()) {
|
||||
QHttpPart textPart;
|
||||
textPart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\"model_name\"");
|
||||
textPart.setBody(nameField);
|
||||
_dataMultiPart->append(textPart);
|
||||
_url = S3_URL + ((_isHead)? "/models/heads/" : "/models/skeletons/") + nameField + ".fst";
|
||||
} else {
|
||||
QMessageBox::warning(NULL,
|
||||
QString("ModelUploader::zip()"),
|
||||
QString("Model name is missing in the .fst file."),
|
||||
QMessageBox::Ok);
|
||||
qDebug() << "[Warning] " << QString("Model name is missing in the .fst file.");
|
||||
return false;
|
||||
}
|
||||
|
||||
QByteArray texdirField = mapping.value(TEXDIR_FIELD).toByteArray();
|
||||
QString texDir;
|
||||
if (!texdirField.isEmpty()) {
|
||||
texDir = basePath + "/" + texdirField;
|
||||
QFileInfo texInfo(texDir);
|
||||
if (!texInfo.exists() || !texInfo.isDir()) {
|
||||
QMessageBox::warning(NULL,
|
||||
|
@ -151,8 +169,11 @@ bool ModelUploader::zip() {
|
|||
qDebug() << "[Warning] " << QString("Texture directory could not be found.");
|
||||
return false;
|
||||
}
|
||||
} else if (line[0] == LOD_FIELD) {
|
||||
QFileInfo lod(QFileInfo(fst).path() + "/" + line[1]);
|
||||
}
|
||||
|
||||
QVariantHash lodField = mapping.value(LOD_FIELD).toHash();
|
||||
for (QVariantHash::const_iterator it = lodField.constBegin(); it != lodField.constEnd(); it++) {
|
||||
QFileInfo lod(basePath + "/" + it.key());
|
||||
if (!lod.exists() || !lod.isFile()) { // Check existence
|
||||
QMessageBox::warning(NULL,
|
||||
QString("ModelUploader::zip()"),
|
||||
|
@ -165,9 +186,18 @@ bool ModelUploader::zip() {
|
|||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Write out, compress and copy the fst
|
||||
if (!addPart(*fst, writeMapping(mapping), QString("fst"))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!addTextures(texDir, fbxFile)) {
|
||||
// Compress and copy the fbx
|
||||
if (!addPart(fbx, fbxContents, "fbx")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!addTextures(texDir, geometry)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -181,15 +211,6 @@ bool ModelUploader::zip() {
|
|||
}
|
||||
_dataMultiPart->append(textPart);
|
||||
|
||||
if (!_nameIsPresent) {
|
||||
QMessageBox::warning(NULL,
|
||||
QString("ModelUploader::zip()"),
|
||||
QString("Model name is missing in the .fst file."),
|
||||
QMessageBox::Ok);
|
||||
qDebug() << "[Warning] " << QString("Model name is missing in the .fst file.");
|
||||
return false;
|
||||
}
|
||||
|
||||
_readyToSend = true;
|
||||
return true;
|
||||
}
|
||||
|
@ -350,25 +371,16 @@ void ModelUploader::processCheck() {
|
|||
delete reply;
|
||||
}
|
||||
|
||||
bool ModelUploader::addTextures(const QString& texdir, const QString fbxFile) {
|
||||
QFile fbx(fbxFile);
|
||||
if (!fbx.open(QIODevice::ReadOnly)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QByteArray buffer = fbx.readAll();
|
||||
QVariantHash variantHash = readMapping(buffer);
|
||||
FBXGeometry geometry = readFBX(buffer, variantHash);
|
||||
|
||||
bool ModelUploader::addTextures(const QString& texdir, const FBXGeometry& geometry) {
|
||||
foreach (FBXMesh mesh, geometry.meshes) {
|
||||
foreach (FBXMeshPart part, mesh.parts) {
|
||||
if (!part.diffuseTexture.filename.isEmpty()) {
|
||||
if (!part.diffuseTexture.filename.isEmpty() && part.diffuseTexture.content.isEmpty()) {
|
||||
if (!addPart(texdir + "/" + part.diffuseTexture.filename,
|
||||
QString("texture%1").arg(++_texturesCount))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!part.normalTexture.filename.isEmpty()) {
|
||||
if (!part.normalTexture.filename.isEmpty() && part.normalTexture.content.isEmpty()) {
|
||||
if (!addPart(texdir + "/" + part.normalTexture.filename,
|
||||
QString("texture%1").arg(++_texturesCount))) {
|
||||
return false;
|
||||
|
@ -390,7 +402,11 @@ bool ModelUploader::addPart(const QString &path, const QString& name) {
|
|||
qDebug() << "[Warning] " << QString("Could not open %1").arg(path);
|
||||
return false;
|
||||
}
|
||||
QByteArray buffer = qCompress(file.readAll());
|
||||
return addPart(file, file.readAll(), name);
|
||||
}
|
||||
|
||||
bool ModelUploader::addPart(const QFile& file, const QByteArray& contents, const QString& name) {
|
||||
QByteArray buffer = qCompress(contents);
|
||||
|
||||
// Qt's qCompress() default compression level (-1) is the standard zLib compression.
|
||||
// Here remove Qt's custom header that prevent the data server from uncompressing the files with zLib.
|
||||
|
@ -420,8 +436,45 @@ bool ModelUploader::addPart(const QString &path, const QString& name) {
|
|||
return true;
|
||||
}
|
||||
|
||||
ModelPropertiesDialog::ModelPropertiesDialog(bool isHead, const QVariantHash& originalMapping) :
|
||||
_originalMapping(originalMapping) {
|
||||
|
||||
setWindowTitle("Set Model Properties");
|
||||
|
||||
QFormLayout* form = new QFormLayout();
|
||||
setLayout(form);
|
||||
|
||||
form->addRow("Name:", _name = new QLineEdit());
|
||||
form->addRow("Texture Directory:", _textureDirectory = new QPushButton());
|
||||
connect(_textureDirectory, SIGNAL(clicked(bool)), SLOT(chooseTextureDirectory()));
|
||||
|
||||
QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok |
|
||||
QDialogButtonBox::Cancel | QDialogButtonBox::Reset);
|
||||
connect(buttons, SIGNAL(accepted()), SLOT(accept()));
|
||||
connect(buttons, SIGNAL(rejected()), SLOT(reject()));
|
||||
connect(buttons->button(QDialogButtonBox::Reset), SIGNAL(clicked(bool)), SLOT(reset()));
|
||||
|
||||
form->addRow(buttons);
|
||||
|
||||
// reset to initialize the fields
|
||||
reset();
|
||||
}
|
||||
|
||||
QVariantHash ModelPropertiesDialog::getMapping() const {
|
||||
QVariantHash mapping = _originalMapping;
|
||||
mapping.insert(NAME_FIELD, _name->text());
|
||||
mapping.insert(TEXDIR_FIELD, _textureDirectory->text());
|
||||
return mapping;
|
||||
}
|
||||
|
||||
void ModelPropertiesDialog::reset() {
|
||||
_name->setText(_originalMapping.value(NAME_FIELD).toString());
|
||||
_textureDirectory->setText(_originalMapping.value(TEXDIR_FIELD).toString());
|
||||
}
|
||||
|
||||
void ModelPropertiesDialog::chooseTextureDirectory() {
|
||||
QString directory = QFileDialog::getExistingDirectory(this, "Choose Texture Directory", _textureDirectory->text());
|
||||
if (!directory.isEmpty()) {
|
||||
_textureDirectory->setText(directory);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,12 +12,16 @@
|
|||
#ifndef hifi_ModelUploader_h
|
||||
#define hifi_ModelUploader_h
|
||||
|
||||
#include <QDialog>
|
||||
#include <QTimer>
|
||||
|
||||
class QDialog;
|
||||
class QFileInfo;
|
||||
class QHttpMultiPart;
|
||||
class QLineEdit;
|
||||
class QProgressBar;
|
||||
class QPushButton;
|
||||
|
||||
class FBXGeometry;
|
||||
|
||||
class ModelUploader : public QObject {
|
||||
Q_OBJECT
|
||||
|
@ -56,8 +60,28 @@ private:
|
|||
|
||||
|
||||
bool zip();
|
||||
bool addTextures(const QString& texdir, const QString fbxFile);
|
||||
bool addTextures(const QString& texdir, const FBXGeometry& geometry);
|
||||
bool addPart(const QString& path, const QString& name);
|
||||
bool addPart(const QFile& file, const QByteArray& contents, const QString& name);
|
||||
};
|
||||
|
||||
/// A dialog that allows customization of various model properties.
|
||||
class ModelPropertiesDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ModelPropertiesDialog(bool isHead, const QVariantHash& originalMapping);
|
||||
|
||||
QVariantHash getMapping() const;
|
||||
|
||||
private slots:
|
||||
void reset();
|
||||
void chooseTextureDirectory();
|
||||
|
||||
private:
|
||||
QVariantHash _originalMapping;
|
||||
QLineEdit* _name;
|
||||
QPushButton* _textureDirectory;
|
||||
};
|
||||
|
||||
#endif // hifi_ModelUploader_h
|
||||
|
|
|
@ -1803,6 +1803,33 @@ QVariantHash readMapping(const QByteArray& data) {
|
|||
return parseMapping(&buffer);
|
||||
}
|
||||
|
||||
QByteArray writeMapping(const QVariantHash& mapping) {
|
||||
QBuffer buffer;
|
||||
buffer.open(QIODevice::WriteOnly);
|
||||
for (QVariantHash::const_iterator first = mapping.constBegin(); first != mapping.constEnd(); first++) {
|
||||
QByteArray key = first.key().toUtf8() + " = ";
|
||||
QVariantHash hashValue = first.value().toHash();
|
||||
if (hashValue.isEmpty()) {
|
||||
buffer.write(key + first.value().toByteArray() + "\n");
|
||||
continue;
|
||||
}
|
||||
for (QVariantHash::const_iterator second = hashValue.constBegin(); second != hashValue.constEnd(); second++) {
|
||||
QByteArray extendedKey = key + second.key().toUtf8();
|
||||
QVariantList listValue = second.value().toList();
|
||||
if (listValue.isEmpty()) {
|
||||
buffer.write(extendedKey + " = " + second.value().toByteArray() + "\n");
|
||||
continue;
|
||||
}
|
||||
buffer.write(extendedKey);
|
||||
for (QVariantList::const_iterator third = listValue.constBegin(); third != listValue.constEnd(); third++) {
|
||||
buffer.write(" = " + third->toByteArray());
|
||||
}
|
||||
buffer.write("\n");
|
||||
}
|
||||
}
|
||||
return buffer.data();
|
||||
}
|
||||
|
||||
FBXGeometry readFBX(const QByteArray& model, const QVariantHash& mapping) {
|
||||
QBuffer buffer(const_cast<QByteArray*>(&model));
|
||||
buffer.open(QIODevice::ReadOnly);
|
||||
|
|
|
@ -218,6 +218,9 @@ Q_DECLARE_METATYPE(FBXGeometry)
|
|||
/// Reads an FST mapping from the supplied data.
|
||||
QVariantHash readMapping(const QByteArray& data);
|
||||
|
||||
/// Writes an FST mapping to a byte array.
|
||||
QByteArray writeMapping(const QVariantHash& mapping);
|
||||
|
||||
/// Reads FBX geometry from the supplied model and mapping data.
|
||||
/// \exception QString if an error occurs in parsing
|
||||
FBXGeometry readFBX(const QByteArray& model, const QVariantHash& mapping);
|
||||
|
|
Loading…
Reference in a new issue