Better scale and texture checks

This commit is contained in:
Thijs Wenker 2019-02-15 20:55:27 +01:00
parent 3026fd625a
commit 556a55ff16
5 changed files with 151 additions and 58 deletions

View file

@ -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

View file

@ -32,7 +32,7 @@ Item {
return;
}
avatarProject.reset();
avatarPackager.state = AvatarPackagerState.project;
avatarPackager.state = AvatarPackagerState.avatarDoctorDiagnose;
}
}
}

View file

@ -11,6 +11,8 @@
#include "AvatarDoctor.h"
#include <model-networking/ModelCache.h>
#include <AvatarConstants.h>
#include <ResourceManager.h>
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<ModelCache>()->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<QString> 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<TextureCache>()->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<ResourceManager>()->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());
}
}

View file

@ -45,6 +45,11 @@ signals:
private:
QUrl _avatarFSTFileUrl;
QVector<AvatarDiagnosticResult> _errors;
int _externalTextureCount = 0;
int _checkedTextureCount = 0;
int _missingTextureCount = 0;
int _unsupportedTextureCount = 0;
};
#endif // hifi_AvatarDoctor_h

View file

@ -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 {