mirror of
https://github.com/overte-org/overte.git
synced 2025-08-06 16:36:54 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi into windowsMenuAssert
This commit is contained in:
commit
cd52bab2e8
18 changed files with 991 additions and 1072 deletions
|
@ -95,6 +95,21 @@ var isActive = false;
|
||||||
|
|
||||||
var placingEntityID = null;
|
var placingEntityID = null;
|
||||||
|
|
||||||
|
IMPORTING_SVO_OVERLAY_WIDTH = 130;
|
||||||
|
IMPORTING_SVO_OVERLAY_HEIGHT = 30;
|
||||||
|
IMPORTING_SVO_OVERLAY_MARGIN = 6;
|
||||||
|
var importingSVOOverlay = Overlays.addOverlay("text", {
|
||||||
|
font: { size: 14 },
|
||||||
|
text: "Importing SVO...",
|
||||||
|
x: Window.innerWidth - IMPORTING_SVO_OVERLAY_WIDTH - IMPORTING_SVO_OVERLAY_MARGIN,
|
||||||
|
y: Window.innerHeight - IMPORTING_SVO_OVERLAY_HEIGHT - IMPORTING_SVO_OVERLAY_MARGIN,
|
||||||
|
width: IMPORTING_SVO_OVERLAY_WIDTH,
|
||||||
|
height: IMPORTING_SVO_OVERLAY_HEIGHT,
|
||||||
|
backgroundColor: { red: 80, green: 80, blue: 80 },
|
||||||
|
backgroundAlpha: 0.7,
|
||||||
|
visible: false,
|
||||||
|
});
|
||||||
|
|
||||||
var toolBar = (function () {
|
var toolBar = (function () {
|
||||||
var that = {},
|
var that = {},
|
||||||
toolBar,
|
toolBar,
|
||||||
|
@ -753,6 +768,8 @@ Script.scriptEnding.connect(function() {
|
||||||
tooltip.cleanup();
|
tooltip.cleanup();
|
||||||
selectionDisplay.cleanup();
|
selectionDisplay.cleanup();
|
||||||
Entities.setLightsArePickable(originalLightsArePickable);
|
Entities.setLightsArePickable(originalLightsArePickable);
|
||||||
|
|
||||||
|
Overlays.deleteOverlay(importingSVOOverlay);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Do some stuff regularly, like check for placement of various overlays
|
// Do some stuff regularly, like check for placement of various overlays
|
||||||
|
@ -816,24 +833,7 @@ function handeMenuEvent(menuItem) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (importURL) {
|
if (importURL) {
|
||||||
var success = Clipboard.importEntities(importURL);
|
importSVO(importURL);
|
||||||
|
|
||||||
if (success) {
|
|
||||||
var distance = cameraManager.enabled ? cameraManager.zoomDistance : DEFAULT_ENTITY_DRAG_DROP_DISTANCE;
|
|
||||||
var direction = Quat.getFront(Camera.orientation);
|
|
||||||
var offset = Vec3.multiply(distance, direction);
|
|
||||||
var position = Vec3.sum(Camera.position, offset);
|
|
||||||
|
|
||||||
position.x = Math.max(0, position.x);
|
|
||||||
position.y = Math.max(0, position.y);
|
|
||||||
position.z = Math.max(0, position.z);
|
|
||||||
|
|
||||||
var pastedEntityIDs = Clipboard.pasteEntities(position);
|
|
||||||
|
|
||||||
selectionManager.setSelections(pastedEntityIDs);
|
|
||||||
} else {
|
|
||||||
Window.alert("There was an error importing the entity file.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else if (menuItem == "Entity List...") {
|
} else if (menuItem == "Entity List...") {
|
||||||
entityListTool.toggleVisible();
|
entityListTool.toggleVisible();
|
||||||
|
@ -841,6 +841,34 @@ function handeMenuEvent(menuItem) {
|
||||||
tooltip.show(false);
|
tooltip.show(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function importSVO(importURL) {
|
||||||
|
Overlays.editOverlay(importingSVOOverlay, { visible: true });
|
||||||
|
|
||||||
|
var success = Clipboard.importEntities(importURL);
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
var distance = cameraManager.enabled ? cameraManager.zoomDistance : DEFAULT_ENTITY_DRAG_DROP_DISTANCE;
|
||||||
|
var direction = Quat.getFront(Camera.orientation);
|
||||||
|
var offset = Vec3.multiply(distance, direction);
|
||||||
|
var position = Vec3.sum(Camera.position, offset);
|
||||||
|
|
||||||
|
position.x = Math.max(0, position.x);
|
||||||
|
position.y = Math.max(0, position.y);
|
||||||
|
position.z = Math.max(0, position.z);
|
||||||
|
|
||||||
|
var pastedEntityIDs = Clipboard.pasteEntities(position);
|
||||||
|
|
||||||
|
if (isActive) {
|
||||||
|
selectionManager.setSelections(pastedEntityIDs);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Window.alert("There was an error importing the entity file.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Overlays.editOverlay(importingSVOOverlay, { visible: false });
|
||||||
|
}
|
||||||
|
Window.svoImportRequested.connect(importSVO);
|
||||||
|
|
||||||
Menu.menuItemEvent.connect(handeMenuEvent);
|
Menu.menuItemEvent.connect(handeMenuEvent);
|
||||||
|
|
||||||
Controller.keyPressEvent.connect(function(event) {
|
Controller.keyPressEvent.connect(function(event) {
|
||||||
|
|
|
@ -91,7 +91,7 @@
|
||||||
#include "InterfaceVersion.h"
|
#include "InterfaceVersion.h"
|
||||||
#include "LODManager.h"
|
#include "LODManager.h"
|
||||||
#include "Menu.h"
|
#include "Menu.h"
|
||||||
#include "ModelUploader.h"
|
#include "ModelPackager.h"
|
||||||
#include "Util.h"
|
#include "Util.h"
|
||||||
|
|
||||||
#include "avatar/AvatarManager.h"
|
#include "avatar/AvatarManager.h"
|
||||||
|
@ -244,6 +244,7 @@ bool setupEssentials(int& argc, char** argv) {
|
||||||
auto bandwidthRecorder = DependencyManager::set<BandwidthRecorder>();
|
auto bandwidthRecorder = DependencyManager::set<BandwidthRecorder>();
|
||||||
auto resouceCacheSharedItems = DependencyManager::set<ResouceCacheSharedItems>();
|
auto resouceCacheSharedItems = DependencyManager::set<ResouceCacheSharedItems>();
|
||||||
auto entityScriptingInterface = DependencyManager::set<EntityScriptingInterface>();
|
auto entityScriptingInterface = DependencyManager::set<EntityScriptingInterface>();
|
||||||
|
auto windowScriptingInterface = DependencyManager::set<WindowScriptingInterface>();
|
||||||
#if defined(Q_OS_MAC) || defined(Q_OS_WIN)
|
#if defined(Q_OS_MAC) || defined(Q_OS_WIN)
|
||||||
auto speechRecognizer = DependencyManager::set<SpeechRecognizer>();
|
auto speechRecognizer = DependencyManager::set<SpeechRecognizer>();
|
||||||
#endif
|
#endif
|
||||||
|
@ -882,9 +883,15 @@ bool Application::event(QEvent* event) {
|
||||||
if (event->type() == QEvent::FileOpen) {
|
if (event->type() == QEvent::FileOpen) {
|
||||||
|
|
||||||
QFileOpenEvent* fileEvent = static_cast<QFileOpenEvent*>(event);
|
QFileOpenEvent* fileEvent = static_cast<QFileOpenEvent*>(event);
|
||||||
|
|
||||||
|
QUrl url = fileEvent->url();
|
||||||
|
|
||||||
if (!fileEvent->url().isEmpty()) {
|
if (!url.isEmpty()) {
|
||||||
DependencyManager::get<AddressManager>()->handleLookupString(fileEvent->url().toString());
|
if (url.scheme() == HIFI_URL_SCHEME) {
|
||||||
|
DependencyManager::get<AddressManager>()->handleLookupString(fileEvent->url().toString());
|
||||||
|
} else if (url.url().toLower().endsWith(SVO_EXTENSION)) {
|
||||||
|
emit svoImportRequested(url.url());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
@ -1451,6 +1458,10 @@ void Application::dropEvent(QDropEvent *event) {
|
||||||
if (url.url().toLower().endsWith(SNAPSHOT_EXTENSION)) {
|
if (url.url().toLower().endsWith(SNAPSHOT_EXTENSION)) {
|
||||||
snapshotPath = url.toLocalFile();
|
snapshotPath = url.toLocalFile();
|
||||||
break;
|
break;
|
||||||
|
} else if (url.url().toLower().endsWith(SVO_EXTENSION)) {
|
||||||
|
emit svoImportRequested(url.url());
|
||||||
|
event->acceptProposedAction();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3528,7 +3539,7 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
|
||||||
qScriptRegisterMetaType(scriptEngine, RayToOverlayIntersectionResultToScriptValue,
|
qScriptRegisterMetaType(scriptEngine, RayToOverlayIntersectionResultToScriptValue,
|
||||||
RayToOverlayIntersectionResultFromScriptValue);
|
RayToOverlayIntersectionResultFromScriptValue);
|
||||||
|
|
||||||
QScriptValue windowValue = scriptEngine->registerGlobalObject("Window", WindowScriptingInterface::getInstance());
|
QScriptValue windowValue = scriptEngine->registerGlobalObject("Window", DependencyManager::get<WindowScriptingInterface>().data());
|
||||||
scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter,
|
scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter,
|
||||||
LocationScriptingInterface::locationSetter, windowValue);
|
LocationScriptingInterface::locationSetter, windowValue);
|
||||||
// register `location` on the global object.
|
// register `location` on the global object.
|
||||||
|
@ -3716,20 +3727,8 @@ void Application::toggleRunningScriptsWidget() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::uploadHead() {
|
void Application::packageModel() {
|
||||||
ModelUploader::uploadHead();
|
ModelPackager::package();
|
||||||
}
|
|
||||||
|
|
||||||
void Application::uploadSkeleton() {
|
|
||||||
ModelUploader::uploadSkeleton();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Application::uploadAttachment() {
|
|
||||||
ModelUploader::uploadAttachment();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Application::uploadEntity() {
|
|
||||||
ModelUploader::uploadEntity();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::openUrl(const QUrl& url) {
|
void Application::openUrl(const QUrl& url) {
|
||||||
|
|
|
@ -94,6 +94,7 @@ static const float NODE_KILLED_GREEN = 0.0f;
|
||||||
static const float NODE_KILLED_BLUE = 0.0f;
|
static const float NODE_KILLED_BLUE = 0.0f;
|
||||||
|
|
||||||
static const QString SNAPSHOT_EXTENSION = ".jpg";
|
static const QString SNAPSHOT_EXTENSION = ".jpg";
|
||||||
|
static const QString SVO_EXTENSION = ".svo";
|
||||||
|
|
||||||
static const float BILLBOARD_FIELD_OF_VIEW = 30.0f; // degrees
|
static const float BILLBOARD_FIELD_OF_VIEW = 30.0f; // degrees
|
||||||
static const float BILLBOARD_DISTANCE = 5.56f; // meters
|
static const float BILLBOARD_DISTANCE = 5.56f; // meters
|
||||||
|
@ -315,6 +316,8 @@ signals:
|
||||||
|
|
||||||
void scriptLocationChanged(const QString& newPath);
|
void scriptLocationChanged(const QString& newPath);
|
||||||
|
|
||||||
|
void svoImportRequested(const QString& url);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void domainChanged(const QString& domainHostname);
|
void domainChanged(const QString& domainHostname);
|
||||||
void updateWindowTitle();
|
void updateWindowTitle();
|
||||||
|
@ -340,11 +343,8 @@ public slots:
|
||||||
void loadDefaultScripts();
|
void loadDefaultScripts();
|
||||||
void toggleRunningScriptsWidget();
|
void toggleRunningScriptsWidget();
|
||||||
void saveScripts();
|
void saveScripts();
|
||||||
|
|
||||||
void uploadHead();
|
void packageModel();
|
||||||
void uploadSkeleton();
|
|
||||||
void uploadAttachment();
|
|
||||||
void uploadEntity();
|
|
||||||
|
|
||||||
void openUrl(const QUrl& url);
|
void openUrl(const QUrl& url);
|
||||||
|
|
||||||
|
|
|
@ -170,7 +170,8 @@ void GLCanvas::wheelEvent(QWheelEvent* event) {
|
||||||
void GLCanvas::dragEnterEvent(QDragEnterEvent* event) {
|
void GLCanvas::dragEnterEvent(QDragEnterEvent* event) {
|
||||||
const QMimeData *mimeData = event->mimeData();
|
const QMimeData *mimeData = event->mimeData();
|
||||||
foreach (QUrl url, mimeData->urls()) {
|
foreach (QUrl url, mimeData->urls()) {
|
||||||
if (url.url().toLower().endsWith(SNAPSHOT_EXTENSION)) {
|
auto lower = url.url().toLower();
|
||||||
|
if (lower.endsWith(SNAPSHOT_EXTENSION) || lower.endsWith(SVO_EXTENSION)) {
|
||||||
event->acceptProposedAction();
|
event->acceptProposedAction();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -105,16 +105,6 @@ Menu::Menu() {
|
||||||
addActionToQMenuAndActionHash(fileMenu, MenuOption::CopyPath, 0,
|
addActionToQMenuAndActionHash(fileMenu, MenuOption::CopyPath, 0,
|
||||||
addressManager.data(), SLOT(copyPath()));
|
addressManager.data(), SLOT(copyPath()));
|
||||||
|
|
||||||
addDisabledActionAndSeparator(fileMenu, "Upload Avatar Model");
|
|
||||||
addActionToQMenuAndActionHash(fileMenu, MenuOption::UploadHead, 0,
|
|
||||||
qApp, SLOT(uploadHead()));
|
|
||||||
addActionToQMenuAndActionHash(fileMenu, MenuOption::UploadSkeleton, 0,
|
|
||||||
qApp, SLOT(uploadSkeleton()));
|
|
||||||
addActionToQMenuAndActionHash(fileMenu, MenuOption::UploadAttachment, 0,
|
|
||||||
qApp, SLOT(uploadAttachment()));
|
|
||||||
addActionToQMenuAndActionHash(fileMenu, MenuOption::UploadEntity, 0,
|
|
||||||
qApp, SLOT(uploadEntity()));
|
|
||||||
|
|
||||||
addActionToQMenuAndActionHash(fileMenu,
|
addActionToQMenuAndActionHash(fileMenu,
|
||||||
MenuOption::Quit,
|
MenuOption::Quit,
|
||||||
Qt::CTRL | Qt::Key_Q,
|
Qt::CTRL | Qt::Key_Q,
|
||||||
|
@ -180,6 +170,9 @@ Menu::Menu() {
|
||||||
Qt::Key_Apostrophe,
|
Qt::Key_Apostrophe,
|
||||||
qApp,
|
qApp,
|
||||||
SLOT(resetSensors()));
|
SLOT(resetSensors()));
|
||||||
|
|
||||||
|
addActionToQMenuAndActionHash(toolsMenu, MenuOption::PackageModel, 0,
|
||||||
|
qApp, SLOT(packageModel()));
|
||||||
|
|
||||||
QMenu* avatarMenu = addMenu("Avatar");
|
QMenu* avatarMenu = addMenu("Avatar");
|
||||||
QObject* avatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
QObject* avatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
||||||
|
|
|
@ -251,10 +251,7 @@ namespace MenuOption {
|
||||||
const QString ToolWindow = "Tool Window";
|
const QString ToolWindow = "Tool Window";
|
||||||
const QString TransmitterDrive = "Transmitter Drive";
|
const QString TransmitterDrive = "Transmitter Drive";
|
||||||
const QString TurnWithHead = "Turn using Head";
|
const QString TurnWithHead = "Turn using Head";
|
||||||
const QString UploadAttachment = "Upload Attachment Model";
|
const QString PackageModel = "Package Model";
|
||||||
const QString UploadEntity = "Upload Entity Model";
|
|
||||||
const QString UploadHead = "Upload Head Model";
|
|
||||||
const QString UploadSkeleton = "Upload Skeleton Model";
|
|
||||||
const QString UserInterface = "User Interface";
|
const QString UserInterface = "User Interface";
|
||||||
const QString Visage = "Visage";
|
const QString Visage = "Visage";
|
||||||
const QString Wireframe = "Wireframe";
|
const QString Wireframe = "Wireframe";
|
||||||
|
|
397
interface/src/ModelPackager.cpp
Normal file
397
interface/src/ModelPackager.cpp
Normal file
|
@ -0,0 +1,397 @@
|
||||||
|
//
|
||||||
|
// ModelPackager.cpp
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Created by Clement on 3/9/15.
|
||||||
|
// Copyright 2015 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 <QFile>
|
||||||
|
#include <QFileDialog>
|
||||||
|
#include <QMessageBox>
|
||||||
|
#include <QTemporaryDir>
|
||||||
|
|
||||||
|
#include "ModelSelector.h"
|
||||||
|
#include "ModelPropertiesDialog.h"
|
||||||
|
|
||||||
|
#include "ModelPackager.h"
|
||||||
|
|
||||||
|
static const int MAX_TEXTURE_SIZE = 1024;
|
||||||
|
|
||||||
|
void copyDirectoryContent(QDir& from, QDir& to) {
|
||||||
|
for (auto entry : from.entryInfoList(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot |
|
||||||
|
QDir::NoSymLinks | QDir::Readable)) {
|
||||||
|
if (entry.isDir()) {
|
||||||
|
to.mkdir(entry.fileName());
|
||||||
|
from.cd(entry.fileName());
|
||||||
|
to.cd(entry.fileName());
|
||||||
|
copyDirectoryContent(from, to);
|
||||||
|
from.cdUp();
|
||||||
|
to.cdUp();
|
||||||
|
} else { // Files
|
||||||
|
QFile file(entry.absoluteFilePath());
|
||||||
|
QString newPath = to.absolutePath() + "/" + entry.fileName();
|
||||||
|
if (to.exists(entry.fileName())) {
|
||||||
|
QFile overridenFile(newPath);
|
||||||
|
overridenFile.remove();
|
||||||
|
}
|
||||||
|
file.copy(newPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ModelPackager::package() {
|
||||||
|
ModelPackager packager;
|
||||||
|
if (!packager.selectModel()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!packager.loadModel()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!packager.editProperties()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!packager.zipModel()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ModelPackager::selectModel() {
|
||||||
|
ModelSelector selector;
|
||||||
|
if(selector.exec() == QDialog::Accepted) {
|
||||||
|
_modelFile = selector.getFileInfo();
|
||||||
|
_modelType = selector.getModelType();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ModelPackager::loadModel() {
|
||||||
|
// First we check the FST file (if any)
|
||||||
|
if (_modelFile.completeSuffix().contains("fst")) {
|
||||||
|
QFile fst(_modelFile.filePath());
|
||||||
|
if (!fst.open(QFile::ReadOnly | QFile::Text)) {
|
||||||
|
QMessageBox::warning(NULL,
|
||||||
|
QString("ModelPackager::loadModel()"),
|
||||||
|
QString("Could not open FST file %1").arg(_modelFile.filePath()),
|
||||||
|
QMessageBox::Ok);
|
||||||
|
qWarning() << QString("ModelPackager::loadModel(): Could not open FST file %1").arg(_modelFile.filePath());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
qDebug() << "Reading FST file : " << _modelFile.filePath();
|
||||||
|
_mapping = readMapping(fst.readAll());
|
||||||
|
fst.close();
|
||||||
|
|
||||||
|
_fbxInfo = QFileInfo(_modelFile.path() + "/" + _mapping.value(FILENAME_FIELD).toString());
|
||||||
|
} else {
|
||||||
|
_fbxInfo = QFileInfo(_modelFile.filePath());
|
||||||
|
}
|
||||||
|
|
||||||
|
// open the fbx file
|
||||||
|
QFile fbx(_fbxInfo.filePath());
|
||||||
|
if (!_fbxInfo.exists() || !_fbxInfo.isFile() || !fbx.open(QIODevice::ReadOnly)) {
|
||||||
|
QMessageBox::warning(NULL,
|
||||||
|
QString("ModelPackager::loadModel()"),
|
||||||
|
QString("Could not open FBX file %1").arg(_fbxInfo.filePath()),
|
||||||
|
QMessageBox::Ok);
|
||||||
|
qWarning() << QString("ModelPackager::loadModel(): Could not open FBX file %1").arg(_fbxInfo.filePath());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
qDebug() << "Reading FBX file : " << _fbxInfo.filePath();
|
||||||
|
QByteArray fbxContents = fbx.readAll();
|
||||||
|
_geometry = readFBX(fbxContents, QVariantHash());
|
||||||
|
|
||||||
|
// make sure we have some basic mappings
|
||||||
|
populateBasicMapping(_mapping, _fbxInfo.filePath(), _geometry);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ModelPackager::editProperties() {
|
||||||
|
// open the dialog to configure the rest
|
||||||
|
ModelPropertiesDialog properties(_modelType, _mapping, _modelFile.path(), _geometry);
|
||||||
|
if (properties.exec() == QDialog::Rejected) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
_mapping = properties.getMapping();
|
||||||
|
|
||||||
|
// Make sure that a mapping for the root joint has been specified
|
||||||
|
QVariantHash joints = _mapping.value(JOINT_FIELD).toHash();
|
||||||
|
if (!joints.contains("jointRoot")) {
|
||||||
|
qWarning() << QString("%1 root joint not configured for skeleton.").arg(_modelFile.fileName());
|
||||||
|
|
||||||
|
QString message = "Your did not configure a root joint for your skeleton model.\n\nThe upload will be canceled.";
|
||||||
|
QMessageBox msgBox;
|
||||||
|
msgBox.setWindowTitle("Model Upload");
|
||||||
|
msgBox.setText(message);
|
||||||
|
msgBox.setStandardButtons(QMessageBox::Ok);
|
||||||
|
msgBox.setIcon(QMessageBox::Warning);
|
||||||
|
msgBox.exec();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ModelPackager::zipModel() {
|
||||||
|
QTemporaryDir dir;
|
||||||
|
dir.setAutoRemove(true);
|
||||||
|
QDir tempDir(dir.path());
|
||||||
|
|
||||||
|
QByteArray nameField = _mapping.value(NAME_FIELD).toByteArray();
|
||||||
|
tempDir.mkpath(nameField + "/textures");
|
||||||
|
QDir fbxDir(tempDir.path() + "/" + nameField);
|
||||||
|
QDir texDir(fbxDir.path() + "/textures");
|
||||||
|
|
||||||
|
// Copy textures
|
||||||
|
listTextures();
|
||||||
|
if (!_textures.empty()) {
|
||||||
|
QByteArray texdirField = _mapping.value(TEXDIR_FIELD).toByteArray();
|
||||||
|
_texDir = _modelFile.path() + "/" + texdirField;
|
||||||
|
copyTextures(_texDir, texDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy LODs
|
||||||
|
QVariantHash lodField = _mapping.value(LOD_FIELD).toHash();
|
||||||
|
if (!lodField.empty()) {
|
||||||
|
for (auto it = lodField.constBegin(); it != lodField.constEnd(); ++it) {
|
||||||
|
QString oldPath = _modelFile.path() + "/" + it.key();
|
||||||
|
QFile lod(oldPath);
|
||||||
|
QString newPath = fbxDir.path() + "/" + QFileInfo(lod).fileName();
|
||||||
|
if (lod.exists()) {
|
||||||
|
lod.copy(newPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy FBX
|
||||||
|
QFile fbx(_fbxInfo.filePath());
|
||||||
|
QByteArray filenameField = _mapping.value(FILENAME_FIELD).toByteArray();
|
||||||
|
QString newPath = fbxDir.path() + "/" + QFileInfo(filenameField).fileName();
|
||||||
|
fbx.copy(newPath);
|
||||||
|
|
||||||
|
// Correct FST
|
||||||
|
_mapping[FILENAME_FIELD] = tempDir.relativeFilePath(newPath);
|
||||||
|
_mapping[TEXDIR_FIELD] = tempDir.relativeFilePath(texDir.path());
|
||||||
|
|
||||||
|
// Copy FST
|
||||||
|
QFile fst(tempDir.path() + "/" + nameField + ".fst");
|
||||||
|
if (fst.open(QIODevice::WriteOnly)) {
|
||||||
|
fst.write(writeMapping(_mapping));
|
||||||
|
fst.close();
|
||||||
|
} else {
|
||||||
|
qDebug() << "Couldn't write FST file" << fst.fileName();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
QString saveDirPath = QFileDialog::getExistingDirectory(nullptr, "Save Model",
|
||||||
|
"", QFileDialog::ShowDirsOnly);
|
||||||
|
if (saveDirPath.isEmpty()) {
|
||||||
|
qDebug() << "Invalid directory" << saveDirPath;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QDir saveDir(saveDirPath);
|
||||||
|
copyDirectoryContent(tempDir, saveDir);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModelPackager::populateBasicMapping(QVariantHash& mapping, QString filename, const FBXGeometry& geometry) {
|
||||||
|
if (!mapping.contains(NAME_FIELD)) {
|
||||||
|
mapping.insert(NAME_FIELD, QFileInfo(filename).baseName());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mapping.contains(FILENAME_FIELD)) {
|
||||||
|
QDir root(_modelFile.path());
|
||||||
|
mapping.insert(FILENAME_FIELD, root.relativeFilePath(filename));
|
||||||
|
}
|
||||||
|
if (!mapping.contains(TEXDIR_FIELD)) {
|
||||||
|
mapping.insert(TEXDIR_FIELD, ".");
|
||||||
|
}
|
||||||
|
|
||||||
|
// mixamo/autodesk defaults
|
||||||
|
if (!mapping.contains(SCALE_FIELD)) {
|
||||||
|
mapping.insert(SCALE_FIELD, 1.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 - in the event that a mixamo file was edited by some other tool, it's likely the applicationName will
|
||||||
|
// be rewritten, so we detect the existence of several different blendshapes which indicate we're likely a mixamo file
|
||||||
|
bool likelyMixamoFile = geometry.applicationName == "mixamo.com" ||
|
||||||
|
(geometry.blendshapeChannelNames.contains("BrowsDown_Right") &&
|
||||||
|
geometry.blendshapeChannelNames.contains("MouthOpen") &&
|
||||||
|
geometry.blendshapeChannelNames.contains("Blink_Left") &&
|
||||||
|
geometry.blendshapeChannelNames.contains("Blink_Right") &&
|
||||||
|
geometry.blendshapeChannelNames.contains("Squint_Right"));
|
||||||
|
|
||||||
|
if (!mapping.contains(BLENDSHAPE_FIELD) && likelyMixamoFile) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModelPackager::listTextures() {
|
||||||
|
_textures.clear();
|
||||||
|
foreach (FBXMesh mesh, _geometry.meshes) {
|
||||||
|
foreach (FBXMeshPart part, mesh.parts) {
|
||||||
|
if (!part.diffuseTexture.filename.isEmpty() && part.diffuseTexture.content.isEmpty() &&
|
||||||
|
!_textures.contains(part.diffuseTexture.filename)) {
|
||||||
|
_textures << part.diffuseTexture.filename;
|
||||||
|
}
|
||||||
|
if (!part.normalTexture.filename.isEmpty() && part.normalTexture.content.isEmpty() &&
|
||||||
|
!_textures.contains(part.normalTexture.filename)) {
|
||||||
|
|
||||||
|
_textures << part.normalTexture.filename;
|
||||||
|
}
|
||||||
|
if (!part.specularTexture.filename.isEmpty() && part.specularTexture.content.isEmpty() &&
|
||||||
|
!_textures.contains(part.specularTexture.filename)) {
|
||||||
|
_textures << part.specularTexture.filename;
|
||||||
|
}
|
||||||
|
if (!part.emissiveTexture.filename.isEmpty() && part.emissiveTexture.content.isEmpty() &&
|
||||||
|
!_textures.contains(part.emissiveTexture.filename)) {
|
||||||
|
_textures << part.emissiveTexture.filename;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ModelPackager::copyTextures(const QString& oldDir, const QDir& newDir) {
|
||||||
|
QString errors;
|
||||||
|
for (auto texture : _textures) {
|
||||||
|
QString oldPath = oldDir + "/" + texture;
|
||||||
|
QString newPath = newDir.path() + "/" + texture;
|
||||||
|
|
||||||
|
// Make sure path exists
|
||||||
|
if (texture.contains("/")) {
|
||||||
|
QString dirPath = newDir.relativeFilePath(QFileInfo(newPath).path());
|
||||||
|
newDir.mkpath(dirPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
QFile texFile(oldPath);
|
||||||
|
if (texFile.exists() && texFile.open(QIODevice::ReadOnly)) {
|
||||||
|
// Check if texture needs to be recoded
|
||||||
|
QFileInfo fileInfo(oldPath);
|
||||||
|
QString extension = fileInfo.suffix().toLower();
|
||||||
|
bool isJpeg = (extension == "jpg");
|
||||||
|
bool mustRecode = !(isJpeg || extension == "png");
|
||||||
|
QImage image = QImage::fromData(texFile.readAll());
|
||||||
|
|
||||||
|
// Recode texture if too big
|
||||||
|
if (image.width() > MAX_TEXTURE_SIZE || image.height() > MAX_TEXTURE_SIZE) {
|
||||||
|
image = image.scaled(MAX_TEXTURE_SIZE, MAX_TEXTURE_SIZE, Qt::KeepAspectRatio);
|
||||||
|
mustRecode = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy texture
|
||||||
|
if (mustRecode) {
|
||||||
|
QFile newTexFile(newPath);
|
||||||
|
newTexFile.open(QIODevice::WriteOnly);
|
||||||
|
image.save(&newTexFile, isJpeg ? "JPG" : "PNG");
|
||||||
|
} else {
|
||||||
|
texFile.copy(newPath);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
errors += QString("\n%1").arg(oldPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!errors.isEmpty()) {
|
||||||
|
QMessageBox::warning(nullptr, "ModelPackager::copyTextures()",
|
||||||
|
"Missing textures:" + errors);
|
||||||
|
qDebug() << "ModelPackager::copyTextures():" << errors;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
51
interface/src/ModelPackager.h
Normal file
51
interface/src/ModelPackager.h
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
//
|
||||||
|
// ModelPackager.h
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Created by Clement on 3/9/15.
|
||||||
|
// Copyright 2015 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_ModelPackager_h
|
||||||
|
#define hifi_ModelPackager_h
|
||||||
|
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QVariantHash>
|
||||||
|
|
||||||
|
#include <FBXReader.h>
|
||||||
|
|
||||||
|
#include "ui/ModelsBrowser.h"
|
||||||
|
|
||||||
|
class ModelPackager : public QObject {
|
||||||
|
public:
|
||||||
|
static bool package();
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool selectModel();
|
||||||
|
|
||||||
|
bool loadModel();
|
||||||
|
bool editProperties();
|
||||||
|
bool zipModel();
|
||||||
|
|
||||||
|
void populateBasicMapping(QVariantHash& mapping, QString filename, const FBXGeometry& geometry);
|
||||||
|
|
||||||
|
void listTextures();
|
||||||
|
bool copyTextures(const QString& oldDir, const QDir& newDir);
|
||||||
|
|
||||||
|
QFileInfo _modelFile;
|
||||||
|
QFileInfo _fbxInfo;
|
||||||
|
ModelType _modelType;
|
||||||
|
QString _texDir;
|
||||||
|
|
||||||
|
QVariantHash _mapping;
|
||||||
|
FBXGeometry _geometry;
|
||||||
|
QStringList _textures;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#endif // hifi_ModelPackager_h
|
244
interface/src/ModelPropertiesDialog.cpp
Normal file
244
interface/src/ModelPropertiesDialog.cpp
Normal file
|
@ -0,0 +1,244 @@
|
||||||
|
//
|
||||||
|
// ModelPropertiesDialog.cpp
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Created by Clement on 3/10/15.
|
||||||
|
// Copyright 2015 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 <QDialogButtonBox>
|
||||||
|
#include <QDoubleSpinBox>
|
||||||
|
#include <QFileDialog>
|
||||||
|
#include <QFormLayout>
|
||||||
|
#include <QLineEdit>
|
||||||
|
#include <QMessageBox>
|
||||||
|
#include <QPushButton>
|
||||||
|
|
||||||
|
#include <GLMHelpers.h>
|
||||||
|
|
||||||
|
#include "ModelPropertiesDialog.h"
|
||||||
|
|
||||||
|
|
||||||
|
ModelPropertiesDialog::ModelPropertiesDialog(ModelType modelType, const QVariantHash& originalMapping,
|
||||||
|
const QString& basePath, const FBXGeometry& geometry) :
|
||||||
|
_modelType(modelType),
|
||||||
|
_originalMapping(originalMapping),
|
||||||
|
_basePath(basePath),
|
||||||
|
_geometry(geometry)
|
||||||
|
{
|
||||||
|
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()));
|
||||||
|
|
||||||
|
form->addRow("Scale:", _scale = new QDoubleSpinBox());
|
||||||
|
_scale->setMaximum(FLT_MAX);
|
||||||
|
_scale->setSingleStep(0.01);
|
||||||
|
|
||||||
|
if (_modelType != ENTITY_MODEL) {
|
||||||
|
if (_modelType == ATTACHMENT_MODEL) {
|
||||||
|
QHBoxLayout* translation = new QHBoxLayout();
|
||||||
|
form->addRow("Translation:", translation);
|
||||||
|
translation->addWidget(_translationX = createTranslationBox());
|
||||||
|
translation->addWidget(_translationY = createTranslationBox());
|
||||||
|
translation->addWidget(_translationZ = createTranslationBox());
|
||||||
|
form->addRow("Pivot About Center:", _pivotAboutCenter = new QCheckBox());
|
||||||
|
form->addRow("Pivot Joint:", _pivotJoint = createJointBox());
|
||||||
|
connect(_pivotAboutCenter, SIGNAL(toggled(bool)), SLOT(updatePivotJoint()));
|
||||||
|
_pivotAboutCenter->setChecked(true);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
form->addRow("Left Eye Joint:", _leftEyeJoint = createJointBox());
|
||||||
|
form->addRow("Right Eye Joint:", _rightEyeJoint = createJointBox());
|
||||||
|
form->addRow("Neck Joint:", _neckJoint = createJointBox());
|
||||||
|
}
|
||||||
|
if (_modelType == SKELETON_MODEL) {
|
||||||
|
form->addRow("Root Joint:", _rootJoint = createJointBox());
|
||||||
|
form->addRow("Lean Joint:", _leanJoint = createJointBox());
|
||||||
|
form->addRow("Head Joint:", _headJoint = createJointBox());
|
||||||
|
form->addRow("Left Hand Joint:", _leftHandJoint = createJointBox());
|
||||||
|
form->addRow("Right Hand Joint:", _rightHandJoint = createJointBox());
|
||||||
|
|
||||||
|
form->addRow("Free Joints:", _freeJoints = new QVBoxLayout());
|
||||||
|
QPushButton* newFreeJoint = new QPushButton("New Free Joint");
|
||||||
|
_freeJoints->addWidget(newFreeJoint);
|
||||||
|
connect(newFreeJoint, SIGNAL(clicked(bool)), SLOT(createNewFreeJoint()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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());
|
||||||
|
mapping.insert(SCALE_FIELD, QString::number(_scale->value()));
|
||||||
|
|
||||||
|
// update the joint indices
|
||||||
|
QVariantHash jointIndices;
|
||||||
|
for (int i = 0; i < _geometry.joints.size(); i++) {
|
||||||
|
jointIndices.insert(_geometry.joints.at(i).name, QString::number(i));
|
||||||
|
}
|
||||||
|
mapping.insert(JOINT_INDEX_FIELD, jointIndices);
|
||||||
|
|
||||||
|
if (_modelType != ENTITY_MODEL) {
|
||||||
|
QVariantHash joints = mapping.value(JOINT_FIELD).toHash();
|
||||||
|
if (_modelType == ATTACHMENT_MODEL) {
|
||||||
|
glm::vec3 pivot;
|
||||||
|
if (_pivotAboutCenter->isChecked()) {
|
||||||
|
pivot = (_geometry.meshExtents.minimum + _geometry.meshExtents.maximum) * 0.5f;
|
||||||
|
|
||||||
|
} else if (_pivotJoint->currentIndex() != 0) {
|
||||||
|
pivot = extractTranslation(_geometry.joints.at(_pivotJoint->currentIndex() - 1).transform);
|
||||||
|
}
|
||||||
|
mapping.insert(TRANSLATION_X_FIELD, -pivot.x * _scale->value() + _translationX->value());
|
||||||
|
mapping.insert(TRANSLATION_Y_FIELD, -pivot.y * _scale->value() + _translationY->value());
|
||||||
|
mapping.insert(TRANSLATION_Z_FIELD, -pivot.z * _scale->value() + _translationZ->value());
|
||||||
|
|
||||||
|
} else {
|
||||||
|
insertJointMapping(joints, "jointEyeLeft", _leftEyeJoint->currentText());
|
||||||
|
insertJointMapping(joints, "jointEyeRight", _rightEyeJoint->currentText());
|
||||||
|
insertJointMapping(joints, "jointNeck", _neckJoint->currentText());
|
||||||
|
}
|
||||||
|
if (_modelType == SKELETON_MODEL) {
|
||||||
|
insertJointMapping(joints, "jointRoot", _rootJoint->currentText());
|
||||||
|
insertJointMapping(joints, "jointLean", _leanJoint->currentText());
|
||||||
|
insertJointMapping(joints, "jointHead", _headJoint->currentText());
|
||||||
|
insertJointMapping(joints, "jointLeftHand", _leftHandJoint->currentText());
|
||||||
|
insertJointMapping(joints, "jointRightHand", _rightHandJoint->currentText());
|
||||||
|
|
||||||
|
mapping.remove(FREE_JOINT_FIELD);
|
||||||
|
for (int i = 0; i < _freeJoints->count() - 1; i++) {
|
||||||
|
QComboBox* box = static_cast<QComboBox*>(_freeJoints->itemAt(i)->widget()->layout()->itemAt(0)->widget());
|
||||||
|
mapping.insertMulti(FREE_JOINT_FIELD, box->currentText());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mapping.insert(JOINT_FIELD, joints);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mapping;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void setJointText(QComboBox* box, const QString& text) {
|
||||||
|
box->setCurrentIndex(qMax(box->findText(text), 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModelPropertiesDialog::reset() {
|
||||||
|
_name->setText(_originalMapping.value(NAME_FIELD).toString());
|
||||||
|
_textureDirectory->setText(_originalMapping.value(TEXDIR_FIELD).toString());
|
||||||
|
_scale->setValue(_originalMapping.value(SCALE_FIELD).toDouble());
|
||||||
|
|
||||||
|
QVariantHash jointHash = _originalMapping.value(JOINT_FIELD).toHash();
|
||||||
|
|
||||||
|
if (_modelType != ENTITY_MODEL) {
|
||||||
|
if (_modelType == ATTACHMENT_MODEL) {
|
||||||
|
_translationX->setValue(_originalMapping.value(TRANSLATION_X_FIELD).toDouble());
|
||||||
|
_translationY->setValue(_originalMapping.value(TRANSLATION_Y_FIELD).toDouble());
|
||||||
|
_translationZ->setValue(_originalMapping.value(TRANSLATION_Z_FIELD).toDouble());
|
||||||
|
_pivotAboutCenter->setChecked(true);
|
||||||
|
_pivotJoint->setCurrentIndex(0);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
setJointText(_leftEyeJoint, jointHash.value("jointEyeLeft").toString());
|
||||||
|
setJointText(_rightEyeJoint, jointHash.value("jointEyeRight").toString());
|
||||||
|
setJointText(_neckJoint, jointHash.value("jointNeck").toString());
|
||||||
|
}
|
||||||
|
if (_modelType == SKELETON_MODEL) {
|
||||||
|
setJointText(_rootJoint, jointHash.value("jointRoot").toString());
|
||||||
|
setJointText(_leanJoint, jointHash.value("jointLean").toString());
|
||||||
|
setJointText(_headJoint, jointHash.value("jointHead").toString());
|
||||||
|
setJointText(_leftHandJoint, jointHash.value("jointLeftHand").toString());
|
||||||
|
setJointText(_rightHandJoint, jointHash.value("jointRightHand").toString());
|
||||||
|
|
||||||
|
while (_freeJoints->count() > 1) {
|
||||||
|
delete _freeJoints->itemAt(0)->widget();
|
||||||
|
}
|
||||||
|
foreach (const QVariant& joint, _originalMapping.values(FREE_JOINT_FIELD)) {
|
||||||
|
QString jointName = joint.toString();
|
||||||
|
if (_geometry.jointIndices.contains(jointName)) {
|
||||||
|
createNewFreeJoint(jointName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModelPropertiesDialog::chooseTextureDirectory() {
|
||||||
|
QString directory = QFileDialog::getExistingDirectory(this, "Choose Texture Directory",
|
||||||
|
_basePath + "/" + _textureDirectory->text());
|
||||||
|
if (directory.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!directory.startsWith(_basePath)) {
|
||||||
|
QMessageBox::warning(NULL, "Invalid texture directory", "Texture directory must be child of base path.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_textureDirectory->setText(directory.length() == _basePath.length() ? "." : directory.mid(_basePath.length() + 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModelPropertiesDialog::updatePivotJoint() {
|
||||||
|
_pivotJoint->setEnabled(!_pivotAboutCenter->isChecked());
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModelPropertiesDialog::createNewFreeJoint(const QString& joint) {
|
||||||
|
QWidget* freeJoint = new QWidget();
|
||||||
|
QHBoxLayout* freeJointLayout = new QHBoxLayout();
|
||||||
|
freeJointLayout->setContentsMargins(QMargins());
|
||||||
|
freeJoint->setLayout(freeJointLayout);
|
||||||
|
QComboBox* jointBox = createJointBox(false);
|
||||||
|
jointBox->setCurrentText(joint);
|
||||||
|
freeJointLayout->addWidget(jointBox, 1);
|
||||||
|
QPushButton* deleteJoint = new QPushButton("Delete");
|
||||||
|
freeJointLayout->addWidget(deleteJoint);
|
||||||
|
freeJoint->connect(deleteJoint, SIGNAL(clicked(bool)), SLOT(deleteLater()));
|
||||||
|
_freeJoints->insertWidget(_freeJoints->count() - 1, freeJoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
QComboBox* ModelPropertiesDialog::createJointBox(bool withNone) const {
|
||||||
|
QComboBox* box = new QComboBox();
|
||||||
|
if (withNone) {
|
||||||
|
box->addItem("(none)");
|
||||||
|
}
|
||||||
|
foreach (const FBXJoint& joint, _geometry.joints) {
|
||||||
|
if (joint.isSkeletonJoint || !_geometry.hasSkeletonJoints) {
|
||||||
|
box->addItem(joint.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return box;
|
||||||
|
}
|
||||||
|
|
||||||
|
QDoubleSpinBox* ModelPropertiesDialog::createTranslationBox() const {
|
||||||
|
QDoubleSpinBox* box = new QDoubleSpinBox();
|
||||||
|
const double MAX_TRANSLATION = 1000000.0;
|
||||||
|
box->setMinimum(-MAX_TRANSLATION);
|
||||||
|
box->setMaximum(MAX_TRANSLATION);
|
||||||
|
return box;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModelPropertiesDialog::insertJointMapping(QVariantHash& joints, const QString& joint, const QString& name) const {
|
||||||
|
if (_geometry.jointIndices.contains(name)) {
|
||||||
|
joints.insert(joint, name);
|
||||||
|
} else {
|
||||||
|
joints.remove(joint);
|
||||||
|
}
|
||||||
|
}
|
83
interface/src/ModelPropertiesDialog.h
Normal file
83
interface/src/ModelPropertiesDialog.h
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
//
|
||||||
|
// ModelPropertiesDialog.h
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Created by Clement on 3/10/15.
|
||||||
|
// Copyright 2015 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_ModelPropertiesDialog_h
|
||||||
|
#define hifi_ModelPropertiesDialog_h
|
||||||
|
|
||||||
|
#include <QDialog>
|
||||||
|
|
||||||
|
#include <FBXReader.h>
|
||||||
|
|
||||||
|
#include "ui/ModelsBrowser.h"
|
||||||
|
|
||||||
|
class QDoubleSpinBox;
|
||||||
|
class QComboBox;
|
||||||
|
class QCheckBox;
|
||||||
|
class QVBoxLayout;
|
||||||
|
|
||||||
|
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";
|
||||||
|
|
||||||
|
/// A dialog that allows customization of various model properties.
|
||||||
|
class ModelPropertiesDialog : public QDialog {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
ModelPropertiesDialog(ModelType modelType, const QVariantHash& originalMapping,
|
||||||
|
const QString& basePath, const FBXGeometry& geometry);
|
||||||
|
|
||||||
|
QVariantHash getMapping() const;
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void reset();
|
||||||
|
void chooseTextureDirectory();
|
||||||
|
void updatePivotJoint();
|
||||||
|
void createNewFreeJoint(const QString& joint = QString());
|
||||||
|
|
||||||
|
private:
|
||||||
|
QComboBox* createJointBox(bool withNone = true) const;
|
||||||
|
QDoubleSpinBox* createTranslationBox() const;
|
||||||
|
void insertJointMapping(QVariantHash& joints, const QString& joint, const QString& name) const;
|
||||||
|
|
||||||
|
ModelType _modelType;
|
||||||
|
QVariantHash _originalMapping;
|
||||||
|
QString _basePath;
|
||||||
|
FBXGeometry _geometry;
|
||||||
|
QLineEdit* _name = nullptr;
|
||||||
|
QPushButton* _textureDirectory = nullptr;
|
||||||
|
QDoubleSpinBox* _scale = nullptr;
|
||||||
|
QDoubleSpinBox* _translationX = nullptr;
|
||||||
|
QDoubleSpinBox* _translationY = nullptr;
|
||||||
|
QDoubleSpinBox* _translationZ = nullptr;
|
||||||
|
QCheckBox* _pivotAboutCenter = nullptr;
|
||||||
|
QComboBox* _pivotJoint = nullptr;
|
||||||
|
QComboBox* _leftEyeJoint = nullptr;
|
||||||
|
QComboBox* _rightEyeJoint = nullptr;
|
||||||
|
QComboBox* _neckJoint = nullptr;
|
||||||
|
QComboBox* _rootJoint = nullptr;
|
||||||
|
QComboBox* _leanJoint = nullptr;
|
||||||
|
QComboBox* _headJoint = nullptr;
|
||||||
|
QComboBox* _leftHandJoint = nullptr;
|
||||||
|
QComboBox* _rightHandJoint = nullptr;
|
||||||
|
QVBoxLayout* _freeJoints = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // hifi_ModelPropertiesDialog_h
|
89
interface/src/ModelSelector.cpp
Normal file
89
interface/src/ModelSelector.cpp
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
//
|
||||||
|
// ModelSelector.cpp
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Created by Clement on 3/10/15.
|
||||||
|
// Copyright 2015 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 <QDialogButtonBox>
|
||||||
|
#include <QComboBox>
|
||||||
|
#include <QFileDialog>
|
||||||
|
#include <QFormLayout>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QStandardPaths>
|
||||||
|
|
||||||
|
#include "ModelSelector.h"
|
||||||
|
|
||||||
|
static const QString AVATAR_HEAD_STRING = "Avatar Head";
|
||||||
|
static const QString AVATAR_BODY_STRING = "Avatar Body";
|
||||||
|
static const QString AVATAR_ATTACHEMENT_STRING = "Avatar Attachment";
|
||||||
|
static const QString ENTITY_MODEL_STRING = "Entity Model";
|
||||||
|
|
||||||
|
ModelSelector::ModelSelector() {
|
||||||
|
QFormLayout* form = new QFormLayout(this);
|
||||||
|
|
||||||
|
setWindowTitle("Select Model");
|
||||||
|
setLayout(form);
|
||||||
|
|
||||||
|
_browseButton = new QPushButton("Browse", this);
|
||||||
|
connect(_browseButton, &QPushButton::clicked, this, &ModelSelector::browse);
|
||||||
|
form->addRow("Model File:", _browseButton);
|
||||||
|
|
||||||
|
_modelType = new QComboBox(this);
|
||||||
|
_modelType->addItem(AVATAR_HEAD_STRING);
|
||||||
|
_modelType->addItem(AVATAR_BODY_STRING);
|
||||||
|
_modelType->addItem(AVATAR_ATTACHEMENT_STRING);
|
||||||
|
_modelType->addItem(ENTITY_MODEL_STRING);
|
||||||
|
form->addRow("Model Type:", _modelType);
|
||||||
|
|
||||||
|
QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
|
||||||
|
connect(buttons, &QDialogButtonBox::accepted, this, &ModelSelector::accept);
|
||||||
|
connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||||
|
form->addRow(buttons);
|
||||||
|
}
|
||||||
|
|
||||||
|
QFileInfo ModelSelector::getFileInfo() const {
|
||||||
|
return _modelFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
ModelType ModelSelector::getModelType() const {
|
||||||
|
QString text = _modelType->currentText();
|
||||||
|
|
||||||
|
if (text == AVATAR_HEAD_STRING) {
|
||||||
|
return HEAD_MODEL;
|
||||||
|
} else if (text == AVATAR_BODY_STRING) {
|
||||||
|
return SKELETON_MODEL;
|
||||||
|
} else if (text == AVATAR_ATTACHEMENT_STRING) {
|
||||||
|
return ATTACHMENT_MODEL;
|
||||||
|
} else if (text == ENTITY_MODEL_STRING) {
|
||||||
|
return ENTITY_MODEL;
|
||||||
|
} else {
|
||||||
|
Q_UNREACHABLE();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModelSelector::accept() {
|
||||||
|
if (!_modelFile.isFile()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QDialog::accept();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModelSelector::browse() {
|
||||||
|
static Setting::Handle<QString> lastModelBrowseLocation("LastModelBrowseLocation",
|
||||||
|
QStandardPaths::writableLocation(QStandardPaths::DownloadLocation));
|
||||||
|
QString filename = QFileDialog::getOpenFileName(NULL, "Select your model file ...",
|
||||||
|
lastModelBrowseLocation.get(),
|
||||||
|
"Model files (*.fst *.fbx)");
|
||||||
|
QFileInfo fileInfo(filename);
|
||||||
|
|
||||||
|
if (fileInfo.isFile() && fileInfo.completeSuffix().contains(QRegExp("fst|fbx|FST|FBX"))) {
|
||||||
|
_modelFile = fileInfo;
|
||||||
|
_browseButton->setText(fileInfo.fileName());
|
||||||
|
lastModelBrowseLocation.set(fileInfo.path());
|
||||||
|
}
|
||||||
|
}
|
46
interface/src/ModelSelector.h
Normal file
46
interface/src/ModelSelector.h
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
//
|
||||||
|
// ModelSelector.h
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Created by Clement on 3/10/15.
|
||||||
|
// Copyright 2015 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_ModelSelector_h
|
||||||
|
#define hifi_ModelSelector_h
|
||||||
|
|
||||||
|
#include <QDialog>
|
||||||
|
#include <QFileInfo>
|
||||||
|
|
||||||
|
#include <SettingHandle.h>
|
||||||
|
|
||||||
|
#include "ui/ModelsBrowser.h"
|
||||||
|
|
||||||
|
class QComboBox;
|
||||||
|
class QPushButton;
|
||||||
|
|
||||||
|
class ModelSelector : public QDialog {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
ModelSelector();
|
||||||
|
|
||||||
|
QFileInfo getFileInfo() const;
|
||||||
|
ModelType getModelType() const;
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
virtual void accept();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void browse();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QFileInfo _modelFile;
|
||||||
|
QPushButton* _browseButton;
|
||||||
|
QComboBox* _modelType;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // hifi_ModelSelector_h
|
|
@ -1,881 +0,0 @@
|
||||||
//
|
|
||||||
// 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 <QBuffer>
|
|
||||||
#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 <QJsonDocument>
|
|
||||||
#include <QLineEdit>
|
|
||||||
#include <QMessageBox>
|
|
||||||
#include <QProgressBar>
|
|
||||||
#include <QPushButton>
|
|
||||||
#include <QStandardPaths>
|
|
||||||
#include <QTemporaryFile>
|
|
||||||
#include <QTextStream>
|
|
||||||
#include <QThread>
|
|
||||||
#include <QVBoxLayout>
|
|
||||||
#include <QVariant>
|
|
||||||
|
|
||||||
#include <AccountManager.h>
|
|
||||||
#include <GeometryCache.h>
|
|
||||||
#include <GLMHelpers.h>
|
|
||||||
#include <ResourceCache.h>
|
|
||||||
#include <TextureCache.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 unsigned long long MAX_SIZE = 50 * 1024 * BYTES_PER_MEGABYTES; // 50 GB (Virtually remove limit)
|
|
||||||
static 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;
|
|
||||||
|
|
||||||
Setting::Handle<QString> ModelUploader::_lastModelUploadLocation("LastModelUploadLocation",
|
|
||||||
QStandardPaths::writableLocation(QStandardPaths::DownloadLocation));
|
|
||||||
|
|
||||||
void ModelUploader::uploadModel(ModelType modelType) {
|
|
||||||
ModelUploader* uploader = new ModelUploader(modelType);
|
|
||||||
QThread* thread = new QThread();
|
|
||||||
thread->setObjectName("Model Uploader");
|
|
||||||
thread->connect(uploader, SIGNAL(destroyed()), SLOT(quit()));
|
|
||||||
thread->connect(thread, SIGNAL(finished()), SLOT(deleteLater()));
|
|
||||||
uploader->connect(thread, SIGNAL(started()), SLOT(send()));
|
|
||||||
|
|
||||||
thread->start();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModelUploader::uploadHead() {
|
|
||||||
uploadModel(HEAD_MODEL);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModelUploader::uploadSkeleton() {
|
|
||||||
uploadModel(SKELETON_MODEL);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModelUploader::uploadAttachment() {
|
|
||||||
uploadModel(ATTACHMENT_MODEL);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModelUploader::uploadEntity() {
|
|
||||||
uploadModel(ENTITY_MODEL);
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
QString lastLocation = _lastModelUploadLocation.get();
|
|
||||||
|
|
||||||
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.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
_lastModelUploadLocation.set(filename);
|
|
||||||
|
|
||||||
// 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
|
|
||||||
populateBasicMapping(mapping, filename, geometry);
|
|
||||||
|
|
||||||
// open the dialog to configure the rest
|
|
||||||
ModelPropertiesDialog properties(_modelType, mapping, basePath, geometry);
|
|
||||||
if (properties.exec() == QDialog::Rejected) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
mapping = properties.getMapping();
|
|
||||||
|
|
||||||
// Make sure that a mapping for the root joint has been specified
|
|
||||||
QVariantHash joints = mapping.value(JOINT_FIELD).toHash();
|
|
||||||
if (!joints.contains("jointRoot")) {
|
|
||||||
qDebug() << QString("[Warning] %1 root joint not configured for skeleton.").arg(filename);
|
|
||||||
|
|
||||||
QString message = "Your did not configure a root joint for your skeleton model.\n\nThe upload will be canceled.";
|
|
||||||
QMessageBox msgBox;
|
|
||||||
msgBox.setWindowTitle("Model Upload");
|
|
||||||
msgBox.setText(message);
|
|
||||||
msgBox.setStandardButtons(QMessageBox::Ok);
|
|
||||||
msgBox.setIcon(QMessageBox::Warning);
|
|
||||||
msgBox.exec();
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
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::populateBasicMapping(QVariantHash& mapping, QString filename, const FBXGeometry& geometry) {
|
|
||||||
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, 1.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 - in the event that a mixamo file was edited by some other tool, it's likely the applicationName will
|
|
||||||
// be rewritten, so we detect the existence of several different blendshapes which indicate we're likely a mixamo file
|
|
||||||
bool likelyMixamoFile = geometry.applicationName == "mixamo.com" ||
|
|
||||||
(geometry.blendshapeChannelNames.contains("BrowsDown_Right") &&
|
|
||||||
geometry.blendshapeChannelNames.contains("MouthOpen") &&
|
|
||||||
geometry.blendshapeChannelNames.contains("Blink_Left") &&
|
|
||||||
geometry.blendshapeChannelNames.contains("Blink_Right") &&
|
|
||||||
geometry.blendshapeChannelNames.contains("Squint_Right"));
|
|
||||||
|
|
||||||
if (!mapping.contains(BLENDSHAPE_FIELD) && likelyMixamoFile) {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
request.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT);
|
|
||||||
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);
|
|
||||||
DependencyManager::get<GeometryCache>()->refresh(_url);
|
|
||||||
auto textureCache = DependencyManager::get<TextureCache>();
|
|
||||||
foreach (const QByteArray& filename, _textureFilenames) {
|
|
||||||
textureCache->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) {
|
|
||||||
if (!part.diffuseTexture.filename.isEmpty() && part.diffuseTexture.content.isEmpty() &&
|
|
||||||
!_textureFilenames.contains(part.diffuseTexture.filename)) {
|
|
||||||
if (!addPart(texdir + "/" + part.diffuseTexture.filename,
|
|
||||||
QString("texture%1").arg(++_texturesCount), true)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
_textureFilenames.insert(part.diffuseTexture.filename);
|
|
||||||
}
|
|
||||||
if (!part.normalTexture.filename.isEmpty() && part.normalTexture.content.isEmpty() &&
|
|
||||||
!_textureFilenames.contains(part.normalTexture.filename)) {
|
|
||||||
if (!addPart(texdir + "/" + part.normalTexture.filename,
|
|
||||||
QString("texture%1").arg(++_texturesCount), true)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
_textureFilenames.insert(part.normalTexture.filename);
|
|
||||||
}
|
|
||||||
if (!part.specularTexture.filename.isEmpty() && part.specularTexture.content.isEmpty() &&
|
|
||||||
!_textureFilenames.contains(part.specularTexture.filename)) {
|
|
||||||
if (!addPart(texdir + "/" + part.specularTexture.filename,
|
|
||||||
QString("texture%1").arg(++_texturesCount), true)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
_textureFilenames.insert(part.specularTexture.filename);
|
|
||||||
}
|
|
||||||
if (!part.emissiveTexture.filename.isEmpty() && part.emissiveTexture.content.isEmpty() &&
|
|
||||||
!_textureFilenames.contains(part.emissiveTexture.filename)) {
|
|
||||||
if (!addPart(texdir + "/" + part.emissiveTexture.filename,
|
|
||||||
QString("texture%1").arg(++_texturesCount), true)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
_textureFilenames.insert(part.emissiveTexture.filename);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ModelUploader::addPart(const QString &path, const QString& name, bool isTexture) {
|
|
||||||
QFile file(path);
|
|
||||||
if (!file.open(QIODevice::ReadOnly)) {
|
|
||||||
QMessageBox::warning(NULL,
|
|
||||||
QString("ModelUploader::addPart()"),
|
|
||||||
QString("Could not open %1").arg(path),
|
|
||||||
QMessageBox::Ok);
|
|
||||||
qDebug() << "[Warning] " << QString("Could not open %1").arg(path);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return addPart(file, file.readAll(), name, isTexture);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ModelUploader::addPart(const QFile& file, const QByteArray& contents, const QString& name, bool isTexture) {
|
|
||||||
QFileInfo fileInfo(file);
|
|
||||||
QByteArray recodedContents = contents;
|
|
||||||
if (isTexture) {
|
|
||||||
QString extension = fileInfo.suffix().toLower();
|
|
||||||
bool isJpeg = (extension == "jpg");
|
|
||||||
bool mustRecode = !(isJpeg || extension == "png");
|
|
||||||
QImage image = QImage::fromData(contents);
|
|
||||||
if (image.width() > MAX_TEXTURE_SIZE || image.height() > MAX_TEXTURE_SIZE) {
|
|
||||||
image = image.scaled(MAX_TEXTURE_SIZE, MAX_TEXTURE_SIZE, Qt::KeepAspectRatio);
|
|
||||||
mustRecode = true;
|
|
||||||
}
|
|
||||||
if (mustRecode) {
|
|
||||||
QBuffer buffer;
|
|
||||||
buffer.open(QIODevice::WriteOnly);
|
|
||||||
image.save(&buffer, isJpeg ? "JPG" : "PNG");
|
|
||||||
recodedContents = buffer.data();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
QByteArray buffer = qCompress(recodedContents);
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
buffer.remove(QCOMPRESS_HEADER_POSITION, QCOMPRESS_HEADER_SIZE);
|
|
||||||
|
|
||||||
QHttpPart part;
|
|
||||||
part.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data;"
|
|
||||||
" name=\"" + name.toUtf8() + "\";"
|
|
||||||
" filename=\"" + QFileInfo(file).fileName().toUtf8() + "\""));
|
|
||||||
part.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/octet-stream"));
|
|
||||||
part.setBody(buffer);
|
|
||||||
_dataMultiPart->append(part);
|
|
||||||
|
|
||||||
|
|
||||||
qDebug() << "File " << QFileInfo(file).fileName() << " added to model.";
|
|
||||||
_totalSize += recodedContents.size();
|
|
||||||
if (_totalSize > MAX_SIZE) {
|
|
||||||
QMessageBox::warning(NULL,
|
|
||||||
QString("ModelUploader::zip()"),
|
|
||||||
QString("Model too big, over %1 MB.").arg(MAX_SIZE / BYTES_PER_MEGABYTES),
|
|
||||||
QMessageBox::Ok);
|
|
||||||
qDebug() << "[Warning] " << QString("Model too big, over %1 MB.").arg(MAX_SIZE / BYTES_PER_MEGABYTES);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
qDebug() << "Current model size: " << _totalSize;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static QDoubleSpinBox* createTranslationBox() {
|
|
||||||
QDoubleSpinBox* box = new QDoubleSpinBox();
|
|
||||||
const double MAX_TRANSLATION = 1000000.0;
|
|
||||||
box->setMinimum(-MAX_TRANSLATION);
|
|
||||||
box->setMaximum(MAX_TRANSLATION);
|
|
||||||
return box;
|
|
||||||
}
|
|
||||||
|
|
||||||
ModelPropertiesDialog::ModelPropertiesDialog(ModelType modelType, const QVariantHash& originalMapping,
|
|
||||||
const QString& basePath, const FBXGeometry& geometry) :
|
|
||||||
_modelType(modelType),
|
|
||||||
_originalMapping(originalMapping),
|
|
||||||
_basePath(basePath),
|
|
||||||
_geometry(geometry)
|
|
||||||
{
|
|
||||||
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()));
|
|
||||||
|
|
||||||
form->addRow("Scale:", _scale = new QDoubleSpinBox());
|
|
||||||
_scale->setMaximum(FLT_MAX);
|
|
||||||
_scale->setSingleStep(0.01);
|
|
||||||
|
|
||||||
if (_modelType != ENTITY_MODEL) {
|
|
||||||
if (_modelType == ATTACHMENT_MODEL) {
|
|
||||||
QHBoxLayout* translation = new QHBoxLayout();
|
|
||||||
form->addRow("Translation:", translation);
|
|
||||||
translation->addWidget(_translationX = createTranslationBox());
|
|
||||||
translation->addWidget(_translationY = createTranslationBox());
|
|
||||||
translation->addWidget(_translationZ = createTranslationBox());
|
|
||||||
form->addRow("Pivot About Center:", _pivotAboutCenter = new QCheckBox());
|
|
||||||
form->addRow("Pivot Joint:", _pivotJoint = createJointBox());
|
|
||||||
connect(_pivotAboutCenter, SIGNAL(toggled(bool)), SLOT(updatePivotJoint()));
|
|
||||||
_pivotAboutCenter->setChecked(true);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
form->addRow("Left Eye Joint:", _leftEyeJoint = createJointBox());
|
|
||||||
form->addRow("Right Eye Joint:", _rightEyeJoint = createJointBox());
|
|
||||||
form->addRow("Neck Joint:", _neckJoint = createJointBox());
|
|
||||||
}
|
|
||||||
if (_modelType == SKELETON_MODEL) {
|
|
||||||
form->addRow("Root Joint:", _rootJoint = createJointBox());
|
|
||||||
form->addRow("Lean Joint:", _leanJoint = createJointBox());
|
|
||||||
form->addRow("Head Joint:", _headJoint = createJointBox());
|
|
||||||
form->addRow("Left Hand Joint:", _leftHandJoint = createJointBox());
|
|
||||||
form->addRow("Right Hand Joint:", _rightHandJoint = createJointBox());
|
|
||||||
|
|
||||||
form->addRow("Free Joints:", _freeJoints = new QVBoxLayout());
|
|
||||||
QPushButton* newFreeJoint = new QPushButton("New Free Joint");
|
|
||||||
_freeJoints->addWidget(newFreeJoint);
|
|
||||||
connect(newFreeJoint, SIGNAL(clicked(bool)), SLOT(createNewFreeJoint()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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());
|
|
||||||
mapping.insert(SCALE_FIELD, QString::number(_scale->value()));
|
|
||||||
|
|
||||||
// update the joint indices
|
|
||||||
QVariantHash jointIndices;
|
|
||||||
for (int i = 0; i < _geometry.joints.size(); i++) {
|
|
||||||
jointIndices.insert(_geometry.joints.at(i).name, QString::number(i));
|
|
||||||
}
|
|
||||||
mapping.insert(JOINT_INDEX_FIELD, jointIndices);
|
|
||||||
|
|
||||||
if (_modelType != ENTITY_MODEL) {
|
|
||||||
QVariantHash joints = mapping.value(JOINT_FIELD).toHash();
|
|
||||||
if (_modelType == ATTACHMENT_MODEL) {
|
|
||||||
glm::vec3 pivot;
|
|
||||||
if (_pivotAboutCenter->isChecked()) {
|
|
||||||
pivot = (_geometry.meshExtents.minimum + _geometry.meshExtents.maximum) * 0.5f;
|
|
||||||
|
|
||||||
} else if (_pivotJoint->currentIndex() != 0) {
|
|
||||||
pivot = extractTranslation(_geometry.joints.at(_pivotJoint->currentIndex() - 1).transform);
|
|
||||||
}
|
|
||||||
mapping.insert(TRANSLATION_X_FIELD, -pivot.x * _scale->value() + _translationX->value());
|
|
||||||
mapping.insert(TRANSLATION_Y_FIELD, -pivot.y * _scale->value() + _translationY->value());
|
|
||||||
mapping.insert(TRANSLATION_Z_FIELD, -pivot.z * _scale->value() + _translationZ->value());
|
|
||||||
|
|
||||||
} else {
|
|
||||||
insertJointMapping(joints, "jointEyeLeft", _leftEyeJoint->currentText());
|
|
||||||
insertJointMapping(joints, "jointEyeRight", _rightEyeJoint->currentText());
|
|
||||||
insertJointMapping(joints, "jointNeck", _neckJoint->currentText());
|
|
||||||
}
|
|
||||||
if (_modelType == SKELETON_MODEL) {
|
|
||||||
insertJointMapping(joints, "jointRoot", _rootJoint->currentText());
|
|
||||||
insertJointMapping(joints, "jointLean", _leanJoint->currentText());
|
|
||||||
insertJointMapping(joints, "jointHead", _headJoint->currentText());
|
|
||||||
insertJointMapping(joints, "jointLeftHand", _leftHandJoint->currentText());
|
|
||||||
insertJointMapping(joints, "jointRightHand", _rightHandJoint->currentText());
|
|
||||||
|
|
||||||
mapping.remove(FREE_JOINT_FIELD);
|
|
||||||
for (int i = 0; i < _freeJoints->count() - 1; i++) {
|
|
||||||
QComboBox* box = static_cast<QComboBox*>(_freeJoints->itemAt(i)->widget()->layout()->itemAt(0)->widget());
|
|
||||||
mapping.insertMulti(FREE_JOINT_FIELD, box->currentText());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mapping.insert(JOINT_FIELD, joints);
|
|
||||||
}
|
|
||||||
|
|
||||||
return mapping;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void setJointText(QComboBox* box, const QString& text) {
|
|
||||||
box->setCurrentIndex(qMax(box->findText(text), 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModelPropertiesDialog::reset() {
|
|
||||||
_name->setText(_originalMapping.value(NAME_FIELD).toString());
|
|
||||||
_textureDirectory->setText(_originalMapping.value(TEXDIR_FIELD).toString());
|
|
||||||
_scale->setValue(_originalMapping.value(SCALE_FIELD).toDouble());
|
|
||||||
|
|
||||||
QVariantHash jointHash = _originalMapping.value(JOINT_FIELD).toHash();
|
|
||||||
|
|
||||||
if (_modelType != ENTITY_MODEL) {
|
|
||||||
if (_modelType == ATTACHMENT_MODEL) {
|
|
||||||
_translationX->setValue(_originalMapping.value(TRANSLATION_X_FIELD).toDouble());
|
|
||||||
_translationY->setValue(_originalMapping.value(TRANSLATION_Y_FIELD).toDouble());
|
|
||||||
_translationZ->setValue(_originalMapping.value(TRANSLATION_Z_FIELD).toDouble());
|
|
||||||
_pivotAboutCenter->setChecked(true);
|
|
||||||
_pivotJoint->setCurrentIndex(0);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
setJointText(_leftEyeJoint, jointHash.value("jointEyeLeft").toString());
|
|
||||||
setJointText(_rightEyeJoint, jointHash.value("jointEyeRight").toString());
|
|
||||||
setJointText(_neckJoint, jointHash.value("jointNeck").toString());
|
|
||||||
}
|
|
||||||
if (_modelType == SKELETON_MODEL) {
|
|
||||||
setJointText(_rootJoint, jointHash.value("jointRoot").toString());
|
|
||||||
setJointText(_leanJoint, jointHash.value("jointLean").toString());
|
|
||||||
setJointText(_headJoint, jointHash.value("jointHead").toString());
|
|
||||||
setJointText(_leftHandJoint, jointHash.value("jointLeftHand").toString());
|
|
||||||
setJointText(_rightHandJoint, jointHash.value("jointRightHand").toString());
|
|
||||||
|
|
||||||
while (_freeJoints->count() > 1) {
|
|
||||||
delete _freeJoints->itemAt(0)->widget();
|
|
||||||
}
|
|
||||||
foreach (const QVariant& joint, _originalMapping.values(FREE_JOINT_FIELD)) {
|
|
||||||
QString jointName = joint.toString();
|
|
||||||
if (_geometry.jointIndices.contains(jointName)) {
|
|
||||||
createNewFreeJoint(jointName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModelPropertiesDialog::chooseTextureDirectory() {
|
|
||||||
QString directory = QFileDialog::getExistingDirectory(this, "Choose Texture Directory",
|
|
||||||
_basePath + "/" + _textureDirectory->text());
|
|
||||||
if (directory.isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!directory.startsWith(_basePath)) {
|
|
||||||
QMessageBox::warning(NULL, "Invalid texture directory", "Texture directory must be child of base path.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_textureDirectory->setText(directory.length() == _basePath.length() ? "." : directory.mid(_basePath.length() + 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModelPropertiesDialog::updatePivotJoint() {
|
|
||||||
_pivotJoint->setEnabled(!_pivotAboutCenter->isChecked());
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModelPropertiesDialog::createNewFreeJoint(const QString& joint) {
|
|
||||||
QWidget* freeJoint = new QWidget();
|
|
||||||
QHBoxLayout* freeJointLayout = new QHBoxLayout();
|
|
||||||
freeJointLayout->setContentsMargins(QMargins());
|
|
||||||
freeJoint->setLayout(freeJointLayout);
|
|
||||||
QComboBox* jointBox = createJointBox(false);
|
|
||||||
jointBox->setCurrentText(joint);
|
|
||||||
freeJointLayout->addWidget(jointBox, 1);
|
|
||||||
QPushButton* deleteJoint = new QPushButton("Delete");
|
|
||||||
freeJointLayout->addWidget(deleteJoint);
|
|
||||||
freeJoint->connect(deleteJoint, SIGNAL(clicked(bool)), SLOT(deleteLater()));
|
|
||||||
_freeJoints->insertWidget(_freeJoints->count() - 1, freeJoint);
|
|
||||||
}
|
|
||||||
|
|
||||||
QComboBox* ModelPropertiesDialog::createJointBox(bool withNone) const {
|
|
||||||
QComboBox* box = new QComboBox();
|
|
||||||
if (withNone) {
|
|
||||||
box->addItem("(none)");
|
|
||||||
}
|
|
||||||
foreach (const FBXJoint& joint, _geometry.joints) {
|
|
||||||
if (joint.isSkeletonJoint || !_geometry.hasSkeletonJoints) {
|
|
||||||
box->addItem(joint.name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return box;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ModelPropertiesDialog::insertJointMapping(QVariantHash& joints, const QString& joint, const QString& name) const {
|
|
||||||
if (_geometry.jointIndices.contains(name)) {
|
|
||||||
joints.insert(joint, name);
|
|
||||||
} else {
|
|
||||||
joints.remove(joint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,126 +0,0 @@
|
||||||
//
|
|
||||||
// ModelUploader.h
|
|
||||||
// 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
|
|
||||||
//
|
|
||||||
|
|
||||||
#ifndef hifi_ModelUploader_h
|
|
||||||
#define hifi_ModelUploader_h
|
|
||||||
|
|
||||||
#include <QDialog>
|
|
||||||
#include <QTimer>
|
|
||||||
|
|
||||||
#include <FBXReader.h>
|
|
||||||
#include <SettingHandle.h>
|
|
||||||
|
|
||||||
#include "ui/ModelsBrowser.h"
|
|
||||||
|
|
||||||
class QCheckBox;
|
|
||||||
class QComboBox;
|
|
||||||
class QDoubleSpinBox;
|
|
||||||
class QFileInfo;
|
|
||||||
class QHttpMultiPart;
|
|
||||||
class QLineEdit;
|
|
||||||
class QProgressBar;
|
|
||||||
class QPushButton;
|
|
||||||
class QVBoxLayout;
|
|
||||||
|
|
||||||
class ModelUploader : public QObject {
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
static void uploadModel(ModelType modelType);
|
|
||||||
|
|
||||||
static void uploadHead();
|
|
||||||
static void uploadSkeleton();
|
|
||||||
static void uploadAttachment();
|
|
||||||
static void uploadEntity();
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void send();
|
|
||||||
void checkJSON(QNetworkReply& requestReply);
|
|
||||||
void uploadUpdate(qint64 bytesSent, qint64 bytesTotal);
|
|
||||||
void uploadSuccess(QNetworkReply& requestReply);
|
|
||||||
void uploadFailed(QNetworkReply& errorReply);
|
|
||||||
void checkS3();
|
|
||||||
void processCheck();
|
|
||||||
|
|
||||||
private:
|
|
||||||
ModelUploader(ModelType type);
|
|
||||||
~ModelUploader();
|
|
||||||
|
|
||||||
void populateBasicMapping(QVariantHash& mapping, QString filename, const FBXGeometry& geometry);
|
|
||||||
bool zip();
|
|
||||||
bool addTextures(const QString& texdir, const FBXGeometry& geometry);
|
|
||||||
bool addPart(const QString& path, const QString& name, bool isTexture = false);
|
|
||||||
bool addPart(const QFile& file, const QByteArray& contents, const QString& name, bool isTexture = false);
|
|
||||||
|
|
||||||
QString _url;
|
|
||||||
QString _textureBase;
|
|
||||||
QSet<QByteArray> _textureFilenames;
|
|
||||||
int _lodCount;
|
|
||||||
int _texturesCount;
|
|
||||||
unsigned long _totalSize;
|
|
||||||
ModelType _modelType;
|
|
||||||
bool _readyToSend;
|
|
||||||
|
|
||||||
QHttpMultiPart* _dataMultiPart = nullptr;
|
|
||||||
|
|
||||||
int _numberOfChecks;
|
|
||||||
QTimer _timer;
|
|
||||||
|
|
||||||
QDialog* _progressDialog = nullptr;
|
|
||||||
QProgressBar* _progressBar = nullptr;
|
|
||||||
|
|
||||||
static Setting::Handle<QString> _lastModelUploadLocation;
|
|
||||||
};
|
|
||||||
|
|
||||||
/// A dialog that allows customization of various model properties.
|
|
||||||
class ModelPropertiesDialog : public QDialog {
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
ModelPropertiesDialog(ModelType modelType, const QVariantHash& originalMapping,
|
|
||||||
const QString& basePath, const FBXGeometry& geometry);
|
|
||||||
|
|
||||||
QVariantHash getMapping() const;
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void reset();
|
|
||||||
void chooseTextureDirectory();
|
|
||||||
void updatePivotJoint();
|
|
||||||
void createNewFreeJoint(const QString& joint = QString());
|
|
||||||
|
|
||||||
private:
|
|
||||||
QComboBox* createJointBox(bool withNone = true) const;
|
|
||||||
void insertJointMapping(QVariantHash& joints, const QString& joint, const QString& name) const;
|
|
||||||
|
|
||||||
ModelType _modelType;
|
|
||||||
QVariantHash _originalMapping;
|
|
||||||
QString _basePath;
|
|
||||||
FBXGeometry _geometry;
|
|
||||||
QLineEdit* _name = nullptr;
|
|
||||||
QPushButton* _textureDirectory = nullptr;
|
|
||||||
QDoubleSpinBox* _scale = nullptr;
|
|
||||||
QDoubleSpinBox* _translationX = nullptr;
|
|
||||||
QDoubleSpinBox* _translationY = nullptr;
|
|
||||||
QDoubleSpinBox* _translationZ = nullptr;
|
|
||||||
QCheckBox* _pivotAboutCenter = nullptr;
|
|
||||||
QComboBox* _pivotJoint = nullptr;
|
|
||||||
QComboBox* _leftEyeJoint = nullptr;
|
|
||||||
QComboBox* _rightEyeJoint = nullptr;
|
|
||||||
QComboBox* _neckJoint = nullptr;
|
|
||||||
QComboBox* _rootJoint = nullptr;
|
|
||||||
QComboBox* _leanJoint = nullptr;
|
|
||||||
QComboBox* _headJoint = nullptr;
|
|
||||||
QComboBox* _leftHandJoint = nullptr;
|
|
||||||
QComboBox* _rightHandJoint = nullptr;
|
|
||||||
QVBoxLayout* _freeJoints = nullptr;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // hifi_ModelUploader_h
|
|
|
@ -73,7 +73,7 @@ void WebWindowClass::setVisible(bool visible) {
|
||||||
QScriptValue WebWindowClass::constructor(QScriptContext* context, QScriptEngine* engine) {
|
QScriptValue WebWindowClass::constructor(QScriptContext* context, QScriptEngine* engine) {
|
||||||
WebWindowClass* retVal;
|
WebWindowClass* retVal;
|
||||||
QString file = context->argument(0).toString();
|
QString file = context->argument(0).toString();
|
||||||
QMetaObject::invokeMethod(WindowScriptingInterface::getInstance(), "doCreateWebWindow", Qt::BlockingQueuedConnection,
|
QMetaObject::invokeMethod(DependencyManager::get<WindowScriptingInterface>().data(), "doCreateWebWindow", Qt::BlockingQueuedConnection,
|
||||||
Q_RETURN_ARG(WebWindowClass*, retVal),
|
Q_RETURN_ARG(WebWindowClass*, retVal),
|
||||||
Q_ARG(const QString&, file),
|
Q_ARG(const QString&, file),
|
||||||
Q_ARG(QString, context->argument(1).toString()),
|
Q_ARG(QString, context->argument(1).toString()),
|
||||||
|
|
|
@ -25,11 +25,6 @@
|
||||||
|
|
||||||
#include "WindowScriptingInterface.h"
|
#include "WindowScriptingInterface.h"
|
||||||
|
|
||||||
WindowScriptingInterface* WindowScriptingInterface::getInstance() {
|
|
||||||
static WindowScriptingInterface sharedInstance;
|
|
||||||
return &sharedInstance;
|
|
||||||
}
|
|
||||||
|
|
||||||
WindowScriptingInterface::WindowScriptingInterface() :
|
WindowScriptingInterface::WindowScriptingInterface() :
|
||||||
_editDialog(NULL),
|
_editDialog(NULL),
|
||||||
_nonBlockingFormActive(false),
|
_nonBlockingFormActive(false),
|
||||||
|
@ -37,6 +32,7 @@ WindowScriptingInterface::WindowScriptingInterface() :
|
||||||
{
|
{
|
||||||
const DomainHandler& domainHandler = DependencyManager::get<NodeList>()->getDomainHandler();
|
const DomainHandler& domainHandler = DependencyManager::get<NodeList>()->getDomainHandler();
|
||||||
connect(&domainHandler, &DomainHandler::hostnameChanged, this, &WindowScriptingInterface::domainChanged);
|
connect(&domainHandler, &DomainHandler::hostnameChanged, this, &WindowScriptingInterface::domainChanged);
|
||||||
|
connect(Application::getInstance(), &Application::svoImportRequested, this, &WindowScriptingInterface::svoImportRequested);
|
||||||
}
|
}
|
||||||
|
|
||||||
WebWindowClass* WindowScriptingInterface::doCreateWebWindow(const QString& title, const QString& url, int width, int height) {
|
WebWindowClass* WindowScriptingInterface::doCreateWebWindow(const QString& title, const QString& url, int width, int height) {
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
|
|
||||||
#include "WebWindowClass.h"
|
#include "WebWindowClass.h"
|
||||||
|
|
||||||
class WindowScriptingInterface : public QObject {
|
class WindowScriptingInterface : public QObject, public Dependency {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_PROPERTY(int innerWidth READ getInnerWidth)
|
Q_PROPERTY(int innerWidth READ getInnerWidth)
|
||||||
Q_PROPERTY(int innerHeight READ getInnerHeight)
|
Q_PROPERTY(int innerHeight READ getInnerHeight)
|
||||||
|
@ -29,7 +29,7 @@ class WindowScriptingInterface : public QObject {
|
||||||
Q_PROPERTY(int y READ getY)
|
Q_PROPERTY(int y READ getY)
|
||||||
Q_PROPERTY(bool cursorVisible READ isCursorVisible WRITE setCursorVisible)
|
Q_PROPERTY(bool cursorVisible READ isCursorVisible WRITE setCursorVisible)
|
||||||
public:
|
public:
|
||||||
static WindowScriptingInterface* getInstance();
|
WindowScriptingInterface();
|
||||||
int getInnerWidth();
|
int getInnerWidth();
|
||||||
int getInnerHeight();
|
int getInnerHeight();
|
||||||
int getX();
|
int getX();
|
||||||
|
@ -60,6 +60,7 @@ signals:
|
||||||
void domainChanged(const QString& domainHostname);
|
void domainChanged(const QString& domainHostname);
|
||||||
void inlineButtonClicked(const QString& name);
|
void inlineButtonClicked(const QString& name);
|
||||||
void nonBlockingFormClosed();
|
void nonBlockingFormClosed();
|
||||||
|
void svoImportRequested(const QString& url);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
QScriptValue showAlert(const QString& message);
|
QScriptValue showAlert(const QString& message);
|
||||||
|
@ -85,7 +86,6 @@ private slots:
|
||||||
WebWindowClass* doCreateWebWindow(const QString& title, const QString& url, int width, int height);
|
WebWindowClass* doCreateWebWindow(const QString& title, const QString& url, int width, int height);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
WindowScriptingInterface();
|
|
||||||
QString jsRegExp2QtRegExp(QString string);
|
QString jsRegExp2QtRegExp(QString string);
|
||||||
QDialog* createForm(const QString& title, QScriptValue form);
|
QDialog* createForm(const QString& title, QScriptValue form);
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
#include <QStandardItemModel>
|
#include <QStandardItemModel>
|
||||||
#include <QTreeView>
|
#include <QTreeView>
|
||||||
|
|
||||||
|
class QNetworkReply;
|
||||||
|
|
||||||
enum ModelType {
|
enum ModelType {
|
||||||
ENTITY_MODEL,
|
ENTITY_MODEL,
|
||||||
HEAD_MODEL,
|
HEAD_MODEL,
|
||||||
|
|
Loading…
Reference in a new issue