// // AvatarProject.cpp // // // Created by Thijs Wenker on 12/7/2018 // Copyright 2018 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 "AvatarProject.h" #include #include #include #include #include #include "FBXSerializer.h" #include #include "scripting/HMDScriptingInterface.h" AvatarProject* AvatarProject::openAvatarProject(const QString& path, AvatarProjectStatus::AvatarProjectStatus& status) { status = AvatarProjectStatus::NONE; if (!path.toLower().endsWith(".fst")) { status = AvatarProjectStatus::ERROR_OPEN_INVALID_FILE_TYPE; return nullptr; } QFileInfo fstFileInfo{ path }; if (!fstFileInfo.absoluteDir().exists()) { status = AvatarProjectStatus::ERROR_OPEN_PROJECT_FOLDER; return nullptr; } if (!fstFileInfo.exists()) { status = AvatarProjectStatus::ERROR_OPEN_FIND_FST; return nullptr; } QFile file{ fstFileInfo.filePath() }; if (!file.open(QIODevice::ReadOnly)) { status = AvatarProjectStatus::ERROR_OPEN_OPEN_FST; return nullptr; } const auto project = new AvatarProject(path, file.readAll()); QFileInfo fbxFileInfo{ project->getFBXPath() }; if (!fbxFileInfo.exists()) { project->deleteLater(); status = AvatarProjectStatus::ERROR_OPEN_FIND_MODEL; return nullptr; } QQmlEngine::setObjectOwnership(project, QQmlEngine::CppOwnership); status = AvatarProjectStatus::SUCCESS; return project; } AvatarProject* AvatarProject::createAvatarProject(const QString& projectsFolder, const QString& avatarProjectName, const QString& avatarModelPath, const QString& textureFolder, AvatarProjectStatus::AvatarProjectStatus& status) { status = AvatarProjectStatus::NONE; if (!isValidNewProjectName(projectsFolder, avatarProjectName)) { status = AvatarProjectStatus::ERROR_CREATE_PROJECT_NAME; return nullptr; } QDir projectDir(projectsFolder + "/" + avatarProjectName); if (!projectDir.mkpath(".")) { status = AvatarProjectStatus::ERROR_CREATE_CREATING_DIRECTORIES; return nullptr; } QDir projectTexturesDir(projectDir.path() + "/textures"); if (!projectTexturesDir.mkpath(".")) { status = AvatarProjectStatus::ERROR_CREATE_CREATING_DIRECTORIES; return nullptr; } QDir projectScriptsDir(projectDir.path() + "/scripts"); if (!projectScriptsDir.mkpath(".")) { status = AvatarProjectStatus::ERROR_CREATE_CREATING_DIRECTORIES; return nullptr; } const auto fileName = QFileInfo(avatarModelPath).fileName(); const auto newModelPath = projectDir.absoluteFilePath(fileName); const auto newFSTPath = projectDir.absoluteFilePath("avatar.fst"); QFile::copy(avatarModelPath, newModelPath); QFileInfo fbxInfo{ newModelPath }; if (!fbxInfo.exists() || !fbxInfo.isFile()) { status = AvatarProjectStatus::ERROR_CREATE_FIND_MODEL; return nullptr; } QFile fbx{ fbxInfo.filePath() }; if (!fbx.open(QIODevice::ReadOnly)) { status = AvatarProjectStatus::ERROR_CREATE_OPEN_MODEL; return nullptr; } std::shared_ptr hfmModel; try { const QByteArray fbxContents = fbx.readAll(); hfmModel = FBXSerializer().read(fbxContents, QVariantHash(), fbxInfo.filePath()); } catch (const QString& error) { Q_UNUSED(error) status = AvatarProjectStatus::ERROR_CREATE_READ_MODEL; return nullptr; } QStringList textures{}; auto addTextureToList = [&textures](hfm::Texture texture) mutable { if (!texture.filename.isEmpty() && texture.content.isEmpty() && !textures.contains(texture.filename)) { textures << texture.filename; } }; foreach(const HFMMaterial material, hfmModel->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 (material.isMToonMaterial) { addTextureToList(material.shadeTexture); addTextureToList(material.shadingShiftTexture); addTextureToList(material.matcapTexture); addTextureToList(material.rimTexture); addTextureToList(material.uvAnimationTexture); } } QDir textureDir(textureFolder.isEmpty() ? fbxInfo.absoluteDir() : textureFolder); for (const auto& texture : textures) { QString sourcePath = textureDir.path() + "/" + texture; QString targetPath = projectTexturesDir.path() + "/" + texture; QFileInfo sourceTexturePath(sourcePath); if (sourceTexturePath.exists()) { QFile::copy(sourcePath, targetPath); } } auto fst = FST::createFSTFromModel(newFSTPath, newModelPath, *hfmModel); fst->setName(avatarProjectName); if (!fst->write()) { status = AvatarProjectStatus::ERROR_CREATE_WRITE_FST; return nullptr; } status = AvatarProjectStatus::SUCCESS; return new AvatarProject(fst); } QStringList AvatarProject::getScriptPaths(const QDir& scriptsDir) const { QStringList result{}; constexpr auto flags = QDir::Files | QDir::NoSymLinks | QDir::NoDotAndDotDot | QDir::Hidden; if (!scriptsDir.exists()) { return result; } for (const auto& script : scriptsDir.entryInfoList({}, flags)) { if (script.fileName().toLower().endsWith(".js")) { result.push_back("scripts/" + script.fileName()); } } return result; } bool AvatarProject::isValidNewProjectName(const QString& projectPath, const QString& projectName) { if (projectPath.trimmed().isEmpty() || projectName.trimmed().isEmpty()) { return false; } QDir dir(projectPath + "/" + projectName); return !dir.exists(); } AvatarProject::AvatarProject(const QString& fstPath, const QByteArray& data) : AvatarProject::AvatarProject(new FST(fstPath, FSTReader::readMapping(data))) { } AvatarProject::AvatarProject(FST* fst) { _fst = fst; auto fileInfo = QFileInfo(getFSTPath()); _directory = fileInfo.absoluteDir(); _fst->setScriptPaths(getScriptPaths(QDir(_directory.path() + "/scripts"))); _fst->write(); refreshProjectFiles(); _projectPath = fileInfo.absoluteDir().absolutePath(); } void AvatarProject::appendDirectory(const QString& prefix, const QDir& dir) { constexpr auto flags = QDir::Dirs | QDir::Files | QDir::NoSymLinks | QDir::NoDotAndDotDot | QDir::Hidden; for (auto& entry : dir.entryInfoList({}, flags)) { if (entry.isFile()) { _projectFiles.append({ entry.absoluteFilePath(), prefix + entry.fileName() }); } else if (entry.isDir()) { appendDirectory(prefix + entry.fileName() + "/", entry.absoluteFilePath()); } } } void AvatarProject::refreshProjectFiles() { _projectFiles.clear(); appendDirectory("", _directory); } QStringList AvatarProject::getProjectFiles() const { QStringList paths; for (auto& path : _projectFiles) { paths.append(path.relativePath); } return paths; } AvatarDoctor* AvatarProject::diagnose() { return new AvatarDoctor(QUrl(getFSTPath())); }