From 556a55ff160bc936ef2db2fc58edc9f9bf2b7bbc Mon Sep 17 00:00:00 2001 From: Thijs Wenker Date: Fri, 15 Feb 2019 20:55:27 +0100 Subject: [PATCH] Better scale and texture checks --- .../avatarPackager/AvatarDoctorDiagnose.qml | 64 +++++---- .../avatarPackager/CreateAvatarProject.qml | 2 +- interface/src/avatar/AvatarDoctor.cpp | 134 +++++++++++++++--- interface/src/avatar/AvatarDoctor.h | 5 + interface/src/avatar/AvatarProject.cpp | 4 +- 5 files changed, 151 insertions(+), 58 deletions(-) diff --git a/interface/resources/qml/hifi/avatarPackager/AvatarDoctorDiagnose.qml b/interface/resources/qml/hifi/avatarPackager/AvatarDoctorDiagnose.qml index d329b903bd..302930dee0 100644 --- a/interface/resources/qml/hifi/avatarPackager/AvatarDoctorDiagnose.qml +++ b/interface/resources/qml/hifi/avatarPackager/AvatarDoctorDiagnose.qml @@ -4,39 +4,59 @@ import "../../controlsUit" 1.0 as HifiControls import "../../stylesUit" 1.0 Item { - id: diagnosingScreen + id: root visible: false property var avatarDoctor: null property var errors: [] + property int minimumDiagnoseTimeMS: 1000 + signal doneDiagnosing onVisibleChanged: { - if (!diagnosingScreen.visible) { - //if (debugDelay.running) { - // debugDelay.stop(); - //} + if (root.avatarDoctor !== null) { + root.avatarDoctor.complete.disconnect(_private.avatarDoctorComplete); + root.avatarDoctor = null; + } + if (doneTimer.running) { + doneTimer.stop(); + } + + if (!root.visible) { return; } - //debugDelay.start(); - avatarDoctor = AvatarPackagerCore.currentAvatarProject.diagnose(); - avatarDoctor.complete.connect(function(errors) { + + root.avatarDoctor = AvatarPackagerCore.currentAvatarProject.diagnose(); + root.avatarDoctor.complete.connect(this, _private.avatarDoctorComplete); + _private.startTime = Date.now(); + root.avatarDoctor.startDiagnosing(); + } + + QtObject { + id: _private + property real startTime: 0 + + function avatarDoctorComplete(errors) { + if (!root.visible) { + return; + } + console.warn("avatarDoctor.complete " + JSON.stringify(errors)); - diagnosingScreen.errors = errors; + root.errors = errors; AvatarPackagerCore.currentAvatarProject.hasErrors = errors.length > 0; AvatarPackagerCore.addCurrentProjectToRecentProjects(); - // FIXME: can't seem to change state here so do it with a timer instead + let timeSpendDiagnosingMS = Date.now() - _private.startTime; + let timeLeftMS = root.minimumDiagnoseTimeMS - timeSpendDiagnosingMS; + doneTimer.interval = timeLeftMS < 0 ? 0 : timeLeftMS; doneTimer.start(); - }); - avatarDoctor.startDiagnosing(); + } } Timer { id: doneTimer - interval: 1 repeat: false running: false onTriggered: { @@ -44,24 +64,6 @@ Item { } } -/* - Timer { - id: debugDelay - interval: 5000 - repeat: false - running: false - onTriggered: { - if (Math.random() > 0.5) { - // ERROR - avatarPackager.state = AvatarPackagerState.avatarDoctorErrorReport; - } else { - // SUCCESS - avatarPackager.state = AvatarPackagerState.project; - } - } - } -*/ - property var footer: Item { anchors.fill: parent anchors.rightMargin: 17 diff --git a/interface/resources/qml/hifi/avatarPackager/CreateAvatarProject.qml b/interface/resources/qml/hifi/avatarPackager/CreateAvatarProject.qml index c299417c27..a0149b118f 100644 --- a/interface/resources/qml/hifi/avatarPackager/CreateAvatarProject.qml +++ b/interface/resources/qml/hifi/avatarPackager/CreateAvatarProject.qml @@ -32,7 +32,7 @@ Item { return; } avatarProject.reset(); - avatarPackager.state = AvatarPackagerState.project; + avatarPackager.state = AvatarPackagerState.avatarDoctorDiagnose; } } } diff --git a/interface/src/avatar/AvatarDoctor.cpp b/interface/src/avatar/AvatarDoctor.cpp index d2397ed21f..c8f5d52336 100644 --- a/interface/src/avatar/AvatarDoctor.cpp +++ b/interface/src/avatar/AvatarDoctor.cpp @@ -11,6 +11,8 @@ #include "AvatarDoctor.h" #include +#include +#include AvatarDoctor::AvatarDoctor(QUrl avatarFSTFileUrl) : _avatarFSTFileUrl(std::move(avatarFSTFileUrl)) { @@ -18,63 +20,149 @@ AvatarDoctor::AvatarDoctor(QUrl avatarFSTFileUrl) : void AvatarDoctor::startDiagnosing() { _errors.clear(); + + _externalTextureCount = 0; + _checkedTextureCount = 0; + _missingTextureCount = 0; + _unsupportedTextureCount = 0; + const auto resource = DependencyManager::get()->getGeometryResource(_avatarFSTFileUrl); - const auto resourceLoaded = [this, resource](bool success) { + resource->refresh(); + const QUrl DEFAULT_URL = QUrl("https://docs.highfidelity.com/create/avatars/create-avatars.html#create-your-own-avatar"); + const auto resourceLoaded = [this, resource, DEFAULT_URL](bool success) { // MODEL if (!success) { - _errors.push_back({ "Model file cannot be opened", QUrl("http://www.highfidelity.com/docs") }); + _errors.push_back({ "Model file cannot be opened", DEFAULT_URL }); emit complete(getErrors()); return; } + const auto model = resource.data(); const auto avatarModel = resource.data()->getHFMModel(); if (!avatarModel.originalURL.endsWith(".fbx")) { - _errors.push_back({ "Unsupported avatar model format", QUrl("http://www.highfidelity.com/docs") }); + _errors.push_back({ "Unsupported avatar model format", DEFAULT_URL }); emit complete(getErrors()); return; } // RIG if (avatarModel.joints.isEmpty()) { - _errors.push_back({ "Avatar has no rig", QUrl("http://www.highfidelity.com/docs") }); + _errors.push_back({ "Avatar has no rig", DEFAULT_URL }); } else { if (avatarModel.joints.length() > 256) { - _errors.push_back({ "Avatar has over 256 bones", QUrl("http://www.highfidelity.com/docs") }); + _errors.push_back({ "Avatar has over 256 bones", DEFAULT_URL }); } // Avatar does not have Hips bone mapped if (!avatarModel.getJointNames().contains("Hips")) { - _errors.push_back({ "Hips are not mapped", QUrl("http://www.highfidelity.com/docs") }); + _errors.push_back({ "Hips are not mapped", DEFAULT_URL }); } if (!avatarModel.getJointNames().contains("Spine")) { - _errors.push_back({ "Spine is not mapped", QUrl("http://www.highfidelity.com/docs") }); + _errors.push_back({ "Spine is not mapped", DEFAULT_URL }); } if (!avatarModel.getJointNames().contains("Head")) { - _errors.push_back({ "Head is not mapped", QUrl("http://www.highfidelity.com/docs") }); + _errors.push_back({ "Head is not mapped", DEFAULT_URL }); } } // SCALE - const float DEFAULT_HEIGHT = 1.75f; - const float RECOMMENDED_MIN_HEIGHT = DEFAULT_HEIGHT * 0.25; - const float RECOMMENDED_MAX_HEIGHT = DEFAULT_HEIGHT * 1.5; + const float RECOMMENDED_MIN_HEIGHT = DEFAULT_AVATAR_HEIGHT * 0.25f; + const float RECOMMENDED_MAX_HEIGHT = DEFAULT_AVATAR_HEIGHT * 1.5f; + + const float avatarHeight = avatarModel.bindExtents.largestDimension(); - float avatarHeight = avatarModel.getMeshExtents().largestDimension(); - - qWarning() << "avatarHeight" << avatarHeight; + qDebug() << "avatarHeight" << avatarHeight; + qDebug() << "defined Scale =" << model->getMapping()["scale"].toFloat(); if (avatarHeight < RECOMMENDED_MIN_HEIGHT) { - _errors.push_back({ "Avatar is possibly smaller then expected.", QUrl("http://www.highfidelity.com/docs") }); + _errors.push_back({ "Avatar is possibly smaller then expected.", DEFAULT_URL }); + } else if (avatarHeight > RECOMMENDED_MAX_HEIGHT) { + _errors.push_back({ "Avatar is possibly larger then expected.", DEFAULT_URL }); } - else if (avatarHeight > RECOMMENDED_MAX_HEIGHT) { - _errors.push_back({ "Avatar is possibly larger then expected.", QUrl("http://www.highfidelity.com/docs") }); - } - - // BLENDSHAPES // TEXTURES - //avatarModel.materials. + QStringList externalTextures{}; + QSet textureNames{}; + auto addTextureToList = [&externalTextures](hfm::Texture texture) mutable { + if (!texture.filename.isEmpty() && texture.content.isEmpty() && !externalTextures.contains(texture.name)) { + externalTextures << texture.name; + } + }; + + foreach(const HFMMaterial material, avatarModel.materials) { + addTextureToList(material.normalTexture); + addTextureToList(material.albedoTexture); + addTextureToList(material.opacityTexture); + addTextureToList(material.glossTexture); + addTextureToList(material.roughnessTexture); + addTextureToList(material.specularTexture); + addTextureToList(material.metallicTexture); + addTextureToList(material.emissiveTexture); + addTextureToList(material.occlusionTexture); + addTextureToList(material.scatteringTexture); + addTextureToList(material.lightmapTexture); + } + if (!externalTextures.empty()) { + // Check External Textures: + auto modelTexturesURLs = model->getTextures(); + _externalTextureCount = externalTextures.length(); + foreach(const QString textureKey, externalTextures) { + if (!modelTexturesURLs.contains(textureKey)) { + _missingTextureCount++; + _checkedTextureCount++; + continue; + } - emit complete(getErrors()); + const QUrl textureURL = modelTexturesURLs[textureKey].toUrl(); + + auto textureResource = DependencyManager::get()->getTexture(textureURL); + auto checkTextureLoadingComplete = [this, DEFAULT_URL] () mutable { + qDebug() << "checkTextureLoadingComplete" << _checkedTextureCount << "/" << _externalTextureCount; + + if (_checkedTextureCount == _externalTextureCount) { + if (_missingTextureCount == 1) { + _errors.push_back({ tr("Missing %n texture(s).","", _missingTextureCount), DEFAULT_URL }); + } + if (_unsupportedTextureCount > 0) { + _errors.push_back({ tr("%n unsupported texture(s) found.", "", _unsupportedTextureCount), DEFAULT_URL }); + } + emit complete(getErrors()); + } + }; + + auto textureLoaded = [this, textureResource, checkTextureLoadingComplete] (bool success) mutable { + if (!success) { + auto normalizedURL = DependencyManager::get()->normalizeURL(textureResource->getURL()); + if (normalizedURL.isLocalFile()) { + QFile textureFile(normalizedURL.toLocalFile()); + if (textureFile.exists()) { + _unsupportedTextureCount++; + } else { + _missingTextureCount++; + } + } else { + _missingTextureCount++; + } + } + _checkedTextureCount++; + checkTextureLoadingComplete(); + }; + + if (textureResource) { + textureResource->refresh(); + if (textureResource->isLoaded()) { + textureLoaded(!textureResource->isFailed()); + } else { + connect(textureResource.data(), &NetworkTexture::finished, this, textureLoaded); + } + } else { + _missingTextureCount++; + _checkedTextureCount++; + checkTextureLoadingComplete(); + } + } + } else { + emit complete(getErrors()); + } }; if (resource) { @@ -84,7 +172,7 @@ void AvatarDoctor::startDiagnosing() { connect(resource.data(), &GeometryResource::finished, this, resourceLoaded); } } else { - _errors.push_back({ "Model file cannot be opened", QUrl("http://www.highfidelity.com/docs") }); + _errors.push_back({ "Model file cannot be opened", DEFAULT_URL }); emit complete(getErrors()); } } diff --git a/interface/src/avatar/AvatarDoctor.h b/interface/src/avatar/AvatarDoctor.h index 65a184af71..f11bc7377c 100644 --- a/interface/src/avatar/AvatarDoctor.h +++ b/interface/src/avatar/AvatarDoctor.h @@ -45,6 +45,11 @@ signals: private: QUrl _avatarFSTFileUrl; QVector _errors; + + int _externalTextureCount = 0; + int _checkedTextureCount = 0; + int _missingTextureCount = 0; + int _unsupportedTextureCount = 0; }; #endif // hifi_AvatarDoctor_h diff --git a/interface/src/avatar/AvatarProject.cpp b/interface/src/avatar/AvatarProject.cpp index 74edabd1f5..b020cdb627 100644 --- a/interface/src/avatar/AvatarProject.cpp +++ b/interface/src/avatar/AvatarProject.cpp @@ -244,9 +244,7 @@ MarketplaceItemUploader* AvatarProject::upload(bool updateExisting) { } AvatarDoctor* AvatarProject::diagnose() { - auto avatarDoctor = new AvatarDoctor(QUrl(getFSTPath())); - - return avatarDoctor; + return new AvatarDoctor(QUrl(getFSTPath())); } void AvatarProject::openInInventory() const {