mirror of
https://github.com/Armored-Dragon/overte.git
synced 2025-03-11 16:13:16 +01:00
Merge pull request #14917 from thoys/feat/avatarTools/avatarDoctor
Case 21012: Avatar Doctor
This commit is contained in:
commit
4520236873
12 changed files with 555 additions and 9 deletions
|
@ -0,0 +1,127 @@
|
|||
import QtQuick 2.0
|
||||
|
||||
import "../../controlsUit" 1.0 as HifiControls
|
||||
import "../../stylesUit" 1.0
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
visible: false
|
||||
|
||||
property var avatarDoctor: null
|
||||
property var errors: []
|
||||
|
||||
property int minimumDiagnoseTimeMS: 1000
|
||||
|
||||
signal doneDiagnosing
|
||||
|
||||
onVisibleChanged: {
|
||||
if (root.avatarDoctor !== null) {
|
||||
root.avatarDoctor.complete.disconnect(_private.avatarDoctorComplete);
|
||||
root.avatarDoctor = null;
|
||||
}
|
||||
if (doneTimer.running) {
|
||||
doneTimer.stop();
|
||||
}
|
||||
|
||||
if (!root.visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
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));
|
||||
root.errors = errors;
|
||||
AvatarPackagerCore.currentAvatarProject.hasErrors = errors.length > 0;
|
||||
AvatarPackagerCore.addCurrentProjectToRecentProjects();
|
||||
|
||||
let timeSpendDiagnosingMS = Date.now() - _private.startTime;
|
||||
let timeLeftMS = root.minimumDiagnoseTimeMS - timeSpendDiagnosingMS;
|
||||
doneTimer.interval = timeLeftMS < 0 ? 0 : timeLeftMS;
|
||||
doneTimer.start();
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: doneTimer
|
||||
repeat: false
|
||||
running: false
|
||||
onTriggered: {
|
||||
doneDiagnosing();
|
||||
}
|
||||
}
|
||||
|
||||
property var footer: Item {
|
||||
anchors.fill: parent
|
||||
anchors.rightMargin: 17
|
||||
HifiControls.Button {
|
||||
id: cancelButton
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.right: parent.right
|
||||
height: 30
|
||||
width: 133
|
||||
text: qsTr("Cancel")
|
||||
onClicked: {
|
||||
avatarPackager.state = AvatarPackagerState.main;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LoadingCircle {
|
||||
id: loadingCircle
|
||||
anchors {
|
||||
top: parent.top
|
||||
topMargin: 46
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
width: 163
|
||||
height: 163
|
||||
}
|
||||
|
||||
RalewayRegular {
|
||||
id: testingPackageTitle
|
||||
|
||||
anchors {
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
top: loadingCircle.bottom
|
||||
topMargin: 5
|
||||
}
|
||||
|
||||
text: "Testing package for errors"
|
||||
size: 28
|
||||
color: "white"
|
||||
}
|
||||
|
||||
RalewayRegular {
|
||||
id: testingPackageText
|
||||
|
||||
anchors {
|
||||
top: testingPackageTitle.bottom
|
||||
topMargin: 26
|
||||
left: parent.left
|
||||
leftMargin: 21
|
||||
right: parent.right
|
||||
rightMargin: 16
|
||||
}
|
||||
|
||||
text: "We are trying to find errors in your project so you can quickly understand and resolve them."
|
||||
size: 21
|
||||
color: "white"
|
||||
lineHeight: 33
|
||||
lineHeightMode: Text.FixedHeight
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
import QtQuick 2.0
|
||||
|
||||
import "../../controlsUit" 1.0 as HifiControls
|
||||
import "../../stylesUit" 1.0
|
||||
|
||||
Item {
|
||||
id: errorReport
|
||||
|
||||
visible: false
|
||||
|
||||
property alias errors: errorRepeater.model
|
||||
|
||||
property var footer: Item {
|
||||
anchors.fill: parent
|
||||
anchors.rightMargin: 17
|
||||
HifiControls.Button {
|
||||
id: tryAgainButton
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.right: continueButton.left
|
||||
anchors.rightMargin: 22
|
||||
height: 40
|
||||
width: 134
|
||||
text: qsTr("Try Again")
|
||||
onClicked: {
|
||||
avatarPackager.state = AvatarPackagerState.avatarDoctorDiagnose;
|
||||
}
|
||||
}
|
||||
|
||||
HifiControls.Button {
|
||||
id: continueButton
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.right: parent.right
|
||||
height: 40
|
||||
width: 133
|
||||
text: qsTr("Continue")
|
||||
color: hifi.buttons.blue
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
avatarPackager.state = AvatarPackagerState.project;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HiFiGlyphs {
|
||||
id: errorReportIcon
|
||||
text: hifi.glyphs.alert
|
||||
size: 315
|
||||
color: "#EA4C5F"
|
||||
anchors {
|
||||
top: parent.top
|
||||
topMargin: -20
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
bottom: parent.bottom
|
||||
top: errorReportIcon.bottom
|
||||
topMargin: -40
|
||||
leftMargin: 13
|
||||
rightMargin: 13
|
||||
}
|
||||
spacing: 7
|
||||
|
||||
Repeater {
|
||||
id: errorRepeater
|
||||
Item {
|
||||
height: 37
|
||||
width: parent.width
|
||||
|
||||
HiFiGlyphs {
|
||||
id: errorIcon
|
||||
text: hifi.glyphs.alert
|
||||
size: 56
|
||||
color: "#EA4C5F"
|
||||
anchors {
|
||||
top: parent.top
|
||||
left: parent.left
|
||||
leftMargin: -5
|
||||
}
|
||||
}
|
||||
|
||||
RalewayRegular {
|
||||
id: errorLink
|
||||
anchors {
|
||||
top: parent.top
|
||||
topMargin: 5
|
||||
left: errorIcon.right
|
||||
right: parent.right
|
||||
}
|
||||
color: "#00B4EF"
|
||||
linkColor: "#00B4EF"
|
||||
size: 28
|
||||
text: "<a href='javascript:void'>" + modelData.message + "</a>"
|
||||
onLinkActivated: Qt.openUrlExternally(modelData.url)
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -143,6 +143,18 @@ Item {
|
|||
PropertyChanges { target: createAvatarProject; visible: true }
|
||||
PropertyChanges { target: avatarPackagerFooter; content: createAvatarProject.footer }
|
||||
},
|
||||
State {
|
||||
name: AvatarPackagerState.avatarDoctorDiagnose
|
||||
PropertyChanges { target: avatarPackagerHeader; title: AvatarPackagerCore.currentAvatarProject.name }
|
||||
PropertyChanges { target: avatarDoctorDiagnose; visible: true }
|
||||
PropertyChanges { target: avatarPackagerFooter; content: avatarDoctorDiagnose.footer }
|
||||
},
|
||||
State {
|
||||
name: AvatarPackagerState.avatarDoctorErrorReport
|
||||
PropertyChanges { target: avatarPackagerHeader; title: AvatarPackagerCore.currentAvatarProject.name }
|
||||
PropertyChanges { target: avatarDoctorErrorReport; visible: true }
|
||||
PropertyChanges { target: avatarPackagerFooter; content: avatarDoctorErrorReport.footer }
|
||||
},
|
||||
State {
|
||||
name: AvatarPackagerState.project
|
||||
PropertyChanges { target: avatarPackagerHeader; title: AvatarPackagerCore.currentAvatarProject.name; canRename: true }
|
||||
|
@ -168,7 +180,7 @@ Item {
|
|||
return status;
|
||||
}
|
||||
avatarProject.reset();
|
||||
avatarPackager.state = AvatarPackagerState.project;
|
||||
avatarPackager.state = AvatarPackagerState.avatarDoctorDiagnose;
|
||||
return status;
|
||||
}
|
||||
|
||||
|
@ -242,6 +254,23 @@ Item {
|
|||
color: "#404040"
|
||||
}
|
||||
|
||||
AvatarDoctorDiagnose {
|
||||
id: avatarDoctorDiagnose
|
||||
anchors.fill: parent
|
||||
onErrorsChanged: {
|
||||
avatarDoctorErrorReport.errors = avatarDoctorDiagnose.errors;
|
||||
}
|
||||
onDoneDiagnosing: {
|
||||
avatarPackager.state = avatarDoctorDiagnose.errors.length > 0 ? AvatarPackagerState.avatarDoctorErrorReport
|
||||
: AvatarPackagerState.project;
|
||||
}
|
||||
}
|
||||
|
||||
AvatarDoctorErrorReport {
|
||||
id: avatarDoctorErrorReport
|
||||
anchors.fill: parent
|
||||
}
|
||||
|
||||
AvatarProject {
|
||||
id: avatarProject
|
||||
colorScheme: root.colorScheme
|
||||
|
@ -383,6 +412,7 @@ Item {
|
|||
title: modelData.name
|
||||
path: modelData.projectPath
|
||||
onOpen: avatarPackager.openProject(modelData.path)
|
||||
hasError: modelData.hadErrors
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,4 +7,6 @@ Item {
|
|||
readonly property string project: "project"
|
||||
readonly property string createProject: "createProject"
|
||||
readonly property string projectUpload: "projectUpload"
|
||||
readonly property string avatarDoctorDiagnose: "avatarDoctorDiagnose"
|
||||
readonly property string avatarDoctorErrorReport: "avatarDoctorErrorReport"
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ Item {
|
|||
|
||||
property alias title: title.text
|
||||
property alias path: path.text
|
||||
property alias hasError: errorIcon.visible
|
||||
|
||||
property color textColor: "#E3E3E3"
|
||||
property color hoverTextColor: "#121212"
|
||||
|
@ -54,7 +55,7 @@ Item {
|
|||
|
||||
RalewayBold {
|
||||
id: title
|
||||
elide: "ElideRight"
|
||||
elide: Text.ElideRight
|
||||
anchors {
|
||||
top: parent.top
|
||||
topMargin: 13
|
||||
|
@ -76,12 +77,24 @@ Item {
|
|||
right: background.right
|
||||
rightMargin: 16
|
||||
}
|
||||
elide: "ElideLeft"
|
||||
elide: Text.ElideLeft
|
||||
horizontalAlignment: Text.AlignRight
|
||||
text: "<path missing>"
|
||||
size: 20
|
||||
}
|
||||
|
||||
HiFiGlyphs {
|
||||
id: errorIcon
|
||||
visible: false
|
||||
text: hifi.glyphs.alert
|
||||
size: 56
|
||||
color: "#EA4C5F"
|
||||
anchors {
|
||||
top: parent.top
|
||||
right: parent.right
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
|
|
|
@ -32,7 +32,7 @@ Item {
|
|||
return;
|
||||
}
|
||||
avatarProject.reset();
|
||||
avatarPackager.state = AvatarPackagerState.project;
|
||||
avatarPackager.state = AvatarPackagerState.avatarDoctorDiagnose;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
196
interface/src/avatar/AvatarDoctor.cpp
Normal file
196
interface/src/avatar/AvatarDoctor.cpp
Normal file
|
@ -0,0 +1,196 @@
|
|||
//
|
||||
// AvatarDoctor.cpp
|
||||
//
|
||||
//
|
||||
// Created by Thijs Wenker on 2/12/2019.
|
||||
// Copyright 2019 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 "AvatarDoctor.h"
|
||||
#include <model-networking/ModelCache.h>
|
||||
#include <AvatarConstants.h>
|
||||
#include <ResourceManager.h>
|
||||
|
||||
|
||||
AvatarDoctor::AvatarDoctor(QUrl avatarFSTFileUrl) :
|
||||
_avatarFSTFileUrl(avatarFSTFileUrl) {
|
||||
|
||||
connect(this, &AvatarDoctor::complete, this, [this](QVariantList errors) {
|
||||
_isDiagnosing = false;
|
||||
});
|
||||
}
|
||||
|
||||
void AvatarDoctor::startDiagnosing() {
|
||||
if (_isDiagnosing) {
|
||||
// One diagnose at a time for now
|
||||
return;
|
||||
}
|
||||
_isDiagnosing = true;
|
||||
|
||||
_errors.clear();
|
||||
|
||||
_externalTextureCount = 0;
|
||||
_checkedTextureCount = 0;
|
||||
_missingTextureCount = 0;
|
||||
_unsupportedTextureCount = 0;
|
||||
|
||||
const auto resource = DependencyManager::get<ModelCache>()->getGeometryResource(_avatarFSTFileUrl);
|
||||
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", 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", DEFAULT_URL });
|
||||
emit complete(getErrors());
|
||||
return;
|
||||
}
|
||||
|
||||
// RIG
|
||||
if (avatarModel.joints.isEmpty()) {
|
||||
_errors.push_back({ "Avatar has no rig", DEFAULT_URL });
|
||||
} else {
|
||||
if (avatarModel.joints.length() > 256) {
|
||||
_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", DEFAULT_URL });
|
||||
}
|
||||
if (!avatarModel.getJointNames().contains("Spine")) {
|
||||
_errors.push_back({ "Spine is not mapped", DEFAULT_URL });
|
||||
}
|
||||
if (!avatarModel.getJointNames().contains("Head")) {
|
||||
_errors.push_back({ "Head is not mapped", DEFAULT_URL });
|
||||
}
|
||||
}
|
||||
|
||||
// SCALE
|
||||
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();
|
||||
if (avatarHeight < RECOMMENDED_MIN_HEIGHT) {
|
||||
_errors.push_back({ "Avatar is possibly too small.", DEFAULT_URL });
|
||||
} else if (avatarHeight > RECOMMENDED_MAX_HEIGHT) {
|
||||
_errors.push_back({ "Avatar is possibly too large.", DEFAULT_URL });
|
||||
}
|
||||
|
||||
// TEXTURES
|
||||
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;
|
||||
}
|
||||
|
||||
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 > 0) {
|
||||
_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) {
|
||||
if (resource->isLoaded()) {
|
||||
resourceLoaded(!resource->isFailed());
|
||||
} else {
|
||||
connect(resource.data(), &GeometryResource::finished, this, resourceLoaded);
|
||||
}
|
||||
} else {
|
||||
_errors.push_back({ "Model file cannot be opened", DEFAULT_URL });
|
||||
emit complete(getErrors());
|
||||
}
|
||||
}
|
||||
|
||||
QVariantList AvatarDoctor::getErrors() const {
|
||||
QVariantList result;
|
||||
for (const auto& error : _errors) {
|
||||
QVariantMap errorVariant;
|
||||
errorVariant.insert("message", error.message);
|
||||
errorVariant.insert("url", error.url);
|
||||
result.append(errorVariant);
|
||||
}
|
||||
return result;
|
||||
}
|
51
interface/src/avatar/AvatarDoctor.h
Normal file
51
interface/src/avatar/AvatarDoctor.h
Normal file
|
@ -0,0 +1,51 @@
|
|||
//
|
||||
// AvatarDoctor.h
|
||||
//
|
||||
//
|
||||
// Created by Thijs Wenker on 02/12/2019.
|
||||
// Copyright 2019 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
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef hifi_AvatarDoctor_h
|
||||
#define hifi_AvatarDoctor_h
|
||||
|
||||
#include <QUrl>
|
||||
#include <QVector>
|
||||
#include <QVariantMap>
|
||||
|
||||
struct AvatarDiagnosticResult {
|
||||
QString message;
|
||||
QUrl url;
|
||||
};
|
||||
Q_DECLARE_METATYPE(AvatarDiagnosticResult)
|
||||
Q_DECLARE_METATYPE(QVector<AvatarDiagnosticResult>)
|
||||
|
||||
class AvatarDoctor : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
AvatarDoctor(QUrl avatarFSTFileUrl);
|
||||
|
||||
Q_INVOKABLE void startDiagnosing();
|
||||
|
||||
Q_INVOKABLE QVariantList getErrors() const;
|
||||
|
||||
signals:
|
||||
void complete(QVariantList errors);
|
||||
|
||||
private:
|
||||
QUrl _avatarFSTFileUrl;
|
||||
QVector<AvatarDiagnosticResult> _errors;
|
||||
|
||||
int _externalTextureCount = 0;
|
||||
int _checkedTextureCount = 0;
|
||||
int _missingTextureCount = 0;
|
||||
int _unsupportedTextureCount = 0;
|
||||
|
||||
bool _isDiagnosing = false;
|
||||
};
|
||||
|
||||
#endif // hifi_AvatarDoctor_h
|
|
@ -31,6 +31,7 @@ AvatarPackager::AvatarPackager() {
|
|||
qmlRegisterType<MarketplaceItemUploader>();
|
||||
qRegisterMetaType<AvatarPackager*>();
|
||||
qRegisterMetaType<AvatarProject*>();
|
||||
qRegisterMetaType<AvatarDoctor*>();
|
||||
qRegisterMetaType<AvatarProjectStatus::AvatarProjectStatus>();
|
||||
qmlRegisterUncreatableMetaObject(
|
||||
AvatarProjectStatus::staticMetaObject,
|
||||
|
@ -84,7 +85,7 @@ void AvatarPackager::addCurrentProjectToRecentProjects() {
|
|||
_recentProjects.removeOne(removeProject);
|
||||
}
|
||||
|
||||
const auto newRecentProject = RecentAvatarProject(_currentAvatarProject->getProjectName(), fstPath);
|
||||
const auto newRecentProject = RecentAvatarProject(_currentAvatarProject->getProjectName(), fstPath, _currentAvatarProject->getHasErrors());
|
||||
_recentProjects.prepend(newRecentProject);
|
||||
|
||||
while (_recentProjects.size() > MAX_RECENT_PROJECTS) {
|
||||
|
@ -101,6 +102,7 @@ QVariantList AvatarPackager::recentProjectsToVariantList(bool includeProjectPath
|
|||
QVariantMap projectVariant;
|
||||
projectVariant.insert("name", project.getProjectName());
|
||||
projectVariant.insert("path", project.getProjectFSTPath());
|
||||
projectVariant.insert("hadErrors", project.getHadErrors());
|
||||
if (includeProjectPaths) {
|
||||
projectVariant.insert("projectPath", project.getProjectPath());
|
||||
}
|
||||
|
@ -113,7 +115,10 @@ void AvatarPackager::recentProjectsFromVariantList(QVariantList projectsVariant)
|
|||
_recentProjects.clear();
|
||||
for (const auto& projectVariant : projectsVariant) {
|
||||
auto map = projectVariant.toMap();
|
||||
_recentProjects.append(RecentAvatarProject(map.value("name").toString(), map.value("path").toString()));
|
||||
_recentProjects.append(RecentAvatarProject(
|
||||
map.value("name").toString(),
|
||||
map.value("path").toString(),
|
||||
map.value("hadErrors", false).toBool()));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -26,19 +26,23 @@ public:
|
|||
RecentAvatarProject() = default;
|
||||
|
||||
|
||||
RecentAvatarProject(QString projectName, QString projectFSTPath) {
|
||||
RecentAvatarProject(QString projectName, QString projectFSTPath, bool hadErrors) {
|
||||
_projectName = projectName;
|
||||
_projectFSTPath = projectFSTPath;
|
||||
_hadErrors = hadErrors;
|
||||
}
|
||||
RecentAvatarProject(const RecentAvatarProject& other) {
|
||||
_projectName = other._projectName;
|
||||
_projectFSTPath = other._projectFSTPath;
|
||||
_hadErrors = other._hadErrors;
|
||||
}
|
||||
|
||||
QString getProjectName() const { return _projectName; }
|
||||
|
||||
QString getProjectFSTPath() const { return _projectFSTPath; }
|
||||
|
||||
bool getHadErrors() const { return _hadErrors; }
|
||||
|
||||
QString getProjectPath() const {
|
||||
return QFileInfo(_projectFSTPath).absoluteDir().absolutePath();
|
||||
}
|
||||
|
@ -50,6 +54,7 @@ public:
|
|||
private:
|
||||
QString _projectName;
|
||||
QString _projectFSTPath;
|
||||
bool _hadErrors;
|
||||
|
||||
};
|
||||
|
||||
|
@ -73,6 +78,8 @@ public:
|
|||
return AvatarProject::isValidNewProjectName(projectPath, projectName);
|
||||
}
|
||||
|
||||
Q_INVOKABLE void addCurrentProjectToRecentProjects();
|
||||
|
||||
signals:
|
||||
void avatarProjectChanged();
|
||||
void recentProjectsChanged();
|
||||
|
@ -84,8 +91,6 @@ private:
|
|||
|
||||
void setAvatarProject(AvatarProject* avatarProject);
|
||||
|
||||
void addCurrentProjectToRecentProjects();
|
||||
|
||||
AvatarProject* _currentAvatarProject { nullptr };
|
||||
QVector<RecentAvatarProject> _recentProjects;
|
||||
|
||||
|
|
|
@ -243,6 +243,10 @@ MarketplaceItemUploader* AvatarProject::upload(bool updateExisting) {
|
|||
return uploader;
|
||||
}
|
||||
|
||||
AvatarDoctor* AvatarProject::diagnose() {
|
||||
return new AvatarDoctor(QUrl(getFSTPath()));
|
||||
}
|
||||
|
||||
void AvatarProject::openInInventory() const {
|
||||
constexpr int TIME_TO_WAIT_FOR_INVENTORY_TO_OPEN_MS { 1000 };
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#define hifi_AvatarProject_h
|
||||
|
||||
#include "MarketplaceItemUploader.h"
|
||||
#include "AvatarDoctor.h"
|
||||
#include "ProjectFile.h"
|
||||
#include "FST.h"
|
||||
|
||||
|
@ -53,11 +54,14 @@ class AvatarProject : public QObject {
|
|||
Q_PROPERTY(QString projectFSTPath READ getFSTPath CONSTANT)
|
||||
Q_PROPERTY(QString projectFBXPath READ getFBXPath CONSTANT)
|
||||
Q_PROPERTY(QString name READ getProjectName WRITE setProjectName NOTIFY nameChanged)
|
||||
Q_PROPERTY(bool hasErrors READ getHasErrors WRITE setHasErrors NOTIFY hasErrorsChanged)
|
||||
|
||||
public:
|
||||
Q_INVOKABLE MarketplaceItemUploader* upload(bool updateExisting);
|
||||
Q_INVOKABLE void openInInventory() const;
|
||||
Q_INVOKABLE QStringList getProjectFiles() const;
|
||||
Q_INVOKABLE AvatarDoctor* diagnose();
|
||||
|
||||
|
||||
Q_INVOKABLE QString getProjectName() const { return _fst->getName(); }
|
||||
Q_INVOKABLE void setProjectName(const QString& newProjectName) {
|
||||
|
@ -72,6 +76,8 @@ public:
|
|||
Q_INVOKABLE QString getFBXPath() const {
|
||||
return QDir::cleanPath(QDir(_projectPath).absoluteFilePath(_fst->getModelPath()));
|
||||
}
|
||||
Q_INVOKABLE bool getHasErrors() const { return _hasErrors; }
|
||||
Q_INVOKABLE void setHasErrors(bool hasErrors) { _hasErrors = hasErrors; }
|
||||
|
||||
/**
|
||||
* returns the AvatarProject or a nullptr on failure.
|
||||
|
@ -92,6 +98,7 @@ public:
|
|||
signals:
|
||||
void nameChanged();
|
||||
void projectFilesChanged();
|
||||
void hasErrorsChanged();
|
||||
|
||||
private:
|
||||
AvatarProject(const QString& fstPath, const QByteArray& data);
|
||||
|
@ -110,6 +117,8 @@ private:
|
|||
QDir _directory;
|
||||
QList<ProjectFilePath> _projectFiles{};
|
||||
QString _projectPath;
|
||||
|
||||
bool _hasErrors { false };
|
||||
};
|
||||
|
||||
#endif // hifi_AvatarProject_h
|
||||
|
|
Loading…
Reference in a new issue