804 lines · 34.5 KB · C++
RawDownload
////  ModelUploader.cpp//  interface/src////  Created by Clément Brisset on 3/4/14.//  Copyright 2014 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 <QCheckBox>#include <QComboBox>#include <QDebug>#include <QDialogButtonBox>#include <QDoubleSpinBox>#include <QFile>#include <QFileDialog>#include <QFormLayout>#include <QGridLayout>#include <QHBoxLayout>#include <QHttpMultiPart>#include <QImage>#include <QLineEdit>#include <QMessageBox>#include <QProgressBar>#include <QPushButton>#include <QStandardPaths>#include <QTemporaryFile>#include <QTextStream>#include <QVBoxLayout>#include <QVariant> #include <AccountManager.h> #include "Application.h"#include "ModelUploader.h"  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 SCALE_FIELD = "scale";static const QString TRANSLATION_X_FIELD = "tx";static const QString TRANSLATION_Y_FIELD = "ty";static const QString TRANSLATION_Z_FIELD = "tz";static const QString JOINT_FIELD = "joint";static const QString FREE_JOINT_FIELD = "freeJoint";static const QString BLENDSHAPE_FIELD = "bs"; static const QString S3_URL = "http://public.highfidelity.io";static const QString MODEL_URL = "/api/v1/models"; static const QString SETTING_NAME = "LastModelUploadLocation"; static const int MAX_SIZE = 10 * 1024 * 1024; // 10 MBstatic const int MAX_TEXTURE_SIZE = 1024;static const int TIMEOUT = 1000;static const int MAX_CHECK = 30; static const int QCOMPRESS_HEADER_POSITION = 0;static const int QCOMPRESS_HEADER_SIZE = 4; ModelUploader::ModelUploader(ModelType modelType) :    _lodCount(-1),    _texturesCount(-1),    _totalSize(0),    _modelType(modelType),    _readyToSend(false),    _dataMultiPart(new QHttpMultiPart(QHttpMultiPart::FormDataType)),    _numberOfChecks(MAX_CHECK){    connect(&_timer, SIGNAL(timeout()), SLOT(checkS3()));} ModelUploader::~ModelUploader() {    delete _dataMultiPart;} bool ModelUploader::zip() {    // File Dialog    QSettings* settings = Application::getInstance()->lockSettings();    QString lastLocation = settings->value(SETTING_NAME).toString();        if (lastLocation.isEmpty()) {       lastLocation  = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);    // Temporary fix to Qt bug: http://stackoverflow.com/questions/16194475#ifdef __APPLE__    lastLocation.append("/model.fst");#endif    }                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();        return false;    }    settings->setValue(SETTING_NAME, filename);    Application::getInstance()->unlockSettings();        // First we check the FST file (if any)    QFile* fst;    QVariantHash mapping;    QString basePath;    QString fbxFile;    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."),                                 QMessageBox::Ok);            qDebug() << "[Warning] " << QString("Could not open FST file.");            return false;        }        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,                                 QString("ModelUploader::zip()"),                                 QString("FBX file %1 could not be found.").arg(fbxInfo.fileName()),                                 QMessageBox::Ok);            qDebug() << "[Warning] " << QString("FBX file %1 could not be found.").arg(fbxInfo.fileName());            return false;        }    } else {        fst = new QTemporaryFile(this);        fst->open(QFile::WriteOnly);        fbxFile = filename;        basePath = QFileInfo(filename).path();        mapping.insert(FILENAME_FIELD, QFileInfo(filename).fileName());    }        // open the fbx file    QFile fbx(fbxFile);    if (!fbx.open(QIODevice::ReadOnly)) {        return false;    }    QByteArray fbxContents = fbx.readAll();    FBXGeometry geometry = readFBX(fbxContents, QVariantHash());        // make sure we have some basic mappings    if (!mapping.contains(NAME_FIELD)) {        mapping.insert(NAME_FIELD, QFileInfo(filename).baseName());    }    if (!mapping.contains(TEXDIR_FIELD)) {        mapping.insert(TEXDIR_FIELD, ".");    }        // mixamo/autodesk defaults    if (!mapping.contains(SCALE_FIELD)) {        mapping.insert(SCALE_FIELD, 15.0);    }    QVariantHash joints = mapping.value(JOINT_FIELD).toHash();    if (!joints.contains("jointEyeLeft")) {        joints.insert("jointEyeLeft", geometry.jointIndices.contains("jointEyeLeft") ? "jointEyeLeft" :            (geometry.jointIndices.contains("EyeLeft") ? "EyeLeft" : "LeftEye"));    }    if (!joints.contains("jointEyeRight")) {        joints.insert("jointEyeRight", geometry.jointIndices.contains("jointEyeRight") ? "jointEyeRight" :            geometry.jointIndices.contains("EyeRight") ? "EyeRight" : "RightEye");    }    if (!joints.contains("jointNeck")) {        joints.insert("jointNeck", geometry.jointIndices.contains("jointNeck") ? "jointNeck" : "Neck");    }    if (!joints.contains("jointRoot")) {        joints.insert("jointRoot", "Hips");    }    if (!joints.contains("jointLean")) {        joints.insert("jointLean", "Spine");    }    if (!joints.contains("jointHead")) {        const char* topName = (geometry.applicationName == "mixamo.com") ? "HeadTop_End" : "HeadEnd";        joints.insert("jointHead", geometry.jointIndices.contains(topName) ? topName : "Head");    }    if (!joints.contains("jointLeftHand")) {        joints.insert("jointLeftHand", "LeftHand");    }    if (!joints.contains("jointRightHand")) {        joints.insert("jointRightHand", "RightHand");    }    mapping.insert(JOINT_FIELD, joints);    if (!mapping.contains(FREE_JOINT_FIELD)) {        mapping.insertMulti(FREE_JOINT_FIELD, "LeftArm");        mapping.insertMulti(FREE_JOINT_FIELD, "LeftForeArm");        mapping.insertMulti(FREE_JOINT_FIELD, "RightArm");        mapping.insertMulti(FREE_JOINT_FIELD, "RightForeArm");    }        // mixamo blendshapes    if (!mapping.contains(BLENDSHAPE_FIELD) && geometry.applicationName == "mixamo.com") {        QVariantHash blendshapes;        blendshapes.insertMulti("BrowsD_L", QVariantList() << "BrowsDown_Left" << 1.0);        blendshapes.insertMulti("BrowsD_R", QVariantList() << "BrowsDown_Right" << 1.0);        blendshapes.insertMulti("BrowsU_C", QVariantList() << "BrowsUp_Left" << 1.0);        blendshapes.insertMulti("BrowsU_C", QVariantList() << "BrowsUp_Right" << 1.0);        blendshapes.insertMulti("BrowsU_L", QVariantList() << "BrowsUp_Left" << 1.0);        blendshapes.insertMulti("BrowsU_R", QVariantList() << "BrowsUp_Right" << 1.0);        blendshapes.insertMulti("ChinLowerRaise", QVariantList() << "Jaw_Up" << 1.0);        blendshapes.insertMulti("ChinUpperRaise", QVariantList() << "UpperLipUp_Left" << 0.5);        blendshapes.insertMulti("ChinUpperRaise", QVariantList() << "UpperLipUp_Right" << 0.5);        blendshapes.insertMulti("EyeBlink_L", QVariantList() << "Blink_Left" << 1.0);        blendshapes.insertMulti("EyeBlink_R", QVariantList() << "Blink_Right" << 1.0);        blendshapes.insertMulti("EyeOpen_L", QVariantList() << "EyesWide_Left" << 1.0);        blendshapes.insertMulti("EyeOpen_R", QVariantList() << "EyesWide_Right" << 1.0);        blendshapes.insertMulti("EyeSquint_L", QVariantList() << "Squint_Left" << 1.0);        blendshapes.insertMulti("EyeSquint_R", QVariantList() << "Squint_Right" << 1.0);        blendshapes.insertMulti("JawFwd", QVariantList() << "JawForeward" << 1.0);        blendshapes.insertMulti("JawLeft", QVariantList() << "JawRotateY_Left" << 0.5);        blendshapes.insertMulti("JawOpen", QVariantList() << "MouthOpen" << 0.7);        blendshapes.insertMulti("JawRight", QVariantList() << "Jaw_Right" << 1.0);        blendshapes.insertMulti("LipsFunnel", QVariantList() << "JawForeward" << 0.39);        blendshapes.insertMulti("LipsFunnel", QVariantList() << "Jaw_Down" << 0.36);        blendshapes.insertMulti("LipsFunnel", QVariantList() << "MouthNarrow_Left" << 1.0);        blendshapes.insertMulti("LipsFunnel", QVariantList() << "MouthNarrow_Right" << 1.0);        blendshapes.insertMulti("LipsFunnel", QVariantList() << "MouthWhistle_NarrowAdjust_Left" << 0.5);        blendshapes.insertMulti("LipsFunnel", QVariantList() << "MouthWhistle_NarrowAdjust_Right" << 0.5);        blendshapes.insertMulti("LipsFunnel", QVariantList() << "TongueUp" << 1.0);        blendshapes.insertMulti("LipsLowerClose", QVariantList() << "LowerLipIn" << 1.0);        blendshapes.insertMulti("LipsLowerDown", QVariantList() << "LowerLipDown_Left" << 0.7);        blendshapes.insertMulti("LipsLowerDown", QVariantList() << "LowerLipDown_Right" << 0.7);        blendshapes.insertMulti("LipsLowerOpen", QVariantList() << "LowerLipOut" << 1.0);        blendshapes.insertMulti("LipsPucker", QVariantList() << "MouthNarrow_Left" << 1.0);        blendshapes.insertMulti("LipsPucker", QVariantList() << "MouthNarrow_Right" << 1.0);        blendshapes.insertMulti("LipsUpperClose", QVariantList() << "UpperLipIn" << 1.0);        blendshapes.insertMulti("LipsUpperOpen", QVariantList() << "UpperLipOut" << 1.0);        blendshapes.insertMulti("LipsUpperUp", QVariantList() << "UpperLipUp_Left" << 0.7);        blendshapes.insertMulti("LipsUpperUp", QVariantList() << "UpperLipUp_Right" << 0.7);        blendshapes.insertMulti("MouthDimple_L", QVariantList() << "Smile_Left" << 0.25);        blendshapes.insertMulti("MouthDimple_R", QVariantList() << "Smile_Right" << 0.25);        blendshapes.insertMulti("MouthFrown_L", QVariantList() << "Frown_Left" << 1.0);        blendshapes.insertMulti("MouthFrown_R", QVariantList() << "Frown_Right" << 1.0);        blendshapes.insertMulti("MouthLeft", QVariantList() << "Midmouth_Left" << 1.0);        blendshapes.insertMulti("MouthRight", QVariantList() << "Midmouth_Right" << 1.0);        blendshapes.insertMulti("MouthSmile_L", QVariantList() << "Smile_Left" << 1.0);        blendshapes.insertMulti("MouthSmile_R", QVariantList() << "Smile_Right" << 1.0);        blendshapes.insertMulti("Puff", QVariantList() << "CheekPuff_Left" << 1.0);        blendshapes.insertMulti("Puff", QVariantList() << "CheekPuff_Right" << 1.0);        blendshapes.insertMulti("Sneer", QVariantList() << "NoseScrunch_Left" << 0.75);        blendshapes.insertMulti("Sneer", QVariantList() << "NoseScrunch_Right" << 0.75);        blendshapes.insertMulti("Sneer", QVariantList() << "Squint_Left" << 0.5);        blendshapes.insertMulti("Sneer", QVariantList() << "Squint_Right" << 0.5);        mapping.insert(BLENDSHAPE_FIELD, blendshapes);    }        // open the dialog to configure the rest    ModelPropertiesDialog properties(_modelType, mapping, basePath, geometry);    if (properties.exec() == QDialog::Rejected) {        return false;    }    mapping = properties.getMapping();        QByteArray nameField = mapping.value(NAME_FIELD).toByteArray();    QString urlBase;    if (!nameField.isEmpty()) {        QHttpPart textPart;        textPart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\"model_name\"");        textPart.setBody(nameField);        _dataMultiPart->append(textPart);        urlBase = S3_URL + "/models/" + MODEL_TYPE_NAMES[_modelType] + "/" + nameField;        _url = urlBase + ".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;    _textureBase = urlBase + "/textures/";    if (!texdirField.isEmpty()) {        texDir = basePath + "/" + texdirField;        QFileInfo texInfo(texDir);        if (!texInfo.exists() || !texInfo.isDir()) {            QMessageBox::warning(NULL,                                 QString("ModelUploader::zip()"),                                 QString("Texture directory could not be found."),                                 QMessageBox::Ok);            qDebug() << "[Warning] " << QString("Texture directory could not be found.");            return false;        }    }        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()"),                                 QString("LOD file %1 could not be found.").arg(lod.fileName()),                                 QMessageBox::Ok);            qDebug() << "[Warning] " << QString("FBX file %1 could not be found.").arg(lod.fileName());        }        // Compress and copy        if (!addPart(lod.filePath(), QString("lod%1").arg(++_lodCount))) {            return false;        }    }        // Write out, compress and copy the fst    if (!addPart(*fst, writeMapping(mapping), QString("fst"))) {        return false;    }        // Compress and copy the fbx    if (!addPart(fbx, fbxContents, "fbx")) {        return false;    }                    if (!addTextures(texDir, geometry)) {        return false;    }        QHttpPart textPart;    textPart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data;"                       " name=\"model_category\"");    textPart.setBody(MODEL_TYPE_NAMES[_modelType]);    _dataMultiPart->append(textPart);        _readyToSend = true;    return true;} void ModelUploader::send() {    if (!zip()) {        deleteLater();        return;    }        JSONCallbackParameters callbackParams;    callbackParams.jsonCallbackReceiver = this;    callbackParams.jsonCallbackMethod = "checkJSON";    callbackParams.errorCallbackReceiver = this;    callbackParams.errorCallbackMethod = "uploadFailed";        AccountManager::getInstance().authenticatedRequest(MODEL_URL + "/" + QFileInfo(_url).baseName(),                                                       QNetworkAccessManager::GetOperation,                                                       callbackParams);        qDebug() << "Sending model...";    _progressDialog = new QDialog();    _progressBar = new QProgressBar(_progressDialog);    _progressBar->setRange(0, 100);    _progressBar->setValue(0);        _progressDialog->setWindowTitle("Uploading model...");    _progressDialog->setLayout(new QGridLayout(_progressDialog));    _progressDialog->layout()->addWidget(_progressBar);        _progressDialog->exec();     delete _progressDialog;    _progressDialog = NULL;    _progressBar = NULL;} void ModelUploader::checkJSON(QNetworkReply& requestReply) {    QJsonObject jsonResponse = QJsonDocument::fromJson(requestReply.readAll()).object();        if (jsonResponse.contains("status") && jsonResponse.value("status").toString() == "success") {        qDebug() << "status : success";        JSONCallbackParameters callbackParams;        callbackParams.jsonCallbackReceiver = this;        callbackParams.jsonCallbackMethod = "uploadSuccess";        callbackParams.errorCallbackReceiver = this;        callbackParams.errorCallbackMethod = "uploadFailed";        callbackParams.updateReciever = this;        callbackParams.updateSlot = SLOT(uploadUpdate(qint64, qint64));                if (jsonResponse.contains("exists") && jsonResponse.value("exists").toBool()) {            qDebug() << "exists : true";            if (jsonResponse.contains("can_update") && jsonResponse.value("can_update").toBool()) {                qDebug() << "can_update : true";                                AccountManager::getInstance().authenticatedRequest(MODEL_URL + "/" + QFileInfo(_url).baseName(),                                                                   QNetworkAccessManager::PutOperation,                                                                   callbackParams,                                                                   QByteArray(),                                                                   _dataMultiPart);                _dataMultiPart = NULL;            } else {                qDebug() << "can_update : false";                if (_progressDialog) {                    _progressDialog->reject();                }                QMessageBox::warning(NULL,                                     QString("ModelUploader::checkJSON()"),                                     QString("This model already exist and is own by someone else."),                                     QMessageBox::Ok);                deleteLater();            }        } else {            qDebug() << "exists : false";            AccountManager::getInstance().authenticatedRequest(MODEL_URL,                                                               QNetworkAccessManager::PostOperation,                                                               callbackParams,                                                               QByteArray(),                                                               _dataMultiPart);            _dataMultiPart = NULL;        }    } else {        qDebug() << "status : failed";        if (_progressDialog) {            _progressDialog->reject();        }        QMessageBox::warning(NULL,                             QString("ModelUploader::checkJSON()"),                             QString("Something went wrong with the data-server."),                             QMessageBox::Ok);        deleteLater();    }} void ModelUploader::uploadUpdate(qint64 bytesSent, qint64 bytesTotal) {    if (_progressDialog) {        _progressBar->setRange(0, bytesTotal);        _progressBar->setValue(bytesSent);    }} void ModelUploader::uploadSuccess(QNetworkReply& requestReply) {    if (_progressDialog) {        _progressDialog->accept();    }    QMessageBox::information(NULL,                             QString("ModelUploader::uploadSuccess()"),                             QString("We are reading your model information."),                             QMessageBox::Ok);    qDebug() << "Model sent with success";    checkS3();} void ModelUploader::uploadFailed(QNetworkReply& errorReply) {    if (_progressDialog) {        _progressDialog->reject();    }    qDebug() << "Model upload failed (" << errorReply.error() << "): " << errorReply.errorString();    QMessageBox::warning(NULL,                         QString("ModelUploader::uploadFailed()"),                         QString("There was a problem with your upload, please try again later."),                         QMessageBox::Ok);    deleteLater();} void ModelUploader::checkS3() {    qDebug() << "Checking S3 for " << _url;    QNetworkRequest request(_url);    QNetworkReply* reply = NetworkAccessManager::getInstance().head(request);    connect(reply, SIGNAL(finished()), SLOT(processCheck()));} void ModelUploader::processCheck() {    QNetworkReply* reply = static_cast<QNetworkReply*>(sender());    _timer.stop();        switch (reply->error()) {        case QNetworkReply::NoError:            QMessageBox::information(NULL,                                     QString("ModelUploader::processCheck()"),                                     QString("Your model is now available in the browser."),                                     QMessageBox::Ok);            Application::getInstance()->getGeometryCache()->refresh(_url);            foreach (const QByteArray& filename, _textureFilenames) {                Application::getInstance()->getTextureCache()->refresh(_textureBase + filename);            }            deleteLater();            break;        case QNetworkReply::ContentNotFoundError:            if (--_numberOfChecks) {                _timer.start(TIMEOUT);                break;            }        default:            QMessageBox::warning(NULL,                                 QString("ModelUploader::processCheck()"),                                 QString("We could not verify that your model was sent sucessfully\n"                                         "but it may have. If you do not see it in the model browser, try to upload again."),                                 QMessageBox::Ok);            deleteLater();            break;    }        delete reply;} bool ModelUploader::addTextures(const QString& texdir, const FBXGeometry& geometry) {    foreach (FBXMesh mesh, geometry.meshes) {        foreach (FBXMeshPart part, mesh.parts) {

Lines 1–500 of 804.