From a586a31a93c61a4ba7fd5d40cc180e2aa6e3b262 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 29 Mar 2017 13:48:24 -0700 Subject: [PATCH 01/71] stub the oven tool and add a find module for FBX SDK --- cmake/modules/FindFBXSDK.cmake | 110 +++++++++++++++++++++++++++++++++ tools/CMakeLists.txt | 3 + tools/oven/CMakeLists.txt | 5 ++ tools/oven/src/main.cpp | 14 +++++ 4 files changed, 132 insertions(+) create mode 100644 cmake/modules/FindFBXSDK.cmake create mode 100644 tools/oven/CMakeLists.txt create mode 100644 tools/oven/src/main.cpp diff --git a/cmake/modules/FindFBXSDK.cmake b/cmake/modules/FindFBXSDK.cmake new file mode 100644 index 0000000000..477aff6bf2 --- /dev/null +++ b/cmake/modules/FindFBXSDK.cmake @@ -0,0 +1,110 @@ +# Locate the FBX SDK +# +# Defines the following variables: +# +# FBX_FOUND - Found the FBX SDK +# FBX_VERSION - Version number +# FBX_INCLUDE_DIRS - Include directories +# FBX_LIBRARIES - The libraries to link to +# +# Accepts the following variables as input: +# +# FBX_VERSION - as a CMake variable, e.g. 2017.0.1 +# FBX_ROOT - (as a CMake or environment variable) +# The root directory of the FBX SDK install + +# adapted from https://github.com/ufz-vislab/VtkFbxConverter/blob/master/FindFBX.cmake +# which uses the MIT license (https://github.com/ufz-vislab/VtkFbxConverter/blob/master/LICENSE.txt) + +if (NOT FBX_VERSION) + set(FBX_VERSION 2017.0.1) +endif() + +string(REGEX REPLACE "^([0-9]+).*$" "\\1" FBX_VERSION_MAJOR "${FBX_VERSION}") +string(REGEX REPLACE "^[0-9]+\\.([0-9]+).*$" "\\1" FBX_VERSION_MINOR "${FBX_VERSION}") +string(REGEX REPLACE "^[0-9]+\\.[0-9]+\\.([0-9]+).*$" "\\1" FBX_VERSION_PATCH "${FBX_VERSION}") + +set(FBX_MAC_LOCATIONS "/Applications/Autodesk/FBX\ SDK/${FBX_VERSION}") + +if (WIN32) + string(REGEX REPLACE "\\\\" "/" WIN_PROGRAM_FILES_X64_DIRECTORY $ENV{ProgramW6432}) +endif() + +set(FBX_WIN_LOCATIONS "${WIN_PROGRAM_FILES_X64_DIRECTORY}/Autodesk/FBX/FBX SDK/${FBX_VERSION}") + +set(FBX_SEARCH_LOCATIONS $ENV{FBX_ROOT} ${FBX_ROOT} ${FBX_MAC_LOCATIONS} ${FBX_WIN_LOCATIONS}) + +function(_fbx_append_debugs _endvar _library) + if (${_library} AND ${_library}_DEBUG) + set(_output optimized ${${_library}} debug ${${_library}_DEBUG}) + else() + set(_output ${${_library}}) + endif() + + set(${_endvar} ${_output} PARENT_SCOPE) +endfunction() + +if (${CMAKE_CXX_COMPILER_ID} MATCHES "Clang") + set(fbx_compiler clang) +elseif (${CMAKE_CXX_COMPILER_ID} MATCHES "GNU") + set(fbx_compiler gcc4) +endif() + +function(_fbx_find_library _name _lib _suffix) + if (MSVC12) + set(VS_PREFIX vs2013) + endif() + + if (MSVC11) + set(VS_PREFIX vs2012) + endif() + + if (MSVC10) + set(VS_PREFIX vs2010) + endif() + + if (MSVC90) + set(VS_PREFIX vs2008) + endif() + + find_library(${_name} + NAMES ${_lib} + HINTS ${FBX_SEARCH_LOCATIONS} + PATH_SUFFIXES lib/${fbx_compiler}/${_suffix} lib/${fbx_compiler}/ub/${_suffix} lib/${VS_PREFIX}/x64/${_suffix} + ) + + mark_as_advanced(${_name}) +endfunction() + +find_path(FBX_INCLUDE_DIR fbxsdk.h + PATHS ${FBX_SEARCH_LOCATIONS} + PATH_SUFFIXES include +) +mark_as_advanced(FBX_INCLUDE_DIR) + +if (WIN32) + _fbx_find_library(FBX_LIBRARY libfbxsdk-md release) + _fbx_find_library(FBX_LIBRARY_DEBUG libfbxsdk-md debug) +elseif (APPLE) + find_library(CARBON NAMES Carbon) + find_library(SYSTEM_CONFIGURATION NAMES SystemConfiguration) + _fbx_find_library(FBX_LIBRARY libfbxsdk.a release) + _fbx_find_library(FBX_LIBRARY_DEBUG libfbxsdk.a debug) +endif() + +include(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(FBX DEFAULT_MSG FBX_LIBRARY FBX_INCLUDE_DIR) + +if (FBX_FOUND) + set(FBX_INCLUDE_DIRS ${FBX_INCLUDE_DIR}) + _fbx_append_debugs(FBX_LIBRARIES FBX_LIBRARY) + add_definitions(-DFBXSDK_NEW_API) + + if (WIN32) + add_definitions(-DK_PLUGIN -DK_FBXSDK -DK_NODLL) + set(CMAKE_EXE_LINKER_FLAGS /NODEFAULTLIB:\"LIBCMT\") + set(FBX_LIBRARIES ${FBX_LIBRARIES} Wininet.lib) + elseif (APPLE) + set(FBX_LIBRARIES ${FBX_LIBRARIES} ${CARBON} ${SYSTEM_CONFIGURATION}) + endif() +endif() diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index 8dc993e6fe..0561956709 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -19,3 +19,6 @@ set_target_properties(skeleton-dump PROPERTIES FOLDER "Tools") add_subdirectory(atp-get) set_target_properties(atp-get PROPERTIES FOLDER "Tools") + +add_subdirectory(oven) +set_target_properties(oven PROPERTIES FOLDER "Tools") diff --git a/tools/oven/CMakeLists.txt b/tools/oven/CMakeLists.txt new file mode 100644 index 0000000000..5244b68217 --- /dev/null +++ b/tools/oven/CMakeLists.txt @@ -0,0 +1,5 @@ +set(TARGET_NAME oven) + +setup_hifi_project() + +find_package(FBXSDK REQUIRED) diff --git a/tools/oven/src/main.cpp b/tools/oven/src/main.cpp new file mode 100644 index 0000000000..831ba00328 --- /dev/null +++ b/tools/oven/src/main.cpp @@ -0,0 +1,14 @@ +// +// main.cpp +// tools/oven/src +// +// Created by Stephen Birarda on 3/28/2017. +// Copyright 2017 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 + + +int main (int argc, char** argv) { + return 0; +} From b04d50709014ba725b5977ebb64df340c605ce67 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 29 Mar 2017 14:01:40 -0700 Subject: [PATCH 02/71] use correct FBX_VERSION for WIN32 --- cmake/modules/FindFBXSDK.cmake | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cmake/modules/FindFBXSDK.cmake b/cmake/modules/FindFBXSDK.cmake index 477aff6bf2..7f6a424aa1 100644 --- a/cmake/modules/FindFBXSDK.cmake +++ b/cmake/modules/FindFBXSDK.cmake @@ -17,7 +17,11 @@ # which uses the MIT license (https://github.com/ufz-vislab/VtkFbxConverter/blob/master/LICENSE.txt) if (NOT FBX_VERSION) - set(FBX_VERSION 2017.0.1) + if (WIN32) + set(FBX_VERSION 2017.1) + else() + set(FBX_VERSION 2017.0.1) + endif() endif() string(REGEX REPLACE "^([0-9]+).*$" "\\1" FBX_VERSION_MAJOR "${FBX_VERSION}") From 26d4cc73e0c9ec3667aec35b5a0f196c0ea2b47a Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 30 Mar 2017 15:16:23 -0700 Subject: [PATCH 03/71] add stubbed FBXBaker leveraging FBX SDK for read/write --- libraries/model-baking/CMakeLists.txt | 7 ++ libraries/model-baking/src/FBXBaker.cpp | 156 ++++++++++++++++++++++++ libraries/model-baking/src/FBXBaker.h | 38 ++++++ tools/oven/CMakeLists.txt | 2 +- 4 files changed, 202 insertions(+), 1 deletion(-) create mode 100644 libraries/model-baking/CMakeLists.txt create mode 100644 libraries/model-baking/src/FBXBaker.cpp create mode 100644 libraries/model-baking/src/FBXBaker.h diff --git a/libraries/model-baking/CMakeLists.txt b/libraries/model-baking/CMakeLists.txt new file mode 100644 index 0000000000..45c0350bbe --- /dev/null +++ b/libraries/model-baking/CMakeLists.txt @@ -0,0 +1,7 @@ +set(TARGET_NAME model-baking) + +setup_hifi_library() + +find_package(FBXSDK REQUIRED) +target_link_libraries(${TARGET_NAME} ${FBX_LIBRARIES}) +target_include_directories(${TARGET_NAME} SYSTEM PRIVATE ${FBX_INCLUDE_DIR}) diff --git a/libraries/model-baking/src/FBXBaker.cpp b/libraries/model-baking/src/FBXBaker.cpp new file mode 100644 index 0000000000..af981b471e --- /dev/null +++ b/libraries/model-baking/src/FBXBaker.cpp @@ -0,0 +1,156 @@ +// +// FBXBaker.cpp +// libraries/model-baking/src +// +// Created by Stephen Birarda on 3/30/17. +// Copyright 2017 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 +#include + +#include + +#include "FBXBaker.h" + +Q_LOGGING_CATEGORY(model_baking, "hifi.model-baking"); + +FBXBaker::FBXBaker(std::string fbxPath) : + _fbxPath(fbxPath) +{ + // create an FBX SDK manager + _sdkManager = FbxManager::Create(); +} + +bool FBXBaker::bakeFBX() { + + // load the scene from the FBX file + if (importScene()) { + // enumerate the textures found in the scene and bake them + rewriteAndCollectSceneTextures(); + } else { + return false; + } + + return true; +} + +bool FBXBaker::importScene() { + // create an FBX SDK importer + FbxImporter* importer = FbxImporter::Create(_sdkManager, ""); + + // import the FBX file at the given path + bool importStatus = importer->Initialize(_fbxPath.c_str()); + + if (!importStatus) { + // failed to import the FBX file, print an error and return + qCDebug(model_baking) << "Failed to import FBX file at" << _fbxPath.c_str() << "- error:" << importer->GetStatus().GetErrorString(); + + return false; + } + + // setup a new scene to hold the imported file + _scene = FbxScene::Create(_sdkManager, "bakeScene"); + + // import the file to the created scene + importer->Import(_scene); + + // destroy the importer that is no longer needed + importer->Destroy(); + + return true; +} + +bool FBXBaker::rewriteAndCollectSceneTextures() { + // grab the root node from the scene + FbxNode* rootNode = _scene->GetRootNode(); + + if (rootNode) { + // enumerate the children of the root node + for (int i = 0; i < rootNode->GetChildCount(); ++i) { + FbxNode* node = rootNode->GetChild(i); + + // check if this child is a mesh node + if (node->GetNodeAttribute()->GetAttributeType() == FbxNodeAttribute::eMesh) { + FbxMesh* mesh = (FbxMesh*) node->GetNodeAttribute(); + + // make sure this mesh is valid + if (mesh->GetNode() != nullptr) { + // figure out the number of materials in this mesh + int numMaterials = mesh->GetNode()->GetSrcObjectCount(); + + // enumerate the materials in this mesh + for (int materialIndex = 0; materialIndex < numMaterials; materialIndex++) { + // grab this material + FbxSurfaceMaterial* material = mesh->GetNode()->GetSrcObject(materialIndex); + + if (material) { + // enumerate the textures in this valid material + int textureIndex; + FBXSDK_FOR_EACH_TEXTURE(textureIndex) { + // collect this texture so we know later to bake it + FbxProperty property = material->FindProperty(FbxLayerElement::sTextureChannelNames[textureIndex]); + if (property.IsValid()) { + rewriteAndCollectChannelTextures(property); + } + } + + } + } + + } + } + } + } + + return true; +} + +bool FBXBaker::rewriteAndCollectChannelTextures(FbxProperty& property) { + if (property.IsValid()) { + int textureCount = property.GetSrcObjectCount(); + + // enumerate the textures for this channel + for (int i = 0; i < textureCount; ++i) { + // check if this texture is layered + FbxLayeredTexture* layeredTexture = property.GetSrcObject(i); + if (layeredTexture) { + // enumerate the layers of the layered texture + int numberOfLayers = layeredTexture->GetSrcObjectCount(); + + for (int j = 0; j < numberOfLayers; ++j) { + FbxTexture* texture = layeredTexture->GetSrcObject(j); + rewriteAndCollectTexture(texture); + } + } else { + FbxTexture* texture = property.GetSrcObject(i); + rewriteAndCollectTexture(texture); + } + } + } + + return true; +} + +static const QString BAKED_TEXTURE_DIRECTORY = "textures"; + +bool FBXBaker::rewriteAndCollectTexture(fbxsdk::FbxTexture* texture) { + FbxFileTexture* fileTexture = FbxCast(texture); + if (fileTexture) { + qCDebug(model_baking) << "Flagging" << fileTexture->GetRelativeFileName() << "for bake and re-mapping to .xtk in FBX"; + + // use QFileInfo to easily split up the existing texture filename into its components + QFileInfo textureFileInfo { fileTexture->GetRelativeFileName() }; + + // construct the new baked texture file name + QString bakedTextureFileName { BAKED_TEXTURE_DIRECTORY + "/" + textureFileInfo.baseName() + ".xtk" }; + + // write the new filename into the FBX scene + fileTexture->SetRelativeFileName(bakedTextureFileName.toLocal8Bit()); + } + + return true; +} diff --git a/libraries/model-baking/src/FBXBaker.h b/libraries/model-baking/src/FBXBaker.h new file mode 100644 index 0000000000..4ebea6f2fa --- /dev/null +++ b/libraries/model-baking/src/FBXBaker.h @@ -0,0 +1,38 @@ +// +// FBXBaker.h +// libraries/model-baking/src +// +// Created by Stephen Birarda on 3/30/17. +// Copyright 2017 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_FBXBaker_h +#define hifi_FBXBaker_h + +#include + +#include + +Q_DECLARE_LOGGING_CATEGORY(model_baking) + +class FBXBaker { + +public: + FBXBaker(std::string fbxPath); + bool bakeFBX(); + +private: + bool importScene(); + bool rewriteAndCollectSceneTextures(); + bool rewriteAndCollectChannelTextures(FbxProperty& property); + bool rewriteAndCollectTexture(FbxTexture* texture); + + std::string _fbxPath; + FbxManager* _sdkManager; + FbxScene* _scene { nullptr }; +}; + +#endif // hifi_FBXBaker_h diff --git a/tools/oven/CMakeLists.txt b/tools/oven/CMakeLists.txt index 5244b68217..473fa707f1 100644 --- a/tools/oven/CMakeLists.txt +++ b/tools/oven/CMakeLists.txt @@ -2,4 +2,4 @@ set(TARGET_NAME oven) setup_hifi_project() -find_package(FBXSDK REQUIRED) +link_hifi_libraries(model-baking) From 86208f237b4c31d147f4780c4046d9c905b11388 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 30 Mar 2017 15:17:07 -0700 Subject: [PATCH 04/71] constantize the baked texture extension --- libraries/model-baking/src/FBXBaker.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libraries/model-baking/src/FBXBaker.cpp b/libraries/model-baking/src/FBXBaker.cpp index af981b471e..e4de73b991 100644 --- a/libraries/model-baking/src/FBXBaker.cpp +++ b/libraries/model-baking/src/FBXBaker.cpp @@ -136,6 +136,7 @@ bool FBXBaker::rewriteAndCollectChannelTextures(FbxProperty& property) { } static const QString BAKED_TEXTURE_DIRECTORY = "textures"; +static const QString BAKED_TEXTURE_EXT = ".xtk"; bool FBXBaker::rewriteAndCollectTexture(fbxsdk::FbxTexture* texture) { FbxFileTexture* fileTexture = FbxCast(texture); @@ -146,7 +147,7 @@ bool FBXBaker::rewriteAndCollectTexture(fbxsdk::FbxTexture* texture) { QFileInfo textureFileInfo { fileTexture->GetRelativeFileName() }; // construct the new baked texture file name - QString bakedTextureFileName { BAKED_TEXTURE_DIRECTORY + "/" + textureFileInfo.baseName() + ".xtk" }; + QString bakedTextureFileName { BAKED_TEXTURE_DIRECTORY + "/" + textureFileInfo.baseName() + BAKED_TEXTURE_EXT }; // write the new filename into the FBX scene fileTexture->SetRelativeFileName(bakedTextureFileName.toLocal8Bit()); From 32c348eb3de6c04f1c464cce5d2706c3d7d522d0 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 5 Apr 2017 11:19:51 -0700 Subject: [PATCH 05/71] use SDK 2017 with new texture discovery strategy --- cmake/modules/FindFBXSDK.cmake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmake/modules/FindFBXSDK.cmake b/cmake/modules/FindFBXSDK.cmake index 7f6a424aa1..d6e9ed801d 100644 --- a/cmake/modules/FindFBXSDK.cmake +++ b/cmake/modules/FindFBXSDK.cmake @@ -18,9 +18,9 @@ if (NOT FBX_VERSION) if (WIN32) - set(FBX_VERSION 2017.1) + set(FBX_VERSION 2016.1.1) else() - set(FBX_VERSION 2017.0.1) + set(FBX_VERSION 2016.1.1) endif() endif() From 711938fb3d6702a95796be2e90867c07c5d9d9d3 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 5 Apr 2017 11:20:05 -0700 Subject: [PATCH 06/71] lay async foundation for FBXBaker --- libraries/model-baking/src/FBXBaker.cpp | 197 +++++++++++++----------- libraries/model-baking/src/FBXBaker.h | 39 +++-- tools/oven/src/main.cpp | 6 + 3 files changed, 138 insertions(+), 104 deletions(-) diff --git a/libraries/model-baking/src/FBXBaker.cpp b/libraries/model-baking/src/FBXBaker.cpp index e4de73b991..41fa0e1a12 100644 --- a/libraries/model-baking/src/FBXBaker.cpp +++ b/libraries/model-baking/src/FBXBaker.cpp @@ -13,29 +13,44 @@ #include #include +#include #include "FBXBaker.h" Q_LOGGING_CATEGORY(model_baking, "hifi.model-baking"); -FBXBaker::FBXBaker(std::string fbxPath) : +FBXBaker::FBXBaker(QUrl fbxPath) : _fbxPath(fbxPath) { // create an FBX SDK manager _sdkManager = FbxManager::Create(); } -bool FBXBaker::bakeFBX() { +FBXBaker::~FBXBaker() { + _sdkManager->Destroy(); +} - // load the scene from the FBX file - if (importScene()) { - // enumerate the textures found in the scene and bake them - rewriteAndCollectSceneTextures(); +void FBXBaker::start() { + // check if the FBX is local or first needs to be downloaded + if (_fbxPath.isLocalFile()) { + // local file, bake now + bake(); } else { - return false; + // remote file, kick off a download } +} - return true; +void FBXBaker::bake() { + // (1) load the scene from the FBX file + // (2) enumerate the textures found in the scene and bake them + // (3) export the FBX with re-written texture references + // (4) enumerate the collected texture paths and bake the textures + + // a failure at any step along the way stops the chain + importScene() && rewriteAndCollectSceneTextures() && exportScene() && bakeTextures(); + + // emit a signal saying that we are done, with whatever errors were produced + emit finished(_errorList); } bool FBXBaker::importScene() { @@ -43,11 +58,11 @@ bool FBXBaker::importScene() { FbxImporter* importer = FbxImporter::Create(_sdkManager, ""); // import the FBX file at the given path - bool importStatus = importer->Initialize(_fbxPath.c_str()); + bool importStatus = importer->Initialize(_fbxPath.toLocalFile().toLocal8Bit().data()); if (!importStatus) { - // failed to import the FBX file, print an error and return - qCDebug(model_baking) << "Failed to import FBX file at" << _fbxPath.c_str() << "- error:" << importer->GetStatus().GetErrorString(); + // failed to initialize importer, print an error and return + qCDebug(model_baking) << "Failed to import FBX file at" << _fbxPath << "- error:" << importer->GetStatus().GetErrorString(); return false; } @@ -64,93 +79,89 @@ bool FBXBaker::importScene() { return true; } -bool FBXBaker::rewriteAndCollectSceneTextures() { - // grab the root node from the scene - FbxNode* rootNode = _scene->GetRootNode(); - - if (rootNode) { - // enumerate the children of the root node - for (int i = 0; i < rootNode->GetChildCount(); ++i) { - FbxNode* node = rootNode->GetChild(i); - - // check if this child is a mesh node - if (node->GetNodeAttribute()->GetAttributeType() == FbxNodeAttribute::eMesh) { - FbxMesh* mesh = (FbxMesh*) node->GetNodeAttribute(); - - // make sure this mesh is valid - if (mesh->GetNode() != nullptr) { - // figure out the number of materials in this mesh - int numMaterials = mesh->GetNode()->GetSrcObjectCount(); - - // enumerate the materials in this mesh - for (int materialIndex = 0; materialIndex < numMaterials; materialIndex++) { - // grab this material - FbxSurfaceMaterial* material = mesh->GetNode()->GetSrcObject(materialIndex); - - if (material) { - // enumerate the textures in this valid material - int textureIndex; - FBXSDK_FOR_EACH_TEXTURE(textureIndex) { - // collect this texture so we know later to bake it - FbxProperty property = material->FindProperty(FbxLayerElement::sTextureChannelNames[textureIndex]); - if (property.IsValid()) { - rewriteAndCollectChannelTextures(property); - } - } - - } - } - - } - } - } - } - - return true; -} - -bool FBXBaker::rewriteAndCollectChannelTextures(FbxProperty& property) { - if (property.IsValid()) { - int textureCount = property.GetSrcObjectCount(); - - // enumerate the textures for this channel - for (int i = 0; i < textureCount; ++i) { - // check if this texture is layered - FbxLayeredTexture* layeredTexture = property.GetSrcObject(i); - if (layeredTexture) { - // enumerate the layers of the layered texture - int numberOfLayers = layeredTexture->GetSrcObjectCount(); - - for (int j = 0; j < numberOfLayers; ++j) { - FbxTexture* texture = layeredTexture->GetSrcObject(j); - rewriteAndCollectTexture(texture); - } - } else { - FbxTexture* texture = property.GetSrcObject(i); - rewriteAndCollectTexture(texture); - } - } - } - - return true; -} - static const QString BAKED_TEXTURE_DIRECTORY = "textures"; -static const QString BAKED_TEXTURE_EXT = ".xtk"; +static const QString BAKED_TEXTURE_EXT = ".ktx"; -bool FBXBaker::rewriteAndCollectTexture(fbxsdk::FbxTexture* texture) { - FbxFileTexture* fileTexture = FbxCast(texture); - if (fileTexture) { - qCDebug(model_baking) << "Flagging" << fileTexture->GetRelativeFileName() << "for bake and re-mapping to .xtk in FBX"; +static const QString EXPORT_PATH { "/Users/birarda/code/hifi/lod/test-oven/export/DiscGolfBasket.ktx.fbx" }; - // use QFileInfo to easily split up the existing texture filename into its components - QFileInfo textureFileInfo { fileTexture->GetRelativeFileName() }; +bool FBXBaker::rewriteAndCollectSceneTextures() { + // get a count of the textures used in the scene + int numTextures = _scene->GetTextureCount(); - // construct the new baked texture file name - QString bakedTextureFileName { BAKED_TEXTURE_DIRECTORY + "/" + textureFileInfo.baseName() + BAKED_TEXTURE_EXT }; + // enumerate the textures in the scene + for (int i = 0; i < numTextures; i++) { + // grab each file texture + FbxFileTexture* fileTexture = FbxCast(_scene->GetTexture(i)); - // write the new filename into the FBX scene - fileTexture->SetRelativeFileName(bakedTextureFileName.toLocal8Bit()); + if (fileTexture) { + // use QFileInfo to easily split up the existing texture filename into its components + QFileInfo textureFileInfo { fileTexture->GetFileName() }; + + // make sure this texture points to something + if (!textureFileInfo.filePath().isEmpty()) { + + // construct the new baked texture file name and file path + QString bakedTextureFileName { textureFileInfo.baseName() + BAKED_TEXTURE_EXT }; + QString bakedTextureFilePath { QFileInfo(EXPORT_PATH).absolutePath() + "/textures/" + bakedTextureFileName }; + + qCDebug(model_baking).noquote() << "Re-mapping" << fileTexture->GetFileName() << "to" << bakedTextureFilePath; + + // write the new filename into the FBX scene + fileTexture->SetFileName(bakedTextureFilePath.toLocal8Bit()); + + // add the texture to the list of textures needing to be baked + if (textureFileInfo.exists() && textureFileInfo.isFile()) { + // append the URL to the local texture that we have confirmed exists + _unbakedTextures.append(QUrl::fromLocalFile(textureFileInfo.absoluteFilePath())); + } else { + + } + } + } + } + + return true; +} + +bool FBXBaker::exportScene() { + // setup the exporter + FbxExporter* exporter = FbxExporter::Create(_sdkManager, ""); + + bool exportStatus = exporter->Initialize(EXPORT_PATH.toLocal8Bit().data()); + + if (!exportStatus) { + // failed to initialize exporter, print an error and return + qCDebug(model_baking) << "Failed to export FBX file at" << _fbxPath + << "to" << EXPORT_PATH << "- error:" << exporter->GetStatus().GetErrorString(); + + return false; + } + + // export the scene + exporter->Export(_scene); + + return true; +} + +bool FBXBaker::bakeTextures() { + // enumerate the list of unbaked textures + foreach(const QUrl& textureUrl, _unbakedTextures) { + qCDebug(model_baking) << "Baking texture at" << textureUrl; + + if (textureUrl.isLocalFile()) { + // this is a local file that we've already determined is available on the filesystem + + // load the file + QFile localTexture { textureUrl.toLocalFile() }; + + if (!localTexture.open(QIODevice::ReadOnly)) { + // add an error to the list stating that this texture couldn't be baked because it could not be loaded + } + + // call the image library to produce a compressed KTX for this image + } else { + // this is a remote texture that we'll need to download first + } } return true; diff --git a/libraries/model-baking/src/FBXBaker.h b/libraries/model-baking/src/FBXBaker.h index 4ebea6f2fa..0ae8311522 100644 --- a/libraries/model-baking/src/FBXBaker.h +++ b/libraries/model-baking/src/FBXBaker.h @@ -12,27 +12,44 @@ #ifndef hifi_FBXBaker_h #define hifi_FBXBaker_h -#include - #include +#include Q_DECLARE_LOGGING_CATEGORY(model_baking) -class FBXBaker { +namespace fbxsdk { + class FbxManager; + class FbxProperty; + class FbxScene; + class FbxTexture; +} +class FBXBaker : public QObject { + Q_OBJECT public: - FBXBaker(std::string fbxPath); - bool bakeFBX(); + FBXBaker(QUrl fbxPath); + ~FBXBaker(); + + void start(); + +signals: + void finished(QStringList errorList); private: + void bake(); bool importScene(); bool rewriteAndCollectSceneTextures(); - bool rewriteAndCollectChannelTextures(FbxProperty& property); - bool rewriteAndCollectTexture(FbxTexture* texture); - - std::string _fbxPath; - FbxManager* _sdkManager; - FbxScene* _scene { nullptr }; + bool exportScene(); + bool bakeTextures(); + bool bakeTexture(); + + QUrl _fbxPath; + fbxsdk::FbxManager* _sdkManager; + fbxsdk::FbxScene* _scene { nullptr }; + + QStringList _errorList; + + QList _unbakedTextures; }; #endif // hifi_FBXBaker_h diff --git a/tools/oven/src/main.cpp b/tools/oven/src/main.cpp index 831ba00328..e74df068dd 100644 --- a/tools/oven/src/main.cpp +++ b/tools/oven/src/main.cpp @@ -8,7 +8,13 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +#include + int main (int argc, char** argv) { + + FBXBaker baker(QUrl("file:///Users/birarda/code/hifi/lod/test-oven/DiscGolfBasket.fbx")); + baker.start(); + return 0; } From 1b30afa03ebd72c1b6d33bf821dc8a9acfffc80b Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 5 Apr 2017 15:20:19 -0700 Subject: [PATCH 07/71] add basic Oven QCoreApplication, start to output results --- libraries/model-baking/CMakeLists.txt | 2 + libraries/model-baking/src/FBXBaker.cpp | 123 +++++++++++++++++++++--- libraries/model-baking/src/FBXBaker.h | 22 ++++- tools/oven/src/Oven.cpp | 23 +++++ tools/oven/src/Oven.h | 30 ++++++ tools/oven/src/main.cpp | 10 +- 6 files changed, 182 insertions(+), 28 deletions(-) create mode 100644 tools/oven/src/Oven.cpp create mode 100644 tools/oven/src/Oven.h diff --git a/libraries/model-baking/CMakeLists.txt b/libraries/model-baking/CMakeLists.txt index 45c0350bbe..487b8536c9 100644 --- a/libraries/model-baking/CMakeLists.txt +++ b/libraries/model-baking/CMakeLists.txt @@ -2,6 +2,8 @@ set(TARGET_NAME model-baking) setup_hifi_library() +link_hifi_libraries(networking) + find_package(FBXSDK REQUIRED) target_link_libraries(${TARGET_NAME} ${FBX_LIBRARIES}) target_include_directories(${TARGET_NAME} SYSTEM PRIVATE ${FBX_INCLUDE_DIR}) diff --git a/libraries/model-baking/src/FBXBaker.cpp b/libraries/model-baking/src/FBXBaker.cpp index 41fa0e1a12..0be4222f59 100644 --- a/libraries/model-baking/src/FBXBaker.cpp +++ b/libraries/model-baking/src/FBXBaker.cpp @@ -10,33 +10,117 @@ // #include -#include +#include #include -#include + +#include #include "FBXBaker.h" Q_LOGGING_CATEGORY(model_baking, "hifi.model-baking"); -FBXBaker::FBXBaker(QUrl fbxPath) : - _fbxPath(fbxPath) +FBXBaker::FBXBaker(QUrl fbxURL, QString baseOutputPath) : + _fbxURL(fbxURL), + _baseOutputPath(baseOutputPath) { // create an FBX SDK manager _sdkManager = FbxManager::Create(); + + // grab the name of the FBX from the URL, this is used for folder output names + auto fileName = fbxURL.fileName(); + _fbxName = fileName.left(fileName.indexOf('.')); } FBXBaker::~FBXBaker() { _sdkManager->Destroy(); } + void FBXBaker::start() { + // setup the output folder for the results of this bake + if (!setupOutputFolder()) { + return; + } + // check if the FBX is local or first needs to be downloaded - if (_fbxPath.isLocalFile()) { - // local file, bake now + if (_fbxURL.isLocalFile()) { + // load up the local file + QFile localFBX { _fbxURL.toLocalFile() }; + + // make a copy in the output folder + localFBX.copy(_uniqueOutputPath + _fbxURL.fileName()); + + // start the bake now that we have everything in place bake(); } else { // remote file, kick off a download + auto& networkAccessManager = NetworkAccessManager::getInstance(); + + QNetworkRequest networkRequest; + + // setup the request to follow re-directs and always hit the network + networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); + networkRequest.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork); + + networkRequest.setUrl(_fbxURL); + + qCDebug(model_baking) << "Downloading" << _fbxURL; + + auto networkReply = networkAccessManager.get(networkRequest); + connect(networkReply, &QNetworkReply::finished, this, &FBXBaker::handleFBXNetworkReply); + } +} + +bool FBXBaker::setupOutputFolder() { + // construct the output path using the name of the fbx and the base output path + _uniqueOutputPath = _baseOutputPath + "/" + _fbxName + "/"; + + // make sure there isn't already an output directory using the same name + int iteration = 0; + + while (QDir(_uniqueOutputPath).exists()) { + _uniqueOutputPath = _baseOutputPath + "/" + _fbxName + "-" + QString::number(++iteration) + "/"; + } + + qCDebug(model_baking) << "Creating FBX output folder" << _uniqueOutputPath; + + // attempt to make the output folder + if (!QDir().mkdir(_uniqueOutputPath)) { + qCWarning(model_baking) << "Failed to created FBX output folder" << _uniqueOutputPath; + + emit finished(); + return false; + } + + return true; +} + +void FBXBaker::handleFBXNetworkReply() { + QNetworkReply* requestReply = qobject_cast(sender()); + + if (requestReply->error() == QNetworkReply::NoError) { + qCDebug(model_baking) << "Downloaded" << _fbxURL; + + // grab the contents of the reply and make a copy in the output folder + QFile copyOfOriginal(_uniqueOutputPath + _fbxURL.fileName()); + + qDebug(model_baking) << "Writing copy of original FBX to" << copyOfOriginal.fileName(); + + if (!copyOfOriginal.open(QIODevice::WriteOnly) || (copyOfOriginal.write(requestReply->readAll()) == -1)) { + + // add an error to the error list for this FBX stating that a duplicate of the original could not be made + emit finished(); + return; + } + + // close that file now that we are done writing to it + copyOfOriginal.close(); + + // kick off the bake process now that everything is ready to go + bake(); + } else { + qDebug() << "ERROR DOWNLOADING FBX" << requestReply->errorString(); } } @@ -46,25 +130,31 @@ void FBXBaker::bake() { // (3) export the FBX with re-written texture references // (4) enumerate the collected texture paths and bake the textures + qCDebug(model_baking) << "Baking" << _fbxURL; + // a failure at any step along the way stops the chain importScene() && rewriteAndCollectSceneTextures() && exportScene() && bakeTextures(); // emit a signal saying that we are done, with whatever errors were produced - emit finished(_errorList); + emit finished(); } bool FBXBaker::importScene() { // create an FBX SDK importer FbxImporter* importer = FbxImporter::Create(_sdkManager, ""); - // import the FBX file at the given path - bool importStatus = importer->Initialize(_fbxPath.toLocalFile().toLocal8Bit().data()); + // import the copy of the original FBX file + QString originalCopyPath = _uniqueOutputPath + _fbxURL.fileName(); + bool importStatus = importer->Initialize(originalCopyPath.toLocal8Bit().data()); if (!importStatus) { // failed to initialize importer, print an error and return - qCDebug(model_baking) << "Failed to import FBX file at" << _fbxPath << "- error:" << importer->GetStatus().GetErrorString(); + qCCritical(model_baking) << "Failed to import FBX file at" << _fbxURL + << "- error:" << importer->GetStatus().GetErrorString(); return false; + } else { + qCDebug(model_baking) << "Imported" << _fbxURL << "to FbxScene"; } // setup a new scene to hold the imported file @@ -82,8 +172,6 @@ bool FBXBaker::importScene() { static const QString BAKED_TEXTURE_DIRECTORY = "textures"; static const QString BAKED_TEXTURE_EXT = ".ktx"; -static const QString EXPORT_PATH { "/Users/birarda/code/hifi/lod/test-oven/export/DiscGolfBasket.ktx.fbx" }; - bool FBXBaker::rewriteAndCollectSceneTextures() { // get a count of the textures used in the scene int numTextures = _scene->GetTextureCount(); @@ -102,7 +190,7 @@ bool FBXBaker::rewriteAndCollectSceneTextures() { // construct the new baked texture file name and file path QString bakedTextureFileName { textureFileInfo.baseName() + BAKED_TEXTURE_EXT }; - QString bakedTextureFilePath { QFileInfo(EXPORT_PATH).absolutePath() + "/textures/" + bakedTextureFileName }; + QString bakedTextureFilePath { _uniqueOutputPath + "ktx/" + bakedTextureFileName }; qCDebug(model_baking).noquote() << "Re-mapping" << fileTexture->GetFileName() << "to" << bakedTextureFilePath; @@ -127,12 +215,13 @@ bool FBXBaker::exportScene() { // setup the exporter FbxExporter* exporter = FbxExporter::Create(_sdkManager, ""); - bool exportStatus = exporter->Initialize(EXPORT_PATH.toLocal8Bit().data()); + auto rewrittenFBXPath = _uniqueOutputPath + _fbxName + ".ktx.fbx"; + bool exportStatus = exporter->Initialize(rewrittenFBXPath.toLocal8Bit().data()); if (!exportStatus) { // failed to initialize exporter, print an error and return - qCDebug(model_baking) << "Failed to export FBX file at" << _fbxPath - << "to" << EXPORT_PATH << "- error:" << exporter->GetStatus().GetErrorString(); + qCCritical(model_baking) << "Failed to export FBX file at" << _fbxURL + << "to" << rewrittenFBXPath << "- error:" << exporter->GetStatus().GetErrorString(); return false; } @@ -140,6 +229,8 @@ bool FBXBaker::exportScene() { // export the scene exporter->Export(_scene); + qCDebug(model_baking) << "Exported" << _fbxURL << "with re-written paths to" << rewrittenFBXPath; + return true; } diff --git a/libraries/model-baking/src/FBXBaker.h b/libraries/model-baking/src/FBXBaker.h index 0ae8311522..3bd721253f 100644 --- a/libraries/model-baking/src/FBXBaker.h +++ b/libraries/model-baking/src/FBXBaker.h @@ -12,8 +12,10 @@ #ifndef hifi_FBXBaker_h #define hifi_FBXBaker_h -#include -#include +#include +#include +#include +#include Q_DECLARE_LOGGING_CATEGORY(model_baking) @@ -27,23 +29,33 @@ namespace fbxsdk { class FBXBaker : public QObject { Q_OBJECT public: - FBXBaker(QUrl fbxPath); + FBXBaker(QUrl fbxURL, QString baseOutputPath); ~FBXBaker(); void start(); signals: - void finished(QStringList errorList); + void finished(); +private slots: + void handleFBXNetworkReply(); + private: void bake(); + + bool setupOutputFolder(); bool importScene(); bool rewriteAndCollectSceneTextures(); bool exportScene(); bool bakeTextures(); bool bakeTexture(); - QUrl _fbxPath; + QUrl _fbxURL; + QString _fbxName; + + QString _baseOutputPath; + QString _uniqueOutputPath; + fbxsdk::FbxManager* _sdkManager; fbxsdk::FbxScene* _scene { nullptr }; diff --git a/tools/oven/src/Oven.cpp b/tools/oven/src/Oven.cpp new file mode 100644 index 0000000000..b517da8151 --- /dev/null +++ b/tools/oven/src/Oven.cpp @@ -0,0 +1,23 @@ +// +// Oven.cpp +// tools/oven/src +// +// Created by Stephen Birarda on 4/5/17. +// Copyright 2017 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 + +#include "Oven.h" + +static const QString OUTPUT_FOLDER = "/Users/birarda/code/hifi/lod/test-oven/export"; + +Oven::Oven(int argc, char* argv[]) : + QCoreApplication(argc, argv), + _testBake(QUrl("file:///Users/birarda/code/hifi/lod/test-oven/DiscGolfBasket.fbx"), OUTPUT_FOLDER) +{ + _testBake.start(); +} diff --git a/tools/oven/src/Oven.h b/tools/oven/src/Oven.h new file mode 100644 index 0000000000..72de77b889 --- /dev/null +++ b/tools/oven/src/Oven.h @@ -0,0 +1,30 @@ +// +// Oven.h +// tools/oven/src +// +// Created by Stephen Birarda on 4/5/17. +// Copyright 2017 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_Oven_h +#define hifi_Oven_h + +#include + +#include + +class Oven : public QCoreApplication { + Q_OBJECT + +public: + Oven(int argc, char* argv[]); + +private: + FBXBaker _testBake; +}; + + +#endif // hifi_Oven_h diff --git a/tools/oven/src/main.cpp b/tools/oven/src/main.cpp index e74df068dd..9c778245b5 100644 --- a/tools/oven/src/main.cpp +++ b/tools/oven/src/main.cpp @@ -8,13 +8,9 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -#include - +#include "Oven.h" int main (int argc, char** argv) { - - FBXBaker baker(QUrl("file:///Users/birarda/code/hifi/lod/test-oven/DiscGolfBasket.fbx")); - baker.start(); - - return 0; + Oven app(argc, argv); + return app.exec(); } From 6af7ecf47b7b3c553249456b14dbd517fe701fcd Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 5 Apr 2017 17:03:17 -0700 Subject: [PATCH 08/71] add subfolders in output, add logic to find linked textures --- libraries/model-baking/src/FBXBaker.cpp | 79 +++++++++++++++++++++---- libraries/model-baking/src/FBXBaker.h | 4 +- 2 files changed, 72 insertions(+), 11 deletions(-) diff --git a/libraries/model-baking/src/FBXBaker.cpp b/libraries/model-baking/src/FBXBaker.cpp index 0be4222f59..589e74ab77 100644 --- a/libraries/model-baking/src/FBXBaker.cpp +++ b/libraries/model-baking/src/FBXBaker.cpp @@ -36,8 +36,16 @@ FBXBaker::~FBXBaker() { _sdkManager->Destroy(); } +static const QString BAKED_OUTPUT_SUBFOLDER = "baked/"; +static const QString ORIGINAL_OUTPUT_SUBFOLDER = "original/"; + +QString FBXBaker::pathToCopyOfOriginal() const { + return _uniqueOutputPath + ORIGINAL_OUTPUT_SUBFOLDER + _fbxURL.fileName(); +} void FBXBaker::start() { + qCDebug(model_baking) << "Baking" << _fbxURL; + // setup the output folder for the results of this bake if (!setupOutputFolder()) { return; @@ -49,7 +57,7 @@ void FBXBaker::start() { QFile localFBX { _fbxURL.toLocalFile() }; // make a copy in the output folder - localFBX.copy(_uniqueOutputPath + _fbxURL.fileName()); + localFBX.copy(pathToCopyOfOriginal()); // start the bake now that we have everything in place bake(); @@ -87,7 +95,16 @@ bool FBXBaker::setupOutputFolder() { // attempt to make the output folder if (!QDir().mkdir(_uniqueOutputPath)) { - qCWarning(model_baking) << "Failed to created FBX output folder" << _uniqueOutputPath; + qCCritical(model_baking) << "Failed to create FBX output folder" << _uniqueOutputPath; + + emit finished(); + return false; + } + + // make the baked and original sub-folders used during export + QDir uniqueOutputDir = _uniqueOutputPath; + if (!uniqueOutputDir.mkdir(BAKED_OUTPUT_SUBFOLDER) || !uniqueOutputDir.mkdir(ORIGINAL_OUTPUT_SUBFOLDER)) { + qCCritical(model_baking) << "Failed to create baked/original subfolders in" << _uniqueOutputPath; emit finished(); return false; @@ -103,7 +120,7 @@ void FBXBaker::handleFBXNetworkReply() { qCDebug(model_baking) << "Downloaded" << _fbxURL; // grab the contents of the reply and make a copy in the output folder - QFile copyOfOriginal(_uniqueOutputPath + _fbxURL.fileName()); + QFile copyOfOriginal(pathToCopyOfOriginal()); qDebug(model_baking) << "Writing copy of original FBX to" << copyOfOriginal.fileName(); @@ -130,8 +147,6 @@ void FBXBaker::bake() { // (3) export the FBX with re-written texture references // (4) enumerate the collected texture paths and bake the textures - qCDebug(model_baking) << "Baking" << _fbxURL; - // a failure at any step along the way stops the chain importScene() && rewriteAndCollectSceneTextures() && exportScene() && bakeTextures(); @@ -144,7 +159,7 @@ bool FBXBaker::importScene() { FbxImporter* importer = FbxImporter::Create(_sdkManager, ""); // import the copy of the original FBX file - QString originalCopyPath = _uniqueOutputPath + _fbxURL.fileName(); + QString originalCopyPath = pathToCopyOfOriginal(); bool importStatus = importer->Initialize(originalCopyPath.toLocal8Bit().data()); if (!importStatus) { @@ -169,7 +184,7 @@ bool FBXBaker::importScene() { return true; } -static const QString BAKED_TEXTURE_DIRECTORY = "textures"; +static const QString BAKED_TEXTURE_DIRECTORY = "textures/"; static const QString BAKED_TEXTURE_EXT = ".ktx"; bool FBXBaker::rewriteAndCollectSceneTextures() { @@ -190,7 +205,7 @@ bool FBXBaker::rewriteAndCollectSceneTextures() { // construct the new baked texture file name and file path QString bakedTextureFileName { textureFileInfo.baseName() + BAKED_TEXTURE_EXT }; - QString bakedTextureFilePath { _uniqueOutputPath + "ktx/" + bakedTextureFileName }; + QString bakedTextureFilePath { _uniqueOutputPath + BAKED_OUTPUT_SUBFOLDER + BAKED_TEXTURE_DIRECTORY + bakedTextureFileName }; qCDebug(model_baking).noquote() << "Re-mapping" << fileTexture->GetFileName() << "to" << bakedTextureFilePath; @@ -200,9 +215,51 @@ bool FBXBaker::rewriteAndCollectSceneTextures() { // add the texture to the list of textures needing to be baked if (textureFileInfo.exists() && textureFileInfo.isFile()) { // append the URL to the local texture that we have confirmed exists - _unbakedTextures.append(QUrl::fromLocalFile(textureFileInfo.absoluteFilePath())); + _unbakedTextures.insert(QUrl::fromLocalFile(textureFileInfo.absoluteFilePath())); } else { + // external texture that we'll need to download or find + // first check if it the RelativePath to the texture in the FBX was relative + QString relativeFileName = fileTexture->GetRelativeFileName(); + auto apparentRelativePath = QFileInfo(relativeFileName.replace("\\", "/")); + +#ifndef Q_OS_WIN + // it turns out that paths that start with a drive letter and a colon appear to QFileInfo + // as a relative path on UNIX systems - we perform a special check here to handle that case + bool isAbsolute = relativeFileName[1] == ':' || apparentRelativePath.isAbsolute(); +#else + bool isAbsolute = apparentRelativePath.isAbsolute(); +#endif + + if (isAbsolute) { + // this is a relative file path which will require different handling + // depending on the location of the original FBX + if (_fbxURL.isLocalFile()) { + // since the loaded FBX is loaded, first check if we actually have the texture locally + // at the absolute path + if (apparentRelativePath.exists() && apparentRelativePath.isFile()) { + // the absolute path we ran into for the texture in the FBX exists on this machine + // so use that file + _unbakedTextures.insert(QUrl::fromLocalFile(apparentRelativePath.absoluteFilePath())); + } else { + // we didn't find the texture on this machine at the absolute path + // so assume that it is right beside the FBX to match the behaviour of interface + _unbakedTextures.insert(_fbxURL.resolved(apparentRelativePath.fileName())); + } + } else { + // the original FBX was remote and downloaded + + // since this "relative" texture path is actually absolute, we have to assume it is beside the FBX + // which matches the behaviour of Interface + + // append that path to our list of unbaked textures + _unbakedTextures.insert(_fbxURL.resolved(apparentRelativePath.fileName())); + } + } else { + // simply construct a URL with the relative path to the asset, locally or remotely + // and append that to the list of unbaked textures + _unbakedTextures.insert(_fbxURL.resolved(apparentRelativePath.filePath())); + } } } } @@ -211,11 +268,13 @@ bool FBXBaker::rewriteAndCollectSceneTextures() { return true; } +static const QString BAKED_FBX_EXTENSION = ".baked.fbx"; + bool FBXBaker::exportScene() { // setup the exporter FbxExporter* exporter = FbxExporter::Create(_sdkManager, ""); - auto rewrittenFBXPath = _uniqueOutputPath + _fbxName + ".ktx.fbx"; + auto rewrittenFBXPath = _uniqueOutputPath + BAKED_OUTPUT_SUBFOLDER + _fbxName + BAKED_FBX_EXTENSION; bool exportStatus = exporter->Initialize(rewrittenFBXPath.toLocal8Bit().data()); if (!exportStatus) { diff --git a/libraries/model-baking/src/FBXBaker.h b/libraries/model-baking/src/FBXBaker.h index 3bd721253f..da1bf10d34 100644 --- a/libraries/model-baking/src/FBXBaker.h +++ b/libraries/model-baking/src/FBXBaker.h @@ -50,6 +50,8 @@ private: bool bakeTextures(); bool bakeTexture(); + QString pathToCopyOfOriginal() const; + QUrl _fbxURL; QString _fbxName; @@ -61,7 +63,7 @@ private: QStringList _errorList; - QList _unbakedTextures; + QSet _unbakedTextures; }; #endif // hifi_FBXBaker_h From c9bc22334f3ba08578706ce32a24cf2b968da710 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 5 Apr 2017 17:12:29 -0700 Subject: [PATCH 09/71] add embedded media folder removal to FBXBaker --- libraries/model-baking/src/FBXBaker.cpp | 11 ++++++++++- libraries/model-baking/src/FBXBaker.h | 1 + 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/libraries/model-baking/src/FBXBaker.cpp b/libraries/model-baking/src/FBXBaker.cpp index 589e74ab77..0ff6b39704 100644 --- a/libraries/model-baking/src/FBXBaker.cpp +++ b/libraries/model-baking/src/FBXBaker.cpp @@ -148,7 +148,7 @@ void FBXBaker::bake() { // (4) enumerate the collected texture paths and bake the textures // a failure at any step along the way stops the chain - importScene() && rewriteAndCollectSceneTextures() && exportScene() && bakeTextures(); + importScene() && rewriteAndCollectSceneTextures() && exportScene() && bakeTextures() && removeEmbeddedMediaFolder(); // emit a signal saying that we are done, with whatever errors were produced emit finished(); @@ -316,3 +316,12 @@ bool FBXBaker::bakeTextures() { return true; } + +bool FBXBaker::removeEmbeddedMediaFolder() { + // now that the bake is complete, remove the embedded media folder produced by the FBX SDK when it imports an FBX + auto embeddedMediaFolderName = _fbxURL.fileName().replace(".fbx", ".fbm"); + QDir(_uniqueOutputPath + ORIGINAL_OUTPUT_SUBFOLDER + embeddedMediaFolderName).removeRecursively(); + + // we always return true because a failure to delete the embedded media folder is not a failure of the bake + return true; +} diff --git a/libraries/model-baking/src/FBXBaker.h b/libraries/model-baking/src/FBXBaker.h index da1bf10d34..a6cf64ebaf 100644 --- a/libraries/model-baking/src/FBXBaker.h +++ b/libraries/model-baking/src/FBXBaker.h @@ -49,6 +49,7 @@ private: bool exportScene(); bool bakeTextures(); bool bakeTexture(); + bool removeEmbeddedMediaFolder(); QString pathToCopyOfOriginal() const; From 95ce9d1b25d549a40b2727e316d93f5933ecbca7 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 5 Apr 2017 17:32:15 -0700 Subject: [PATCH 10/71] add handling for multiple textures with same base name --- libraries/model-baking/src/FBXBaker.cpp | 40 ++++++++++++++++++++----- libraries/model-baking/src/FBXBaker.h | 3 +- 2 files changed, 34 insertions(+), 9 deletions(-) diff --git a/libraries/model-baking/src/FBXBaker.cpp b/libraries/model-baking/src/FBXBaker.cpp index 0ff6b39704..6717cbf69e 100644 --- a/libraries/model-baking/src/FBXBaker.cpp +++ b/libraries/model-baking/src/FBXBaker.cpp @@ -204,7 +204,24 @@ bool FBXBaker::rewriteAndCollectSceneTextures() { if (!textureFileInfo.filePath().isEmpty()) { // construct the new baked texture file name and file path - QString bakedTextureFileName { textureFileInfo.baseName() + BAKED_TEXTURE_EXT }; + + // first make sure we have a unique base name for this texture + // in case another texture referenced by this model has the same base name + auto& nameMatches = _textureNameMatchCount[textureFileInfo.baseName()]; + + QString bakedTextureFileName { textureFileInfo.baseName() }; + + if (nameMatches > 0) { + // there are already nameMatches texture with this name + // append - and that number to our baked texture file name so that it is unique + bakedTextureFileName += "-" + QString::number(nameMatches); + } + + bakedTextureFileName += BAKED_TEXTURE_EXT; + + // increment the number of name matches + ++nameMatches; + QString bakedTextureFilePath { _uniqueOutputPath + BAKED_OUTPUT_SUBFOLDER + BAKED_TEXTURE_DIRECTORY + bakedTextureFileName }; qCDebug(model_baking).noquote() << "Re-mapping" << fileTexture->GetFileName() << "to" << bakedTextureFilePath; @@ -212,10 +229,12 @@ bool FBXBaker::rewriteAndCollectSceneTextures() { // write the new filename into the FBX scene fileTexture->SetFileName(bakedTextureFilePath.toLocal8Bit()); + QUrl urlToTexture; + // add the texture to the list of textures needing to be baked if (textureFileInfo.exists() && textureFileInfo.isFile()) { - // append the URL to the local texture that we have confirmed exists - _unbakedTextures.insert(QUrl::fromLocalFile(textureFileInfo.absoluteFilePath())); + // set the texture URL to the local texture that we have confirmed exists + urlToTexture = QUrl::fromLocalFile(textureFileInfo.absoluteFilePath()); } else { // external texture that we'll need to download or find @@ -240,11 +259,11 @@ bool FBXBaker::rewriteAndCollectSceneTextures() { if (apparentRelativePath.exists() && apparentRelativePath.isFile()) { // the absolute path we ran into for the texture in the FBX exists on this machine // so use that file - _unbakedTextures.insert(QUrl::fromLocalFile(apparentRelativePath.absoluteFilePath())); + urlToTexture = QUrl::fromLocalFile(apparentRelativePath.absoluteFilePath()); } else { // we didn't find the texture on this machine at the absolute path // so assume that it is right beside the FBX to match the behaviour of interface - _unbakedTextures.insert(_fbxURL.resolved(apparentRelativePath.fileName())); + urlToTexture = _fbxURL.resolved(apparentRelativePath.fileName()); } } else { // the original FBX was remote and downloaded @@ -253,14 +272,17 @@ bool FBXBaker::rewriteAndCollectSceneTextures() { // which matches the behaviour of Interface // append that path to our list of unbaked textures - _unbakedTextures.insert(_fbxURL.resolved(apparentRelativePath.fileName())); + urlToTexture = _fbxURL.resolved(apparentRelativePath.fileName()); } } else { // simply construct a URL with the relative path to the asset, locally or remotely // and append that to the list of unbaked textures - _unbakedTextures.insert(_fbxURL.resolved(apparentRelativePath.filePath())); + urlToTexture = _fbxURL.resolved(apparentRelativePath.filePath()); } } + + // add the deduced url to the texture, associated with the resulting baked texture file name, to our hash + _unbakedTextures.insert(urlToTexture, bakedTextureFileName); } } } @@ -295,7 +317,9 @@ bool FBXBaker::exportScene() { bool FBXBaker::bakeTextures() { // enumerate the list of unbaked textures - foreach(const QUrl& textureUrl, _unbakedTextures) { + for (auto it = _unbakedTextures.begin(); it != _unbakedTextures.end(); ++it) { + auto& textureUrl = it.key(); + qCDebug(model_baking) << "Baking texture at" << textureUrl; if (textureUrl.isLocalFile()) { diff --git a/libraries/model-baking/src/FBXBaker.h b/libraries/model-baking/src/FBXBaker.h index a6cf64ebaf..fe28922f66 100644 --- a/libraries/model-baking/src/FBXBaker.h +++ b/libraries/model-baking/src/FBXBaker.h @@ -64,7 +64,8 @@ private: QStringList _errorList; - QSet _unbakedTextures; + QHash _unbakedTextures; + QHash _textureNameMatchCount; }; #endif // hifi_FBXBaker_h From e75f17a7f9bedfdfde1ca8c102d0937b358c7b46 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 5 Apr 2017 18:10:52 -0700 Subject: [PATCH 11/71] add a TextureBaker called from the FBXBaker --- libraries/model-baking/src/FBXBaker.cpp | 47 +++-------- libraries/model-baking/src/FBXBaker.h | 13 +-- .../src/ModelBakingLoggingCategory.cpp | 14 ++++ .../src/ModelBakingLoggingCategory.h | 19 +++++ libraries/model-baking/src/TextureBaker.cpp | 79 +++++++++++++++++++ libraries/model-baking/src/TextureBaker.h | 43 ++++++++++ 6 files changed, 175 insertions(+), 40 deletions(-) create mode 100644 libraries/model-baking/src/ModelBakingLoggingCategory.cpp create mode 100644 libraries/model-baking/src/ModelBakingLoggingCategory.h create mode 100644 libraries/model-baking/src/TextureBaker.cpp create mode 100644 libraries/model-baking/src/TextureBaker.h diff --git a/libraries/model-baking/src/FBXBaker.cpp b/libraries/model-baking/src/FBXBaker.cpp index 6717cbf69e..3370698738 100644 --- a/libraries/model-baking/src/FBXBaker.cpp +++ b/libraries/model-baking/src/FBXBaker.cpp @@ -16,9 +16,11 @@ #include +#include "ModelBakingLoggingCategory.h" +#include "TextureBaker.h" + #include "FBXBaker.h" -Q_LOGGING_CATEGORY(model_baking, "hifi.model-baking"); FBXBaker::FBXBaker(QUrl fbxURL, QString baseOutputPath) : _fbxURL(fbxURL), @@ -143,15 +145,12 @@ void FBXBaker::handleFBXNetworkReply() { void FBXBaker::bake() { // (1) load the scene from the FBX file - // (2) enumerate the textures found in the scene and bake them + // (2) enumerate the textures found in the scene and start a bake for them // (3) export the FBX with re-written texture references - // (4) enumerate the collected texture paths and bake the textures - // a failure at any step along the way stops the chain - importScene() && rewriteAndCollectSceneTextures() && exportScene() && bakeTextures() && removeEmbeddedMediaFolder(); - - // emit a signal saying that we are done, with whatever errors were produced - emit finished(); + importScene(); + rewriteAndBakeSceneTextures(); + exportScene(); } bool FBXBaker::importScene() { @@ -187,7 +186,7 @@ bool FBXBaker::importScene() { static const QString BAKED_TEXTURE_DIRECTORY = "textures/"; static const QString BAKED_TEXTURE_EXT = ".ktx"; -bool FBXBaker::rewriteAndCollectSceneTextures() { +bool FBXBaker::rewriteAndBakeSceneTextures() { // get a count of the textures used in the scene int numTextures = _scene->GetTextureCount(); @@ -283,6 +282,11 @@ bool FBXBaker::rewriteAndCollectSceneTextures() { // add the deduced url to the texture, associated with the resulting baked texture file name, to our hash _unbakedTextures.insert(urlToTexture, bakedTextureFileName); + + // start a bake for this texture and add it to our list to keep track of + auto bakingTexture = new TextureBaker(urlToTexture); + bakingTexture->start(); + _bakingTextures.emplace_back(bakingTexture); } } } @@ -315,31 +319,6 @@ bool FBXBaker::exportScene() { return true; } -bool FBXBaker::bakeTextures() { - // enumerate the list of unbaked textures - for (auto it = _unbakedTextures.begin(); it != _unbakedTextures.end(); ++it) { - auto& textureUrl = it.key(); - - qCDebug(model_baking) << "Baking texture at" << textureUrl; - - if (textureUrl.isLocalFile()) { - // this is a local file that we've already determined is available on the filesystem - - // load the file - QFile localTexture { textureUrl.toLocalFile() }; - - if (!localTexture.open(QIODevice::ReadOnly)) { - // add an error to the list stating that this texture couldn't be baked because it could not be loaded - } - - // call the image library to produce a compressed KTX for this image - } else { - // this is a remote texture that we'll need to download first - } - } - - return true; -} bool FBXBaker::removeEmbeddedMediaFolder() { // now that the bake is complete, remove the embedded media folder produced by the FBX SDK when it imports an FBX diff --git a/libraries/model-baking/src/FBXBaker.h b/libraries/model-baking/src/FBXBaker.h index fe28922f66..78a0dde0bb 100644 --- a/libraries/model-baking/src/FBXBaker.h +++ b/libraries/model-baking/src/FBXBaker.h @@ -13,12 +13,9 @@ #define hifi_FBXBaker_h #include -#include #include #include -Q_DECLARE_LOGGING_CATEGORY(model_baking) - namespace fbxsdk { class FbxManager; class FbxProperty; @@ -26,6 +23,8 @@ namespace fbxsdk { class FbxTexture; } +class TextureBaker; + class FBXBaker : public QObject { Q_OBJECT public: @@ -45,10 +44,8 @@ private: bool setupOutputFolder(); bool importScene(); - bool rewriteAndCollectSceneTextures(); + bool rewriteAndBakeSceneTextures(); bool exportScene(); - bool bakeTextures(); - bool bakeTexture(); bool removeEmbeddedMediaFolder(); QString pathToCopyOfOriginal() const; @@ -66,6 +63,10 @@ private: QHash _unbakedTextures; QHash _textureNameMatchCount; + + QHash _downloadedTextures; + + std::list> _bakingTextures; }; #endif // hifi_FBXBaker_h diff --git a/libraries/model-baking/src/ModelBakingLoggingCategory.cpp b/libraries/model-baking/src/ModelBakingLoggingCategory.cpp new file mode 100644 index 0000000000..c2ad6360d2 --- /dev/null +++ b/libraries/model-baking/src/ModelBakingLoggingCategory.cpp @@ -0,0 +1,14 @@ +// +// ModelBakingLoggingCategory.cpp +// libraries/model-baking/src +// +// Created by Stephen Birarda on 4/5/17. +// Copyright 2017 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 "ModelBakingLoggingCategory.h" + +Q_LOGGING_CATEGORY(model_baking, "hifi.model-baking"); diff --git a/libraries/model-baking/src/ModelBakingLoggingCategory.h b/libraries/model-baking/src/ModelBakingLoggingCategory.h new file mode 100644 index 0000000000..600618ed5e --- /dev/null +++ b/libraries/model-baking/src/ModelBakingLoggingCategory.h @@ -0,0 +1,19 @@ +// +// ModelBakingLoggingCategory.h +// libraries/model-baking/src +// +// Created by Stephen Birarda on 4/5/17. +// Copyright 2017 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_ModelBakingLoggingCategory_h +#define hifi_ModelBakingLoggingCategory_h + +#include + +Q_DECLARE_LOGGING_CATEGORY(model_baking) + +#endif // hifi_ModelBakingLoggingCategory_h diff --git a/libraries/model-baking/src/TextureBaker.cpp b/libraries/model-baking/src/TextureBaker.cpp new file mode 100644 index 0000000000..ce86b18479 --- /dev/null +++ b/libraries/model-baking/src/TextureBaker.cpp @@ -0,0 +1,79 @@ +// +// TextureBaker.cpp +// libraries/model-baker/src +// +// Created by Stephen Birarda on 4/5/17. +// Copyright 2017 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 +#include + +#include + +#include "ModelBakingLoggingCategory.h" + +#include "TextureBaker.h" + +TextureBaker::TextureBaker(const QUrl& textureURL) : + _textureURL(textureURL) +{ + +} + +void TextureBaker::start() { + + // check if the texture is local or first needs to be downloaded + if (_textureURL.isLocalFile()) { + // load up the local file + QFile localTexture { _textureURL.toLocalFile() }; + + if (!localTexture.open(QIODevice::ReadOnly)) { + qCWarning(model_baking) << "Unable to open local texture at" << _textureURL << "for baking"; + + emit finished(); + return; + } + + _originalTexture = localTexture.readAll(); + + // start the bake now that we have everything in place + bake(); + } else { + // remote file, kick off a download + auto& networkAccessManager = NetworkAccessManager::getInstance(); + + QNetworkRequest networkRequest; + + // setup the request to follow re-directs and always hit the network + networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); + networkRequest.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork); + + networkRequest.setUrl(_textureURL); + + qCDebug(model_baking) << "Downloading" << _textureURL; + + auto networkReply = networkAccessManager.get(networkRequest); + connect(networkReply, &QNetworkReply::finished, this, &TextureBaker::handleTextureNetworkReply); + } +} + +void TextureBaker::handleTextureNetworkReply() { + QNetworkReply* requestReply = qobject_cast(sender()); + + if (requestReply->error() == QNetworkReply::NoError) { + qCDebug(model_baking) << "Downloaded texture at" << _textureURL; + _originalTexture = requestReply->readAll(); + } else { + qCDebug(model_baking) << "Error downloading texture" << requestReply->errorString(); + } +} + +void TextureBaker::bake() { + qCDebug(model_baking) << "Baking texture at" << _textureURL; + + // call image library to asynchronously bake this texture +} diff --git a/libraries/model-baking/src/TextureBaker.h b/libraries/model-baking/src/TextureBaker.h new file mode 100644 index 0000000000..5544352005 --- /dev/null +++ b/libraries/model-baking/src/TextureBaker.h @@ -0,0 +1,43 @@ +// +// TextureBaker.h +// libraries/model-baker/src +// +// Created by Stephen Birarda on 4/5/17. +// Copyright 2017 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_TextureBaker_h +#define hifi_TextureBaker_h + +#include +#include + +class TextureBaker : public QObject { + Q_OBJECT + +public: + TextureBaker(const QUrl& textureURL); + + void start(); + + const QByteArray& getOriginalTexture() const { return _originalTexture; } + + const QUrl& getTextureURL() const { return _textureURL; } + +signals: + void finished(); + +private slots: + void handleTextureNetworkReply(); + +private: + void bake(); + + QUrl _textureURL; + QByteArray _originalTexture; +}; + +#endif // hifi_TextureBaker_h From 1a17da6ecc447115dc88724fcff2e6371f0f04f2 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 5 Apr 2017 18:21:53 -0700 Subject: [PATCH 12/71] add helper to deduce texture relative URL after download --- libraries/model-baking/src/FBXBaker.cpp | 13 +++++++++++++ libraries/model-baking/src/TextureBaker.cpp | 2 ++ 2 files changed, 15 insertions(+) diff --git a/libraries/model-baking/src/FBXBaker.cpp b/libraries/model-baking/src/FBXBaker.cpp index 3370698738..5f9037ddb9 100644 --- a/libraries/model-baking/src/FBXBaker.cpp +++ b/libraries/model-baking/src/FBXBaker.cpp @@ -186,6 +186,19 @@ bool FBXBaker::importScene() { static const QString BAKED_TEXTURE_DIRECTORY = "textures/"; static const QString BAKED_TEXTURE_EXT = ".ktx"; +QString texturePathRelativeToFBX(QUrl fbxURL, QUrl textureURL) { + auto fbxPath = fbxURL.toString(QUrl::RemoveFilename | QUrl::RemoveQuery | QUrl::RemoveFragment); + auto texturePath = textureURL.toString(QUrl::RemoveFilename | QUrl::RemoveQuery | QUrl::RemoveFragment); + + if (texturePath.startsWith(fbxPath)) { + // texture path is a child of the FBX path, return the texture path without the fbx path + return texturePath.mid(fbxPath.length()); + } else { + // the texture path was not a child of the FBX path, return the empty string + return ""; + } +} + bool FBXBaker::rewriteAndBakeSceneTextures() { // get a count of the textures used in the scene int numTextures = _scene->GetTextureCount(); diff --git a/libraries/model-baking/src/TextureBaker.cpp b/libraries/model-baking/src/TextureBaker.cpp index ce86b18479..4f21ccd624 100644 --- a/libraries/model-baking/src/TextureBaker.cpp +++ b/libraries/model-baking/src/TextureBaker.cpp @@ -66,6 +66,8 @@ void TextureBaker::handleTextureNetworkReply() { if (requestReply->error() == QNetworkReply::NoError) { qCDebug(model_baking) << "Downloaded texture at" << _textureURL; + + // store the original texture so it can be passed along for the bake _originalTexture = requestReply->readAll(); } else { qCDebug(model_baking) << "Error downloading texture" << requestReply->errorString(); From 679580a620b0fc51f546cc9bb30b8ca5ff5bb094 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 6 Apr 2017 10:39:23 -0700 Subject: [PATCH 13/71] don't save copy of embedded raw textures --- libraries/model-baking/src/FBXBaker.cpp | 85 ++++++++++++++++----- libraries/model-baking/src/FBXBaker.h | 5 +- libraries/model-baking/src/TextureBaker.cpp | 4 + 3 files changed, 73 insertions(+), 21 deletions(-) diff --git a/libraries/model-baking/src/FBXBaker.cpp b/libraries/model-baking/src/FBXBaker.cpp index 5f9037ddb9..aae48a51a5 100644 --- a/libraries/model-baking/src/FBXBaker.cpp +++ b/libraries/model-baking/src/FBXBaker.cpp @@ -39,10 +39,10 @@ FBXBaker::~FBXBaker() { } static const QString BAKED_OUTPUT_SUBFOLDER = "baked/"; -static const QString ORIGINAL_OUTPUT_SUBFOLDER = "original/"; +static const QString RAW_OUTPUT_SUBFOLDER = "raw/"; -QString FBXBaker::pathToCopyOfOriginal() const { - return _uniqueOutputPath + ORIGINAL_OUTPUT_SUBFOLDER + _fbxURL.fileName(); +QString FBXBaker::pathToCopyOfRaw() const { + return _uniqueOutputPath + RAW_OUTPUT_SUBFOLDER + _fbxURL.fileName(); } void FBXBaker::start() { @@ -59,7 +59,7 @@ void FBXBaker::start() { QFile localFBX { _fbxURL.toLocalFile() }; // make a copy in the output folder - localFBX.copy(pathToCopyOfOriginal()); + localFBX.copy(pathToCopyOfRaw()); // start the bake now that we have everything in place bake(); @@ -103,10 +103,10 @@ bool FBXBaker::setupOutputFolder() { return false; } - // make the baked and original sub-folders used during export + // make the baked and raw sub-folders used during export QDir uniqueOutputDir = _uniqueOutputPath; - if (!uniqueOutputDir.mkdir(BAKED_OUTPUT_SUBFOLDER) || !uniqueOutputDir.mkdir(ORIGINAL_OUTPUT_SUBFOLDER)) { - qCCritical(model_baking) << "Failed to create baked/original subfolders in" << _uniqueOutputPath; + if (!uniqueOutputDir.mkdir(BAKED_OUTPUT_SUBFOLDER) || !uniqueOutputDir.mkdir(RAW_OUTPUT_SUBFOLDER)) { + qCCritical(model_baking) << "Failed to create baked/raw subfolders in" << _uniqueOutputPath; emit finished(); return false; @@ -122,19 +122,19 @@ void FBXBaker::handleFBXNetworkReply() { qCDebug(model_baking) << "Downloaded" << _fbxURL; // grab the contents of the reply and make a copy in the output folder - QFile copyOfOriginal(pathToCopyOfOriginal()); + QFile copyOfRaw(pathToCopyOfRaw()); - qDebug(model_baking) << "Writing copy of original FBX to" << copyOfOriginal.fileName(); + qDebug(model_baking) << "Writing copy of raw FBX to" << copyOfRaw.fileName(); - if (!copyOfOriginal.open(QIODevice::WriteOnly) || (copyOfOriginal.write(requestReply->readAll()) == -1)) { + if (!copyOfRaw.open(QIODevice::WriteOnly) || (copyOfRaw.write(requestReply->readAll()) == -1)) { - // add an error to the error list for this FBX stating that a duplicate of the original could not be made + // add an error to the error list for this FBX stating that a duplicate of the raw FBX could not be made emit finished(); return; } // close that file now that we are done writing to it - copyOfOriginal.close(); + copyOfRaw.close(); // kick off the bake process now that everything is ready to go bake(); @@ -157,9 +157,9 @@ bool FBXBaker::importScene() { // create an FBX SDK importer FbxImporter* importer = FbxImporter::Create(_sdkManager, ""); - // import the copy of the original FBX file - QString originalCopyPath = pathToCopyOfOriginal(); - bool importStatus = importer->Initialize(originalCopyPath.toLocal8Bit().data()); + // import the copy of the raw FBX file + QString rawCopyPath = pathToCopyOfRaw(); + bool importStatus = importer->Initialize(rawCopyPath.toLocal8Bit().data()); if (!importStatus) { // failed to initialize importer, print an error and return @@ -296,10 +296,8 @@ bool FBXBaker::rewriteAndBakeSceneTextures() { // add the deduced url to the texture, associated with the resulting baked texture file name, to our hash _unbakedTextures.insert(urlToTexture, bakedTextureFileName); - // start a bake for this texture and add it to our list to keep track of - auto bakingTexture = new TextureBaker(urlToTexture); - bakingTexture->start(); - _bakingTextures.emplace_back(bakingTexture); + // bake this texture asynchronously + bakeTexture(urlToTexture); } } } @@ -307,6 +305,53 @@ bool FBXBaker::rewriteAndBakeSceneTextures() { return true; } +void FBXBaker::bakeTexture(const QUrl& textureURL) { + // start a bake for this texture and add it to our list to keep track of + auto bakingTexture = new TextureBaker(textureURL); + + connect(bakingTexture, &TextureBaker::finished, this, &FBXBaker::handleBakedTexture); + + bakingTexture->start(); + + _bakingTextures.emplace_back(bakingTexture); +} + +void FBXBaker::handleBakedTexture() { + auto bakedTexture = qobject_cast(sender()); + + // use the path to the texture being baked to determine if this was an embedded or a linked texture + + // it is embeddded if the texure being baked was inside the unbaked output folder + // + + auto rawOutputFolder = QUrl::fromLocalFile(_uniqueOutputPath + RAW_OUTPUT_SUBFOLDER); + + if (!rawOutputFolder.isParentOf(bakedTexture->getTextureURL())) { + // for linked textures we want to save a copy of original texture beside the original FBX + + qCDebug(model_baking) << "Saving raw texture for" << bakedTexture->getTextureURL(); + + // check if we have a relative path to use for the texture + auto relativeTexturePath = texturePathRelativeToFBX(_fbxURL, bakedTexture->getTextureURL()); + + QFile originalTextureFile { + _uniqueOutputPath + RAW_OUTPUT_SUBFOLDER + relativeTexturePath + bakedTexture->getTextureURL().fileName() + }; + + if (relativeTexturePath.length() > 0) { + // make the folders needed by the relative path + } + + if (originalTextureFile.open(QIODevice::WriteOnly) && originalTextureFile.write(bakedTexture->getOriginalTexture()) != -1) { + qCDebug(model_baking) << "Saved original texture file" << originalTextureFile.fileName() + << "for" << _fbxURL; + } else { + qCWarning(model_baking) << "Could not save original external texture" << originalTextureFile.fileName() + << "for" << _fbxURL; + } + } +} + static const QString BAKED_FBX_EXTENSION = ".baked.fbx"; bool FBXBaker::exportScene() { @@ -336,7 +381,7 @@ bool FBXBaker::exportScene() { bool FBXBaker::removeEmbeddedMediaFolder() { // now that the bake is complete, remove the embedded media folder produced by the FBX SDK when it imports an FBX auto embeddedMediaFolderName = _fbxURL.fileName().replace(".fbx", ".fbm"); - QDir(_uniqueOutputPath + ORIGINAL_OUTPUT_SUBFOLDER + embeddedMediaFolderName).removeRecursively(); + QDir(_uniqueOutputPath + RAW_OUTPUT_SUBFOLDER + embeddedMediaFolderName).removeRecursively(); // we always return true because a failure to delete the embedded media folder is not a failure of the bake return true; diff --git a/libraries/model-baking/src/FBXBaker.h b/libraries/model-baking/src/FBXBaker.h index 78a0dde0bb..b8638372f2 100644 --- a/libraries/model-baking/src/FBXBaker.h +++ b/libraries/model-baking/src/FBXBaker.h @@ -38,6 +38,7 @@ signals: private slots: void handleFBXNetworkReply(); + void handleBakedTexture(); private: void bake(); @@ -48,7 +49,9 @@ private: bool exportScene(); bool removeEmbeddedMediaFolder(); - QString pathToCopyOfOriginal() const; + void bakeTexture(const QUrl& textureURL); + + QString pathToCopyOfRaw() const; QUrl _fbxURL; QString _fbxName; diff --git a/libraries/model-baking/src/TextureBaker.cpp b/libraries/model-baking/src/TextureBaker.cpp index 4f21ccd624..4cf1e09410 100644 --- a/libraries/model-baking/src/TextureBaker.cpp +++ b/libraries/model-baking/src/TextureBaker.cpp @@ -69,6 +69,9 @@ void TextureBaker::handleTextureNetworkReply() { // store the original texture so it can be passed along for the bake _originalTexture = requestReply->readAll(); + + // kickoff the texture bake now that everything is ready to go + bake(); } else { qCDebug(model_baking) << "Error downloading texture" << requestReply->errorString(); } @@ -78,4 +81,5 @@ void TextureBaker::bake() { qCDebug(model_baking) << "Baking texture at" << _textureURL; // call image library to asynchronously bake this texture + emit finished(); } From fcefe3ff817a4b1291ac036a37ccf8e6bea9a807 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 6 Apr 2017 10:47:13 -0700 Subject: [PATCH 14/71] rename the raw output folder back to original --- libraries/model-baking/src/FBXBaker.cpp | 47 +++++++++++++------------ libraries/model-baking/src/FBXBaker.h | 2 +- 2 files changed, 26 insertions(+), 23 deletions(-) diff --git a/libraries/model-baking/src/FBXBaker.cpp b/libraries/model-baking/src/FBXBaker.cpp index aae48a51a5..b57ab88315 100644 --- a/libraries/model-baking/src/FBXBaker.cpp +++ b/libraries/model-baking/src/FBXBaker.cpp @@ -39,10 +39,10 @@ FBXBaker::~FBXBaker() { } static const QString BAKED_OUTPUT_SUBFOLDER = "baked/"; -static const QString RAW_OUTPUT_SUBFOLDER = "raw/"; +static const QString ORIGINAL_OUTPUT_SUBFOLDER = "original/"; -QString FBXBaker::pathToCopyOfRaw() const { - return _uniqueOutputPath + RAW_OUTPUT_SUBFOLDER + _fbxURL.fileName(); +QString FBXBaker::pathToCopyOfOriginal() const { + return _uniqueOutputPath + ORIGINAL_OUTPUT_SUBFOLDER + _fbxURL.fileName(); } void FBXBaker::start() { @@ -59,7 +59,7 @@ void FBXBaker::start() { QFile localFBX { _fbxURL.toLocalFile() }; // make a copy in the output folder - localFBX.copy(pathToCopyOfRaw()); + localFBX.copy(pathToCopyOfOriginal()); // start the bake now that we have everything in place bake(); @@ -103,10 +103,10 @@ bool FBXBaker::setupOutputFolder() { return false; } - // make the baked and raw sub-folders used during export + // make the baked and original sub-folders used during export QDir uniqueOutputDir = _uniqueOutputPath; - if (!uniqueOutputDir.mkdir(BAKED_OUTPUT_SUBFOLDER) || !uniqueOutputDir.mkdir(RAW_OUTPUT_SUBFOLDER)) { - qCCritical(model_baking) << "Failed to create baked/raw subfolders in" << _uniqueOutputPath; + if (!uniqueOutputDir.mkdir(BAKED_OUTPUT_SUBFOLDER) || !uniqueOutputDir.mkdir(ORIGINAL_OUTPUT_SUBFOLDER)) { + qCCritical(model_baking) << "Failed to create baked/original subfolders in" << _uniqueOutputPath; emit finished(); return false; @@ -122,19 +122,19 @@ void FBXBaker::handleFBXNetworkReply() { qCDebug(model_baking) << "Downloaded" << _fbxURL; // grab the contents of the reply and make a copy in the output folder - QFile copyOfRaw(pathToCopyOfRaw()); + QFile copyOfOriginal(pathToCopyOfOriginal()); - qDebug(model_baking) << "Writing copy of raw FBX to" << copyOfRaw.fileName(); + qDebug(model_baking) << "Writing copy of original FBX to" << copyOfOriginal.fileName(); - if (!copyOfRaw.open(QIODevice::WriteOnly) || (copyOfRaw.write(requestReply->readAll()) == -1)) { + if (!copyOfOriginal.open(QIODevice::WriteOnly) || (copyOfOriginal.write(requestReply->readAll()) == -1)) { - // add an error to the error list for this FBX stating that a duplicate of the raw FBX could not be made + // add an error to the error list for this FBX stating that a duplicate of the original FBX could not be made emit finished(); return; } // close that file now that we are done writing to it - copyOfRaw.close(); + copyOfOriginal.close(); // kick off the bake process now that everything is ready to go bake(); @@ -151,15 +151,18 @@ void FBXBaker::bake() { importScene(); rewriteAndBakeSceneTextures(); exportScene(); + + + removeEmbeddedMediaFolder(); } bool FBXBaker::importScene() { // create an FBX SDK importer FbxImporter* importer = FbxImporter::Create(_sdkManager, ""); - // import the copy of the raw FBX file - QString rawCopyPath = pathToCopyOfRaw(); - bool importStatus = importer->Initialize(rawCopyPath.toLocal8Bit().data()); + // import the copy of the original FBX file + QString originalCopyPath = pathToCopyOfOriginal(); + bool importStatus = importer->Initialize(originalCopyPath.toLocal8Bit().data()); if (!importStatus) { // failed to initialize importer, print an error and return @@ -321,21 +324,21 @@ void FBXBaker::handleBakedTexture() { // use the path to the texture being baked to determine if this was an embedded or a linked texture - // it is embeddded if the texure being baked was inside the unbaked output folder - // + // it is embeddded if the texure being baked was inside the original output folder + // since that is where the FBX SDK places the .fbm folder it generates when importing the FBX - auto rawOutputFolder = QUrl::fromLocalFile(_uniqueOutputPath + RAW_OUTPUT_SUBFOLDER); + auto originalOutputFolder = QUrl::fromLocalFile(_uniqueOutputPath + ORIGINAL_OUTPUT_SUBFOLDER); - if (!rawOutputFolder.isParentOf(bakedTexture->getTextureURL())) { + if (!originalOutputFolder.isParentOf(bakedTexture->getTextureURL())) { // for linked textures we want to save a copy of original texture beside the original FBX - qCDebug(model_baking) << "Saving raw texture for" << bakedTexture->getTextureURL(); + qCDebug(model_baking) << "Saving original texture for" << bakedTexture->getTextureURL(); // check if we have a relative path to use for the texture auto relativeTexturePath = texturePathRelativeToFBX(_fbxURL, bakedTexture->getTextureURL()); QFile originalTextureFile { - _uniqueOutputPath + RAW_OUTPUT_SUBFOLDER + relativeTexturePath + bakedTexture->getTextureURL().fileName() + _uniqueOutputPath + ORIGINAL_OUTPUT_SUBFOLDER + relativeTexturePath + bakedTexture->getTextureURL().fileName() }; if (relativeTexturePath.length() > 0) { @@ -381,7 +384,7 @@ bool FBXBaker::exportScene() { bool FBXBaker::removeEmbeddedMediaFolder() { // now that the bake is complete, remove the embedded media folder produced by the FBX SDK when it imports an FBX auto embeddedMediaFolderName = _fbxURL.fileName().replace(".fbx", ".fbm"); - QDir(_uniqueOutputPath + RAW_OUTPUT_SUBFOLDER + embeddedMediaFolderName).removeRecursively(); + QDir(_uniqueOutputPath + ORIGINAL_OUTPUT_SUBFOLDER + embeddedMediaFolderName).removeRecursively(); // we always return true because a failure to delete the embedded media folder is not a failure of the bake return true; diff --git a/libraries/model-baking/src/FBXBaker.h b/libraries/model-baking/src/FBXBaker.h index b8638372f2..44c3be2955 100644 --- a/libraries/model-baking/src/FBXBaker.h +++ b/libraries/model-baking/src/FBXBaker.h @@ -51,7 +51,7 @@ private: void bakeTexture(const QUrl& textureURL); - QString pathToCopyOfRaw() const; + QString pathToCopyOfOriginal() const; QUrl _fbxURL; QString _fbxName; From 647377d07ae8739f2e4c6d4f5ae74eb6604efe98 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 6 Apr 2017 16:08:11 -0700 Subject: [PATCH 15/71] enumerate materials to find textures with types --- libraries/model-baking/src/FBXBaker.cpp | 247 ++++++++++++++++-------- libraries/model-baking/src/FBXBaker.h | 27 ++- tools/oven/src/Oven.cpp | 2 +- 3 files changed, 191 insertions(+), 85 deletions(-) diff --git a/libraries/model-baking/src/FBXBaker.cpp b/libraries/model-baking/src/FBXBaker.cpp index b57ab88315..625d0ae963 100644 --- a/libraries/model-baking/src/FBXBaker.cpp +++ b/libraries/model-baking/src/FBXBaker.cpp @@ -202,105 +202,190 @@ QString texturePathRelativeToFBX(QUrl fbxURL, QUrl textureURL) { } } -bool FBXBaker::rewriteAndBakeSceneTextures() { - // get a count of the textures used in the scene - int numTextures = _scene->GetTextureCount(); +QString FBXBaker::createBakedTextureFileName(const QFileInfo& textureFileInfo) { + // first make sure we have a unique base name for this texture + // in case another texture referenced by this model has the same base name + auto& nameMatches = _textureNameMatchCount[textureFileInfo.baseName()]; - // enumerate the textures in the scene - for (int i = 0; i < numTextures; i++) { - // grab each file texture - FbxFileTexture* fileTexture = FbxCast(_scene->GetTexture(i)); + QString bakedTextureFileName { textureFileInfo.baseName() }; - if (fileTexture) { - // use QFileInfo to easily split up the existing texture filename into its components - QFileInfo textureFileInfo { fileTexture->GetFileName() }; + if (nameMatches > 0) { + // there are already nameMatches texture with this name + // append - and that number to our baked texture file name so that it is unique + bakedTextureFileName += "-" + QString::number(nameMatches); + } - // make sure this texture points to something - if (!textureFileInfo.filePath().isEmpty()) { + bakedTextureFileName += BAKED_TEXTURE_EXT; - // construct the new baked texture file name and file path + // increment the number of name matches + ++nameMatches; - // first make sure we have a unique base name for this texture - // in case another texture referenced by this model has the same base name - auto& nameMatches = _textureNameMatchCount[textureFileInfo.baseName()]; + return bakedTextureFileName; +} - QString bakedTextureFileName { textureFileInfo.baseName() }; +QUrl FBXBaker::getTextureURL(const QFileInfo& textureFileInfo, FbxFileTexture* fileTexture) { + QUrl urlToTexture; - if (nameMatches > 0) { - // there are already nameMatches texture with this name - // append - and that number to our baked texture file name so that it is unique - bakedTextureFileName += "-" + QString::number(nameMatches); - } + if (textureFileInfo.exists() && textureFileInfo.isFile()) { + // set the texture URL to the local texture that we have confirmed exists + urlToTexture = QUrl::fromLocalFile(textureFileInfo.absoluteFilePath()); + } else { + // external texture that we'll need to download or find - bakedTextureFileName += BAKED_TEXTURE_EXT; - - // increment the number of name matches - ++nameMatches; - - QString bakedTextureFilePath { _uniqueOutputPath + BAKED_OUTPUT_SUBFOLDER + BAKED_TEXTURE_DIRECTORY + bakedTextureFileName }; - - qCDebug(model_baking).noquote() << "Re-mapping" << fileTexture->GetFileName() << "to" << bakedTextureFilePath; - - // write the new filename into the FBX scene - fileTexture->SetFileName(bakedTextureFilePath.toLocal8Bit()); - - QUrl urlToTexture; - - // add the texture to the list of textures needing to be baked - if (textureFileInfo.exists() && textureFileInfo.isFile()) { - // set the texture URL to the local texture that we have confirmed exists - urlToTexture = QUrl::fromLocalFile(textureFileInfo.absoluteFilePath()); - } else { - // external texture that we'll need to download or find - - // first check if it the RelativePath to the texture in the FBX was relative - QString relativeFileName = fileTexture->GetRelativeFileName(); - auto apparentRelativePath = QFileInfo(relativeFileName.replace("\\", "/")); + // first check if it the RelativePath to the texture in the FBX was relative + QString relativeFileName = fileTexture->GetRelativeFileName(); + auto apparentRelativePath = QFileInfo(relativeFileName.replace("\\", "/")); #ifndef Q_OS_WIN - // it turns out that paths that start with a drive letter and a colon appear to QFileInfo - // as a relative path on UNIX systems - we perform a special check here to handle that case - bool isAbsolute = relativeFileName[1] == ':' || apparentRelativePath.isAbsolute(); + // it turns out that paths that start with a drive letter and a colon appear to QFileInfo + // as a relative path on UNIX systems - we perform a special check here to handle that case + bool isAbsolute = relativeFileName[1] == ':' || apparentRelativePath.isAbsolute(); #else - bool isAbsolute = apparentRelativePath.isAbsolute(); + bool isAbsolute = apparentRelativePath.isAbsolute(); #endif - if (isAbsolute) { - // this is a relative file path which will require different handling - // depending on the location of the original FBX - if (_fbxURL.isLocalFile()) { - // since the loaded FBX is loaded, first check if we actually have the texture locally - // at the absolute path - if (apparentRelativePath.exists() && apparentRelativePath.isFile()) { - // the absolute path we ran into for the texture in the FBX exists on this machine - // so use that file - urlToTexture = QUrl::fromLocalFile(apparentRelativePath.absoluteFilePath()); - } else { - // we didn't find the texture on this machine at the absolute path - // so assume that it is right beside the FBX to match the behaviour of interface - urlToTexture = _fbxURL.resolved(apparentRelativePath.fileName()); + if (isAbsolute) { + // this is a relative file path which will require different handling + // depending on the location of the original FBX + if (_fbxURL.isLocalFile()) { + // since the loaded FBX is loaded, first check if we actually have the texture locally + // at the absolute path + if (apparentRelativePath.exists() && apparentRelativePath.isFile()) { + // the absolute path we ran into for the texture in the FBX exists on this machine + // so use that file + urlToTexture = QUrl::fromLocalFile(apparentRelativePath.absoluteFilePath()); + } else { + // we didn't find the texture on this machine at the absolute path + // so assume that it is right beside the FBX to match the behaviour of interface + urlToTexture = _fbxURL.resolved(apparentRelativePath.fileName()); + } + } else { + // the original FBX was remote and downloaded + + // since this "relative" texture path is actually absolute, we have to assume it is beside the FBX + // which matches the behaviour of Interface + + // append that path to our list of unbaked textures + urlToTexture = _fbxURL.resolved(apparentRelativePath.fileName()); + } + } else { + // simply construct a URL with the relative path to the asset, locally or remotely + // and append that to the list of unbaked textures + urlToTexture = _fbxURL.resolved(apparentRelativePath.filePath()); + } + } + + return urlToTexture; +} + +TextureType textureTypeForMaterialProperty(FbxProperty& property, FbxSurfaceMaterial* material) { + // this is a property we know has a texture, we need to match it to a High Fidelity known texture type + // since that information is passed to the baking process + + // grab the hierarchical name for this property and lowercase it for case-insensitive compare + auto propertyName = QString(property.GetHierarchicalName()).toLower(); + + // figure out the type of the property based on what known value string it matches + if ((propertyName.contains("diffuse") && !propertyName.contains("tex_global_diffuse")) + || propertyName.contains("tex_color_map")) { + return ALBEDO_TEXTURE; + } else if (propertyName.contains("transparentcolor") || propertyName.contains("transparencyfactor")) { + return ALBEDO_TEXTURE; + } else if (propertyName.contains("bump")) { + return BUMP_TEXTURE; + } else if (propertyName.contains("normal")) { + return NORMAL_TEXTURE; + } else if ((propertyName.contains("specular") && !propertyName.contains("tex_global_specular")) + || propertyName.contains("reflection")) { + return SPECULAR_TEXTURE; + } else if (propertyName.contains("tex_metallic_map")) { + return METALLIC_TEXTURE; + } else if (propertyName.contains("shininess")) { + return GLOSS_TEXTURE; + } else if (propertyName.contains("tex_roughness_map")) { + return ROUGHNESS_TEXTURE; + } else if (propertyName.contains("emissive")) { + return EMISSIVE_TEXTURE; + } else if (propertyName.contains("ambientcolor")) { + return LIGHTMAP_TEXTURE; + } else if (propertyName.contains("ambientfactor")) { + // we need to check what the ambient factor is, since that tells Interface to process this texture + // either as an occlusion texture or a light map + auto lambertMaterial = FbxCast(material); + + if (lambertMaterial->AmbientFactor == 0) { + return LIGHTMAP_TEXTURE; + } else if (lambertMaterial->AmbientFactor > 0) { + return OCCLUSION_TEXTURE; + } else { + return UNUSED_TEXTURE; + } + + } else if (propertyName.contains("tex_ao_map")) { + return OCCLUSION_TEXTURE; + } + + return UNUSED_TEXTURE; +} + +bool FBXBaker::rewriteAndBakeSceneTextures() { + + // enumerate the surface materials to find the textures used in the scene + int numMaterials = _scene->GetMaterialCount(); + for (int i = 0; i < numMaterials; i++) { + FbxSurfaceMaterial* material = _scene->GetMaterial(i); + + if (material) { + // enumerate the properties of this material to see what texture channels it might have + FbxProperty property = material->GetFirstProperty(); + + while (property.IsValid()) { + // first check if this property has connected textures, if not we don't need to bother with it here + if (property.GetSrcObjectCount() > 0) { + + // figure out the type of texture from the material property + auto textureType = textureTypeForMaterialProperty(property, material); + + if (textureType != UNUSED_TEXTURE) { + int numTextures = property.GetSrcObjectCount(); + + for (int j = 0; j < numTextures; j++) { + FbxFileTexture* fileTexture = property.GetSrcObject(j); + + // use QFileInfo to easily split up the existing texture filename into its components + QFileInfo textureFileInfo { fileTexture->GetFileName() }; + + // make sure this texture points to something + if (!textureFileInfo.filePath().isEmpty()) { + + // construct the new baked texture file name and file path + // ensuring that the baked texture will have a unique name + // even if there was another texture with the same name at a different path + auto bakedTextureFileName = createBakedTextureFileName(textureFileInfo); + QString bakedTextureFilePath { + _uniqueOutputPath + BAKED_OUTPUT_SUBFOLDER + BAKED_TEXTURE_DIRECTORY + bakedTextureFileName + }; + + qCDebug(model_baking).noquote() << "Re-mapping" << fileTexture->GetFileName() << "to" << bakedTextureFilePath; + + // write the new filename into the FBX scene + fileTexture->SetFileName(bakedTextureFilePath.toLocal8Bit()); + + // figure out the URL to this texture, embedded or external + auto urlToTexture = getTextureURL(textureFileInfo, fileTexture); + + // add the deduced url to the texture, associated with the resulting baked texture file name, + // to our hash of textures needing to be baked + _unbakedTextures.insert(urlToTexture, bakedTextureFileName); + + // bake this texture asynchronously + bakeTexture(urlToTexture); } - } else { - // the original FBX was remote and downloaded - - // since this "relative" texture path is actually absolute, we have to assume it is beside the FBX - // which matches the behaviour of Interface - - // append that path to our list of unbaked textures - urlToTexture = _fbxURL.resolved(apparentRelativePath.fileName()); } - } else { - // simply construct a URL with the relative path to the asset, locally or remotely - // and append that to the list of unbaked textures - urlToTexture = _fbxURL.resolved(apparentRelativePath.filePath()); } } - // add the deduced url to the texture, associated with the resulting baked texture file name, to our hash - _unbakedTextures.insert(urlToTexture, bakedTextureFileName); - - // bake this texture asynchronously - bakeTexture(urlToTexture); + property = material->GetNextProperty(property); } } } diff --git a/libraries/model-baking/src/FBXBaker.h b/libraries/model-baking/src/FBXBaker.h index 44c3be2955..2e15e30bce 100644 --- a/libraries/model-baking/src/FBXBaker.h +++ b/libraries/model-baking/src/FBXBaker.h @@ -20,9 +20,28 @@ namespace fbxsdk { class FbxManager; class FbxProperty; class FbxScene; - class FbxTexture; + class FbxFileTexture; } +enum TextureType { + DEFAULT_TEXTURE, + STRICT_TEXTURE, + ALBEDO_TEXTURE, + NORMAL_TEXTURE, + BUMP_TEXTURE, + SPECULAR_TEXTURE, + METALLIC_TEXTURE = SPECULAR_TEXTURE, // for now spec and metallic texture are the same, converted to grey + ROUGHNESS_TEXTURE, + GLOSS_TEXTURE, + EMISSIVE_TEXTURE, + CUBE_TEXTURE, + OCCLUSION_TEXTURE, + SCATTERING_TEXTURE = OCCLUSION_TEXTURE, + LIGHTMAP_TEXTURE, + CUSTOM_TEXTURE, + UNUSED_TEXTURE = -1 +}; + class TextureBaker; class FBXBaker : public QObject { @@ -49,6 +68,9 @@ private: bool exportScene(); bool removeEmbeddedMediaFolder(); + QString createBakedTextureFileName(const QFileInfo& textureFileInfo); + QUrl getTextureURL(const QFileInfo& textureFileInfo, fbxsdk::FbxFileTexture* fileTexture); + void bakeTexture(const QUrl& textureURL); QString pathToCopyOfOriginal() const; @@ -66,8 +88,7 @@ private: QHash _unbakedTextures; QHash _textureNameMatchCount; - - QHash _downloadedTextures; + QHash _textureTypes; std::list> _bakingTextures; }; diff --git a/tools/oven/src/Oven.cpp b/tools/oven/src/Oven.cpp index b517da8151..90025522a9 100644 --- a/tools/oven/src/Oven.cpp +++ b/tools/oven/src/Oven.cpp @@ -17,7 +17,7 @@ static const QString OUTPUT_FOLDER = "/Users/birarda/code/hifi/lod/test-oven/exp Oven::Oven(int argc, char* argv[]) : QCoreApplication(argc, argv), - _testBake(QUrl("file:///Users/birarda/code/hifi/lod/test-oven/DiscGolfBasket.fbx"), OUTPUT_FOLDER) + _testBake(QUrl("file:///Users/birarda/code/hifi/lod/test-oven/Test-Object6.fbx"), OUTPUT_FOLDER) { _testBake.start(); } From 6519a9c45b4a6710de5fa10c9cd0ce1a2d7f24c4 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 6 Apr 2017 16:30:21 -0700 Subject: [PATCH 16/71] bump FBX SDK version back to 2017 --- cmake/modules/FindFBXSDK.cmake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmake/modules/FindFBXSDK.cmake b/cmake/modules/FindFBXSDK.cmake index d6e9ed801d..7f6a424aa1 100644 --- a/cmake/modules/FindFBXSDK.cmake +++ b/cmake/modules/FindFBXSDK.cmake @@ -18,9 +18,9 @@ if (NOT FBX_VERSION) if (WIN32) - set(FBX_VERSION 2016.1.1) + set(FBX_VERSION 2017.1) else() - set(FBX_VERSION 2016.1.1) + set(FBX_VERSION 2017.0.1) endif() endif() From 8d3b854e69c317e57467d9d902bf3467d5d9f710 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 7 Apr 2017 14:13:22 -0700 Subject: [PATCH 17/71] add a simple UI to Oven to bake individual model --- tools/oven/CMakeLists.txt | 2 +- tools/oven/src/Oven.cpp | 24 ++++- tools/oven/src/Oven.h | 10 ++- tools/oven/src/ui/ModelBakeWidget.cpp | 121 ++++++++++++++++++++++++++ tools/oven/src/ui/ModelBakeWidget.h | 43 +++++++++ tools/oven/src/ui/OvenMainWindow.cpp | 12 +++ tools/oven/src/ui/OvenMainWindow.h | 21 +++++ 7 files changed, 224 insertions(+), 9 deletions(-) create mode 100644 tools/oven/src/ui/ModelBakeWidget.cpp create mode 100644 tools/oven/src/ui/ModelBakeWidget.h create mode 100644 tools/oven/src/ui/OvenMainWindow.cpp create mode 100644 tools/oven/src/ui/OvenMainWindow.h diff --git a/tools/oven/CMakeLists.txt b/tools/oven/CMakeLists.txt index 473fa707f1..4d64126fb8 100644 --- a/tools/oven/CMakeLists.txt +++ b/tools/oven/CMakeLists.txt @@ -1,5 +1,5 @@ set(TARGET_NAME oven) -setup_hifi_project() +setup_hifi_project(Widgets Gui) link_hifi_libraries(model-baking) diff --git a/tools/oven/src/Oven.cpp b/tools/oven/src/Oven.cpp index 90025522a9..e77dd9b988 100644 --- a/tools/oven/src/Oven.cpp +++ b/tools/oven/src/Oven.cpp @@ -9,15 +9,31 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include +#include "ui/OvenMainWindow.h" +#include "ui/ModelBakeWidget.h" #include "Oven.h" static const QString OUTPUT_FOLDER = "/Users/birarda/code/hifi/lod/test-oven/export"; Oven::Oven(int argc, char* argv[]) : - QCoreApplication(argc, argv), - _testBake(QUrl("file:///Users/birarda/code/hifi/lod/test-oven/Test-Object6.fbx"), OUTPUT_FOLDER) + QApplication(argc, argv) { - _testBake.start(); + QCoreApplication::setOrganizationName("High Fidelity"); + QCoreApplication::setApplicationName("Oven"); + + // check if we were passed any command line arguments that would tell us just to run without the GUI + + // setup the GUI + setupGUI(); } + +void Oven::setupGUI() { + _mainWindow = new OvenMainWindow; + + _mainWindow->setWindowTitle("High Fidelity Oven"); + + _mainWindow->setCentralWidget(new ModelBakeWidget); + _mainWindow->show(); +} + diff --git a/tools/oven/src/Oven.h b/tools/oven/src/Oven.h index 72de77b889..2a628fa0c8 100644 --- a/tools/oven/src/Oven.h +++ b/tools/oven/src/Oven.h @@ -12,18 +12,20 @@ #ifndef hifi_Oven_h #define hifi_Oven_h -#include +#include -#include +class OvenMainWindow; -class Oven : public QCoreApplication { +class Oven : public QApplication { Q_OBJECT public: Oven(int argc, char* argv[]); private: - FBXBaker _testBake; + void setupGUI(); + + OvenMainWindow* _mainWindow; }; diff --git a/tools/oven/src/ui/ModelBakeWidget.cpp b/tools/oven/src/ui/ModelBakeWidget.cpp new file mode 100644 index 0000000000..b32afa156d --- /dev/null +++ b/tools/oven/src/ui/ModelBakeWidget.cpp @@ -0,0 +1,121 @@ +// +// ModelBakeWidget.cpp +// tools/oven/src/ui +// +// Created by Stephen Birarda on 4/6/17. +// Copyright 2017 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 +#include +#include +#include +#include + +#include +#include + +#include "ModelBakeWidget.h" + +ModelBakeWidget::ModelBakeWidget(QWidget* parent, Qt::WindowFlags flags) : + QWidget(parent, flags) +{ + setupUI(); +} + +void ModelBakeWidget::setupUI() { + // setup a grid layout to hold everything + QGridLayout* gridLayout = new QGridLayout; + + int rowIndex = 0; + + // setup a section to choose the file being baked + QLabel* modelFileLabel = new QLabel("Model File"); + + _modelLineEdit = new QLineEdit; + + QPushButton* chooseFileButton = new QPushButton("Browse..."); + connect(chooseFileButton, &QPushButton::clicked, this, &ModelBakeWidget::chooseFileButtonClicked); + + // add the components for the model file picker to the layout + gridLayout->addWidget(modelFileLabel, rowIndex, 0); + gridLayout->addWidget(_modelLineEdit, rowIndex, 1, 1, 3); + gridLayout->addWidget(chooseFileButton, rowIndex, 4); + + // start a new row for next component + ++rowIndex; + + // setup a section to choose the output directory + QLabel* outputDirectoryLabel = new QLabel("Output Directory"); + + _outputDirLineEdit = new QLineEdit; + + QPushButton* chooseOutputDirectoryButton = new QPushButton("Browse..."); + connect(chooseOutputDirectoryButton, &QPushButton::clicked, this, &ModelBakeWidget::chooseOutputDirButtonClicked); + + // add the components for the output directory picker to the layout + gridLayout->addWidget(outputDirectoryLabel, rowIndex, 0); + gridLayout->addWidget(_outputDirLineEdit, rowIndex, 1, 1, 3); + gridLayout->addWidget(chooseOutputDirectoryButton, rowIndex, 4); + + // start a new row for the next component + ++rowIndex; + + // add a button that will kickoff the bake + QPushButton* bakeButton = new QPushButton("Bake Model"); + connect(bakeButton, &QPushButton::clicked, this, &ModelBakeWidget::bakeButtonClicked); + + // add the bake button to the grid + gridLayout->addWidget(bakeButton, rowIndex, 0, -1, -1); + + setLayout(gridLayout); +} + +void ModelBakeWidget::chooseFileButtonClicked() { + // pop a file dialog so the user can select the model file + auto selectedFile = QFileDialog::getOpenFileName(this, "Choose Model", QDir::homePath()); + + if (!selectedFile.isEmpty()) { + // set the contents of the model file text box to be the path to the selected file + _modelLineEdit->setText(selectedFile); + } +} + +void ModelBakeWidget::chooseOutputDirButtonClicked() { + // pop a file dialog so the user can select the output directory + auto selectedDir = QFileDialog::getExistingDirectory(this, "Choose Output Directory", QDir::homePath()); + + if (!selectedDir.isEmpty()) { + // set the contents of the output directory text box to be the path to the directory + _outputDirLineEdit->setText(selectedDir); + } +} + +void ModelBakeWidget::bakeButtonClicked() { + // make sure we have a valid output directory + QDir outputDirectory(_outputDirLineEdit->text()); + + if (!outputDirectory.exists()) { + + } + + // make sure we have a non empty URL to a model to bake + if (_modelLineEdit->text().isEmpty()) { + + } + + // construct a URL from the path in the model file text box + QUrl modelToBakeURL(_modelLineEdit->text()); + + // if the URL doesn't have a scheme, assume it is a local file + if (modelToBakeURL.scheme().isEmpty()) { + modelToBakeURL.setScheme("file"); + } + + // everything seems to be in place, kick off a bake now + _baker.reset(new FBXBaker(modelToBakeURL, outputDirectory.absolutePath())); + _baker->start(); +} diff --git a/tools/oven/src/ui/ModelBakeWidget.h b/tools/oven/src/ui/ModelBakeWidget.h new file mode 100644 index 0000000000..adcaaf2a50 --- /dev/null +++ b/tools/oven/src/ui/ModelBakeWidget.h @@ -0,0 +1,43 @@ +// +// ModelBakeWidget.h +// tools/oven/src/ui +// +// Created by Stephen Birarda on 4/6/17. +// Copyright 2017 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_ModelBakeWidget_h +#define hifi_ModelBakeWidget_h + +#include + +#include + +class QLineEdit; + +class ModelBakeWidget : public QWidget { + Q_OBJECT + +public: + ModelBakeWidget(QWidget* parent = nullptr, Qt::WindowFlags flags = Qt::WindowFlags()); + +private slots: + void chooseFileButtonClicked(); + void chooseOutputDirButtonClicked(); + void bakeButtonClicked(); + +private: + void setupUI(); + + std::unique_ptr _baker; + + QLineEdit* _modelLineEdit; + QLineEdit* _outputDirLineEdit; + + QUrl modelToBakeURL; +}; + +#endif // hifi_ModelBakeWidget_h diff --git a/tools/oven/src/ui/OvenMainWindow.cpp b/tools/oven/src/ui/OvenMainWindow.cpp new file mode 100644 index 0000000000..8f7829d765 --- /dev/null +++ b/tools/oven/src/ui/OvenMainWindow.cpp @@ -0,0 +1,12 @@ +// +// OvenMainWindow.cpp +// tools/oven/src/ui +// +// Created by Stephen Birarda on 4/6/17. +// Copyright 2017 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 "OvenMainWindow.h" diff --git a/tools/oven/src/ui/OvenMainWindow.h b/tools/oven/src/ui/OvenMainWindow.h new file mode 100644 index 0000000000..b9813be34e --- /dev/null +++ b/tools/oven/src/ui/OvenMainWindow.h @@ -0,0 +1,21 @@ +// +// OvenMainWindow.h +// tools/oven/src/ui +// +// Created by Stephen Birarda on 4/6/17. +// Copyright 2017 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_OvenMainWindow_h +#define hifi_OvenMainWindow_h + +#include + +class OvenMainWindow : public QMainWindow { + +}; + +#endif // hifi_OvenMainWindow_h From e1840eb4fedace904111cf0fe6ec9d336958d462 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 7 Apr 2017 14:19:23 -0700 Subject: [PATCH 18/71] give the Oven window a fixed width --- tools/oven/src/Oven.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tools/oven/src/Oven.cpp b/tools/oven/src/Oven.cpp index e77dd9b988..7f185a404e 100644 --- a/tools/oven/src/Oven.cpp +++ b/tools/oven/src/Oven.cpp @@ -33,7 +33,12 @@ void Oven::setupGUI() { _mainWindow->setWindowTitle("High Fidelity Oven"); + // give the window a fixed width that will never change + const int FIXED_WINDOW_WIDTH = 640; + _mainWindow->setFixedWidth(FIXED_WINDOW_WIDTH); + _mainWindow->setCentralWidget(new ModelBakeWidget); + _mainWindow->show(); } From 425385d9829f07d5791cfcc81ec5214e3bb1c653 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 7 Apr 2017 14:44:09 -0700 Subject: [PATCH 19/71] leverage settings to remember paths used before --- libraries/shared/src/SettingHandle.h | 4 +++ libraries/shared/src/SettingInterface.h | 1 - tools/oven/CMakeLists.txt | 2 +- tools/oven/src/Oven.cpp | 5 ++++ tools/oven/src/ui/ModelBakeWidget.cpp | 39 +++++++++++++++++++++++-- tools/oven/src/ui/ModelBakeWidget.h | 7 +++++ 6 files changed, 53 insertions(+), 5 deletions(-) diff --git a/libraries/shared/src/SettingHandle.h b/libraries/shared/src/SettingHandle.h index 54694dfd0a..258d1f8491 100644 --- a/libraries/shared/src/SettingHandle.h +++ b/libraries/shared/src/SettingHandle.h @@ -106,6 +106,10 @@ namespace Setting { return (_isSet) ? _value : other; } + bool isSet() const { + return _isSet; + } + const T& getDefault() const { return _defaultValue; } diff --git a/libraries/shared/src/SettingInterface.h b/libraries/shared/src/SettingInterface.h index 082adf3e54..575641c0e7 100644 --- a/libraries/shared/src/SettingInterface.h +++ b/libraries/shared/src/SettingInterface.h @@ -21,7 +21,6 @@ namespace Setting { class Manager; void init(); - void cleanupSettings(); class Interface { public: diff --git a/tools/oven/CMakeLists.txt b/tools/oven/CMakeLists.txt index 4d64126fb8..2e40cc9de4 100644 --- a/tools/oven/CMakeLists.txt +++ b/tools/oven/CMakeLists.txt @@ -2,4 +2,4 @@ set(TARGET_NAME oven) setup_hifi_project(Widgets Gui) -link_hifi_libraries(model-baking) +link_hifi_libraries(model-baking shared) diff --git a/tools/oven/src/Oven.cpp b/tools/oven/src/Oven.cpp index 7f185a404e..1e7eb3906e 100644 --- a/tools/oven/src/Oven.cpp +++ b/tools/oven/src/Oven.cpp @@ -9,6 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include + #include "ui/OvenMainWindow.h" #include "ui/ModelBakeWidget.h" @@ -22,6 +24,9 @@ Oven::Oven(int argc, char* argv[]) : QCoreApplication::setOrganizationName("High Fidelity"); QCoreApplication::setApplicationName("Oven"); + // init the settings interface so we can save and load settings + Setting::init(); + // check if we were passed any command line arguments that would tell us just to run without the GUI // setup the GUI diff --git a/tools/oven/src/ui/ModelBakeWidget.cpp b/tools/oven/src/ui/ModelBakeWidget.cpp index b32afa156d..a3fd874306 100644 --- a/tools/oven/src/ui/ModelBakeWidget.cpp +++ b/tools/oven/src/ui/ModelBakeWidget.cpp @@ -20,8 +20,13 @@ #include "ModelBakeWidget.h" +static const QString EXPORT_DIR_SETTING_KEY = "model_export_directory"; +static const QString MODEL_START_DIR_SETTING_KEY = "model_search_directory"; + ModelBakeWidget::ModelBakeWidget(QWidget* parent, Qt::WindowFlags flags) : - QWidget(parent, flags) + QWidget(parent, flags), + _exportDirectory(EXPORT_DIR_SETTING_KEY), + _modelStartDirectory(MODEL_START_DIR_SETTING_KEY) { setupUI(); } @@ -53,6 +58,12 @@ void ModelBakeWidget::setupUI() { _outputDirLineEdit = new QLineEdit; + // set the current export directory to whatever was last used + _outputDirLineEdit->setText(_exportDirectory.get()); + + // whenever the output directory line edit changes, update the value in settings + connect(_outputDirLineEdit, &QLineEdit::textChanged, this, &ModelBakeWidget::outputDirectoryChanged); + QPushButton* chooseOutputDirectoryButton = new QPushButton("Browse..."); connect(chooseOutputDirectoryButton, &QPushButton::clicked, this, &ModelBakeWidget::chooseOutputDirButtonClicked); @@ -76,17 +87,34 @@ void ModelBakeWidget::setupUI() { void ModelBakeWidget::chooseFileButtonClicked() { // pop a file dialog so the user can select the model file - auto selectedFile = QFileDialog::getOpenFileName(this, "Choose Model", QDir::homePath()); + + // if we have picked an FBX before, start in the folder that matches the last path + // otherwise start in the home directory + auto startDir = _modelStartDirectory.get(); + if (startDir.isEmpty()) { + startDir = QDir::homePath(); + } + + auto selectedFile = QFileDialog::getOpenFileName(this, "Choose Model", startDir); if (!selectedFile.isEmpty()) { // set the contents of the model file text box to be the path to the selected file _modelLineEdit->setText(selectedFile); + _modelStartDirectory.set(QDir(selectedFile).absolutePath()); } } void ModelBakeWidget::chooseOutputDirButtonClicked() { // pop a file dialog so the user can select the output directory - auto selectedDir = QFileDialog::getExistingDirectory(this, "Choose Output Directory", QDir::homePath()); + + // if we have a previously selected output directory, use that as the initial path in the choose dialog + // otherwise use the user's home directory + auto startDir = _exportDirectory.get(); + if (startDir.isEmpty()) { + startDir = QDir::homePath(); + } + + auto selectedDir = QFileDialog::getExistingDirectory(this, "Choose Output Directory", startDir); if (!selectedDir.isEmpty()) { // set the contents of the output directory text box to be the path to the directory @@ -94,6 +122,11 @@ void ModelBakeWidget::chooseOutputDirButtonClicked() { } } +void ModelBakeWidget::outputDirectoryChanged(const QString& newDirectory) { + // update the export directory setting so we can re-use it next time + _exportDirectory.set(newDirectory); +} + void ModelBakeWidget::bakeButtonClicked() { // make sure we have a valid output directory QDir outputDirectory(_outputDirLineEdit->text()); diff --git a/tools/oven/src/ui/ModelBakeWidget.h b/tools/oven/src/ui/ModelBakeWidget.h index adcaaf2a50..0f7efa8aad 100644 --- a/tools/oven/src/ui/ModelBakeWidget.h +++ b/tools/oven/src/ui/ModelBakeWidget.h @@ -14,6 +14,8 @@ #include +#include + #include class QLineEdit; @@ -29,6 +31,8 @@ private slots: void chooseOutputDirButtonClicked(); void bakeButtonClicked(); + void outputDirectoryChanged(const QString& newDirectory); + private: void setupUI(); @@ -38,6 +42,9 @@ private: QLineEdit* _outputDirLineEdit; QUrl modelToBakeURL; + + Setting::Handle _exportDirectory; + Setting::Handle _modelStartDirectory; }; #endif // hifi_ModelBakeWidget_h From 1fc678a92900bd8e8b8119a3fa489c278abddc61 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 7 Apr 2017 15:29:09 -0700 Subject: [PATCH 20/71] add placeholder text, set export folder from FBX if not set --- tools/oven/src/ui/ModelBakeWidget.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tools/oven/src/ui/ModelBakeWidget.cpp b/tools/oven/src/ui/ModelBakeWidget.cpp index a3fd874306..eb4f792c33 100644 --- a/tools/oven/src/ui/ModelBakeWidget.cpp +++ b/tools/oven/src/ui/ModelBakeWidget.cpp @@ -41,6 +41,7 @@ void ModelBakeWidget::setupUI() { QLabel* modelFileLabel = new QLabel("Model File"); _modelLineEdit = new QLineEdit; + _modelLineEdit->setPlaceholderText("File or URL"); QPushButton* chooseFileButton = new QPushButton("Browse..."); connect(chooseFileButton, &QPushButton::clicked, this, &ModelBakeWidget::chooseFileButtonClicked); @@ -100,7 +101,14 @@ void ModelBakeWidget::chooseFileButtonClicked() { if (!selectedFile.isEmpty()) { // set the contents of the model file text box to be the path to the selected file _modelLineEdit->setText(selectedFile); - _modelStartDirectory.set(QDir(selectedFile).absolutePath()); + + auto directoryOfModel = QFileInfo(selectedFile).absolutePath(); + + // save the directory containing this model so we can default to it next time we show the file dialog + _modelStartDirectory.set(directoryOfModel); + + // if our output directory is not yet set, set it to the directory of this model + _outputDirLineEdit->setText(directoryOfModel); } } From d9efd4adef647ec0dc2329af374dad771cf13d56 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 7 Apr 2017 15:40:40 -0700 Subject: [PATCH 21/71] don't save copy of originals for one-off bake --- libraries/model-baking/src/FBXBaker.cpp | 19 ++++++++++++------- libraries/model-baking/src/FBXBaker.h | 7 +++++-- tools/oven/src/ui/ModelBakeWidget.cpp | 2 +- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/libraries/model-baking/src/FBXBaker.cpp b/libraries/model-baking/src/FBXBaker.cpp index 625d0ae963..af1c861623 100644 --- a/libraries/model-baking/src/FBXBaker.cpp +++ b/libraries/model-baking/src/FBXBaker.cpp @@ -22,9 +22,10 @@ #include "FBXBaker.h" -FBXBaker::FBXBaker(QUrl fbxURL, QString baseOutputPath) : +FBXBaker::FBXBaker(QUrl fbxURL, QString baseOutputPath, bool copyOriginals) : _fbxURL(fbxURL), - _baseOutputPath(baseOutputPath) + _baseOutputPath(baseOutputPath), + _copyOriginals(copyOriginals) { // create an FBX SDK manager _sdkManager = FbxManager::Create(); @@ -152,8 +153,8 @@ void FBXBaker::bake() { rewriteAndBakeSceneTextures(); exportScene(); - removeEmbeddedMediaFolder(); + possiblyCleanupOriginals(); } bool FBXBaker::importScene() { @@ -466,11 +467,15 @@ bool FBXBaker::exportScene() { } -bool FBXBaker::removeEmbeddedMediaFolder() { +void FBXBaker::removeEmbeddedMediaFolder() { // now that the bake is complete, remove the embedded media folder produced by the FBX SDK when it imports an FBX auto embeddedMediaFolderName = _fbxURL.fileName().replace(".fbx", ".fbm"); QDir(_uniqueOutputPath + ORIGINAL_OUTPUT_SUBFOLDER + embeddedMediaFolderName).removeRecursively(); - - // we always return true because a failure to delete the embedded media folder is not a failure of the bake - return true; +} + +void FBXBaker::possiblyCleanupOriginals() { + if (!_copyOriginals) { + // caller did not ask us to keep the original around, so delete the original output folder now + QDir(_uniqueOutputPath + ORIGINAL_OUTPUT_SUBFOLDER).removeRecursively(); + } } diff --git a/libraries/model-baking/src/FBXBaker.h b/libraries/model-baking/src/FBXBaker.h index 2e15e30bce..cc357cd251 100644 --- a/libraries/model-baking/src/FBXBaker.h +++ b/libraries/model-baking/src/FBXBaker.h @@ -47,7 +47,7 @@ class TextureBaker; class FBXBaker : public QObject { Q_OBJECT public: - FBXBaker(QUrl fbxURL, QString baseOutputPath); + FBXBaker(QUrl fbxURL, QString baseOutputPath, bool copyOriginals = true); ~FBXBaker(); void start(); @@ -66,7 +66,8 @@ private: bool importScene(); bool rewriteAndBakeSceneTextures(); bool exportScene(); - bool removeEmbeddedMediaFolder(); + void removeEmbeddedMediaFolder(); + void possiblyCleanupOriginals(); QString createBakedTextureFileName(const QFileInfo& textureFileInfo); QUrl getTextureURL(const QFileInfo& textureFileInfo, fbxsdk::FbxFileTexture* fileTexture); @@ -91,6 +92,8 @@ private: QHash _textureTypes; std::list> _bakingTextures; + + bool _copyOriginals { true }; }; #endif // hifi_FBXBaker_h diff --git a/tools/oven/src/ui/ModelBakeWidget.cpp b/tools/oven/src/ui/ModelBakeWidget.cpp index eb4f792c33..1fe214a7f6 100644 --- a/tools/oven/src/ui/ModelBakeWidget.cpp +++ b/tools/oven/src/ui/ModelBakeWidget.cpp @@ -157,6 +157,6 @@ void ModelBakeWidget::bakeButtonClicked() { } // everything seems to be in place, kick off a bake now - _baker.reset(new FBXBaker(modelToBakeURL, outputDirectory.absolutePath())); + _baker.reset(new FBXBaker(modelToBakeURL, outputDirectory.absolutePath(), false)); _baker->start(); } From 31bf01250386f00e4b7116ade23d29a6df2eac07 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 7 Apr 2017 15:54:26 -0700 Subject: [PATCH 22/71] handle multi-file select for model bake UI --- tools/oven/src/ui/ModelBakeWidget.cpp | 35 ++++++++++++++++----------- tools/oven/src/ui/ModelBakeWidget.h | 2 +- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/tools/oven/src/ui/ModelBakeWidget.cpp b/tools/oven/src/ui/ModelBakeWidget.cpp index 1fe214a7f6..1a8156eaf3 100644 --- a/tools/oven/src/ui/ModelBakeWidget.cpp +++ b/tools/oven/src/ui/ModelBakeWidget.cpp @@ -38,7 +38,7 @@ void ModelBakeWidget::setupUI() { int rowIndex = 0; // setup a section to choose the file being baked - QLabel* modelFileLabel = new QLabel("Model File"); + QLabel* modelFileLabel = new QLabel("Model File(s)"); _modelLineEdit = new QLineEdit; _modelLineEdit->setPlaceholderText("File or URL"); @@ -77,7 +77,7 @@ void ModelBakeWidget::setupUI() { ++rowIndex; // add a button that will kickoff the bake - QPushButton* bakeButton = new QPushButton("Bake Model"); + QPushButton* bakeButton = new QPushButton("Bake"); connect(bakeButton, &QPushButton::clicked, this, &ModelBakeWidget::bakeButtonClicked); // add the bake button to the grid @@ -96,13 +96,13 @@ void ModelBakeWidget::chooseFileButtonClicked() { startDir = QDir::homePath(); } - auto selectedFile = QFileDialog::getOpenFileName(this, "Choose Model", startDir); + auto selectedFiles = QFileDialog::getOpenFileNames(this, "Choose Model", startDir); - if (!selectedFile.isEmpty()) { + if (!selectedFiles.isEmpty()) { // set the contents of the model file text box to be the path to the selected file - _modelLineEdit->setText(selectedFile); + _modelLineEdit->setText(selectedFiles.join(',')); - auto directoryOfModel = QFileInfo(selectedFile).absolutePath(); + auto directoryOfModel = QFileInfo(selectedFiles[0]).absolutePath(); // save the directory containing this model so we can default to it next time we show the file dialog _modelStartDirectory.set(directoryOfModel); @@ -148,15 +148,22 @@ void ModelBakeWidget::bakeButtonClicked() { } - // construct a URL from the path in the model file text box - QUrl modelToBakeURL(_modelLineEdit->text()); + // split the list from the model line edit to see how many models we need to bake + auto fileURLStrings = _modelLineEdit->text().split(','); + foreach (QString fileURLString, fileURLStrings) { + // construct a URL from the path in the model file text box + QUrl modelToBakeURL(fileURLString); - // if the URL doesn't have a scheme, assume it is a local file - if (modelToBakeURL.scheme().isEmpty()) { - modelToBakeURL.setScheme("file"); + // if the URL doesn't have a scheme, assume it is a local file + if (modelToBakeURL.scheme().isEmpty()) { + modelToBakeURL.setScheme("file"); + } + + // everything seems to be in place, kick off a bake for this model now + auto baker = new FBXBaker(modelToBakeURL, outputDirectory.absolutePath(), false); + baker->start(); + _bakers.emplace_back(baker); } - // everything seems to be in place, kick off a bake now - _baker.reset(new FBXBaker(modelToBakeURL, outputDirectory.absolutePath(), false)); - _baker->start(); + } diff --git a/tools/oven/src/ui/ModelBakeWidget.h b/tools/oven/src/ui/ModelBakeWidget.h index 0f7efa8aad..5fe3a37e0a 100644 --- a/tools/oven/src/ui/ModelBakeWidget.h +++ b/tools/oven/src/ui/ModelBakeWidget.h @@ -36,7 +36,7 @@ private slots: private: void setupUI(); - std::unique_ptr _baker; + std::list> _bakers; QLineEdit* _modelLineEdit; QLineEdit* _outputDirLineEdit; From 4e0aba10bcf055df8e6771cc5daa886fda4384c7 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 7 Apr 2017 16:57:31 -0700 Subject: [PATCH 23/71] add a modes menu and hook it up to the existing model widget --- tools/oven/src/Oven.cpp | 14 -------- tools/oven/src/Oven.h | 2 -- tools/oven/src/ui/ModelBakeWidget.cpp | 30 +++++++++++++--- tools/oven/src/ui/ModelBakeWidget.h | 1 + tools/oven/src/ui/ModesWidget.cpp | 51 +++++++++++++++++++++++++++ tools/oven/src/ui/ModesWidget.h | 29 +++++++++++++++ tools/oven/src/ui/OvenMainWindow.cpp | 20 +++++++++++ tools/oven/src/ui/OvenMainWindow.h | 4 ++- 8 files changed, 130 insertions(+), 21 deletions(-) create mode 100644 tools/oven/src/ui/ModesWidget.cpp create mode 100644 tools/oven/src/ui/ModesWidget.h diff --git a/tools/oven/src/Oven.cpp b/tools/oven/src/Oven.cpp index 1e7eb3906e..ede1b7c1e4 100644 --- a/tools/oven/src/Oven.cpp +++ b/tools/oven/src/Oven.cpp @@ -12,7 +12,6 @@ #include #include "ui/OvenMainWindow.h" -#include "ui/ModelBakeWidget.h" #include "Oven.h" @@ -30,20 +29,7 @@ Oven::Oven(int argc, char* argv[]) : // check if we were passed any command line arguments that would tell us just to run without the GUI // setup the GUI - setupGUI(); -} - -void Oven::setupGUI() { _mainWindow = new OvenMainWindow; - - _mainWindow->setWindowTitle("High Fidelity Oven"); - - // give the window a fixed width that will never change - const int FIXED_WINDOW_WIDTH = 640; - _mainWindow->setFixedWidth(FIXED_WINDOW_WIDTH); - - _mainWindow->setCentralWidget(new ModelBakeWidget); - _mainWindow->show(); } diff --git a/tools/oven/src/Oven.h b/tools/oven/src/Oven.h index 2a628fa0c8..7ec9bdbd3b 100644 --- a/tools/oven/src/Oven.h +++ b/tools/oven/src/Oven.h @@ -23,8 +23,6 @@ public: Oven(int argc, char* argv[]); private: - void setupGUI(); - OvenMainWindow* _mainWindow; }; diff --git a/tools/oven/src/ui/ModelBakeWidget.cpp b/tools/oven/src/ui/ModelBakeWidget.cpp index 1a8156eaf3..ff95e521c5 100644 --- a/tools/oven/src/ui/ModelBakeWidget.cpp +++ b/tools/oven/src/ui/ModelBakeWidget.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -76,12 +77,24 @@ void ModelBakeWidget::setupUI() { // start a new row for the next component ++rowIndex; + // add a horizontal line to split the bake/cancel buttons off + QFrame* lineFrame = new QFrame; + lineFrame->setFrameShape(QFrame::HLine); + lineFrame->setFrameShadow(QFrame::Sunken); + gridLayout->addWidget(lineFrame, rowIndex, 0, 1, -1); + + // start a new row for the next component + ++rowIndex; + // add a button that will kickoff the bake QPushButton* bakeButton = new QPushButton("Bake"); connect(bakeButton, &QPushButton::clicked, this, &ModelBakeWidget::bakeButtonClicked); + gridLayout->addWidget(bakeButton, rowIndex, 3); - // add the bake button to the grid - gridLayout->addWidget(bakeButton, rowIndex, 0, -1, -1); + // add a cancel button to go back to the modes page + QPushButton* cancelButton = new QPushButton("Cancel"); + connect(cancelButton, &QPushButton::clicked, this, &ModelBakeWidget::cancelButtonClicked); + gridLayout->addWidget(cancelButton, rowIndex, 4); setLayout(gridLayout); } @@ -141,11 +154,13 @@ void ModelBakeWidget::bakeButtonClicked() { if (!outputDirectory.exists()) { + return; } // make sure we have a non empty URL to a model to bake if (_modelLineEdit->text().isEmpty()) { + return; } // split the list from the model line edit to see how many models we need to bake @@ -164,6 +179,13 @@ void ModelBakeWidget::bakeButtonClicked() { baker->start(); _bakers.emplace_back(baker); } - - +} + +void ModelBakeWidget::cancelButtonClicked() { + // the user wants to go back to the mode selection screen + // remove ourselves from the stacked widget and call delete later so we'll be cleaned up + auto stackedWidget = qobject_cast(parentWidget()); + stackedWidget->removeWidget(this); + + this->deleteLater(); } diff --git a/tools/oven/src/ui/ModelBakeWidget.h b/tools/oven/src/ui/ModelBakeWidget.h index 5fe3a37e0a..5d20b3fb53 100644 --- a/tools/oven/src/ui/ModelBakeWidget.h +++ b/tools/oven/src/ui/ModelBakeWidget.h @@ -30,6 +30,7 @@ private slots: void chooseFileButtonClicked(); void chooseOutputDirButtonClicked(); void bakeButtonClicked(); + void cancelButtonClicked(); void outputDirectoryChanged(const QString& newDirectory); diff --git a/tools/oven/src/ui/ModesWidget.cpp b/tools/oven/src/ui/ModesWidget.cpp new file mode 100644 index 0000000000..1df2c9fe4f --- /dev/null +++ b/tools/oven/src/ui/ModesWidget.cpp @@ -0,0 +1,51 @@ +// +// ModesWidget.cpp +// tools/oven/src/ui +// +// Created by Stephen Birarda on 4/7/17. +// Copyright 2017 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 +#include +#include + +#include "ModelBakeWidget.h" + +#include "ModesWidget.h" + +ModesWidget::ModesWidget(QWidget* parent, Qt::WindowFlags flags) : + QWidget(parent, flags) +{ + setupUI(); +} + +void ModesWidget::setupUI() { + // setup a horizontal box layout to hold our mode buttons + QHBoxLayout* horizontalLayout = new QHBoxLayout; + + // add a button for model baking + QPushButton* modelsButton = new QPushButton("Bake Models"); + connect(modelsButton, &QPushButton::clicked, this, &ModesWidget::showModelBakingWidget); + horizontalLayout->addWidget(modelsButton); + + // add a button for domain baking + QPushButton* domainButton = new QPushButton("Bake Domain"); + horizontalLayout->addWidget(domainButton); + + // add a button for texture baking + QPushButton* textureButton = new QPushButton("Bake Textures"); + horizontalLayout->addWidget(textureButton); + + setLayout(horizontalLayout); +} + +void ModesWidget::showModelBakingWidget() { + auto stackedWidget = qobject_cast(parentWidget()); + + // add a new widget for making baking to the stack, and switch to it + stackedWidget->setCurrentIndex(stackedWidget->addWidget(new ModelBakeWidget)); +} diff --git a/tools/oven/src/ui/ModesWidget.h b/tools/oven/src/ui/ModesWidget.h new file mode 100644 index 0000000000..bde2898e0c --- /dev/null +++ b/tools/oven/src/ui/ModesWidget.h @@ -0,0 +1,29 @@ +// +// ModesWidget.h +// tools/oven/src/ui +// +// Created by Stephen Birarda on 4/7/17. +// Copyright 2017 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_ModesWidget_h +#define hifi_ModesWidget_h + +#include + +class ModesWidget : public QWidget { + Q_OBJECT +public: + ModesWidget(QWidget* parent = nullptr, Qt::WindowFlags flags = Qt::WindowFlags()); + +private slots: + void showModelBakingWidget(); + +private: + void setupUI(); +}; + +#endif // hifi_ModesWidget_h diff --git a/tools/oven/src/ui/OvenMainWindow.cpp b/tools/oven/src/ui/OvenMainWindow.cpp index 8f7829d765..9c25fb5c72 100644 --- a/tools/oven/src/ui/OvenMainWindow.cpp +++ b/tools/oven/src/ui/OvenMainWindow.cpp @@ -9,4 +9,24 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include + +#include "ModesWidget.h" + #include "OvenMainWindow.h" + +OvenMainWindow::OvenMainWindow(QWidget *parent, Qt::WindowFlags flags) : + QMainWindow(parent, flags) +{ + setWindowTitle("High Fidelity Oven"); + + // give the window a fixed width that will never change + const int FIXED_WINDOW_WIDTH = 640; + setFixedWidth(FIXED_WINDOW_WIDTH); + + // setup a stacked layout for the main "modes" menu and subseq + QStackedWidget* stackedWidget = new QStackedWidget(this); + stackedWidget->addWidget(new ModesWidget); + + setCentralWidget(stackedWidget); +} diff --git a/tools/oven/src/ui/OvenMainWindow.h b/tools/oven/src/ui/OvenMainWindow.h index b9813be34e..0941be543b 100644 --- a/tools/oven/src/ui/OvenMainWindow.h +++ b/tools/oven/src/ui/OvenMainWindow.h @@ -15,7 +15,9 @@ #include class OvenMainWindow : public QMainWindow { - + Q_OBJECT +public: + OvenMainWindow(QWidget *parent = Q_NULLPTR, Qt::WindowFlags flags = Qt::WindowFlags()); }; #endif // hifi_OvenMainWindow_h From 074fb083136fc8ac3a53c720bcc33ff89fe911b3 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 12 Apr 2017 14:56:07 -0700 Subject: [PATCH 24/71] place the baked textures beside the baked FBX --- libraries/model-baking/src/FBXBaker.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libraries/model-baking/src/FBXBaker.cpp b/libraries/model-baking/src/FBXBaker.cpp index af1c861623..437a2f197d 100644 --- a/libraries/model-baking/src/FBXBaker.cpp +++ b/libraries/model-baking/src/FBXBaker.cpp @@ -187,7 +187,6 @@ bool FBXBaker::importScene() { return true; } -static const QString BAKED_TEXTURE_DIRECTORY = "textures/"; static const QString BAKED_TEXTURE_EXT = ".ktx"; QString texturePathRelativeToFBX(QUrl fbxURL, QUrl textureURL) { @@ -364,7 +363,7 @@ bool FBXBaker::rewriteAndBakeSceneTextures() { // even if there was another texture with the same name at a different path auto bakedTextureFileName = createBakedTextureFileName(textureFileInfo); QString bakedTextureFilePath { - _uniqueOutputPath + BAKED_OUTPUT_SUBFOLDER + BAKED_TEXTURE_DIRECTORY + bakedTextureFileName + _uniqueOutputPath + BAKED_OUTPUT_SUBFOLDER + bakedTextureFileName }; qCDebug(model_baking).noquote() << "Re-mapping" << fileTexture->GetFileName() << "to" << bakedTextureFilePath; From 177d4d0e0752ad5f95c900f8e137f91680ceea78 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 12 Apr 2017 16:46:05 -0700 Subject: [PATCH 25/71] add a simple domain baker to enumerate models.json.gz --- tools/oven/src/DomainBaker.cpp | 111 ++++++++++++++++ tools/oven/src/DomainBaker.h | 44 ++++++ tools/oven/src/ui/DomainBakeWidget.cpp | 177 +++++++++++++++++++++++++ tools/oven/src/ui/DomainBakeWidget.h | 49 +++++++ tools/oven/src/ui/ModelBakeWidget.h | 2 - tools/oven/src/ui/ModesWidget.cpp | 9 ++ tools/oven/src/ui/ModesWidget.h | 1 + 7 files changed, 391 insertions(+), 2 deletions(-) create mode 100644 tools/oven/src/DomainBaker.cpp create mode 100644 tools/oven/src/DomainBaker.h create mode 100644 tools/oven/src/ui/DomainBakeWidget.cpp create mode 100644 tools/oven/src/ui/DomainBakeWidget.h diff --git a/tools/oven/src/DomainBaker.cpp b/tools/oven/src/DomainBaker.cpp new file mode 100644 index 0000000000..dbd080d5a7 --- /dev/null +++ b/tools/oven/src/DomainBaker.cpp @@ -0,0 +1,111 @@ +// +// DomainBaker.cpp +// tools/oven/src +// +// Created by Stephen Birarda on 4/12/17. +// Copyright 2017 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 +#include +#include + +#include +#include + +#include "Gzip.h" + +#include "DomainBaker.h" + +DomainBaker::DomainBaker(const QUrl& localModelFileURL, QString baseOutputPath) : + _localEntitiesFileURL(localModelFileURL), + _baseOutputPath(baseOutputPath) +{ + +} + +void DomainBaker::start() { + loadLocalFile(); + enumerateEntities(); +} + +void DomainBaker::loadLocalFile() { + // load up the local entities file + QFile modelsFile { _localEntitiesFileURL.toLocalFile() }; + + if (!modelsFile.open(QIODevice::ReadOnly)) { + // add an error to our list to specify that the file could not be read + + // return to stop processing + return; + } + + // grab a byte array from the file + auto fileContents = modelsFile.readAll(); + + // check if we need to inflate a gzipped models file or if this was already decompressed + static const QString GZIPPED_ENTITIES_FILE_SUFFIX = "gz"; + if (QFileInfo(_localEntitiesFileURL.toLocalFile()).suffix() == "gz") { + // this was a gzipped models file that we need to decompress + QByteArray uncompressedContents; + gunzip(fileContents, uncompressedContents); + fileContents = uncompressedContents; + } + + // read the file contents to a JSON document + auto jsonDocument = QJsonDocument::fromJson(fileContents); + + // grab the entities object from the root JSON object + _entities = jsonDocument.object()["Entities"].toArray(); + + if (_entities.isEmpty()) { + // add an error to our list stating that the models file was empty + + // return to stop processing + return; + } +} + +void DomainBaker::enumerateEntities() { + qDebug() << "Enumerating" << _entities.size() << "entities from domain"; + + foreach(QJsonValue entityValue, _entities) { + // make sure this is a JSON object + if (entityValue.isObject()) { + auto entity = entityValue.toObject(); + + // check if this is an entity with a model URL + static const QString ENTITY_MODEL_URL_KEY = "modelURL"; + if (entity.contains(ENTITY_MODEL_URL_KEY)) { + // grab a QUrl for the model URL + auto modelURL = QUrl(entity[ENTITY_MODEL_URL_KEY].toString()); + + // check if the file pointed to by this URL is a bakeable model, by comparing extensions + auto modelFileName = modelURL.fileName(); + + static const QStringList BAKEABLE_MODEL_EXTENSIONS { ".fbx" }; + auto completeLowerExtension = modelFileName.mid(modelFileName.indexOf('.')).toLower(); + + if (BAKEABLE_MODEL_EXTENSIONS.contains(completeLowerExtension)) { + // grab a clean version of the URL without a query or fragment + modelURL.setFragment(""); + modelURL.setQuery(""); + + // setup an FBXBaker for this URL, as long as we don't already have one + if (!_bakers.contains(modelURL)) { + QSharedPointer baker { new FBXBaker(modelURL, _baseOutputPath) }; + + // start the baker + baker->start(); + + // insert it into our bakers hash so we hold a strong pointer to it + _bakers.insert(modelURL, baker); + } + } + } + } + } +} diff --git a/tools/oven/src/DomainBaker.h b/tools/oven/src/DomainBaker.h new file mode 100644 index 0000000000..50a2a12759 --- /dev/null +++ b/tools/oven/src/DomainBaker.h @@ -0,0 +1,44 @@ +// +// DomainBaker.h +// tools/oven/src +// +// Created by Stephen Birarda on 4/12/17. +// Copyright 2017 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_DomainBaker_h +#define hifi_DomainBaker_h + +#include +#include +#include + +#include + +class DomainBaker : public QObject { + Q_OBJECT +public: + DomainBaker(const QUrl& localEntitiesFileURL, QString baseOutputPath); + +public slots: + void start(); + +signals: + void finished(); + +private: + void loadLocalFile(); + void enumerateEntities(); + + QUrl _localEntitiesFileURL; + QString _baseOutputPath; + QJsonArray _entities; + + + QHash> _bakers; +}; + +#endif // hifi_DomainBaker_h diff --git a/tools/oven/src/ui/DomainBakeWidget.cpp b/tools/oven/src/ui/DomainBakeWidget.cpp new file mode 100644 index 0000000000..d5d5901ffa --- /dev/null +++ b/tools/oven/src/ui/DomainBakeWidget.cpp @@ -0,0 +1,177 @@ +// +// DomainBakeWidget.cpp +// tools/oven/src/ui +// +// Created by Stephen Birarda on 4/12/17. +// Copyright 2017 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 +#include +#include +#include +#include +#include + +#include +#include + +#include "DomainBakeWidget.h" + +static const QString EXPORT_DIR_SETTING_KEY = "domain_export_directory"; +static const QString BROWSE_START_DIR_SETTING_KEY = "domain_search_directory"; + +DomainBakeWidget::DomainBakeWidget(QWidget* parent, Qt::WindowFlags flags) : + QWidget(parent, flags), + _exportDirectory(EXPORT_DIR_SETTING_KEY), + _browseStartDirectory(BROWSE_START_DIR_SETTING_KEY) +{ + setupUI(); +} + +void DomainBakeWidget::setupUI() { + // setup a grid layout to hold everything + QGridLayout* gridLayout = new QGridLayout; + + int rowIndex = 0; + + // setup a section to choose the file being baked + QLabel* entitiesFileLabel = new QLabel("Entities File"); + + _entitiesFileLineEdit = new QLineEdit; + _entitiesFileLineEdit->setPlaceholderText("File or URL"); + + QPushButton* chooseFileButton = new QPushButton("Browse..."); + connect(chooseFileButton, &QPushButton::clicked, this, &DomainBakeWidget::chooseFileButtonClicked); + + // add the components for the entities file picker to the layout + gridLayout->addWidget(entitiesFileLabel, rowIndex, 0); + gridLayout->addWidget(_entitiesFileLineEdit, rowIndex, 1, 1, 3); + gridLayout->addWidget(chooseFileButton, rowIndex, 4); + + // start a new row for next component + ++rowIndex; + + // setup a section to choose the output directory + QLabel* outputDirectoryLabel = new QLabel("Output Directory"); + + _outputDirLineEdit = new QLineEdit; + + // set the current export directory to whatever was last used + _outputDirLineEdit->setText(_exportDirectory.get()); + + // whenever the output directory line edit changes, update the value in settings + connect(_outputDirLineEdit, &QLineEdit::textChanged, this, &DomainBakeWidget::outputDirectoryChanged); + + QPushButton* chooseOutputDirectoryButton = new QPushButton("Browse..."); + connect(chooseOutputDirectoryButton, &QPushButton::clicked, this, &DomainBakeWidget::chooseOutputDirButtonClicked); + + // add the components for the output directory picker to the layout + gridLayout->addWidget(outputDirectoryLabel, rowIndex, 0); + gridLayout->addWidget(_outputDirLineEdit, rowIndex, 1, 1, 3); + gridLayout->addWidget(chooseOutputDirectoryButton, rowIndex, 4); + + // start a new row for the next component + ++rowIndex; + + // add a horizontal line to split the bake/cancel buttons off + QFrame* lineFrame = new QFrame; + lineFrame->setFrameShape(QFrame::HLine); + lineFrame->setFrameShadow(QFrame::Sunken); + gridLayout->addWidget(lineFrame, rowIndex, 0, 1, -1); + + // start a new row for the next component + ++rowIndex; + + // add a button that will kickoff the bake + QPushButton* bakeButton = new QPushButton("Bake"); + connect(bakeButton, &QPushButton::clicked, this, &DomainBakeWidget::bakeButtonClicked); + gridLayout->addWidget(bakeButton, rowIndex, 3); + + // add a cancel button to go back to the modes page + QPushButton* cancelButton = new QPushButton("Cancel"); + connect(cancelButton, &QPushButton::clicked, this, &DomainBakeWidget::cancelButtonClicked); + gridLayout->addWidget(cancelButton, rowIndex, 4); + + setLayout(gridLayout); +} + +void DomainBakeWidget::chooseFileButtonClicked() { + // pop a file dialog so the user can select the entities file + + // if we have picked an FBX before, start in the folder that matches the last path + // otherwise start in the home directory + auto startDir = _browseStartDirectory.get(); + if (startDir.isEmpty()) { + startDir = QDir::homePath(); + } + + auto selectedFile = QFileDialog::getOpenFileName(this, "Choose Entities File", startDir); + + if (!selectedFile.isEmpty()) { + // set the contents of the entities file text box to be the path to the selected file + _entitiesFileLineEdit->setText(selectedFile); + + auto directoryOfEntitiesFile = QFileInfo(selectedFile).absolutePath(); + + // save the directory containing this entities file so we can default to it next time we show the file dialog + _browseStartDirectory.set(directoryOfEntitiesFile); + + // if our output directory is not yet set, set it to the directory of this entities file + _outputDirLineEdit->setText(directoryOfEntitiesFile); + } +} + +void DomainBakeWidget::chooseOutputDirButtonClicked() { + // pop a file dialog so the user can select the output directory + + // if we have a previously selected output directory, use that as the initial path in the choose dialog + // otherwise use the user's home directory + auto startDir = _exportDirectory.get(); + if (startDir.isEmpty()) { + startDir = QDir::homePath(); + } + + auto selectedDir = QFileDialog::getExistingDirectory(this, "Choose Output Directory", startDir); + + if (!selectedDir.isEmpty()) { + // set the contents of the output directory text box to be the path to the directory + _outputDirLineEdit->setText(selectedDir); + } +} + +void DomainBakeWidget::outputDirectoryChanged(const QString& newDirectory) { + // update the export directory setting so we can re-use it next time + _exportDirectory.set(newDirectory); +} + +void DomainBakeWidget::bakeButtonClicked() { + // make sure we have a valid output directory + QDir outputDirectory(_outputDirLineEdit->text()); + + if (!outputDirectory.exists()) { + return; + } + + // make sure we have a non empty URL to an entities file to bake + if (!_entitiesFileLineEdit->text().isEmpty()) { + // everything seems to be in place, kick off a bake for this entities file now + auto fileToBakeURL = QUrl::fromLocalFile(_entitiesFileLineEdit->text()); + _baker = std::unique_ptr { new DomainBaker(fileToBakeURL, outputDirectory.absolutePath()) }; + _baker->start(); + + return; + } +} + +void DomainBakeWidget::cancelButtonClicked() { + // the user wants to go back to the mode selection screen + // remove ourselves from the stacked widget and call delete later so we'll be cleaned up + auto stackedWidget = qobject_cast(parentWidget()); + stackedWidget->removeWidget(this); + + this->deleteLater(); +} diff --git a/tools/oven/src/ui/DomainBakeWidget.h b/tools/oven/src/ui/DomainBakeWidget.h new file mode 100644 index 0000000000..485f80683c --- /dev/null +++ b/tools/oven/src/ui/DomainBakeWidget.h @@ -0,0 +1,49 @@ +// +// DomainBakeWidget.h +// tools/oven/src/ui +// +// Created by Stephen Birarda on 4/12/17. +// Copyright 2017 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_DomainBakeWidget_h +#define hifi_DomainBakeWidget_h + +#include + +#include + +#include "../DomainBaker.h" + +class QLineEdit; + +class DomainBakeWidget : public QWidget { + Q_OBJECT + +public: + DomainBakeWidget(QWidget* parent = nullptr, Qt::WindowFlags flags = Qt::WindowFlags()); + +private slots: + void chooseFileButtonClicked(); + void chooseOutputDirButtonClicked(); + void bakeButtonClicked(); + void cancelButtonClicked(); + + void outputDirectoryChanged(const QString& newDirectory); + +private: + void setupUI(); + + std::unique_ptr _baker; + + QLineEdit* _entitiesFileLineEdit; + QLineEdit* _outputDirLineEdit; + + Setting::Handle _exportDirectory; + Setting::Handle _browseStartDirectory; +}; + +#endif // hifi_ModelBakeWidget_h diff --git a/tools/oven/src/ui/ModelBakeWidget.h b/tools/oven/src/ui/ModelBakeWidget.h index 5d20b3fb53..354ad9f311 100644 --- a/tools/oven/src/ui/ModelBakeWidget.h +++ b/tools/oven/src/ui/ModelBakeWidget.h @@ -42,8 +42,6 @@ private: QLineEdit* _modelLineEdit; QLineEdit* _outputDirLineEdit; - QUrl modelToBakeURL; - Setting::Handle _exportDirectory; Setting::Handle _modelStartDirectory; }; diff --git a/tools/oven/src/ui/ModesWidget.cpp b/tools/oven/src/ui/ModesWidget.cpp index 1df2c9fe4f..867f89b4c4 100644 --- a/tools/oven/src/ui/ModesWidget.cpp +++ b/tools/oven/src/ui/ModesWidget.cpp @@ -13,6 +13,7 @@ #include #include +#include "DomainBakeWidget.h" #include "ModelBakeWidget.h" #include "ModesWidget.h" @@ -34,6 +35,7 @@ void ModesWidget::setupUI() { // add a button for domain baking QPushButton* domainButton = new QPushButton("Bake Domain"); + connect(domainButton, &QPushButton::clicked, this, &ModesWidget::showDomainBakingWidget); horizontalLayout->addWidget(domainButton); // add a button for texture baking @@ -49,3 +51,10 @@ void ModesWidget::showModelBakingWidget() { // add a new widget for making baking to the stack, and switch to it stackedWidget->setCurrentIndex(stackedWidget->addWidget(new ModelBakeWidget)); } + +void ModesWidget::showDomainBakingWidget() { + auto stackedWidget = qobject_cast(parentWidget()); + + // add a new widget for making baking to the stack, and switch to it + stackedWidget->setCurrentIndex(stackedWidget->addWidget(new DomainBakeWidget)); +} diff --git a/tools/oven/src/ui/ModesWidget.h b/tools/oven/src/ui/ModesWidget.h index bde2898e0c..e7e239d63e 100644 --- a/tools/oven/src/ui/ModesWidget.h +++ b/tools/oven/src/ui/ModesWidget.h @@ -21,6 +21,7 @@ public: private slots: void showModelBakingWidget(); + void showDomainBakingWidget(); private: void setupUI(); From a773b0de042ef340932d8564d6af148b326dc692 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 12 Apr 2017 17:14:07 -0700 Subject: [PATCH 26/71] output domain bake to a unique folder with timestamp --- libraries/model-baking/src/FBXBaker.cpp | 2 +- libraries/model-baking/src/FBXBaker.h | 2 +- tools/oven/src/DomainBaker.cpp | 31 +++++++++++++++++++++++-- tools/oven/src/DomainBaker.h | 7 ++++-- tools/oven/src/ui/DomainBakeWidget.cpp | 15 +++++++++++- tools/oven/src/ui/DomainBakeWidget.h | 1 + 6 files changed, 51 insertions(+), 7 deletions(-) diff --git a/libraries/model-baking/src/FBXBaker.cpp b/libraries/model-baking/src/FBXBaker.cpp index 437a2f197d..561ce61994 100644 --- a/libraries/model-baking/src/FBXBaker.cpp +++ b/libraries/model-baking/src/FBXBaker.cpp @@ -22,7 +22,7 @@ #include "FBXBaker.h" -FBXBaker::FBXBaker(QUrl fbxURL, QString baseOutputPath, bool copyOriginals) : +FBXBaker::FBXBaker(const QUrl& fbxURL, const QString& baseOutputPath, bool copyOriginals) : _fbxURL(fbxURL), _baseOutputPath(baseOutputPath), _copyOriginals(copyOriginals) diff --git a/libraries/model-baking/src/FBXBaker.h b/libraries/model-baking/src/FBXBaker.h index cc357cd251..9843d8f298 100644 --- a/libraries/model-baking/src/FBXBaker.h +++ b/libraries/model-baking/src/FBXBaker.h @@ -47,7 +47,7 @@ class TextureBaker; class FBXBaker : public QObject { Q_OBJECT public: - FBXBaker(QUrl fbxURL, QString baseOutputPath, bool copyOriginals = true); + FBXBaker(const QUrl& fbxURL, const QString& baseOutputPath, bool copyOriginals = true); ~FBXBaker(); void start(); diff --git a/tools/oven/src/DomainBaker.cpp b/tools/oven/src/DomainBaker.cpp index dbd080d5a7..c3953aeda3 100644 --- a/tools/oven/src/DomainBaker.cpp +++ b/tools/oven/src/DomainBaker.cpp @@ -20,18 +20,45 @@ #include "DomainBaker.h" -DomainBaker::DomainBaker(const QUrl& localModelFileURL, QString baseOutputPath) : +DomainBaker::DomainBaker(const QUrl& localModelFileURL, const QString& domainName, const QString& baseOutputPath) : _localEntitiesFileURL(localModelFileURL), + _domainName(domainName), _baseOutputPath(baseOutputPath) { } void DomainBaker::start() { + setupOutputFolder(); loadLocalFile(); enumerateEntities(); } +void DomainBaker::setupOutputFolder() { + // in order to avoid overwriting previous bakes, we create a special output folder with the domain name and timestamp + + // first, construct the directory name + auto domainPrefix = !_domainName.isEmpty() ? _domainName + "-" : ""; + auto timeNow = QDateTime::currentDateTime(); + + static const QString FOLDER_TIMESTAMP_FORMAT = "yyyyMMdd-hhmmss"; + QString outputDirectoryName = domainPrefix + timeNow.toString(FOLDER_TIMESTAMP_FORMAT); + + // make sure we can create that directory + QDir baseDir { _baseOutputPath }; + + if (!baseDir.mkpath(outputDirectoryName)) { + + // add an error to specify that the output directory could not be created + + return; + } + + // store the unique output path so we can re-use it when saving baked models + baseDir.cd(outputDirectoryName); + _uniqueOutputPath = baseDir.absolutePath(); +} + void DomainBaker::loadLocalFile() { // load up the local entities file QFile modelsFile { _localEntitiesFileURL.toLocalFile() }; @@ -96,7 +123,7 @@ void DomainBaker::enumerateEntities() { // setup an FBXBaker for this URL, as long as we don't already have one if (!_bakers.contains(modelURL)) { - QSharedPointer baker { new FBXBaker(modelURL, _baseOutputPath) }; + QSharedPointer baker { new FBXBaker(modelURL, _uniqueOutputPath) }; // start the baker baker->start(); diff --git a/tools/oven/src/DomainBaker.h b/tools/oven/src/DomainBaker.h index 50a2a12759..c0fc40bb93 100644 --- a/tools/oven/src/DomainBaker.h +++ b/tools/oven/src/DomainBaker.h @@ -21,7 +21,7 @@ class DomainBaker : public QObject { Q_OBJECT public: - DomainBaker(const QUrl& localEntitiesFileURL, QString baseOutputPath); + DomainBaker(const QUrl& localEntitiesFileURL, const QString& domainName, const QString& baseOutputPath); public slots: void start(); @@ -30,13 +30,16 @@ signals: void finished(); private: + void setupOutputFolder(); void loadLocalFile(); void enumerateEntities(); QUrl _localEntitiesFileURL; + QString _domainName; QString _baseOutputPath; - QJsonArray _entities; + QString _uniqueOutputPath; + QJsonArray _entities; QHash> _bakers; }; diff --git a/tools/oven/src/ui/DomainBakeWidget.cpp b/tools/oven/src/ui/DomainBakeWidget.cpp index d5d5901ffa..9d73036a96 100644 --- a/tools/oven/src/ui/DomainBakeWidget.cpp +++ b/tools/oven/src/ui/DomainBakeWidget.cpp @@ -38,6 +38,17 @@ void DomainBakeWidget::setupUI() { int rowIndex = 0; + // setup a section to enter the name of the domain being baked + QLabel* domainNameLabel = new QLabel("Domain Name"); + + _domainNameLineEdit = new QLineEdit; + _domainNameLineEdit->setPlaceholderText("welcome"); + + gridLayout->addWidget(domainNameLabel); + gridLayout->addWidget(_domainNameLineEdit, rowIndex, 1, 1, -1); + + ++rowIndex; + // setup a section to choose the file being baked QLabel* entitiesFileLabel = new QLabel("Entities File"); @@ -160,7 +171,9 @@ void DomainBakeWidget::bakeButtonClicked() { if (!_entitiesFileLineEdit->text().isEmpty()) { // everything seems to be in place, kick off a bake for this entities file now auto fileToBakeURL = QUrl::fromLocalFile(_entitiesFileLineEdit->text()); - _baker = std::unique_ptr { new DomainBaker(fileToBakeURL, outputDirectory.absolutePath()) }; + _baker = std::unique_ptr { + new DomainBaker(fileToBakeURL, _domainNameLineEdit->text(), outputDirectory.absolutePath()) + }; _baker->start(); return; diff --git a/tools/oven/src/ui/DomainBakeWidget.h b/tools/oven/src/ui/DomainBakeWidget.h index 485f80683c..38a2c6a577 100644 --- a/tools/oven/src/ui/DomainBakeWidget.h +++ b/tools/oven/src/ui/DomainBakeWidget.h @@ -39,6 +39,7 @@ private: std::unique_ptr _baker; + QLineEdit* _domainNameLineEdit; QLineEdit* _entitiesFileLineEdit; QLineEdit* _outputDirLineEdit; From e1dc1990e526350c0fe5eeaa743c605793b33715 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 13 Apr 2017 12:02:14 -0700 Subject: [PATCH 27/71] add writing of new entities file during domain bake --- libraries/model-baking/src/FBXBaker.cpp | 38 +++++- libraries/model-baking/src/FBXBaker.h | 9 ++ libraries/model-baking/src/TextureBaker.cpp | 3 + tools/oven/src/DomainBaker.cpp | 141 +++++++++++++++++--- tools/oven/src/DomainBaker.h | 12 +- tools/oven/src/ui/DomainBakeWidget.cpp | 37 ++++- tools/oven/src/ui/DomainBakeWidget.h | 3 + 7 files changed, 220 insertions(+), 23 deletions(-) diff --git a/libraries/model-baking/src/FBXBaker.cpp b/libraries/model-baking/src/FBXBaker.cpp index 561ce61994..f798a137e4 100644 --- a/libraries/model-baking/src/FBXBaker.cpp +++ b/libraries/model-baking/src/FBXBaker.cpp @@ -140,7 +140,10 @@ void FBXBaker::handleFBXNetworkReply() { // kick off the bake process now that everything is ready to go bake(); } else { - qDebug() << "ERROR DOWNLOADING FBX" << requestReply->errorString(); + // add an error to our list stating that the FBX could not be downloaded + + + emit finished(); } } @@ -155,6 +158,12 @@ void FBXBaker::bake() { removeEmbeddedMediaFolder(); possiblyCleanupOriginals(); + + // at this point we are sure that we've finished everything that does not relate to textures + // so set that flag now + _finishedNonTextureOperations = true; + + checkIfFinished(); } bool FBXBaker::importScene() { @@ -226,6 +235,8 @@ QString FBXBaker::createBakedTextureFileName(const QFileInfo& textureFileInfo) { QUrl FBXBaker::getTextureURL(const QFileInfo& textureFileInfo, FbxFileTexture* fileTexture) { QUrl urlToTexture; + qDebug() << "Looking at" << textureFileInfo.absoluteFilePath(); + if (textureFileInfo.exists() && textureFileInfo.isFile()) { // set the texture URL to the local texture that we have confirmed exists urlToTexture = QUrl::fromLocalFile(textureFileInfo.absoluteFilePath()); @@ -355,8 +366,9 @@ bool FBXBaker::rewriteAndBakeSceneTextures() { // use QFileInfo to easily split up the existing texture filename into its components QFileInfo textureFileInfo { fileTexture->GetFileName() }; - // make sure this texture points to something - if (!textureFileInfo.filePath().isEmpty()) { + // make sure this texture points to something and isn't one we've already re-mapped + if (!textureFileInfo.filePath().isEmpty() + && textureFileInfo.completeSuffix() != BAKED_TEXTURE_EXT.mid(1)) { // construct the new baked texture file name and file path // ensuring that the baked texture will have a unique name @@ -438,15 +450,25 @@ void FBXBaker::handleBakedTexture() { << "for" << _fbxURL; } } -} -static const QString BAKED_FBX_EXTENSION = ".baked.fbx"; + // now that this texture has been baked and handled, we can remove that TextureBaker from our list + _unbakedTextures.remove(bakedTexture->getTextureURL()); + + // since this could have been the last texture we were waiting for + // we should perform a quick check now to see if we are done baking this model + checkIfFinished(); +} bool FBXBaker::exportScene() { // setup the exporter FbxExporter* exporter = FbxExporter::Create(_sdkManager, ""); auto rewrittenFBXPath = _uniqueOutputPath + BAKED_OUTPUT_SUBFOLDER + _fbxName + BAKED_FBX_EXTENSION; + + // save the relative path to this FBX inside our passed output folder + _bakedFBXRelativePath = rewrittenFBXPath; + _bakedFBXRelativePath.remove(_baseOutputPath + "/"); + bool exportStatus = exporter->Initialize(rewrittenFBXPath.toLocal8Bit().data()); if (!exportStatus) { @@ -478,3 +500,9 @@ void FBXBaker::possiblyCleanupOriginals() { QDir(_uniqueOutputPath + ORIGINAL_OUTPUT_SUBFOLDER).removeRecursively(); } } + +void FBXBaker::checkIfFinished() { + if (_unbakedTextures.isEmpty() && _finishedNonTextureOperations) { + emit finished(); + } +} diff --git a/libraries/model-baking/src/FBXBaker.h b/libraries/model-baking/src/FBXBaker.h index 9843d8f298..71ded0c44a 100644 --- a/libraries/model-baking/src/FBXBaker.h +++ b/libraries/model-baking/src/FBXBaker.h @@ -44,6 +44,8 @@ enum TextureType { class TextureBaker; +static const QString BAKED_FBX_EXTENSION = ".baked.fbx"; + class FBXBaker : public QObject { Q_OBJECT public: @@ -52,6 +54,9 @@ public: void start(); + QUrl getFBXUrl() const { return _fbxURL; } + QString getBakedFBXRelativePath() const { return _bakedFBXRelativePath; } + signals: void finished(); @@ -68,6 +73,7 @@ private: bool exportScene(); void removeEmbeddedMediaFolder(); void possiblyCleanupOriginals(); + void checkIfFinished(); QString createBakedTextureFileName(const QFileInfo& textureFileInfo); QUrl getTextureURL(const QFileInfo& textureFileInfo, fbxsdk::FbxFileTexture* fileTexture); @@ -81,6 +87,7 @@ private: QString _baseOutputPath; QString _uniqueOutputPath; + QString _bakedFBXRelativePath; fbxsdk::FbxManager* _sdkManager; fbxsdk::FbxScene* _scene { nullptr }; @@ -94,6 +101,8 @@ private: std::list> _bakingTextures; bool _copyOriginals { true }; + + bool _finishedNonTextureOperations { false }; }; #endif // hifi_FBXBaker_h diff --git a/libraries/model-baking/src/TextureBaker.cpp b/libraries/model-baking/src/TextureBaker.cpp index 4cf1e09410..367ae5f463 100644 --- a/libraries/model-baking/src/TextureBaker.cpp +++ b/libraries/model-baking/src/TextureBaker.cpp @@ -73,7 +73,10 @@ void TextureBaker::handleTextureNetworkReply() { // kickoff the texture bake now that everything is ready to go bake(); } else { + // add an error to our list stating that this texture could not be downloaded qCDebug(model_baking) << "Error downloading texture" << requestReply->errorString(); + + emit finished(); } } diff --git a/tools/oven/src/DomainBaker.cpp b/tools/oven/src/DomainBaker.cpp index c3953aeda3..7cd675de3f 100644 --- a/tools/oven/src/DomainBaker.cpp +++ b/tools/oven/src/DomainBaker.cpp @@ -20,12 +20,18 @@ #include "DomainBaker.h" -DomainBaker::DomainBaker(const QUrl& localModelFileURL, const QString& domainName, const QString& baseOutputPath) : +DomainBaker::DomainBaker(const QUrl& localModelFileURL, const QString& domainName, + const QString& baseOutputPath, const QUrl& destinationPath) : _localEntitiesFileURL(localModelFileURL), _domainName(domainName), _baseOutputPath(baseOutputPath) { - + // make sure the destination path has a trailing slash + if (!destinationPath.toString().endsWith('/')) { + _destinationPath = destinationPath.toString() + '/'; + } else { + _destinationPath = destinationPath; + } } void DomainBaker::start() { @@ -45,9 +51,9 @@ void DomainBaker::setupOutputFolder() { QString outputDirectoryName = domainPrefix + timeNow.toString(FOLDER_TIMESTAMP_FORMAT); // make sure we can create that directory - QDir baseDir { _baseOutputPath }; + QDir outputDir { _baseOutputPath }; - if (!baseDir.mkpath(outputDirectoryName)) { + if (!outputDir.mkpath(outputDirectoryName)) { // add an error to specify that the output directory could not be created @@ -55,10 +61,22 @@ void DomainBaker::setupOutputFolder() { } // store the unique output path so we can re-use it when saving baked models - baseDir.cd(outputDirectoryName); - _uniqueOutputPath = baseDir.absolutePath(); + outputDir.cd(outputDirectoryName); + _uniqueOutputPath = outputDir.absolutePath(); + + // add a content folder inside the unique output folder + static const QString CONTENT_OUTPUT_FOLDER_NAME = "content"; + if (!outputDir.mkpath(CONTENT_OUTPUT_FOLDER_NAME)) { + // add an error to specify that the content output directory could not be created + + return; + } + + _contentOutputPath = outputDir.absoluteFilePath(CONTENT_OUTPUT_FOLDER_NAME); } +const QString ENTITIES_OBJECT_KEY = "Entities"; + void DomainBaker::loadLocalFile() { // load up the local entities file QFile modelsFile { _localEntitiesFileURL.toLocalFile() }; @@ -86,7 +104,7 @@ void DomainBaker::loadLocalFile() { auto jsonDocument = QJsonDocument::fromJson(fileContents); // grab the entities object from the root JSON object - _entities = jsonDocument.object()["Entities"].toArray(); + _entities = jsonDocument.object()[ENTITIES_OBJECT_KEY].toArray(); if (_entities.isEmpty()) { // add an error to our list stating that the models file was empty @@ -96,19 +114,20 @@ void DomainBaker::loadLocalFile() { } } +static const QString ENTITY_MODEL_URL_KEY = "modelURL"; + void DomainBaker::enumerateEntities() { qDebug() << "Enumerating" << _entities.size() << "entities from domain"; - foreach(QJsonValue entityValue, _entities) { + for (auto it = _entities.begin(); it != _entities.end(); ++it) { // make sure this is a JSON object - if (entityValue.isObject()) { - auto entity = entityValue.toObject(); + if (it->isObject()) { + auto entity = it->toObject(); // check if this is an entity with a model URL - static const QString ENTITY_MODEL_URL_KEY = "modelURL"; if (entity.contains(ENTITY_MODEL_URL_KEY)) { // grab a QUrl for the model URL - auto modelURL = QUrl(entity[ENTITY_MODEL_URL_KEY].toString()); + QUrl modelURL { entity[ENTITY_MODEL_URL_KEY].toString() }; // check if the file pointed to by this URL is a bakeable model, by comparing extensions auto modelFileName = modelURL.fileName(); @@ -118,12 +137,15 @@ void DomainBaker::enumerateEntities() { if (BAKEABLE_MODEL_EXTENSIONS.contains(completeLowerExtension)) { // grab a clean version of the URL without a query or fragment - modelURL.setFragment(""); - modelURL.setQuery(""); + modelURL.setFragment(QString()); + modelURL.setQuery(QString()); // setup an FBXBaker for this URL, as long as we don't already have one if (!_bakers.contains(modelURL)) { - QSharedPointer baker { new FBXBaker(modelURL, _uniqueOutputPath) }; + QSharedPointer baker { new FBXBaker(modelURL, _contentOutputPath) }; + + // make sure our handler is called when the baker is done + connect(baker.data(), &FBXBaker::finished, this, &DomainBaker::handleFinishedBaker); // start the baker baker->start(); @@ -131,8 +153,97 @@ void DomainBaker::enumerateEntities() { // insert it into our bakers hash so we hold a strong pointer to it _bakers.insert(modelURL, baker); } + + // add this QJsonValueRef to our multi hash so that we can easily re-write + // the model URL to the baked version once the baker is complete + _entitiesNeedingRewrite.insert(modelURL, *it); } } } } + + _enumeratedAllEntities = true; + + // check if it's time to write out the final entities file with re-written URLs + possiblyOutputEntitiesFile(); } + +void DomainBaker::handleFinishedBaker() { + auto baker = qobject_cast(sender()); + + if (baker) { + // this FBXBaker is done and everything went according to plan + + // enumerate the QJsonRef values for the URL of this FBX from our multi hash of + // entity objects needing a URL re-write + for (QJsonValueRef entityValue : _entitiesNeedingRewrite.values(baker->getFBXUrl())) { + + // convert the entity QJsonValueRef to a QJsonObject so we can modify its URL + auto entity = entityValue.toObject(); + + // grab the old URL + QUrl oldModelURL { entity[ENTITY_MODEL_URL_KEY].toString() }; + + // setup a new URL using the prefix we were passed + QUrl newModelURL = _destinationPath.resolved(baker->getBakedFBXRelativePath().mid(1)); + + // copy the fragment and query from the old model URL + newModelURL.setQuery(oldModelURL.query()); + newModelURL.setFragment(oldModelURL.fragment()); + + // set the new model URL as the value in our temp QJsonObject + entity[ENTITY_MODEL_URL_KEY] = newModelURL.toString(); + + // replace our temp object with the value referenced by our QJsonValueRef + entityValue = entity; + } + + // remove the baked URL from the multi hash of entities needing a re-write + _entitiesNeedingRewrite.remove(baker->getFBXUrl()); + + // check if it's time to write out the final entities file with re-written URLs + possiblyOutputEntitiesFile(); + } +} + +void DomainBaker::possiblyOutputEntitiesFile() { + if (_enumeratedAllEntities && _entitiesNeedingRewrite.isEmpty()) { + // we've enumerated all of our entities and re-written all the URLs we'll be able to re-write + // time to write out a main models.json.gz file + + // first setup a document with the entities array below the entities key + QJsonDocument entitiesDocument; + + QJsonObject rootObject; + rootObject[ENTITIES_OBJECT_KEY] = _entities; + + entitiesDocument.setObject(rootObject); + + // turn that QJsonDocument into a byte array ready for compression + QByteArray jsonByteArray = entitiesDocument.toJson(); + + // compress the json byte array using gzip + QByteArray compressedJson; + gzip(jsonByteArray, compressedJson); + + // write the gzipped json to a new models file + static const QString MODELS_FILE_NAME = "models.json.gz"; + + auto bakedEntitiesFilePath = QDir(_uniqueOutputPath).filePath(MODELS_FILE_NAME); + QFile compressedEntitiesFile { bakedEntitiesFilePath }; + + if (!compressedEntitiesFile.open(QIODevice::WriteOnly) + || (compressedEntitiesFile.write(compressedJson) == -1)) { + qWarning() << "Failed to export baked entities file to" << bakedEntitiesFilePath; + // add an error to our list to state that the output models file could not be created or could not be written to + + return; + } + + qDebug() << "Exported entities file with baked model URLs to" << bakedEntitiesFilePath; + + // we've now written out our new models file - time to say that we are finished up + emit finished(); + } +} + diff --git a/tools/oven/src/DomainBaker.h b/tools/oven/src/DomainBaker.h index c0fc40bb93..17419b736a 100644 --- a/tools/oven/src/DomainBaker.h +++ b/tools/oven/src/DomainBaker.h @@ -21,7 +21,8 @@ class DomainBaker : public QObject { Q_OBJECT public: - DomainBaker(const QUrl& localEntitiesFileURL, const QString& domainName, const QString& baseOutputPath); + DomainBaker(const QUrl& localEntitiesFileURL, const QString& domainName, + const QString& baseOutputPath, const QUrl& destinationPath); public slots: void start(); @@ -29,19 +30,28 @@ public slots: signals: void finished(); +private slots: + void handleFinishedBaker(); + private: void setupOutputFolder(); void loadLocalFile(); void enumerateEntities(); + void possiblyOutputEntitiesFile(); QUrl _localEntitiesFileURL; QString _domainName; QString _baseOutputPath; QString _uniqueOutputPath; + QString _contentOutputPath; + QUrl _destinationPath; QJsonArray _entities; QHash> _bakers; + QMultiHash _entitiesNeedingRewrite; + + bool _enumeratedAllEntities { false }; }; #endif // hifi_DomainBaker_h diff --git a/tools/oven/src/ui/DomainBakeWidget.cpp b/tools/oven/src/ui/DomainBakeWidget.cpp index 9d73036a96..2194f17a39 100644 --- a/tools/oven/src/ui/DomainBakeWidget.cpp +++ b/tools/oven/src/ui/DomainBakeWidget.cpp @@ -21,13 +21,17 @@ #include "DomainBakeWidget.h" +static const QString DOMAIN_NAME_SETTING_KEY = "domain_name"; static const QString EXPORT_DIR_SETTING_KEY = "domain_export_directory"; static const QString BROWSE_START_DIR_SETTING_KEY = "domain_search_directory"; +static const QString DESTINATION_PATH_SETTING_KEY = "destination_path"; DomainBakeWidget::DomainBakeWidget(QWidget* parent, Qt::WindowFlags flags) : QWidget(parent, flags), + _domainNameSetting(DOMAIN_NAME_SETTING_KEY), _exportDirectory(EXPORT_DIR_SETTING_KEY), - _browseStartDirectory(BROWSE_START_DIR_SETTING_KEY) + _browseStartDirectory(BROWSE_START_DIR_SETTING_KEY), + _destinationPathSetting(DESTINATION_PATH_SETTING_KEY) { setupUI(); } @@ -44,6 +48,11 @@ void DomainBakeWidget::setupUI() { _domainNameLineEdit = new QLineEdit; _domainNameLineEdit->setPlaceholderText("welcome"); + // set the text of the domain name from whatever was used during last bake + if (!_domainNameSetting.get().isEmpty()) { + _domainNameLineEdit->setText(_domainNameSetting.get()); + } + gridLayout->addWidget(domainNameLabel); gridLayout->addWidget(_domainNameLineEdit, rowIndex, 1, 1, -1); @@ -88,6 +97,22 @@ void DomainBakeWidget::setupUI() { // start a new row for the next component ++rowIndex; + // setup a section to choose the upload prefix - the URL where baked models will be made available + QLabel* uploadPrefixLabel = new QLabel("Destination URL Path"); + + _destinationPathLineEdit = new QLineEdit; + _destinationPathLineEdit->setPlaceholderText("http://cdn.example.com/baked-domain/"); + + if (!_destinationPathSetting.get().isEmpty()) { + _destinationPathLineEdit->setText(_destinationPathSetting.get()); + } + + gridLayout->addWidget(uploadPrefixLabel, rowIndex, 0); + gridLayout->addWidget(_destinationPathLineEdit, rowIndex, 1, 1, -1); + + // start a new row for the next component + ++rowIndex; + // add a horizontal line to split the bake/cancel buttons off QFrame* lineFrame = new QFrame; lineFrame->setFrameShape(QFrame::HLine); @@ -160,6 +185,13 @@ void DomainBakeWidget::outputDirectoryChanged(const QString& newDirectory) { } void DomainBakeWidget::bakeButtonClicked() { + + // save whatever the current domain name is in settings, we'll re-use it next time the widget is shown + _domainNameSetting.set(_domainNameLineEdit->text()); + + // save whatever the current destination path is in settings, we'll re-use it next time the widget is shown + _destinationPathSetting.set(_destinationPathLineEdit->text()); + // make sure we have a valid output directory QDir outputDirectory(_outputDirLineEdit->text()); @@ -172,7 +204,8 @@ void DomainBakeWidget::bakeButtonClicked() { // everything seems to be in place, kick off a bake for this entities file now auto fileToBakeURL = QUrl::fromLocalFile(_entitiesFileLineEdit->text()); _baker = std::unique_ptr { - new DomainBaker(fileToBakeURL, _domainNameLineEdit->text(), outputDirectory.absolutePath()) + new DomainBaker(fileToBakeURL, _domainNameLineEdit->text(), + outputDirectory.absolutePath(), _destinationPathLineEdit->text()) }; _baker->start(); diff --git a/tools/oven/src/ui/DomainBakeWidget.h b/tools/oven/src/ui/DomainBakeWidget.h index 38a2c6a577..20b4eaa4b9 100644 --- a/tools/oven/src/ui/DomainBakeWidget.h +++ b/tools/oven/src/ui/DomainBakeWidget.h @@ -42,9 +42,12 @@ private: QLineEdit* _domainNameLineEdit; QLineEdit* _entitiesFileLineEdit; QLineEdit* _outputDirLineEdit; + QLineEdit* _destinationPathLineEdit; + Setting::Handle _domainNameSetting; Setting::Handle _exportDirectory; Setting::Handle _browseStartDirectory; + Setting::Handle _destinationPathSetting; }; #endif // hifi_ModelBakeWidget_h From 916cecb8ec147c3d9e2227743ea78f7e0b915059 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 13 Apr 2017 16:17:06 -0700 Subject: [PATCH 28/71] use QtConcurrent to cleanup threading of bakers --- libraries/model-baking/CMakeLists.txt | 2 +- libraries/model-baking/src/FBXBaker.cpp | 151 ++++++++++---------- libraries/model-baking/src/FBXBaker.h | 15 +- libraries/model-baking/src/TextureBaker.cpp | 35 ++--- libraries/model-baking/src/TextureBaker.h | 8 +- tools/oven/CMakeLists.txt | 2 +- tools/oven/src/DomainBaker.cpp | 109 +++++++------- tools/oven/src/DomainBaker.h | 9 +- tools/oven/src/Oven.cpp | 2 + tools/oven/src/ui/DomainBakeWidget.cpp | 6 +- tools/oven/src/ui/ModelBakeWidget.cpp | 2 +- 11 files changed, 174 insertions(+), 167 deletions(-) diff --git a/libraries/model-baking/CMakeLists.txt b/libraries/model-baking/CMakeLists.txt index 487b8536c9..a0774cdcc1 100644 --- a/libraries/model-baking/CMakeLists.txt +++ b/libraries/model-baking/CMakeLists.txt @@ -1,6 +1,6 @@ set(TARGET_NAME model-baking) -setup_hifi_library() +setup_hifi_library(Concurrent) link_hifi_libraries(networking) diff --git a/libraries/model-baking/src/FBXBaker.cpp b/libraries/model-baking/src/FBXBaker.cpp index f798a137e4..ac72fb152c 100644 --- a/libraries/model-baking/src/FBXBaker.cpp +++ b/libraries/model-baking/src/FBXBaker.cpp @@ -11,8 +11,12 @@ #include +#include +#include #include +#include #include +#include #include @@ -46,14 +50,71 @@ QString FBXBaker::pathToCopyOfOriginal() const { return _uniqueOutputPath + ORIGINAL_OUTPUT_SUBFOLDER + _fbxURL.fileName(); } -void FBXBaker::start() { +void FBXBaker::bake() { qCDebug(model_baking) << "Baking" << _fbxURL; // setup the output folder for the results of this bake - if (!setupOutputFolder()) { + setupOutputFolder(); + + // make a local copy of the FBX file + loadSourceFBX(); + + // load the scene from the FBX file + importScene(); + + // enumerate the textures found in the scene and start a bake for them + rewriteAndBakeSceneTextures(); + + // export the FBX with re-written texture references + exportScene(); + + // remove the embedded media folder that the FBX SDK produces when reading the original + removeEmbeddedMediaFolder(); + + // cleanup the originals if we weren't asked to keep them around + possiblyCleanupOriginals(); + + // if the texture baking operations are not complete + // use a QEventLoop to process events until texture bake operations are complete + if (!_unbakedTextures.isEmpty()) { + QEventLoop eventLoop; + connect(this, &FBXBaker::allTexturesBaked, &eventLoop, &QEventLoop::quit); + eventLoop.exec(); + } + + emit finished(); +} + +void FBXBaker::setupOutputFolder() { + // construct the output path using the name of the fbx and the base output path + _uniqueOutputPath = _baseOutputPath + "/" + _fbxName + "/"; + + // make sure there isn't already an output directory using the same name + int iteration = 0; + + while (QDir(_uniqueOutputPath).exists()) { + _uniqueOutputPath = _baseOutputPath + "/" + _fbxName + "-" + QString::number(++iteration) + "/"; + } + + qCDebug(model_baking) << "Creating FBX output folder" << _uniqueOutputPath; + + // attempt to make the output folder + if (!QDir().mkdir(_uniqueOutputPath)) { + qCCritical(model_baking) << "Failed to create FBX output folder" << _uniqueOutputPath; + return; } + // make the baked and original sub-folders used during export + QDir uniqueOutputDir = _uniqueOutputPath; + if (!uniqueOutputDir.mkdir(BAKED_OUTPUT_SUBFOLDER) || !uniqueOutputDir.mkdir(ORIGINAL_OUTPUT_SUBFOLDER)) { + qCCritical(model_baking) << "Failed to create baked/original subfolders in" << _uniqueOutputPath; + + return; + } +} + +void FBXBaker::loadSourceFBX() { // check if the FBX is local or first needs to be downloaded if (_fbxURL.isLocalFile()) { // load up the local file @@ -77,48 +138,18 @@ void FBXBaker::start() { networkRequest.setUrl(_fbxURL); qCDebug(model_baking) << "Downloading" << _fbxURL; - auto networkReply = networkAccessManager.get(networkRequest); - connect(networkReply, &QNetworkReply::finished, this, &FBXBaker::handleFBXNetworkReply); + + // start a QEventLoop so we process events while waiting for the request to complete + QEventLoop eventLoop; + connect(networkReply, &QNetworkReply::finished, &eventLoop, &QEventLoop::quit); + eventLoop.exec(); + + handleFBXNetworkReply(networkReply); } } -bool FBXBaker::setupOutputFolder() { - // construct the output path using the name of the fbx and the base output path - _uniqueOutputPath = _baseOutputPath + "/" + _fbxName + "/"; - - // make sure there isn't already an output directory using the same name - int iteration = 0; - - while (QDir(_uniqueOutputPath).exists()) { - _uniqueOutputPath = _baseOutputPath + "/" + _fbxName + "-" + QString::number(++iteration) + "/"; - } - - qCDebug(model_baking) << "Creating FBX output folder" << _uniqueOutputPath; - - // attempt to make the output folder - if (!QDir().mkdir(_uniqueOutputPath)) { - qCCritical(model_baking) << "Failed to create FBX output folder" << _uniqueOutputPath; - - emit finished(); - return false; - } - - // make the baked and original sub-folders used during export - QDir uniqueOutputDir = _uniqueOutputPath; - if (!uniqueOutputDir.mkdir(BAKED_OUTPUT_SUBFOLDER) || !uniqueOutputDir.mkdir(ORIGINAL_OUTPUT_SUBFOLDER)) { - qCCritical(model_baking) << "Failed to create baked/original subfolders in" << _uniqueOutputPath; - - emit finished(); - return false; - } - - return true; -} - -void FBXBaker::handleFBXNetworkReply() { - QNetworkReply* requestReply = qobject_cast(sender()); - +void FBXBaker::handleFBXNetworkReply(QNetworkReply* requestReply) { if (requestReply->error() == QNetworkReply::NoError) { qCDebug(model_baking) << "Downloaded" << _fbxURL; @@ -136,36 +167,12 @@ void FBXBaker::handleFBXNetworkReply() { // close that file now that we are done writing to it copyOfOriginal.close(); - - // kick off the bake process now that everything is ready to go - bake(); } else { // add an error to our list stating that the FBX could not be downloaded - - emit finished(); } } -void FBXBaker::bake() { - // (1) load the scene from the FBX file - // (2) enumerate the textures found in the scene and start a bake for them - // (3) export the FBX with re-written texture references - - importScene(); - rewriteAndBakeSceneTextures(); - exportScene(); - - removeEmbeddedMediaFolder(); - possiblyCleanupOriginals(); - - // at this point we are sure that we've finished everything that does not relate to textures - // so set that flag now - _finishedNonTextureOperations = true; - - checkIfFinished(); -} - bool FBXBaker::importScene() { // create an FBX SDK importer FbxImporter* importer = FbxImporter::Create(_sdkManager, ""); @@ -235,8 +242,6 @@ QString FBXBaker::createBakedTextureFileName(const QFileInfo& textureFileInfo) { QUrl FBXBaker::getTextureURL(const QFileInfo& textureFileInfo, FbxFileTexture* fileTexture) { QUrl urlToTexture; - qDebug() << "Looking at" << textureFileInfo.absoluteFilePath(); - if (textureFileInfo.exists() && textureFileInfo.isFile()) { // set the texture URL to the local texture that we have confirmed exists urlToTexture = QUrl::fromLocalFile(textureFileInfo.absoluteFilePath()); @@ -411,7 +416,7 @@ void FBXBaker::bakeTexture(const QUrl& textureURL) { connect(bakingTexture, &TextureBaker::finished, this, &FBXBaker::handleBakedTexture); - bakingTexture->start(); + QtConcurrent::run(bakingTexture, &TextureBaker::bake); _bakingTextures.emplace_back(bakingTexture); } @@ -454,9 +459,9 @@ void FBXBaker::handleBakedTexture() { // now that this texture has been baked and handled, we can remove that TextureBaker from our list _unbakedTextures.remove(bakedTexture->getTextureURL()); - // since this could have been the last texture we were waiting for - // we should perform a quick check now to see if we are done baking this model - checkIfFinished(); + if (_unbakedTextures.isEmpty()) { + emit allTexturesBaked(); + } } bool FBXBaker::exportScene() { @@ -500,9 +505,3 @@ void FBXBaker::possiblyCleanupOriginals() { QDir(_uniqueOutputPath + ORIGINAL_OUTPUT_SUBFOLDER).removeRecursively(); } } - -void FBXBaker::checkIfFinished() { - if (_unbakedTextures.isEmpty() && _finishedNonTextureOperations) { - emit finished(); - } -} diff --git a/libraries/model-baking/src/FBXBaker.h b/libraries/model-baking/src/FBXBaker.h index 71ded0c44a..458723dab0 100644 --- a/libraries/model-baking/src/FBXBaker.h +++ b/libraries/model-baking/src/FBXBaker.h @@ -12,6 +12,7 @@ #ifndef hifi_FBXBaker_h #define hifi_FBXBaker_h +#include #include #include #include @@ -52,28 +53,29 @@ public: FBXBaker(const QUrl& fbxURL, const QString& baseOutputPath, bool copyOriginals = true); ~FBXBaker(); - void start(); + void bake(); QUrl getFBXUrl() const { return _fbxURL; } QString getBakedFBXRelativePath() const { return _bakedFBXRelativePath; } signals: void finished(); + void allTexturesBaked(); private slots: - void handleFBXNetworkReply(); void handleBakedTexture(); private: - void bake(); + void setupOutputFolder(); + + void loadSourceFBX(); + void handleFBXNetworkReply(QNetworkReply* requestReply); - bool setupOutputFolder(); bool importScene(); bool rewriteAndBakeSceneTextures(); bool exportScene(); void removeEmbeddedMediaFolder(); void possiblyCleanupOriginals(); - void checkIfFinished(); QString createBakedTextureFileName(const QFileInfo& textureFileInfo); QUrl getTextureURL(const QFileInfo& textureFileInfo, fbxsdk::FbxFileTexture* fileTexture); @@ -99,10 +101,9 @@ private: QHash _textureTypes; std::list> _bakingTextures; + QFutureSynchronizer _textureBakeSynchronizer; bool _copyOriginals { true }; - - bool _finishedNonTextureOperations { false }; }; #endif // hifi_FBXBaker_h diff --git a/libraries/model-baking/src/TextureBaker.cpp b/libraries/model-baking/src/TextureBaker.cpp index 367ae5f463..b8c49c6d50 100644 --- a/libraries/model-baking/src/TextureBaker.cpp +++ b/libraries/model-baking/src/TextureBaker.cpp @@ -9,6 +9,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include #include #include @@ -24,8 +25,16 @@ TextureBaker::TextureBaker(const QUrl& textureURL) : } -void TextureBaker::start() { +void TextureBaker::bake() { + // first load the texture (either locally or remotely) + loadTexture(); + qCDebug(model_baking) << "Baking texture at" << _textureURL; + + emit finished(); +} + +void TextureBaker::loadTexture() { // check if the texture is local or first needs to be downloaded if (_textureURL.isLocalFile()) { // load up the local file @@ -39,9 +48,6 @@ void TextureBaker::start() { } _originalTexture = localTexture.readAll(); - - // start the bake now that we have everything in place - bake(); } else { // remote file, kick off a download auto& networkAccessManager = NetworkAccessManager::getInstance(); @@ -57,13 +63,17 @@ void TextureBaker::start() { qCDebug(model_baking) << "Downloading" << _textureURL; auto networkReply = networkAccessManager.get(networkRequest); - connect(networkReply, &QNetworkReply::finished, this, &TextureBaker::handleTextureNetworkReply); + + // use an event loop to process events while we wait for the network reply + QEventLoop eventLoop; + connect(networkReply, &QNetworkReply::finished, &eventLoop, &QEventLoop::quit); + eventLoop.exec(); + + handleTextureNetworkReply(networkReply); } } -void TextureBaker::handleTextureNetworkReply() { - QNetworkReply* requestReply = qobject_cast(sender()); - +void TextureBaker::handleTextureNetworkReply(QNetworkReply* requestReply) { if (requestReply->error() == QNetworkReply::NoError) { qCDebug(model_baking) << "Downloaded texture at" << _textureURL; @@ -75,14 +85,5 @@ void TextureBaker::handleTextureNetworkReply() { } else { // add an error to our list stating that this texture could not be downloaded qCDebug(model_baking) << "Error downloading texture" << requestReply->errorString(); - - emit finished(); } } - -void TextureBaker::bake() { - qCDebug(model_baking) << "Baking texture at" << _textureURL; - - // call image library to asynchronously bake this texture - emit finished(); -} diff --git a/libraries/model-baking/src/TextureBaker.h b/libraries/model-baking/src/TextureBaker.h index 5544352005..7394a0652e 100644 --- a/libraries/model-baking/src/TextureBaker.h +++ b/libraries/model-baking/src/TextureBaker.h @@ -21,7 +21,7 @@ class TextureBaker : public QObject { public: TextureBaker(const QUrl& textureURL); - void start(); + void bake(); const QByteArray& getOriginalTexture() const { return _originalTexture; } @@ -30,11 +30,9 @@ public: signals: void finished(); -private slots: - void handleTextureNetworkReply(); - private: - void bake(); + void loadTexture(); + void handleTextureNetworkReply(QNetworkReply* requestReply); QUrl _textureURL; QByteArray _originalTexture; diff --git a/tools/oven/CMakeLists.txt b/tools/oven/CMakeLists.txt index 2e40cc9de4..2c5d0b98e5 100644 --- a/tools/oven/CMakeLists.txt +++ b/tools/oven/CMakeLists.txt @@ -1,5 +1,5 @@ set(TARGET_NAME oven) -setup_hifi_project(Widgets Gui) +setup_hifi_project(Widgets Gui Concurrent) link_hifi_libraries(model-baking shared) diff --git a/tools/oven/src/DomainBaker.cpp b/tools/oven/src/DomainBaker.cpp index 7cd675de3f..c8e03e1224 100644 --- a/tools/oven/src/DomainBaker.cpp +++ b/tools/oven/src/DomainBaker.cpp @@ -9,10 +9,10 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include +#include +#include #include #include - #include #include @@ -34,10 +34,22 @@ DomainBaker::DomainBaker(const QUrl& localModelFileURL, const QString& domainNam } } -void DomainBaker::start() { +void DomainBaker::bake() { setupOutputFolder(); loadLocalFile(); enumerateEntities(); + + if (!_entitiesNeedingRewrite.isEmpty()) { + // use a QEventLoop to wait for all entity rewrites to be completed before writing the final models file + QEventLoop eventLoop; + connect(this, &DomainBaker::allModelsFinished, &eventLoop, &QEventLoop::quit); + eventLoop.exec(); + } + + writeNewEntitiesFile(); + + // we've now written out our new models file - time to say that we are finished up + emit finished(); } void DomainBaker::setupOutputFolder() { @@ -147,11 +159,11 @@ void DomainBaker::enumerateEntities() { // make sure our handler is called when the baker is done connect(baker.data(), &FBXBaker::finished, this, &DomainBaker::handleFinishedBaker); - // start the baker - baker->start(); - // insert it into our bakers hash so we hold a strong pointer to it _bakers.insert(modelURL, baker); + + // send the FBXBaker to the thread pool + QtConcurrent::run(baker.data(), &FBXBaker::bake); } // add this QJsonValueRef to our multi hash so that we can easily re-write @@ -161,11 +173,6 @@ void DomainBaker::enumerateEntities() { } } } - - _enumeratedAllEntities = true; - - // check if it's time to write out the final entities file with re-written URLs - possiblyOutputEntitiesFile(); } void DomainBaker::handleFinishedBaker() { @@ -201,49 +208,45 @@ void DomainBaker::handleFinishedBaker() { // remove the baked URL from the multi hash of entities needing a re-write _entitiesNeedingRewrite.remove(baker->getFBXUrl()); - // check if it's time to write out the final entities file with re-written URLs - possiblyOutputEntitiesFile(); - } -} - -void DomainBaker::possiblyOutputEntitiesFile() { - if (_enumeratedAllEntities && _entitiesNeedingRewrite.isEmpty()) { - // we've enumerated all of our entities and re-written all the URLs we'll be able to re-write - // time to write out a main models.json.gz file - - // first setup a document with the entities array below the entities key - QJsonDocument entitiesDocument; - - QJsonObject rootObject; - rootObject[ENTITIES_OBJECT_KEY] = _entities; - - entitiesDocument.setObject(rootObject); - - // turn that QJsonDocument into a byte array ready for compression - QByteArray jsonByteArray = entitiesDocument.toJson(); - - // compress the json byte array using gzip - QByteArray compressedJson; - gzip(jsonByteArray, compressedJson); - - // write the gzipped json to a new models file - static const QString MODELS_FILE_NAME = "models.json.gz"; - - auto bakedEntitiesFilePath = QDir(_uniqueOutputPath).filePath(MODELS_FILE_NAME); - QFile compressedEntitiesFile { bakedEntitiesFilePath }; - - if (!compressedEntitiesFile.open(QIODevice::WriteOnly) - || (compressedEntitiesFile.write(compressedJson) == -1)) { - qWarning() << "Failed to export baked entities file to" << bakedEntitiesFilePath; - // add an error to our list to state that the output models file could not be created or could not be written to - - return; + if (_entitiesNeedingRewrite.isEmpty()) { + emit allModelsFinished(); } - - qDebug() << "Exported entities file with baked model URLs to" << bakedEntitiesFilePath; - - // we've now written out our new models file - time to say that we are finished up - emit finished(); } } +void DomainBaker::writeNewEntitiesFile() { + // we've enumerated all of our entities and re-written all the URLs we'll be able to re-write + // time to write out a main models.json.gz file + + // first setup a document with the entities array below the entities key + QJsonDocument entitiesDocument; + + QJsonObject rootObject; + rootObject[ENTITIES_OBJECT_KEY] = _entities; + + entitiesDocument.setObject(rootObject); + + // turn that QJsonDocument into a byte array ready for compression + QByteArray jsonByteArray = entitiesDocument.toJson(); + + // compress the json byte array using gzip + QByteArray compressedJson; + gzip(jsonByteArray, compressedJson); + + // write the gzipped json to a new models file + static const QString MODELS_FILE_NAME = "models.json.gz"; + + auto bakedEntitiesFilePath = QDir(_uniqueOutputPath).filePath(MODELS_FILE_NAME); + QFile compressedEntitiesFile { bakedEntitiesFilePath }; + + if (!compressedEntitiesFile.open(QIODevice::WriteOnly) + || (compressedEntitiesFile.write(compressedJson) == -1)) { + qWarning() << "Failed to export baked entities file to" << bakedEntitiesFilePath; + // add an error to our list to state that the output models file could not be created or could not be written to + + return; + } + + qDebug() << "Exported entities file with baked model URLs to" << bakedEntitiesFilePath; +} + diff --git a/tools/oven/src/DomainBaker.h b/tools/oven/src/DomainBaker.h index 17419b736a..e3585ba64f 100644 --- a/tools/oven/src/DomainBaker.h +++ b/tools/oven/src/DomainBaker.h @@ -24,11 +24,12 @@ public: DomainBaker(const QUrl& localEntitiesFileURL, const QString& domainName, const QString& baseOutputPath, const QUrl& destinationPath); -public slots: - void start(); +public: + void bake(); signals: void finished(); + void allModelsFinished(); private slots: void handleFinishedBaker(); @@ -37,7 +38,7 @@ private: void setupOutputFolder(); void loadLocalFile(); void enumerateEntities(); - void possiblyOutputEntitiesFile(); + void writeNewEntitiesFile(); QUrl _localEntitiesFileURL; QString _domainName; @@ -50,8 +51,6 @@ private: QHash> _bakers; QMultiHash _entitiesNeedingRewrite; - - bool _enumeratedAllEntities { false }; }; #endif // hifi_DomainBaker_h diff --git a/tools/oven/src/Oven.cpp b/tools/oven/src/Oven.cpp index ede1b7c1e4..60754759f4 100644 --- a/tools/oven/src/Oven.cpp +++ b/tools/oven/src/Oven.cpp @@ -9,6 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include + #include #include "ui/OvenMainWindow.h" diff --git a/tools/oven/src/ui/DomainBakeWidget.cpp b/tools/oven/src/ui/DomainBakeWidget.cpp index 2194f17a39..01bc6110cb 100644 --- a/tools/oven/src/ui/DomainBakeWidget.cpp +++ b/tools/oven/src/ui/DomainBakeWidget.cpp @@ -9,6 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include + #include #include #include @@ -207,7 +209,9 @@ void DomainBakeWidget::bakeButtonClicked() { new DomainBaker(fileToBakeURL, _domainNameLineEdit->text(), outputDirectory.absolutePath(), _destinationPathLineEdit->text()) }; - _baker->start(); + + // run the baker in our thread pool + QtConcurrent::run(_baker.get(), &DomainBaker::bake); return; } diff --git a/tools/oven/src/ui/ModelBakeWidget.cpp b/tools/oven/src/ui/ModelBakeWidget.cpp index ff95e521c5..5f31ed2673 100644 --- a/tools/oven/src/ui/ModelBakeWidget.cpp +++ b/tools/oven/src/ui/ModelBakeWidget.cpp @@ -176,7 +176,7 @@ void ModelBakeWidget::bakeButtonClicked() { // everything seems to be in place, kick off a bake for this model now auto baker = new FBXBaker(modelToBakeURL, outputDirectory.absolutePath(), false); - baker->start(); + baker->bake(); _bakers.emplace_back(baker); } } From c5fbd28ecf3425bb2f110aa59078dc9b0fee36c5 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 13 Apr 2017 18:25:27 -0700 Subject: [PATCH 29/71] put all FBXBaker on same thread for bad FBX SDK --- libraries/model-baking/src/FBXBaker.cpp | 103 +++++++++++--------- libraries/model-baking/src/FBXBaker.h | 23 +++-- libraries/model-baking/src/TextureBaker.cpp | 3 - tools/oven/src/DomainBaker.cpp | 19 +++- tools/oven/src/DomainBaker.h | 3 + 5 files changed, 93 insertions(+), 58 deletions(-) diff --git a/libraries/model-baking/src/FBXBaker.cpp b/libraries/model-baking/src/FBXBaker.cpp index ac72fb152c..5b5a8b7c0a 100644 --- a/libraries/model-baking/src/FBXBaker.cpp +++ b/libraries/model-baking/src/FBXBaker.cpp @@ -50,39 +50,59 @@ QString FBXBaker::pathToCopyOfOriginal() const { return _uniqueOutputPath + ORIGINAL_OUTPUT_SUBFOLDER + _fbxURL.fileName(); } +void FBXBaker::handleError(const QString& error) { + qCCritical(model_baking) << error; + _errorList << error; + emit finished(); +} + void FBXBaker::bake() { qCDebug(model_baking) << "Baking" << _fbxURL; // setup the output folder for the results of this bake setupOutputFolder(); + if (hasErrors()) { + return; + } + + connect(this, &FBXBaker::sourceCopyReadyToLoad, this, &FBXBaker::bakeSourceCopy); + // make a local copy of the FBX file loadSourceFBX(); +} +void FBXBaker::bakeSourceCopy() { // load the scene from the FBX file importScene(); + if (hasErrors()) { + return; + } + // enumerate the textures found in the scene and start a bake for them rewriteAndBakeSceneTextures(); + if (hasErrors()) { + return; + } + // export the FBX with re-written texture references exportScene(); + if (hasErrors()) { + return; + } + // remove the embedded media folder that the FBX SDK produces when reading the original removeEmbeddedMediaFolder(); - // cleanup the originals if we weren't asked to keep them around - possiblyCleanupOriginals(); - - // if the texture baking operations are not complete - // use a QEventLoop to process events until texture bake operations are complete - if (!_unbakedTextures.isEmpty()) { - QEventLoop eventLoop; - connect(this, &FBXBaker::allTexturesBaked, &eventLoop, &QEventLoop::quit); - eventLoop.exec(); + if (hasErrors()) { + return; } - emit finished(); + // cleanup the originals if we weren't asked to keep them around + possiblyCleanupOriginals(); } void FBXBaker::setupOutputFolder() { @@ -100,16 +120,14 @@ void FBXBaker::setupOutputFolder() { // attempt to make the output folder if (!QDir().mkdir(_uniqueOutputPath)) { - qCCritical(model_baking) << "Failed to create FBX output folder" << _uniqueOutputPath; - + handleError("Failed to create FBX output folder " + _uniqueOutputPath); return; } // make the baked and original sub-folders used during export QDir uniqueOutputDir = _uniqueOutputPath; if (!uniqueOutputDir.mkdir(BAKED_OUTPUT_SUBFOLDER) || !uniqueOutputDir.mkdir(ORIGINAL_OUTPUT_SUBFOLDER)) { - qCCritical(model_baking) << "Failed to create baked/original subfolders in" << _uniqueOutputPath; - + handleError("Failed to create baked/original subfolders in " + _uniqueOutputPath); return; } } @@ -123,8 +141,8 @@ void FBXBaker::loadSourceFBX() { // make a copy in the output folder localFBX.copy(pathToCopyOfOriginal()); - // start the bake now that we have everything in place - bake(); + // emit our signal to start the import of the FBX source copy + emit sourceCopyReadyToLoad(); } else { // remote file, kick off a download auto& networkAccessManager = NetworkAccessManager::getInstance(); @@ -140,16 +158,13 @@ void FBXBaker::loadSourceFBX() { qCDebug(model_baking) << "Downloading" << _fbxURL; auto networkReply = networkAccessManager.get(networkRequest); - // start a QEventLoop so we process events while waiting for the request to complete - QEventLoop eventLoop; - connect(networkReply, &QNetworkReply::finished, &eventLoop, &QEventLoop::quit); - eventLoop.exec(); - - handleFBXNetworkReply(networkReply); + connect(networkReply, &QNetworkReply::finished, this, &FBXBaker::handleFBXNetworkReply); } } -void FBXBaker::handleFBXNetworkReply(QNetworkReply* requestReply) { +void FBXBaker::handleFBXNetworkReply() { + auto requestReply = qobject_cast(sender()); + if (requestReply->error() == QNetworkReply::NoError) { qCDebug(model_baking) << "Downloaded" << _fbxURL; @@ -159,21 +174,23 @@ void FBXBaker::handleFBXNetworkReply(QNetworkReply* requestReply) { qDebug(model_baking) << "Writing copy of original FBX to" << copyOfOriginal.fileName(); if (!copyOfOriginal.open(QIODevice::WriteOnly) || (copyOfOriginal.write(requestReply->readAll()) == -1)) { - // add an error to the error list for this FBX stating that a duplicate of the original FBX could not be made - emit finished(); + handleError("Could not create copy of " + _fbxURL.toString()); return; } // close that file now that we are done writing to it copyOfOriginal.close(); + + // emit our signal to start the import of the FBX source copy + emit sourceCopyReadyToLoad(); } else { // add an error to our list stating that the FBX could not be downloaded - + handleError("Failed to download " + _fbxURL.toString()); } } -bool FBXBaker::importScene() { +void FBXBaker::importScene() { // create an FBX SDK importer FbxImporter* importer = FbxImporter::Create(_sdkManager, ""); @@ -183,10 +200,8 @@ bool FBXBaker::importScene() { if (!importStatus) { // failed to initialize importer, print an error and return - qCCritical(model_baking) << "Failed to import FBX file at" << _fbxURL - << "- error:" << importer->GetStatus().GetErrorString(); - - return false; + handleError("Failed to import FBX file at" + _fbxURL.toString() + " - error:" + importer->GetStatus().GetErrorString()); + return; } else { qCDebug(model_baking) << "Imported" << _fbxURL << "to FbxScene"; } @@ -199,8 +214,6 @@ bool FBXBaker::importScene() { // destroy the importer that is no longer needed importer->Destroy(); - - return true; } static const QString BAKED_TEXTURE_EXT = ".ktx"; @@ -344,7 +357,7 @@ TextureType textureTypeForMaterialProperty(FbxProperty& property, FbxSurfaceMate return UNUSED_TEXTURE; } -bool FBXBaker::rewriteAndBakeSceneTextures() { +void FBXBaker::rewriteAndBakeSceneTextures() { // enumerate the surface materials to find the textures used in the scene int numMaterials = _scene->GetMaterialCount(); @@ -406,8 +419,6 @@ bool FBXBaker::rewriteAndBakeSceneTextures() { } } } - - return true; } void FBXBaker::bakeTexture(const QUrl& textureURL) { @@ -449,22 +460,24 @@ void FBXBaker::handleBakedTexture() { if (originalTextureFile.open(QIODevice::WriteOnly) && originalTextureFile.write(bakedTexture->getOriginalTexture()) != -1) { qCDebug(model_baking) << "Saved original texture file" << originalTextureFile.fileName() - << "for" << _fbxURL; + << "for" << _fbxURL; } else { - qCWarning(model_baking) << "Could not save original external texture" << originalTextureFile.fileName() - << "for" << _fbxURL; + handleError("Could not save original external texture " + originalTextureFile.fileName() + + " for " + _fbxURL.toString()); + return; } } // now that this texture has been baked and handled, we can remove that TextureBaker from our list _unbakedTextures.remove(bakedTexture->getTextureURL()); + // check if we're done everything we need to do for this FBX if (_unbakedTextures.isEmpty()) { - emit allTexturesBaked(); + emit finished(); } } -bool FBXBaker::exportScene() { +void FBXBaker::exportScene() { // setup the exporter FbxExporter* exporter = FbxExporter::Create(_sdkManager, ""); @@ -478,18 +491,14 @@ bool FBXBaker::exportScene() { if (!exportStatus) { // failed to initialize exporter, print an error and return - qCCritical(model_baking) << "Failed to export FBX file at" << _fbxURL - << "to" << rewrittenFBXPath << "- error:" << exporter->GetStatus().GetErrorString(); - - return false; + handleError("Failed to export FBX file at " + _fbxURL.toString() + " to " + rewrittenFBXPath + + "- error: " + exporter->GetStatus().GetErrorString()); } // export the scene exporter->Export(_scene); qCDebug(model_baking) << "Exported" << _fbxURL << "with re-written paths to" << rewrittenFBXPath; - - return true; } diff --git a/libraries/model-baking/src/FBXBaker.h b/libraries/model-baking/src/FBXBaker.h index 458723dab0..10c8a5c359 100644 --- a/libraries/model-baking/src/FBXBaker.h +++ b/libraries/model-baking/src/FBXBaker.h @@ -53,7 +53,9 @@ public: FBXBaker(const QUrl& fbxURL, const QString& baseOutputPath, bool copyOriginals = true); ~FBXBaker(); - void bake(); + Q_INVOKABLE void bake(); + + bool hasErrors() const { return !_errorList.isEmpty(); } QUrl getFBXUrl() const { return _fbxURL; } QString getBakedFBXRelativePath() const { return _bakedFBXRelativePath; } @@ -62,18 +64,23 @@ signals: void finished(); void allTexturesBaked(); + void sourceCopyReadyToLoad(); + private slots: + void bakeSourceCopy(); + void handleFBXNetworkReply(); void handleBakedTexture(); - + private: void setupOutputFolder(); void loadSourceFBX(); - void handleFBXNetworkReply(QNetworkReply* requestReply); - bool importScene(); - bool rewriteAndBakeSceneTextures(); - bool exportScene(); + void bakeCopiedFBX(); + + void importScene(); + void rewriteAndBakeSceneTextures(); + void exportScene(); void removeEmbeddedMediaFolder(); void possiblyCleanupOriginals(); @@ -84,6 +91,8 @@ private: QString pathToCopyOfOriginal() const; + void handleError(const QString& error); + QUrl _fbxURL; QString _fbxName; @@ -104,6 +113,8 @@ private: QFutureSynchronizer _textureBakeSynchronizer; bool _copyOriginals { true }; + + bool _finishedNonTextureOperations { false }; }; #endif // hifi_FBXBaker_h diff --git a/libraries/model-baking/src/TextureBaker.cpp b/libraries/model-baking/src/TextureBaker.cpp index b8c49c6d50..a717835ed7 100644 --- a/libraries/model-baking/src/TextureBaker.cpp +++ b/libraries/model-baking/src/TextureBaker.cpp @@ -79,9 +79,6 @@ void TextureBaker::handleTextureNetworkReply(QNetworkReply* requestReply) { // store the original texture so it can be passed along for the bake _originalTexture = requestReply->readAll(); - - // kickoff the texture bake now that everything is ready to go - bake(); } else { // add an error to our list stating that this texture could not be downloaded qCDebug(model_baking) << "Error downloading texture" << requestReply->errorString(); diff --git a/tools/oven/src/DomainBaker.cpp b/tools/oven/src/DomainBaker.cpp index c8e03e1224..c779fd3a40 100644 --- a/tools/oven/src/DomainBaker.cpp +++ b/tools/oven/src/DomainBaker.cpp @@ -37,6 +37,7 @@ DomainBaker::DomainBaker(const QUrl& localModelFileURL, const QString& domainNam void DomainBaker::bake() { setupOutputFolder(); loadLocalFile(); + setupBakerThread(); enumerateEntities(); if (!_entitiesNeedingRewrite.isEmpty()) { @@ -48,6 +49,9 @@ void DomainBaker::bake() { writeNewEntitiesFile(); + // stop the FBX baker thread now that all our bakes have completed + _fbxBakerThread->quit(); + // we've now written out our new models file - time to say that we are finished up emit finished(); } @@ -126,6 +130,15 @@ void DomainBaker::loadLocalFile() { } } +void DomainBaker::setupBakerThread() { + // This is a real bummer, but the FBX SDK is not thread safe - even with separate FBXManager objects. + // This means that we need to put all of the FBX importing/exporting on the same thread. + // We'll setup that thread now and then move the FBXBaker objects to the thread later when enumerating entities. + _fbxBakerThread = std::unique_ptr(new QThread); + _fbxBakerThread->setObjectName("Domain FBX Baker Thread"); + _fbxBakerThread->start(); +} + static const QString ENTITY_MODEL_URL_KEY = "modelURL"; void DomainBaker::enumerateEntities() { @@ -162,8 +175,10 @@ void DomainBaker::enumerateEntities() { // insert it into our bakers hash so we hold a strong pointer to it _bakers.insert(modelURL, baker); - // send the FBXBaker to the thread pool - QtConcurrent::run(baker.data(), &FBXBaker::bake); + // move the baker to the baker thread + // and kickoff the bake + baker->moveToThread(_fbxBakerThread.get()); + QMetaObject::invokeMethod(baker.data(), "bake"); } // add this QJsonValueRef to our multi hash so that we can easily re-write diff --git a/tools/oven/src/DomainBaker.h b/tools/oven/src/DomainBaker.h index e3585ba64f..f949ddba9c 100644 --- a/tools/oven/src/DomainBaker.h +++ b/tools/oven/src/DomainBaker.h @@ -15,6 +15,7 @@ #include #include #include +#include #include @@ -37,6 +38,7 @@ private slots: private: void setupOutputFolder(); void loadLocalFile(); + void setupBakerThread(); void enumerateEntities(); void writeNewEntitiesFile(); @@ -49,6 +51,7 @@ private: QJsonArray _entities; + std::unique_ptr _fbxBakerThread; QHash> _bakers; QMultiHash _entitiesNeedingRewrite; }; From 7c5376bb1f6b8b9191ca1453ea93a0a87cde972f Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 14 Apr 2017 13:06:12 -0700 Subject: [PATCH 30/71] put fbx bakers on their own thread from ModelBakeWidget --- tools/oven/src/ui/ModelBakeWidget.cpp | 37 +++++++++++++++++++-------- tools/oven/src/ui/ModelBakeWidget.h | 4 +++ 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/tools/oven/src/ui/ModelBakeWidget.cpp b/tools/oven/src/ui/ModelBakeWidget.cpp index 5f31ed2673..3b28bb77fa 100644 --- a/tools/oven/src/ui/ModelBakeWidget.cpp +++ b/tools/oven/src/ui/ModelBakeWidget.cpp @@ -18,6 +18,7 @@ #include #include +#include #include "ModelBakeWidget.h" @@ -27,11 +28,18 @@ static const QString MODEL_START_DIR_SETTING_KEY = "model_search_directory"; ModelBakeWidget::ModelBakeWidget(QWidget* parent, Qt::WindowFlags flags) : QWidget(parent, flags), _exportDirectory(EXPORT_DIR_SETTING_KEY), - _modelStartDirectory(MODEL_START_DIR_SETTING_KEY) + _modelStartDirectory(MODEL_START_DIR_SETTING_KEY), + _bakerThread(new QThread(this)) { setupUI(); } +ModelBakeWidget::~ModelBakeWidget() { + // before we go down, stop the baker thread and make sure it's done + _bakerThread->quit(); + _bakerThread->wait(); +} + void ModelBakeWidget::setupUI() { // setup a grid layout to hold everything QGridLayout* gridLayout = new QGridLayout; @@ -115,13 +123,12 @@ void ModelBakeWidget::chooseFileButtonClicked() { // set the contents of the model file text box to be the path to the selected file _modelLineEdit->setText(selectedFiles.join(',')); - auto directoryOfModel = QFileInfo(selectedFiles[0]).absolutePath(); + if (_outputDirLineEdit->text().isEmpty()) { + auto directoryOfModel = QFileInfo(selectedFiles[0]).absolutePath(); - // save the directory containing this model so we can default to it next time we show the file dialog - _modelStartDirectory.set(directoryOfModel); - - // if our output directory is not yet set, set it to the directory of this model - _outputDirLineEdit->setText(directoryOfModel); + // if our output directory is not yet set, set it to the directory of this model + _outputDirLineEdit->setText(directoryOfModel); + } } } @@ -153,13 +160,11 @@ void ModelBakeWidget::bakeButtonClicked() { QDir outputDirectory(_outputDirLineEdit->text()); if (!outputDirectory.exists()) { - return; } // make sure we have a non empty URL to a model to bake if (_modelLineEdit->text().isEmpty()) { - return; } @@ -176,7 +181,19 @@ void ModelBakeWidget::bakeButtonClicked() { // everything seems to be in place, kick off a bake for this model now auto baker = new FBXBaker(modelToBakeURL, outputDirectory.absolutePath(), false); - baker->bake(); + + // move the baker to the baker thread + baker->moveToThread(_bakerThread); + + // make sure we start the baker thread if it isn't already running + if (!_bakerThread->isRunning()) { + _bakerThread->start(); + } + + // invoke the bake method on the baker thread + QMetaObject::invokeMethod(baker, "bake"); + + // keep a unique_ptr to this baker _bakers.emplace_back(baker); } } diff --git a/tools/oven/src/ui/ModelBakeWidget.h b/tools/oven/src/ui/ModelBakeWidget.h index 354ad9f311..f277d91938 100644 --- a/tools/oven/src/ui/ModelBakeWidget.h +++ b/tools/oven/src/ui/ModelBakeWidget.h @@ -19,12 +19,14 @@ #include class QLineEdit; +class QThread; class ModelBakeWidget : public QWidget { Q_OBJECT public: ModelBakeWidget(QWidget* parent = nullptr, Qt::WindowFlags flags = Qt::WindowFlags()); + ~ModelBakeWidget(); private slots: void chooseFileButtonClicked(); @@ -44,6 +46,8 @@ private: Setting::Handle _exportDirectory; Setting::Handle _modelStartDirectory; + + QThread* _bakerThread; }; #endif // hifi_ModelBakeWidget_h From 392549353907a0622705a22431d6b4562c92d917 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 14 Apr 2017 18:08:02 -0700 Subject: [PATCH 31/71] add a simple results window to show bake results --- libraries/model-baking/src/FBXBaker.cpp | 6 +- libraries/model-baking/src/FBXBaker.h | 1 + tools/oven/src/Oven.h | 7 +++ tools/oven/src/ui/ModelBakeWidget.cpp | 35 ++++++++++- tools/oven/src/ui/ModelBakeWidget.h | 4 +- tools/oven/src/ui/OvenMainWindow.cpp | 22 ++++++- tools/oven/src/ui/OvenMainWindow.h | 11 ++++ tools/oven/src/ui/ResultsWindow.cpp | 79 +++++++++++++++++++++++++ tools/oven/src/ui/ResultsWindow.h | 34 +++++++++++ 9 files changed, 191 insertions(+), 8 deletions(-) create mode 100644 tools/oven/src/ui/ResultsWindow.cpp create mode 100644 tools/oven/src/ui/ResultsWindow.h diff --git a/libraries/model-baking/src/FBXBaker.cpp b/libraries/model-baking/src/FBXBaker.cpp index 5b5a8b7c0a..0cc484ce5c 100644 --- a/libraries/model-baking/src/FBXBaker.cpp +++ b/libraries/model-baking/src/FBXBaker.cpp @@ -51,8 +51,8 @@ QString FBXBaker::pathToCopyOfOriginal() const { } void FBXBaker::handleError(const QString& error) { - qCCritical(model_baking) << error; - _errorList << error; + qCCritical(model_baking).noquote() << error; + _errorList.append(error); emit finished(); } @@ -200,7 +200,7 @@ void FBXBaker::importScene() { if (!importStatus) { // failed to initialize importer, print an error and return - handleError("Failed to import FBX file at" + _fbxURL.toString() + " - error:" + importer->GetStatus().GetErrorString()); + handleError("Failed to import " + _fbxURL.toString() + " - " + importer->GetStatus().GetErrorString()); return; } else { qCDebug(model_baking) << "Imported" << _fbxURL << "to FbxScene"; diff --git a/libraries/model-baking/src/FBXBaker.h b/libraries/model-baking/src/FBXBaker.h index 10c8a5c359..a6dd6ad55a 100644 --- a/libraries/model-baking/src/FBXBaker.h +++ b/libraries/model-baking/src/FBXBaker.h @@ -56,6 +56,7 @@ public: Q_INVOKABLE void bake(); bool hasErrors() const { return !_errorList.isEmpty(); } + QStringList getErrors() const { return _errorList; } QUrl getFBXUrl() const { return _fbxURL; } QString getBakedFBXRelativePath() const { return _bakedFBXRelativePath; } diff --git a/tools/oven/src/Oven.h b/tools/oven/src/Oven.h index 7ec9bdbd3b..3fc9a4c0f6 100644 --- a/tools/oven/src/Oven.h +++ b/tools/oven/src/Oven.h @@ -14,6 +14,11 @@ #include +#if defined(qApp) +#undef qApp +#endif +#define qApp (static_cast(QCoreApplication::instance())) + class OvenMainWindow; class Oven : public QApplication { @@ -22,6 +27,8 @@ class Oven : public QApplication { public: Oven(int argc, char* argv[]); + OvenMainWindow* getMainWindow() const { return _mainWindow; } + private: OvenMainWindow* _mainWindow; }; diff --git a/tools/oven/src/ui/ModelBakeWidget.cpp b/tools/oven/src/ui/ModelBakeWidget.cpp index 3b28bb77fa..cf21483255 100644 --- a/tools/oven/src/ui/ModelBakeWidget.cpp +++ b/tools/oven/src/ui/ModelBakeWidget.cpp @@ -20,6 +20,9 @@ #include #include +#include "../Oven.h" +#include "OvenMainWindow.h" + #include "ModelBakeWidget.h" static const QString EXPORT_DIR_SETTING_KEY = "model_export_directory"; @@ -180,7 +183,7 @@ void ModelBakeWidget::bakeButtonClicked() { } // everything seems to be in place, kick off a bake for this model now - auto baker = new FBXBaker(modelToBakeURL, outputDirectory.absolutePath(), false); + auto baker = QSharedPointer { new FBXBaker(modelToBakeURL, outputDirectory.absolutePath(), false) }; // move the baker to the baker thread baker->moveToThread(_bakerThread); @@ -191,10 +194,36 @@ void ModelBakeWidget::bakeButtonClicked() { } // invoke the bake method on the baker thread - QMetaObject::invokeMethod(baker, "bake"); + QMetaObject::invokeMethod(baker.data(), "bake"); + + // make sure we hear about the results of this baker when it is done + connect(baker.data(), &FBXBaker::finished, this, &ModelBakeWidget::handleFinishedBaker); + + // add a pending row to the results window to show that this bake is in process + auto resultsWindow = qApp->getMainWindow()->showResultsWindow(); + auto resultsRow = resultsWindow->addPendingResultRow(modelToBakeURL.fileName()); // keep a unique_ptr to this baker - _bakers.emplace_back(baker); + // and remember the row that represents it in the results table + _bakers.insert(baker, resultsRow); + } +} + +void ModelBakeWidget::handleFinishedBaker() { + if (auto baker = qobject_cast(sender())) { + // turn this baker into a shared pointer + auto sharedBaker = QSharedPointer(baker); + + // add the results of this bake to the results window + auto resultRow = _bakers.value(sharedBaker); + + auto resultsWindow = qApp->getMainWindow()->showResultsWindow(); + + if (sharedBaker->hasErrors()) { + resultsWindow->changeStatusForRow(resultRow, baker->getErrors().join("\n")); + } else { + resultsWindow->changeStatusForRow(resultRow, "Success"); + } } } diff --git a/tools/oven/src/ui/ModelBakeWidget.h b/tools/oven/src/ui/ModelBakeWidget.h index f277d91938..3d76ec0d4d 100644 --- a/tools/oven/src/ui/ModelBakeWidget.h +++ b/tools/oven/src/ui/ModelBakeWidget.h @@ -36,10 +36,12 @@ private slots: void outputDirectoryChanged(const QString& newDirectory); + void handleFinishedBaker(); + private: void setupUI(); - std::list> _bakers; + QHash, int> _bakers; QLineEdit* _modelLineEdit; QLineEdit* _outputDirLineEdit; diff --git a/tools/oven/src/ui/OvenMainWindow.cpp b/tools/oven/src/ui/OvenMainWindow.cpp index 9c25fb5c72..1cf2986d17 100644 --- a/tools/oven/src/ui/OvenMainWindow.cpp +++ b/tools/oven/src/ui/OvenMainWindow.cpp @@ -21,7 +21,6 @@ OvenMainWindow::OvenMainWindow(QWidget *parent, Qt::WindowFlags flags) : setWindowTitle("High Fidelity Oven"); // give the window a fixed width that will never change - const int FIXED_WINDOW_WIDTH = 640; setFixedWidth(FIXED_WINDOW_WIDTH); // setup a stacked layout for the main "modes" menu and subseq @@ -30,3 +29,24 @@ OvenMainWindow::OvenMainWindow(QWidget *parent, Qt::WindowFlags flags) : setCentralWidget(stackedWidget); } + +OvenMainWindow::~OvenMainWindow() { + if (_resultsWindow) { + _resultsWindow->close(); + _resultsWindow->deleteLater(); + } +} + +ResultsWindow* OvenMainWindow::showResultsWindow() { + if (!_resultsWindow) { + // we don't have a results window right now, so make a new one + _resultsWindow = new ResultsWindow; + } + + // show the results window, place it right below our window + _resultsWindow->show(); + _resultsWindow->move(_resultsWindow->x(), this->frameGeometry().bottom()); + + // return a pointer to the results window the caller can use + return _resultsWindow; +} diff --git a/tools/oven/src/ui/OvenMainWindow.h b/tools/oven/src/ui/OvenMainWindow.h index 0941be543b..2d5d2aec99 100644 --- a/tools/oven/src/ui/OvenMainWindow.h +++ b/tools/oven/src/ui/OvenMainWindow.h @@ -12,12 +12,23 @@ #ifndef hifi_OvenMainWindow_h #define hifi_OvenMainWindow_h +#include #include +#include "ResultsWindow.h" + +const int FIXED_WINDOW_WIDTH = 640; + class OvenMainWindow : public QMainWindow { Q_OBJECT public: OvenMainWindow(QWidget *parent = Q_NULLPTR, Qt::WindowFlags flags = Qt::WindowFlags()); + ~OvenMainWindow(); + + ResultsWindow* showResultsWindow(); + +private: + QPointer _resultsWindow; }; #endif // hifi_OvenMainWindow_h diff --git a/tools/oven/src/ui/ResultsWindow.cpp b/tools/oven/src/ui/ResultsWindow.cpp new file mode 100644 index 0000000000..99389f363c --- /dev/null +++ b/tools/oven/src/ui/ResultsWindow.cpp @@ -0,0 +1,79 @@ +// +// ResultsWindow.cpp +// tools/oven/src/ui +// +// Created by Stephen Birarda on 4/14/17. +// Copyright 2017 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 +#include +#include +#include + +#include "OvenMainWindow.h" + +#include "ResultsWindow.h" + +ResultsWindow::ResultsWindow(QWidget* parent) : + QWidget(parent) +{ + // add a title to this window to identify it + setWindowTitle("High Fidelity Oven - Bake Results"); + + // give this dialog the same starting width as the main application window + resize(FIXED_WINDOW_WIDTH, size().height()); + + // have the window delete itself when closed + setAttribute(Qt::WA_DeleteOnClose); + + setupUI(); +} + +void ResultsWindow::setupUI() { + QVBoxLayout* resultsLayout = new QVBoxLayout(this); + + // add a results table to the widget + _resultsTable = new QTableWidget(0, 2, this); + + // add the header to the table widget + _resultsTable->setHorizontalHeaderLabels({"File", "Status"}); + + // add that table widget to the vertical box layout, so we can make it stretch to the size of the parent + resultsLayout->insertWidget(0, _resultsTable); + + // make the filename column hold 25% of the total width + // strech the last column of the table (that holds the results) to fill up the remaining available size + _resultsTable->horizontalHeader()->resizeSection(0, 0.25 * FIXED_WINDOW_WIDTH); + _resultsTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::ResizeToContents); + + // set the layout of this widget to the created layout + setLayout(resultsLayout); +} + + +int ResultsWindow::addPendingResultRow(const QString& fileName) { + int rowIndex = _resultsTable->rowCount(); + + _resultsTable->insertRow(rowIndex); + + // add a new item for the filename, make it non-editable + auto fileNameItem = new QTableWidgetItem(fileName); + fileNameItem->setFlags(fileNameItem->flags() & ~Qt::ItemIsEditable); + _resultsTable->setItem(rowIndex, 0, fileNameItem); + + auto statusItem = new QTableWidgetItem("Baking..."); + statusItem->setFlags(statusItem->flags() & ~Qt::ItemIsEditable); + _resultsTable->setItem(rowIndex, 1, statusItem); + + return rowIndex; +} + +void ResultsWindow::changeStatusForRow(int rowIndex, const QString& result) { + auto statusItem = new QTableWidgetItem(result); + statusItem->setFlags(statusItem->flags() & ~Qt::ItemIsEditable); + _resultsTable->setItem(rowIndex, 1, statusItem); +} diff --git a/tools/oven/src/ui/ResultsWindow.h b/tools/oven/src/ui/ResultsWindow.h new file mode 100644 index 0000000000..b7e380a631 --- /dev/null +++ b/tools/oven/src/ui/ResultsWindow.h @@ -0,0 +1,34 @@ +// +// ResultsWindow.h +// tools/oven/src/ui +// +// Created by Stephen Birarda on 4/14/17. +// Copyright 2017 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_ResultsWindow_h +#define hifi_ResultsWindow_h + +#include + +class QTableWidget; + +class ResultsWindow : public QWidget { + Q_OBJECT + +public: + ResultsWindow(QWidget* parent = nullptr); + + void setupUI(); + + int addPendingResultRow(const QString& fileName); + void changeStatusForRow(int rowIndex, const QString& result); + +private: + QTableWidget* _resultsTable { nullptr }; +}; + +#endif // hifi_ResultsWindow_h From 2b188427f1bb8cc543434653da4826fca1ab561d Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 14 Apr 2017 18:20:55 -0700 Subject: [PATCH 32/71] cleanup memory management in memory bake widget --- tools/oven/src/ui/ModelBakeWidget.cpp | 28 ++++++++++++++------------- tools/oven/src/ui/ModelBakeWidget.h | 4 +++- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/tools/oven/src/ui/ModelBakeWidget.cpp b/tools/oven/src/ui/ModelBakeWidget.cpp index cf21483255..08b5402821 100644 --- a/tools/oven/src/ui/ModelBakeWidget.cpp +++ b/tools/oven/src/ui/ModelBakeWidget.cpp @@ -183,7 +183,7 @@ void ModelBakeWidget::bakeButtonClicked() { } // everything seems to be in place, kick off a bake for this model now - auto baker = QSharedPointer { new FBXBaker(modelToBakeURL, outputDirectory.absolutePath(), false) }; + auto baker = std::unique_ptr { new FBXBaker(modelToBakeURL, outputDirectory.absolutePath(), false) }; // move the baker to the baker thread baker->moveToThread(_bakerThread); @@ -194,10 +194,10 @@ void ModelBakeWidget::bakeButtonClicked() { } // invoke the bake method on the baker thread - QMetaObject::invokeMethod(baker.data(), "bake"); + QMetaObject::invokeMethod(baker.get(), "bake"); // make sure we hear about the results of this baker when it is done - connect(baker.data(), &FBXBaker::finished, this, &ModelBakeWidget::handleFinishedBaker); + connect(baker.get(), &FBXBaker::finished, this, &ModelBakeWidget::handleFinishedBaker); // add a pending row to the results window to show that this bake is in process auto resultsWindow = qApp->getMainWindow()->showResultsWindow(); @@ -205,24 +205,26 @@ void ModelBakeWidget::bakeButtonClicked() { // keep a unique_ptr to this baker // and remember the row that represents it in the results table - _bakers.insert(baker, resultsRow); + _bakers.emplace_back(std::move(baker), resultsRow); } } void ModelBakeWidget::handleFinishedBaker() { if (auto baker = qobject_cast(sender())) { - // turn this baker into a shared pointer - auto sharedBaker = QSharedPointer(baker); - // add the results of this bake to the results window - auto resultRow = _bakers.value(sharedBaker); + auto it = std::remove_if(_bakers.begin(), _bakers.end(), [baker](const BakerRowPair& value) { + return value.first.get() == baker; + }); - auto resultsWindow = qApp->getMainWindow()->showResultsWindow(); + if (it != _bakers.end()) { + auto resultRow = it->second; + auto resultsWindow = qApp->getMainWindow()->showResultsWindow(); - if (sharedBaker->hasErrors()) { - resultsWindow->changeStatusForRow(resultRow, baker->getErrors().join("\n")); - } else { - resultsWindow->changeStatusForRow(resultRow, "Success"); + if (baker->hasErrors()) { + resultsWindow->changeStatusForRow(resultRow, baker->getErrors().join("\n")); + } else { + resultsWindow->changeStatusForRow(resultRow, "Success"); + } } } } diff --git a/tools/oven/src/ui/ModelBakeWidget.h b/tools/oven/src/ui/ModelBakeWidget.h index 3d76ec0d4d..9b7a2fed20 100644 --- a/tools/oven/src/ui/ModelBakeWidget.h +++ b/tools/oven/src/ui/ModelBakeWidget.h @@ -41,7 +41,9 @@ private slots: private: void setupUI(); - QHash, int> _bakers; + using BakerRowPair = std::pair, int>; + using BakerRowPairList = std::list; + BakerRowPairList _bakers; QLineEdit* _modelLineEdit; QLineEdit* _outputDirLineEdit; From 83eb37b8141db997d2054a023732dcaf9b533f28 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 14 Apr 2017 18:33:36 -0700 Subject: [PATCH 33/71] add domain bake to results table --- libraries/model-baking/src/Baker.cpp | 20 ++++++++++++ libraries/model-baking/src/Baker.h | 35 ++++++++++++++++++++ libraries/model-baking/src/FBXBaker.cpp | 6 ---- libraries/model-baking/src/FBXBaker.h | 11 +++---- libraries/model-baking/src/TextureBaker.h | 4 ++- tools/oven/src/DomainBaker.h | 5 +-- tools/oven/src/ui/DomainBakeWidget.cpp | 40 ++++++++++++++++++++--- tools/oven/src/ui/DomainBakeWidget.h | 6 +++- 8 files changed, 106 insertions(+), 21 deletions(-) create mode 100644 libraries/model-baking/src/Baker.cpp create mode 100644 libraries/model-baking/src/Baker.h diff --git a/libraries/model-baking/src/Baker.cpp b/libraries/model-baking/src/Baker.cpp new file mode 100644 index 0000000000..8e118790cc --- /dev/null +++ b/libraries/model-baking/src/Baker.cpp @@ -0,0 +1,20 @@ +// +// Baker.cpp +// libraries/model-baking/src +// +// Created by Stephen Birarda on 4/14/17. +// Copyright 2017 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 "ModelBakingLoggingCategory.h" + +#include "Baker.h" + +void Baker::handleError(const QString& error) { + qCCritical(model_baking).noquote() << error; + _errorList.append(error); + emit finished(); +} diff --git a/libraries/model-baking/src/Baker.h b/libraries/model-baking/src/Baker.h new file mode 100644 index 0000000000..19b1486346 --- /dev/null +++ b/libraries/model-baking/src/Baker.h @@ -0,0 +1,35 @@ +// +// Baker.h +// libraries/model-baking/src +// +// Created by Stephen Birarda on 4/14/17. +// Copyright 2017 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_Baker_h +#define hifi_Baker_h + +#include + +class Baker : public QObject { + Q_OBJECT + +public: + virtual void bake() = 0; + + bool hasErrors() const { return !_errorList.isEmpty(); } + QStringList getErrors() const { return _errorList; } + +signals: + void finished(); + +protected: + void handleError(const QString& error); + + QStringList _errorList; +}; + +#endif // hifi_Baker_h diff --git a/libraries/model-baking/src/FBXBaker.cpp b/libraries/model-baking/src/FBXBaker.cpp index 0cc484ce5c..8181932247 100644 --- a/libraries/model-baking/src/FBXBaker.cpp +++ b/libraries/model-baking/src/FBXBaker.cpp @@ -50,12 +50,6 @@ QString FBXBaker::pathToCopyOfOriginal() const { return _uniqueOutputPath + ORIGINAL_OUTPUT_SUBFOLDER + _fbxURL.fileName(); } -void FBXBaker::handleError(const QString& error) { - qCCritical(model_baking).noquote() << error; - _errorList.append(error); - emit finished(); -} - void FBXBaker::bake() { qCDebug(model_baking) << "Baking" << _fbxURL; diff --git a/libraries/model-baking/src/FBXBaker.h b/libraries/model-baking/src/FBXBaker.h index a6dd6ad55a..a44ce4d0bf 100644 --- a/libraries/model-baking/src/FBXBaker.h +++ b/libraries/model-baking/src/FBXBaker.h @@ -17,6 +17,8 @@ #include #include +#include "Baker.h" + namespace fbxsdk { class FbxManager; class FbxProperty; @@ -47,16 +49,13 @@ class TextureBaker; static const QString BAKED_FBX_EXTENSION = ".baked.fbx"; -class FBXBaker : public QObject { +class FBXBaker : public Baker { Q_OBJECT public: FBXBaker(const QUrl& fbxURL, const QString& baseOutputPath, bool copyOriginals = true); ~FBXBaker(); - Q_INVOKABLE void bake(); - - bool hasErrors() const { return !_errorList.isEmpty(); } - QStringList getErrors() const { return _errorList; } + Q_INVOKABLE virtual void bake() override; QUrl getFBXUrl() const { return _fbxURL; } QString getBakedFBXRelativePath() const { return _bakedFBXRelativePath; } @@ -92,8 +91,6 @@ private: QString pathToCopyOfOriginal() const; - void handleError(const QString& error); - QUrl _fbxURL; QString _fbxName; diff --git a/libraries/model-baking/src/TextureBaker.h b/libraries/model-baking/src/TextureBaker.h index 7394a0652e..06bac0d066 100644 --- a/libraries/model-baking/src/TextureBaker.h +++ b/libraries/model-baking/src/TextureBaker.h @@ -15,7 +15,9 @@ #include #include -class TextureBaker : public QObject { +#include "Baker.h" + +class TextureBaker : public Baker { Q_OBJECT public: diff --git a/tools/oven/src/DomainBaker.h b/tools/oven/src/DomainBaker.h index f949ddba9c..3eae758445 100644 --- a/tools/oven/src/DomainBaker.h +++ b/tools/oven/src/DomainBaker.h @@ -17,16 +17,17 @@ #include #include +#include #include -class DomainBaker : public QObject { +class DomainBaker : public Baker { Q_OBJECT public: DomainBaker(const QUrl& localEntitiesFileURL, const QString& domainName, const QString& baseOutputPath, const QUrl& destinationPath); public: - void bake(); + virtual void bake() override; signals: void finished(); diff --git a/tools/oven/src/ui/DomainBakeWidget.cpp b/tools/oven/src/ui/DomainBakeWidget.cpp index 01bc6110cb..b1f549dd00 100644 --- a/tools/oven/src/ui/DomainBakeWidget.cpp +++ b/tools/oven/src/ui/DomainBakeWidget.cpp @@ -21,6 +21,9 @@ #include #include +#include "../Oven.h" +#include "OvenMainWindow.h" + #include "DomainBakeWidget.h" static const QString DOMAIN_NAME_SETTING_KEY = "domain_name"; @@ -205,15 +208,44 @@ void DomainBakeWidget::bakeButtonClicked() { if (!_entitiesFileLineEdit->text().isEmpty()) { // everything seems to be in place, kick off a bake for this entities file now auto fileToBakeURL = QUrl::fromLocalFile(_entitiesFileLineEdit->text()); - _baker = std::unique_ptr { + auto domainBaker = std::unique_ptr { new DomainBaker(fileToBakeURL, _domainNameLineEdit->text(), outputDirectory.absolutePath(), _destinationPathLineEdit->text()) }; - // run the baker in our thread pool - QtConcurrent::run(_baker.get(), &DomainBaker::bake); + // make sure we hear from the baker when it is done + connect(domainBaker.get(), &DomainBaker::finished, this, &DomainBakeWidget::handleFinishedBaker); - return; + // run the baker in our thread pool + QtConcurrent::run(domainBaker.get(), &DomainBaker::bake); + + // add a pending row to the results window to show that this bake is in process + auto resultsWindow = qApp->getMainWindow()->showResultsWindow(); + auto resultsRowName = _domainNameLineEdit->text().isEmpty() ? fileToBakeURL.fileName() : _domainNameLineEdit->text(); + auto resultsRow = resultsWindow->addPendingResultRow(resultsRowName); + + // keep the unique ptr to the domain baker and the index to the row representing it in the results table + _bakers.emplace_back(std::move(domainBaker), resultsRow); + } +} + +void DomainBakeWidget::handleFinishedBaker() { + if (auto baker = qobject_cast(sender())) { + // add the results of this bake to the results window + auto it = std::remove_if(_bakers.begin(), _bakers.end(), [baker](const BakerRowPair& value) { + return value.first.get() == baker; + }); + + if (it != _bakers.end()) { + auto resultRow = it->second; + auto resultsWindow = qApp->getMainWindow()->showResultsWindow(); + + if (baker->hasErrors()) { + resultsWindow->changeStatusForRow(resultRow, baker->getErrors().join("\n")); + } else { + resultsWindow->changeStatusForRow(resultRow, "Success"); + } + } } } diff --git a/tools/oven/src/ui/DomainBakeWidget.h b/tools/oven/src/ui/DomainBakeWidget.h index 20b4eaa4b9..606f550203 100644 --- a/tools/oven/src/ui/DomainBakeWidget.h +++ b/tools/oven/src/ui/DomainBakeWidget.h @@ -34,10 +34,14 @@ private slots: void outputDirectoryChanged(const QString& newDirectory); + void handleFinishedBaker(); + private: void setupUI(); - std::unique_ptr _baker; + using BakerRowPair = std::pair, int>; + using BakerRowPairList = std::list; + BakerRowPairList _bakers; QLineEdit* _domainNameLineEdit; QLineEdit* _entitiesFileLineEdit; From 429e65888b38a3c86f866029b5bb28c4b4c1378c Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Sun, 16 Apr 2017 15:52:17 -0700 Subject: [PATCH 34/71] cleanup threading and result handling for DomainBaker --- libraries/model-baking/src/Baker.cpp | 12 ++ libraries/model-baking/src/Baker.h | 8 ++ libraries/model-baking/src/FBXBaker.cpp | 150 ++++++++++++-------- libraries/model-baking/src/FBXBaker.h | 14 +- libraries/model-baking/src/TextureBaker.cpp | 8 +- libraries/model-baking/src/TextureBaker.h | 5 +- tools/oven/src/DomainBaker.cpp | 91 ++++++++---- tools/oven/src/DomainBaker.h | 1 - tools/oven/src/ui/DomainBakeWidget.cpp | 2 + 9 files changed, 190 insertions(+), 101 deletions(-) diff --git a/libraries/model-baking/src/Baker.cpp b/libraries/model-baking/src/Baker.cpp index 8e118790cc..b692c1d96b 100644 --- a/libraries/model-baking/src/Baker.cpp +++ b/libraries/model-baking/src/Baker.cpp @@ -18,3 +18,15 @@ void Baker::handleError(const QString& error) { _errorList.append(error); emit finished(); } + +void Baker::appendErrors(const QStringList& errors) { + // we're appending errors, presumably from a baking operation we called + // add those to our list and emit that we are finished + _errorList.append(errors); + emit finished(); +} + +void Baker::handleWarning(const QString& warning) { + qCWarning(model_baking).noquote() << warning; + _warningList.append(warning); +} diff --git a/libraries/model-baking/src/Baker.h b/libraries/model-baking/src/Baker.h index 19b1486346..6853620361 100644 --- a/libraries/model-baking/src/Baker.h +++ b/libraries/model-baking/src/Baker.h @@ -23,13 +23,21 @@ public: bool hasErrors() const { return !_errorList.isEmpty(); } QStringList getErrors() const { return _errorList; } + bool hasWarnings() const { return !_warningList.isEmpty(); } + QStringList getWarnings() const { return _warningList; } + signals: void finished(); protected: void handleError(const QString& error); + void handleWarning(const QString& warning); + + void appendErrors(const QStringList& errors); + void appendWarnings(const QStringList& warnings) { _warningList << warnings; } QStringList _errorList; + QStringList _warningList; }; #endif // hifi_Baker_h diff --git a/libraries/model-baking/src/FBXBaker.cpp b/libraries/model-baking/src/FBXBaker.cpp index 8181932247..ff1d6659aa 100644 --- a/libraries/model-baking/src/FBXBaker.cpp +++ b/libraries/model-baking/src/FBXBaker.cpp @@ -18,6 +18,8 @@ #include #include +#include + #include #include "ModelBakingLoggingCategory.h" @@ -25,24 +27,26 @@ #include "FBXBaker.h" +std::once_flag onceFlag; +FBXSDKManagerUniquePointer FBXBaker::_sdkManager { nullptr }; FBXBaker::FBXBaker(const QUrl& fbxURL, const QString& baseOutputPath, bool copyOriginals) : _fbxURL(fbxURL), _baseOutputPath(baseOutputPath), _copyOriginals(copyOriginals) { - // create an FBX SDK manager - _sdkManager = FbxManager::Create(); + std::call_once(onceFlag, [](){ + // create the static FBX SDK manager + _sdkManager = FBXSDKManagerUniquePointer(FbxManager::Create(), [](FbxManager* manager){ + manager->Destroy(); + }); + }); // grab the name of the FBX from the URL, this is used for folder output names auto fileName = fbxURL.fileName(); _fbxName = fileName.left(fileName.indexOf('.')); } -FBXBaker::~FBXBaker() { - _sdkManager->Destroy(); -} - static const QString BAKED_OUTPUT_SUBFOLDER = "baked/"; static const QString ORIGINAL_OUTPUT_SUBFOLDER = "original/"; @@ -88,15 +92,8 @@ void FBXBaker::bakeSourceCopy() { return; } - // remove the embedded media folder that the FBX SDK produces when reading the original - removeEmbeddedMediaFolder(); - - if (hasErrors()) { - return; - } - - // cleanup the originals if we weren't asked to keep them around - possiblyCleanupOriginals(); + // check if we're already done with textures (in case we had none to re-write) + checkIfTexturesFinished(); } void FBXBaker::setupOutputFolder() { @@ -186,7 +183,7 @@ void FBXBaker::handleFBXNetworkReply() { void FBXBaker::importScene() { // create an FBX SDK importer - FbxImporter* importer = FbxImporter::Create(_sdkManager, ""); + FbxImporter* importer = FbxImporter::Create(_sdkManager.get(), ""); // import the copy of the original FBX file QString originalCopyPath = pathToCopyOfOriginal(); @@ -201,7 +198,7 @@ void FBXBaker::importScene() { } // setup a new scene to hold the imported file - _scene = FbxScene::Create(_sdkManager, "bakeScene"); + _scene = FbxScene::Create(_sdkManager.get(), "bakeScene"); // import the file to the created scene importer->Import(_scene); @@ -397,13 +394,15 @@ void FBXBaker::rewriteAndBakeSceneTextures() { // figure out the URL to this texture, embedded or external auto urlToTexture = getTextureURL(textureFileInfo, fileTexture); - - // add the deduced url to the texture, associated with the resulting baked texture file name, - // to our hash of textures needing to be baked - _unbakedTextures.insert(urlToTexture, bakedTextureFileName); - - // bake this texture asynchronously - bakeTexture(urlToTexture); + + if (!_unbakedTextures.contains(urlToTexture)) { + // add the deduced url to the texture, associated with the resulting baked texture file name, + // to our hash of textures needing to be baked + _unbakedTextures.insert(urlToTexture, bakedTextureFileName); + + // bake this texture asynchronously + bakeTexture(urlToTexture); + } } } } @@ -417,63 +416,68 @@ void FBXBaker::rewriteAndBakeSceneTextures() { void FBXBaker::bakeTexture(const QUrl& textureURL) { // start a bake for this texture and add it to our list to keep track of - auto bakingTexture = new TextureBaker(textureURL); + QSharedPointer bakingTexture { new TextureBaker(textureURL), &TextureBaker::deleteLater }; - connect(bakingTexture, &TextureBaker::finished, this, &FBXBaker::handleBakedTexture); + connect(bakingTexture.data(), &Baker::finished, this, &FBXBaker::handleBakedTexture); - QtConcurrent::run(bakingTexture, &TextureBaker::bake); + QtConcurrent::run(bakingTexture.data(), &TextureBaker::bake); - _bakingTextures.emplace_back(bakingTexture); + _bakingTextures.insert(bakingTexture); } void FBXBaker::handleBakedTexture() { - auto bakedTexture = qobject_cast(sender()); + TextureBaker* bakedTexture = qobject_cast(sender()); - // use the path to the texture being baked to determine if this was an embedded or a linked texture + // make sure we haven't already run into errors, and that this is a valid texture + if (!hasErrors() && bakedTexture) { + if (!bakedTexture->hasErrors()) { + // use the path to the texture being baked to determine if this was an embedded or a linked texture - // it is embeddded if the texure being baked was inside the original output folder - // since that is where the FBX SDK places the .fbm folder it generates when importing the FBX + // it is embeddded if the texure being baked was inside the original output folder + // since that is where the FBX SDK places the .fbm folder it generates when importing the FBX - auto originalOutputFolder = QUrl::fromLocalFile(_uniqueOutputPath + ORIGINAL_OUTPUT_SUBFOLDER); + auto originalOutputFolder = QUrl::fromLocalFile(_uniqueOutputPath + ORIGINAL_OUTPUT_SUBFOLDER); - if (!originalOutputFolder.isParentOf(bakedTexture->getTextureURL())) { - // for linked textures we want to save a copy of original texture beside the original FBX + if (!originalOutputFolder.isParentOf(bakedTexture->getTextureURL())) { + // for linked textures we want to save a copy of original texture beside the original FBX - qCDebug(model_baking) << "Saving original texture for" << bakedTexture->getTextureURL(); + qCDebug(model_baking) << "Saving original texture for" << bakedTexture->getTextureURL(); - // check if we have a relative path to use for the texture - auto relativeTexturePath = texturePathRelativeToFBX(_fbxURL, bakedTexture->getTextureURL()); + // check if we have a relative path to use for the texture + auto relativeTexturePath = texturePathRelativeToFBX(_fbxURL, bakedTexture->getTextureURL()); - QFile originalTextureFile { - _uniqueOutputPath + ORIGINAL_OUTPUT_SUBFOLDER + relativeTexturePath + bakedTexture->getTextureURL().fileName() - }; + QFile originalTextureFile { + _uniqueOutputPath + ORIGINAL_OUTPUT_SUBFOLDER + relativeTexturePath + bakedTexture->getTextureURL().fileName() + }; - if (relativeTexturePath.length() > 0) { - // make the folders needed by the relative path + if (relativeTexturePath.length() > 0) { + // make the folders needed by the relative path + } + + if (originalTextureFile.open(QIODevice::WriteOnly) && originalTextureFile.write(bakedTexture->getOriginalTexture()) != -1) { + qCDebug(model_baking) << "Saved original texture file" << originalTextureFile.fileName() + << "for" << _fbxURL; + } else { + handleError("Could not save original external texture " + originalTextureFile.fileName() + + " for " + _fbxURL.toString()); + return; + } + } + + // now that this texture has been baked and handled, we can remove that TextureBaker from our list + _unbakedTextures.remove(bakedTexture->getTextureURL()); + + checkIfTexturesFinished(); + } else { + // there was an error baking this texture - add it to our list of errors and stop processing this FBX + appendErrors(bakedTexture->getErrors()); } - - if (originalTextureFile.open(QIODevice::WriteOnly) && originalTextureFile.write(bakedTexture->getOriginalTexture()) != -1) { - qCDebug(model_baking) << "Saved original texture file" << originalTextureFile.fileName() - << "for" << _fbxURL; - } else { - handleError("Could not save original external texture " + originalTextureFile.fileName() - + " for " + _fbxURL.toString()); - return; - } - } - - // now that this texture has been baked and handled, we can remove that TextureBaker from our list - _unbakedTextures.remove(bakedTexture->getTextureURL()); - - // check if we're done everything we need to do for this FBX - if (_unbakedTextures.isEmpty()) { - emit finished(); } } void FBXBaker::exportScene() { // setup the exporter - FbxExporter* exporter = FbxExporter::Create(_sdkManager, ""); + FbxExporter* exporter = FbxExporter::Create(_sdkManager.get(), ""); auto rewrittenFBXPath = _uniqueOutputPath + BAKED_OUTPUT_SUBFOLDER + _fbxName + BAKED_FBX_EXTENSION; @@ -508,3 +512,27 @@ void FBXBaker::possiblyCleanupOriginals() { QDir(_uniqueOutputPath + ORIGINAL_OUTPUT_SUBFOLDER).removeRecursively(); } } + +void FBXBaker::checkIfTexturesFinished() { + // check if we're done everything we need to do for this FBX + // and emit our finished signal if we're done + if (_unbakedTextures.isEmpty()) { + // remove the embedded media folder that the FBX SDK produces when reading the original + removeEmbeddedMediaFolder(); + + if (hasErrors()) { + return; + } + + // cleanup the originals if we weren't asked to keep them around + possiblyCleanupOriginals(); + + if (hasErrors()) { + return; + } + + qCDebug(model_baking) << "Finished baking" << _fbxURL; + + emit finished(); + } +} diff --git a/libraries/model-baking/src/FBXBaker.h b/libraries/model-baking/src/FBXBaker.h index a44ce4d0bf..ef94de7a2e 100644 --- a/libraries/model-baking/src/FBXBaker.h +++ b/libraries/model-baking/src/FBXBaker.h @@ -18,6 +18,7 @@ #include #include "Baker.h" +#include "TextureBaker.h" namespace fbxsdk { class FbxManager; @@ -45,23 +46,22 @@ enum TextureType { UNUSED_TEXTURE = -1 }; -class TextureBaker; - static const QString BAKED_FBX_EXTENSION = ".baked.fbx"; +using FBXSDKManagerUniquePointer = std::unique_ptr>; class FBXBaker : public Baker { Q_OBJECT public: FBXBaker(const QUrl& fbxURL, const QString& baseOutputPath, bool copyOriginals = true); - ~FBXBaker(); + // all calls to bake must be from the same thread, because the Autodesk SDK will cause + // a crash if it is called from multiple threads Q_INVOKABLE virtual void bake() override; QUrl getFBXUrl() const { return _fbxURL; } QString getBakedFBXRelativePath() const { return _bakedFBXRelativePath; } signals: - void finished(); void allTexturesBaked(); void sourceCopyReadyToLoad(); @@ -84,6 +84,8 @@ private: void removeEmbeddedMediaFolder(); void possiblyCleanupOriginals(); + void checkIfTexturesFinished(); + QString createBakedTextureFileName(const QFileInfo& textureFileInfo); QUrl getTextureURL(const QFileInfo& textureFileInfo, fbxsdk::FbxFileTexture* fileTexture); @@ -98,7 +100,7 @@ private: QString _uniqueOutputPath; QString _bakedFBXRelativePath; - fbxsdk::FbxManager* _sdkManager; + static FBXSDKManagerUniquePointer _sdkManager; fbxsdk::FbxScene* _scene { nullptr }; QStringList _errorList; @@ -107,7 +109,7 @@ private: QHash _textureNameMatchCount; QHash _textureTypes; - std::list> _bakingTextures; + QSet> _bakingTextures; QFutureSynchronizer _textureBakeSynchronizer; bool _copyOriginals { true }; diff --git a/libraries/model-baking/src/TextureBaker.cpp b/libraries/model-baking/src/TextureBaker.cpp index a717835ed7..bdd20ea270 100644 --- a/libraries/model-baking/src/TextureBaker.cpp +++ b/libraries/model-baking/src/TextureBaker.cpp @@ -29,6 +29,10 @@ void TextureBaker::bake() { // first load the texture (either locally or remotely) loadTexture(); + if (hasErrors()) { + return; + } + qCDebug(model_baking) << "Baking texture at" << _textureURL; emit finished(); @@ -41,9 +45,7 @@ void TextureBaker::loadTexture() { QFile localTexture { _textureURL.toLocalFile() }; if (!localTexture.open(QIODevice::ReadOnly)) { - qCWarning(model_baking) << "Unable to open local texture at" << _textureURL << "for baking"; - - emit finished(); + handleError("Unable to open texture " + _textureURL.toString()); return; } diff --git a/libraries/model-baking/src/TextureBaker.h b/libraries/model-baking/src/TextureBaker.h index 06bac0d066..4f793af37d 100644 --- a/libraries/model-baking/src/TextureBaker.h +++ b/libraries/model-baking/src/TextureBaker.h @@ -22,16 +22,13 @@ class TextureBaker : public Baker { public: TextureBaker(const QUrl& textureURL); - + void bake(); const QByteArray& getOriginalTexture() const { return _originalTexture; } const QUrl& getTextureURL() const { return _textureURL; } -signals: - void finished(); - private: void loadTexture(); void handleTextureNetworkReply(QNetworkReply* requestReply); diff --git a/tools/oven/src/DomainBaker.cpp b/tools/oven/src/DomainBaker.cpp index c779fd3a40..b53b74f227 100644 --- a/tools/oven/src/DomainBaker.cpp +++ b/tools/oven/src/DomainBaker.cpp @@ -36,10 +36,29 @@ DomainBaker::DomainBaker(const QUrl& localModelFileURL, const QString& domainNam void DomainBaker::bake() { setupOutputFolder(); + + if (hasErrors()) { + return; + } + loadLocalFile(); + + if (hasErrors()) { + return; + } + setupBakerThread(); + + if (hasErrors()) { + return; + } + enumerateEntities(); + if (hasErrors()) { + return; + } + if (!_entitiesNeedingRewrite.isEmpty()) { // use a QEventLoop to wait for all entity rewrites to be completed before writing the final models file QEventLoop eventLoop; @@ -47,8 +66,16 @@ void DomainBaker::bake() { eventLoop.exec(); } + if (hasErrors()) { + return; + } + writeNewEntitiesFile(); + if (hasErrors()) { + return; + } + // stop the FBX baker thread now that all our bakes have completed _fbxBakerThread->quit(); @@ -70,8 +97,8 @@ void DomainBaker::setupOutputFolder() { QDir outputDir { _baseOutputPath }; if (!outputDir.mkpath(outputDirectoryName)) { - // add an error to specify that the output directory could not be created + handleError("Could not create output folder"); return; } @@ -84,7 +111,7 @@ void DomainBaker::setupOutputFolder() { static const QString CONTENT_OUTPUT_FOLDER_NAME = "content"; if (!outputDir.mkpath(CONTENT_OUTPUT_FOLDER_NAME)) { // add an error to specify that the content output directory could not be created - + handleError("Could not create content folder"); return; } @@ -95,17 +122,18 @@ const QString ENTITIES_OBJECT_KEY = "Entities"; void DomainBaker::loadLocalFile() { // load up the local entities file - QFile modelsFile { _localEntitiesFileURL.toLocalFile() }; + QFile entitiesFile { _localEntitiesFileURL.toLocalFile() }; - if (!modelsFile.open(QIODevice::ReadOnly)) { + if (!entitiesFile.open(QIODevice::ReadOnly)) { // add an error to our list to specify that the file could not be read + handleError("Could not open entities file"); // return to stop processing return; } // grab a byte array from the file - auto fileContents = modelsFile.readAll(); + auto fileContents = entitiesFile.readAll(); // check if we need to inflate a gzipped models file or if this was already decompressed static const QString GZIPPED_ENTITIES_FILE_SUFFIX = "gz"; @@ -167,10 +195,10 @@ void DomainBaker::enumerateEntities() { // setup an FBXBaker for this URL, as long as we don't already have one if (!_bakers.contains(modelURL)) { - QSharedPointer baker { new FBXBaker(modelURL, _contentOutputPath) }; + QSharedPointer baker { new FBXBaker(modelURL, _contentOutputPath), &FBXBaker::deleteLater }; // make sure our handler is called when the baker is done - connect(baker.data(), &FBXBaker::finished, this, &DomainBaker::handleFinishedBaker); + connect(baker.data(), &Baker::finished, this, &DomainBaker::handleFinishedBaker); // insert it into our bakers hash so we hold a strong pointer to it _bakers.insert(modelURL, baker); @@ -194,35 +222,44 @@ void DomainBaker::handleFinishedBaker() { auto baker = qobject_cast(sender()); if (baker) { - // this FBXBaker is done and everything went according to plan + if (!baker->hasErrors()) { + // this FBXBaker is done and everything went according to plan - // enumerate the QJsonRef values for the URL of this FBX from our multi hash of - // entity objects needing a URL re-write - for (QJsonValueRef entityValue : _entitiesNeedingRewrite.values(baker->getFBXUrl())) { + // enumerate the QJsonRef values for the URL of this FBX from our multi hash of + // entity objects needing a URL re-write + for (QJsonValueRef entityValue : _entitiesNeedingRewrite.values(baker->getFBXUrl())) { - // convert the entity QJsonValueRef to a QJsonObject so we can modify its URL - auto entity = entityValue.toObject(); + // convert the entity QJsonValueRef to a QJsonObject so we can modify its URL + auto entity = entityValue.toObject(); - // grab the old URL - QUrl oldModelURL { entity[ENTITY_MODEL_URL_KEY].toString() }; + // grab the old URL + QUrl oldModelURL { entity[ENTITY_MODEL_URL_KEY].toString() }; - // setup a new URL using the prefix we were passed - QUrl newModelURL = _destinationPath.resolved(baker->getBakedFBXRelativePath().mid(1)); + // setup a new URL using the prefix we were passed + QUrl newModelURL = _destinationPath.resolved(baker->getBakedFBXRelativePath().mid(1)); - // copy the fragment and query from the old model URL - newModelURL.setQuery(oldModelURL.query()); - newModelURL.setFragment(oldModelURL.fragment()); + // copy the fragment and query from the old model URL + newModelURL.setQuery(oldModelURL.query()); + newModelURL.setFragment(oldModelURL.fragment()); - // set the new model URL as the value in our temp QJsonObject - entity[ENTITY_MODEL_URL_KEY] = newModelURL.toString(); - - // replace our temp object with the value referenced by our QJsonValueRef - entityValue = entity; + // set the new model URL as the value in our temp QJsonObject + entity[ENTITY_MODEL_URL_KEY] = newModelURL.toString(); + + // replace our temp object with the value referenced by our QJsonValueRef + entityValue = entity; + } + } else { + // this model failed to bake - this doesn't fail the entire bake but we need to add + // the errors from the model to our errors + appendWarnings(baker->getErrors()); } // remove the baked URL from the multi hash of entities needing a re-write _entitiesNeedingRewrite.remove(baker->getFBXUrl()); + // drop our shared pointer to this baker so that it gets cleaned up + _bakers.remove(baker->getFBXUrl()); + if (_entitiesNeedingRewrite.isEmpty()) { emit allModelsFinished(); } @@ -256,12 +293,14 @@ void DomainBaker::writeNewEntitiesFile() { if (!compressedEntitiesFile.open(QIODevice::WriteOnly) || (compressedEntitiesFile.write(compressedJson) == -1)) { - qWarning() << "Failed to export baked entities file to" << bakedEntitiesFilePath; + // add an error to our list to state that the output models file could not be created or could not be written to + handleError("Failed to export baked entities file"); return; } qDebug() << "Exported entities file with baked model URLs to" << bakedEntitiesFilePath; + qDebug() << "WARNINGS:" << _warningList; } diff --git a/tools/oven/src/DomainBaker.h b/tools/oven/src/DomainBaker.h index 3eae758445..6c8555cc62 100644 --- a/tools/oven/src/DomainBaker.h +++ b/tools/oven/src/DomainBaker.h @@ -30,7 +30,6 @@ public: virtual void bake() override; signals: - void finished(); void allModelsFinished(); private slots: diff --git a/tools/oven/src/ui/DomainBakeWidget.cpp b/tools/oven/src/ui/DomainBakeWidget.cpp index b1f549dd00..7c8c462cfd 100644 --- a/tools/oven/src/ui/DomainBakeWidget.cpp +++ b/tools/oven/src/ui/DomainBakeWidget.cpp @@ -242,6 +242,8 @@ void DomainBakeWidget::handleFinishedBaker() { if (baker->hasErrors()) { resultsWindow->changeStatusForRow(resultRow, baker->getErrors().join("\n")); + } else if (baker->hasWarnings()) { + resultsWindow->changeStatusForRow(resultRow, baker->getWarnings().join("\n")); } else { resultsWindow->changeStatusForRow(resultRow, "Success"); } From 49e7ae6dbc1d2f4a778f24a768a9056765e86b1f Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 17 Apr 2017 09:14:34 -0700 Subject: [PATCH 35/71] call image library for texture baking --- libraries/gpu/src/gpu/Texture.h | 2 + libraries/model-baking/CMakeLists.txt | 2 +- libraries/model-baking/src/Baker.cpp | 2 +- libraries/model-baking/src/Baker.h | 3 +- libraries/model-baking/src/FBXBaker.cpp | 170 +++++++++--------- libraries/model-baking/src/FBXBaker.h | 28 +-- libraries/model-baking/src/TextureBaker.cpp | 50 +++++- libraries/model-baking/src/TextureBaker.h | 12 +- .../src/model-networking/TextureCache.h | 2 - libraries/shared/src/Profile.cpp | 2 +- tools/oven/CMakeLists.txt | 2 +- tools/oven/src/DomainBaker.cpp | 4 +- tools/oven/src/ui/DomainBakeWidget.cpp | 3 - 13 files changed, 158 insertions(+), 124 deletions(-) diff --git a/libraries/gpu/src/gpu/Texture.h b/libraries/gpu/src/gpu/Texture.h index 9b23b4e695..7c6d5d0659 100755 --- a/libraries/gpu/src/gpu/Texture.h +++ b/libraries/gpu/src/gpu/Texture.h @@ -22,6 +22,8 @@ #include "Forward.h" #include "Resource.h" +const int ABSOLUTE_MAX_TEXTURE_NUM_PIXELS = 8192 * 8192; + namespace ktx { class KTX; using KTXUniquePointer = std::unique_ptr; diff --git a/libraries/model-baking/CMakeLists.txt b/libraries/model-baking/CMakeLists.txt index a0774cdcc1..2e08488d69 100644 --- a/libraries/model-baking/CMakeLists.txt +++ b/libraries/model-baking/CMakeLists.txt @@ -2,7 +2,7 @@ set(TARGET_NAME model-baking) setup_hifi_library(Concurrent) -link_hifi_libraries(networking) +link_hifi_libraries(networking image gpu shared ktx) find_package(FBXSDK REQUIRED) target_link_libraries(${TARGET_NAME} ${FBX_LIBRARIES}) diff --git a/libraries/model-baking/src/Baker.cpp b/libraries/model-baking/src/Baker.cpp index b692c1d96b..666e59a073 100644 --- a/libraries/model-baking/src/Baker.cpp +++ b/libraries/model-baking/src/Baker.cpp @@ -19,7 +19,7 @@ void Baker::handleError(const QString& error) { emit finished(); } -void Baker::appendErrors(const QStringList& errors) { +void Baker::handleErrors(const QStringList& errors) { // we're appending errors, presumably from a baking operation we called // add those to our list and emit that we are finished _errorList.append(errors); diff --git a/libraries/model-baking/src/Baker.h b/libraries/model-baking/src/Baker.h index 6853620361..ab9c22ac53 100644 --- a/libraries/model-baking/src/Baker.h +++ b/libraries/model-baking/src/Baker.h @@ -33,8 +33,7 @@ protected: void handleError(const QString& error); void handleWarning(const QString& warning); - void appendErrors(const QStringList& errors); - void appendWarnings(const QStringList& warnings) { _warningList << warnings; } + void handleErrors(const QStringList& errors); QStringList _errorList; QStringList _warningList; diff --git a/libraries/model-baking/src/FBXBaker.cpp b/libraries/model-baking/src/FBXBaker.cpp index ff1d6659aa..0a25d3c299 100644 --- a/libraries/model-baking/src/FBXBaker.cpp +++ b/libraries/model-baking/src/FBXBaker.cpp @@ -207,8 +207,6 @@ void FBXBaker::importScene() { importer->Destroy(); } -static const QString BAKED_TEXTURE_EXT = ".ktx"; - QString texturePathRelativeToFBX(QUrl fbxURL, QUrl textureURL) { auto fbxPath = fbxURL.toString(QUrl::RemoveFilename | QUrl::RemoveQuery | QUrl::RemoveFragment); auto texturePath = textureURL.toString(QUrl::RemoveFilename | QUrl::RemoveQuery | QUrl::RemoveFragment); @@ -256,49 +254,25 @@ QUrl FBXBaker::getTextureURL(const QFileInfo& textureFileInfo, FbxFileTexture* f QString relativeFileName = fileTexture->GetRelativeFileName(); auto apparentRelativePath = QFileInfo(relativeFileName.replace("\\", "/")); -#ifndef Q_OS_WIN - // it turns out that paths that start with a drive letter and a colon appear to QFileInfo - // as a relative path on UNIX systems - we perform a special check here to handle that case - bool isAbsolute = relativeFileName[1] == ':' || apparentRelativePath.isAbsolute(); -#else - bool isAbsolute = apparentRelativePath.isAbsolute(); -#endif - - if (isAbsolute) { - // this is a relative file path which will require different handling - // depending on the location of the original FBX - if (_fbxURL.isLocalFile()) { - // since the loaded FBX is loaded, first check if we actually have the texture locally - // at the absolute path - if (apparentRelativePath.exists() && apparentRelativePath.isFile()) { - // the absolute path we ran into for the texture in the FBX exists on this machine - // so use that file - urlToTexture = QUrl::fromLocalFile(apparentRelativePath.absoluteFilePath()); - } else { - // we didn't find the texture on this machine at the absolute path - // so assume that it is right beside the FBX to match the behaviour of interface - urlToTexture = _fbxURL.resolved(apparentRelativePath.fileName()); - } - } else { - // the original FBX was remote and downloaded - - // since this "relative" texture path is actually absolute, we have to assume it is beside the FBX - // which matches the behaviour of Interface - - // append that path to our list of unbaked textures - urlToTexture = _fbxURL.resolved(apparentRelativePath.fileName()); - } + // this is a relative file path which will require different handling + // depending on the location of the original FBX + if (_fbxURL.isLocalFile() && apparentRelativePath.exists() && apparentRelativePath.isFile()) { + // the absolute path we ran into for the texture in the FBX exists on this machine + // so use that file + urlToTexture = QUrl::fromLocalFile(apparentRelativePath.absoluteFilePath()); } else { - // simply construct a URL with the relative path to the asset, locally or remotely - // and append that to the list of unbaked textures - urlToTexture = _fbxURL.resolved(apparentRelativePath.filePath()); + // we didn't find the texture on this machine at the absolute path + // so assume that it is right beside the FBX to match the behaviour of interface + urlToTexture = _fbxURL.resolved(apparentRelativePath.fileName()); } } return urlToTexture; } -TextureType textureTypeForMaterialProperty(FbxProperty& property, FbxSurfaceMaterial* material) { +gpu::TextureType textureTypeForMaterialProperty(FbxProperty& property, FbxSurfaceMaterial* material) { + using namespace gpu; + // this is a property we know has a texture, we need to match it to a High Fidelity known texture type // since that information is passed to the baking process @@ -366,7 +340,7 @@ void FBXBaker::rewriteAndBakeSceneTextures() { // figure out the type of texture from the material property auto textureType = textureTypeForMaterialProperty(property, material); - if (textureType != UNUSED_TEXTURE) { + if (textureType != gpu::UNUSED_TEXTURE) { int numTextures = property.GetSrcObjectCount(); for (int j = 0; j < numTextures; j++) { @@ -401,7 +375,7 @@ void FBXBaker::rewriteAndBakeSceneTextures() { _unbakedTextures.insert(urlToTexture, bakedTextureFileName); // bake this texture asynchronously - bakeTexture(urlToTexture); + bakeTexture(urlToTexture, textureType, bakedTextureFilePath); } } } @@ -414,63 +388,91 @@ void FBXBaker::rewriteAndBakeSceneTextures() { } } -void FBXBaker::bakeTexture(const QUrl& textureURL) { +void FBXBaker::bakeTexture(const QUrl& textureURL, gpu::TextureType textureType, const QString& destinationFilePath) { // start a bake for this texture and add it to our list to keep track of - QSharedPointer bakingTexture { new TextureBaker(textureURL), &TextureBaker::deleteLater }; + QSharedPointer bakingTexture { + new TextureBaker(textureURL, textureType, destinationFilePath), + &TextureBaker::deleteLater + }; + // make sure we hear when the baking texture is done connect(bakingTexture.data(), &Baker::finished, this, &FBXBaker::handleBakedTexture); - QtConcurrent::run(bakingTexture.data(), &TextureBaker::bake); - + // keep a shared pointer to the baking texture _bakingTextures.insert(bakingTexture); + + // start baking the texture on our thread pool + QtConcurrent::run(bakingTexture.data(), &TextureBaker::bake); } void FBXBaker::handleBakedTexture() { TextureBaker* bakedTexture = qobject_cast(sender()); // make sure we haven't already run into errors, and that this is a valid texture - if (!hasErrors() && bakedTexture) { - if (!bakedTexture->hasErrors()) { - // use the path to the texture being baked to determine if this was an embedded or a linked texture + if (bakedTexture) { + if (!hasErrors()) { + if (!bakedTexture->hasErrors()) { + if (_copyOriginals) { + // we've been asked to make copies of the originals, so we need to make copies of this if it is a linked texture - // it is embeddded if the texure being baked was inside the original output folder - // since that is where the FBX SDK places the .fbm folder it generates when importing the FBX + // use the path to the texture being baked to determine if this was an embedded or a linked texture - auto originalOutputFolder = QUrl::fromLocalFile(_uniqueOutputPath + ORIGINAL_OUTPUT_SUBFOLDER); + // it is embeddded if the texure being baked was inside the original output folder + // since that is where the FBX SDK places the .fbm folder it generates when importing the FBX - if (!originalOutputFolder.isParentOf(bakedTexture->getTextureURL())) { - // for linked textures we want to save a copy of original texture beside the original FBX + auto originalOutputFolder = QUrl::fromLocalFile(_uniqueOutputPath + ORIGINAL_OUTPUT_SUBFOLDER); - qCDebug(model_baking) << "Saving original texture for" << bakedTexture->getTextureURL(); + if (!originalOutputFolder.isParentOf(bakedTexture->getTextureURL())) { + // for linked textures we want to save a copy of original texture beside the original FBX - // check if we have a relative path to use for the texture - auto relativeTexturePath = texturePathRelativeToFBX(_fbxURL, bakedTexture->getTextureURL()); + qCDebug(model_baking) << "Saving original texture for" << bakedTexture->getTextureURL(); - QFile originalTextureFile { - _uniqueOutputPath + ORIGINAL_OUTPUT_SUBFOLDER + relativeTexturePath + bakedTexture->getTextureURL().fileName() - }; + // check if we have a relative path to use for the texture + auto relativeTexturePath = texturePathRelativeToFBX(_fbxURL, bakedTexture->getTextureURL()); - if (relativeTexturePath.length() > 0) { - // make the folders needed by the relative path + QFile originalTextureFile { + _uniqueOutputPath + ORIGINAL_OUTPUT_SUBFOLDER + relativeTexturePath + bakedTexture->getTextureURL().fileName() + }; + + if (relativeTexturePath.length() > 0) { + // make the folders needed by the relative path + } + + if (originalTextureFile.open(QIODevice::WriteOnly) && originalTextureFile.write(bakedTexture->getOriginalTexture()) != -1) { + qCDebug(model_baking) << "Saved original texture file" << originalTextureFile.fileName() + << "for" << _fbxURL; + } else { + handleError("Could not save original external texture " + originalTextureFile.fileName() + + " for " + _fbxURL.toString()); + return; + } + } } - if (originalTextureFile.open(QIODevice::WriteOnly) && originalTextureFile.write(bakedTexture->getOriginalTexture()) != -1) { - qCDebug(model_baking) << "Saved original texture file" << originalTextureFile.fileName() - << "for" << _fbxURL; - } else { - handleError("Could not save original external texture " + originalTextureFile.fileName() - + " for " + _fbxURL.toString()); - return; - } + + // now that this texture has been baked and handled, we can remove that TextureBaker from our list + _unbakedTextures.remove(bakedTexture->getTextureURL()); + + checkIfTexturesFinished(); + } else { + // there was an error baking this texture - add it to our list of errors + _errorList.append(bakedTexture->getErrors()); + + // we don't emit finished yet so that the other textures can finish baking first + _pendingErrorEmission = true; + + // now that this texture has been baked, even though it failed, we can remove that TextureBaker from our list + _unbakedTextures.remove(bakedTexture->getTextureURL()); + + checkIfTexturesFinished(); } - - // now that this texture has been baked and handled, we can remove that TextureBaker from our list + } else { + // we have errors to attend to, so we don't do extra processing for this texture + // but we do need to remove that TextureBaker from our list + // and then check if we're done with all textures _unbakedTextures.remove(bakedTexture->getTextureURL()); checkIfTexturesFinished(); - } else { - // there was an error baking this texture - add it to our list of errors and stop processing this FBX - appendErrors(bakedTexture->getErrors()); } } } @@ -516,23 +518,27 @@ void FBXBaker::possiblyCleanupOriginals() { void FBXBaker::checkIfTexturesFinished() { // check if we're done everything we need to do for this FBX // and emit our finished signal if we're done + if (_unbakedTextures.isEmpty()) { // remove the embedded media folder that the FBX SDK produces when reading the original removeEmbeddedMediaFolder(); - if (hasErrors()) { - return; - } - // cleanup the originals if we weren't asked to keep them around possiblyCleanupOriginals(); if (hasErrors()) { - return; - } + // if we're checking for completion but we have errors + // that means one or more of our texture baking operations failed - qCDebug(model_baking) << "Finished baking" << _fbxURL; - - emit finished(); + if (_pendingErrorEmission) { + emit finished(); + } + + return; + } else { + qCDebug(model_baking) << "Finished baking" << _fbxURL; + + emit finished(); + } } } diff --git a/libraries/model-baking/src/FBXBaker.h b/libraries/model-baking/src/FBXBaker.h index ef94de7a2e..cca4878308 100644 --- a/libraries/model-baking/src/FBXBaker.h +++ b/libraries/model-baking/src/FBXBaker.h @@ -20,6 +20,8 @@ #include "Baker.h" #include "TextureBaker.h" +#include + namespace fbxsdk { class FbxManager; class FbxProperty; @@ -27,25 +29,6 @@ namespace fbxsdk { class FbxFileTexture; } -enum TextureType { - DEFAULT_TEXTURE, - STRICT_TEXTURE, - ALBEDO_TEXTURE, - NORMAL_TEXTURE, - BUMP_TEXTURE, - SPECULAR_TEXTURE, - METALLIC_TEXTURE = SPECULAR_TEXTURE, // for now spec and metallic texture are the same, converted to grey - ROUGHNESS_TEXTURE, - GLOSS_TEXTURE, - EMISSIVE_TEXTURE, - CUBE_TEXTURE, - OCCLUSION_TEXTURE, - SCATTERING_TEXTURE = OCCLUSION_TEXTURE, - LIGHTMAP_TEXTURE, - CUSTOM_TEXTURE, - UNUSED_TEXTURE = -1 -}; - static const QString BAKED_FBX_EXTENSION = ".baked.fbx"; using FBXSDKManagerUniquePointer = std::unique_ptr>; @@ -89,7 +72,7 @@ private: QString createBakedTextureFileName(const QFileInfo& textureFileInfo); QUrl getTextureURL(const QFileInfo& textureFileInfo, fbxsdk::FbxFileTexture* fileTexture); - void bakeTexture(const QUrl& textureURL); + void bakeTexture(const QUrl& textureURL, gpu::TextureType textureType, const QString& destinationFilePath); QString pathToCopyOfOriginal() const; @@ -103,18 +86,15 @@ private: static FBXSDKManagerUniquePointer _sdkManager; fbxsdk::FbxScene* _scene { nullptr }; - QStringList _errorList; - QHash _unbakedTextures; QHash _textureNameMatchCount; - QHash _textureTypes; QSet> _bakingTextures; QFutureSynchronizer _textureBakeSynchronizer; bool _copyOriginals { true }; - bool _finishedNonTextureOperations { false }; + bool _pendingErrorEmission { false }; }; #endif // hifi_FBXBaker_h diff --git a/libraries/model-baking/src/TextureBaker.cpp b/libraries/model-baking/src/TextureBaker.cpp index bdd20ea270..82f42a5776 100644 --- a/libraries/model-baking/src/TextureBaker.cpp +++ b/libraries/model-baking/src/TextureBaker.cpp @@ -9,20 +9,27 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include #include #include #include +#include +#include #include #include "ModelBakingLoggingCategory.h" #include "TextureBaker.h" -TextureBaker::TextureBaker(const QUrl& textureURL) : - _textureURL(textureURL) +const QString BAKED_TEXTURE_EXT = ".ktx"; + +TextureBaker::TextureBaker(const QUrl& textureURL, gpu::TextureType textureType, const QString& destinationFilePath) : + _textureURL(textureURL), + _textureType(textureType), + _destinationFilePath(destinationFilePath) { - + } void TextureBaker::bake() { @@ -35,6 +42,14 @@ void TextureBaker::bake() { qCDebug(model_baking) << "Baking texture at" << _textureURL; + processTexture(); + + if (hasErrors()) { + return; + } + + qCDebug(model_baking) << "Baked texture at" << _textureURL; + emit finished(); } @@ -83,6 +98,33 @@ void TextureBaker::handleTextureNetworkReply(QNetworkReply* requestReply) { _originalTexture = requestReply->readAll(); } else { // add an error to our list stating that this texture could not be downloaded - qCDebug(model_baking) << "Error downloading texture" << requestReply->errorString(); + handleError("Error downloading " + _textureURL.toString() + " - " + requestReply->errorString()); + } +} + +void TextureBaker::processTexture() { + auto processedTexture = image::processImage(_originalTexture, _textureURL.toString().toStdString(), + ABSOLUTE_MAX_TEXTURE_NUM_PIXELS, _textureType); + + if (!processedTexture) { + handleError("Could not process texture " + _textureURL.toString()); + return; + } + + auto memKTX = gpu::Texture::serialize(*processedTexture); + + if (!memKTX) { + handleError("Could not serialize " + _textureURL.toString() + " to KTX"); + return; + } + + const char* data = reinterpret_cast(memKTX->_storage->data()); + const size_t length = memKTX->_storage->size(); + + // attempt to write the baked texture to the destination file path + QFile bakedTextureFile { _destinationFilePath }; + + if (!bakedTextureFile.open(QIODevice::WriteOnly) || bakedTextureFile.write(data, length) == -1) { + handleError("Could not write baked texture for " + _textureURL.toString()); } } diff --git a/libraries/model-baking/src/TextureBaker.h b/libraries/model-baking/src/TextureBaker.h index 4f793af37d..17c725b57d 100644 --- a/libraries/model-baking/src/TextureBaker.h +++ b/libraries/model-baking/src/TextureBaker.h @@ -14,14 +14,19 @@ #include #include +#include + +#include #include "Baker.h" +extern const QString BAKED_TEXTURE_EXT; + class TextureBaker : public Baker { Q_OBJECT public: - TextureBaker(const QUrl& textureURL); + TextureBaker(const QUrl& textureURL, gpu::TextureType textureType, const QString& destinationFilePath); void bake(); @@ -33,8 +38,13 @@ private: void loadTexture(); void handleTextureNetworkReply(QNetworkReply* requestReply); + void processTexture(); + QUrl _textureURL; QByteArray _originalTexture; + gpu::TextureType _textureType; + + QString _destinationFilePath; }; #endif // hifi_TextureBaker_h diff --git a/libraries/model-networking/src/model-networking/TextureCache.h b/libraries/model-networking/src/model-networking/TextureCache.h index 1e61b9ecee..ade1acdb64 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.h +++ b/libraries/model-networking/src/model-networking/TextureCache.h @@ -27,8 +27,6 @@ #include "KTXCache.h" -const int ABSOLUTE_MAX_TEXTURE_NUM_PIXELS = 8192 * 8192; - namespace gpu { class Batch; } diff --git a/libraries/shared/src/Profile.cpp b/libraries/shared/src/Profile.cpp index 7a8a8f0570..eb7440f4b3 100644 --- a/libraries/shared/src/Profile.cpp +++ b/libraries/shared/src/Profile.cpp @@ -34,7 +34,7 @@ Q_LOGGING_CATEGORY(trace_simulation_physics_detail, "trace.simulation.physics.de #endif static bool tracingEnabled() { - return DependencyManager::get()->isEnabled(); + return DependencyManager::isSet() && DependencyManager::get()->isEnabled(); } Duration::Duration(const QLoggingCategory& category, const QString& name, uint32_t argbColor, uint64_t payload, const QVariantMap& baseArgs) : _name(name), _category(category) { diff --git a/tools/oven/CMakeLists.txt b/tools/oven/CMakeLists.txt index 2c5d0b98e5..1e644a2c62 100644 --- a/tools/oven/CMakeLists.txt +++ b/tools/oven/CMakeLists.txt @@ -2,4 +2,4 @@ set(TARGET_NAME oven) setup_hifi_project(Widgets Gui Concurrent) -link_hifi_libraries(model-baking shared) +link_hifi_libraries(model-baking shared image gpu ktx) diff --git a/tools/oven/src/DomainBaker.cpp b/tools/oven/src/DomainBaker.cpp index b53b74f227..6bee07986c 100644 --- a/tools/oven/src/DomainBaker.cpp +++ b/tools/oven/src/DomainBaker.cpp @@ -236,7 +236,7 @@ void DomainBaker::handleFinishedBaker() { QUrl oldModelURL { entity[ENTITY_MODEL_URL_KEY].toString() }; // setup a new URL using the prefix we were passed - QUrl newModelURL = _destinationPath.resolved(baker->getBakedFBXRelativePath().mid(1)); + QUrl newModelURL = _destinationPath.resolved(baker->getBakedFBXRelativePath()); // copy the fragment and query from the old model URL newModelURL.setQuery(oldModelURL.query()); @@ -251,7 +251,7 @@ void DomainBaker::handleFinishedBaker() { } else { // this model failed to bake - this doesn't fail the entire bake but we need to add // the errors from the model to our errors - appendWarnings(baker->getErrors()); + _warningList << baker->getErrors(); } // remove the baked URL from the multi hash of entities needing a re-write diff --git a/tools/oven/src/ui/DomainBakeWidget.cpp b/tools/oven/src/ui/DomainBakeWidget.cpp index 7c8c462cfd..c0b9c85910 100644 --- a/tools/oven/src/ui/DomainBakeWidget.cpp +++ b/tools/oven/src/ui/DomainBakeWidget.cpp @@ -160,9 +160,6 @@ void DomainBakeWidget::chooseFileButtonClicked() { // save the directory containing this entities file so we can default to it next time we show the file dialog _browseStartDirectory.set(directoryOfEntitiesFile); - - // if our output directory is not yet set, set it to the directory of this entities file - _outputDirLineEdit->setText(directoryOfEntitiesFile); } } From 446cbf59b3a59c845d6c423b38e06d0aa3f1ee63 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 17 Apr 2017 09:33:52 -0700 Subject: [PATCH 36/71] add domain bake progress to results table --- tools/oven/src/DomainBaker.cpp | 7 +++++++ tools/oven/src/DomainBaker.h | 3 +++ tools/oven/src/ui/DomainBakeWidget.cpp | 23 +++++++++++++++++++++++ tools/oven/src/ui/DomainBakeWidget.h | 1 + 4 files changed, 34 insertions(+) diff --git a/tools/oven/src/DomainBaker.cpp b/tools/oven/src/DomainBaker.cpp index 6bee07986c..5589df1645 100644 --- a/tools/oven/src/DomainBaker.cpp +++ b/tools/oven/src/DomainBaker.cpp @@ -212,10 +212,14 @@ void DomainBaker::enumerateEntities() { // add this QJsonValueRef to our multi hash so that we can easily re-write // the model URL to the baked version once the baker is complete _entitiesNeedingRewrite.insert(modelURL, *it); + ++_totalNumberOfEntities; } } } } + + // emit progress now to say we're just starting + emit bakeProgress(0, _totalNumberOfEntities); } void DomainBaker::handleFinishedBaker() { @@ -260,6 +264,9 @@ void DomainBaker::handleFinishedBaker() { // drop our shared pointer to this baker so that it gets cleaned up _bakers.remove(baker->getFBXUrl()); + // emit progress to tell listeners how many models we have baked + emit bakeProgress(_totalNumberOfEntities - _entitiesNeedingRewrite.keys().size(), _totalNumberOfEntities); + if (_entitiesNeedingRewrite.isEmpty()) { emit allModelsFinished(); } diff --git a/tools/oven/src/DomainBaker.h b/tools/oven/src/DomainBaker.h index 6c8555cc62..23c332abab 100644 --- a/tools/oven/src/DomainBaker.h +++ b/tools/oven/src/DomainBaker.h @@ -31,6 +31,7 @@ public: signals: void allModelsFinished(); + void bakeProgress(int modelsBaked, int modelsTotal); private slots: void handleFinishedBaker(); @@ -54,6 +55,8 @@ private: std::unique_ptr _fbxBakerThread; QHash> _bakers; QMultiHash _entitiesNeedingRewrite; + + int _totalNumberOfEntities; }; #endif // hifi_DomainBaker_h diff --git a/tools/oven/src/ui/DomainBakeWidget.cpp b/tools/oven/src/ui/DomainBakeWidget.cpp index c0b9c85910..e9b59f005a 100644 --- a/tools/oven/src/ui/DomainBakeWidget.cpp +++ b/tools/oven/src/ui/DomainBakeWidget.cpp @@ -213,6 +213,9 @@ void DomainBakeWidget::bakeButtonClicked() { // make sure we hear from the baker when it is done connect(domainBaker.get(), &DomainBaker::finished, this, &DomainBakeWidget::handleFinishedBaker); + // watch the baker's progress so that we can put its progress in the results table + connect(domainBaker.get(), &DomainBaker::bakeProgress, this, &DomainBakeWidget::handleBakerProgress); + // run the baker in our thread pool QtConcurrent::run(domainBaker.get(), &DomainBaker::bake); @@ -226,6 +229,26 @@ void DomainBakeWidget::bakeButtonClicked() { } } +void DomainBakeWidget::handleBakerProgress(int modelsBaked, int modelsTotal) { + if (auto baker = qobject_cast(sender())) { + // add the results of this bake to the results window + auto it = std::remove_if(_bakers.begin(), _bakers.end(), [baker](const BakerRowPair& value) { + return value.first.get() == baker; + }); + + if (it != _bakers.end()) { + auto resultRow = it->second; + auto resultsWindow = qApp->getMainWindow()->showResultsWindow(); + + int percentage = roundf(float(modelsBaked) / float(modelsTotal) * 100.0f); + qDebug() << percentage; + + auto statusString = QString("Baking - %1 of %2 models baked - %3%").arg(modelsBaked).arg(modelsTotal).arg(percentage); + resultsWindow->changeStatusForRow(resultRow, statusString); + } + } +} + void DomainBakeWidget::handleFinishedBaker() { if (auto baker = qobject_cast(sender())) { // add the results of this bake to the results window diff --git a/tools/oven/src/ui/DomainBakeWidget.h b/tools/oven/src/ui/DomainBakeWidget.h index 606f550203..ea9e6f7049 100644 --- a/tools/oven/src/ui/DomainBakeWidget.h +++ b/tools/oven/src/ui/DomainBakeWidget.h @@ -34,6 +34,7 @@ private slots: void outputDirectoryChanged(const QString& newDirectory); + void handleBakerProgress(int modelsBaked, int modelsTotal); void handleFinishedBaker(); private: From 1798a058da58f11c1e210b49054c7170fe8a8c8a Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 17 Apr 2017 09:40:10 -0700 Subject: [PATCH 37/71] fix filename grabbing for windows absolute path --- libraries/model-baking/src/FBXBaker.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/libraries/model-baking/src/FBXBaker.cpp b/libraries/model-baking/src/FBXBaker.cpp index 0a25d3c299..ddf68b74c7 100644 --- a/libraries/model-baking/src/FBXBaker.cpp +++ b/libraries/model-baking/src/FBXBaker.cpp @@ -223,7 +223,7 @@ QString texturePathRelativeToFBX(QUrl fbxURL, QUrl textureURL) { QString FBXBaker::createBakedTextureFileName(const QFileInfo& textureFileInfo) { // first make sure we have a unique base name for this texture // in case another texture referenced by this model has the same base name - auto& nameMatches = _textureNameMatchCount[textureFileInfo.baseName()]; + auto nameMatches = _textureNameMatchCount[textureFileInfo.baseName()]; QString bakedTextureFileName { textureFileInfo.baseName() }; @@ -347,7 +347,8 @@ void FBXBaker::rewriteAndBakeSceneTextures() { FbxFileTexture* fileTexture = property.GetSrcObject(j); // use QFileInfo to easily split up the existing texture filename into its components - QFileInfo textureFileInfo { fileTexture->GetFileName() }; + QString fbxFileName { fileTexture->GetFileName() }; + QFileInfo textureFileInfo { fbxFileName.replace("\\", "/") }; // make sure this texture points to something and isn't one we've already re-mapped if (!textureFileInfo.filePath().isEmpty() From 3388debe9f9f3515f4086062accac88d058d1c34 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 17 Apr 2017 09:51:59 -0700 Subject: [PATCH 38/71] remove debug for percentage in DomainBakeWidget --- tools/oven/src/ui/DomainBakeWidget.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/tools/oven/src/ui/DomainBakeWidget.cpp b/tools/oven/src/ui/DomainBakeWidget.cpp index e9b59f005a..9f33dbcc29 100644 --- a/tools/oven/src/ui/DomainBakeWidget.cpp +++ b/tools/oven/src/ui/DomainBakeWidget.cpp @@ -241,7 +241,6 @@ void DomainBakeWidget::handleBakerProgress(int modelsBaked, int modelsTotal) { auto resultsWindow = qApp->getMainWindow()->showResultsWindow(); int percentage = roundf(float(modelsBaked) / float(modelsTotal) * 100.0f); - qDebug() << percentage; auto statusString = QString("Baking - %1 of %2 models baked - %3%").arg(modelsBaked).arg(modelsTotal).arg(percentage); resultsWindow->changeStatusForRow(resultRow, statusString); From 383d82fe1dc43c09f1d654c0645ee7efef5754af Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 17 Apr 2017 10:04:38 -0700 Subject: [PATCH 39/71] fix multi-line display in results window --- tools/oven/src/ui/ResultsWindow.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tools/oven/src/ui/ResultsWindow.cpp b/tools/oven/src/ui/ResultsWindow.cpp index 99389f363c..36b7e83177 100644 --- a/tools/oven/src/ui/ResultsWindow.cpp +++ b/tools/oven/src/ui/ResultsWindow.cpp @@ -76,4 +76,7 @@ void ResultsWindow::changeStatusForRow(int rowIndex, const QString& result) { auto statusItem = new QTableWidgetItem(result); statusItem->setFlags(statusItem->flags() & ~Qt::ItemIsEditable); _resultsTable->setItem(rowIndex, 1, statusItem); + + // resize the row for the new contents + _resultsTable->resizeRowToContents(rowIndex); } From cdd9990fe8c36202663bbc69539f18f71e8a1834 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 17 Apr 2017 13:06:22 -0700 Subject: [PATCH 40/71] use worker threads for Oven, re-write animation URLs --- libraries/model-baking/src/Baker.h | 5 +- libraries/model-baking/src/FBXBaker.cpp | 9 +- libraries/model-baking/src/FBXBaker.h | 18 ++-- libraries/model-baking/src/TextureBaker.cpp | 48 +++++---- libraries/model-baking/src/TextureBaker.h | 15 ++- tools/oven/src/DomainBaker.cpp | 102 +++++++++++--------- tools/oven/src/DomainBaker.h | 12 +-- tools/oven/src/Oven.cpp | 63 ++++++++++++ tools/oven/src/Oven.h | 14 +++ tools/oven/src/ui/DomainBakeWidget.cpp | 25 +++-- tools/oven/src/ui/ModelBakeWidget.cpp | 6 +- 11 files changed, 217 insertions(+), 100 deletions(-) diff --git a/libraries/model-baking/src/Baker.h b/libraries/model-baking/src/Baker.h index ab9c22ac53..8f73819dfe 100644 --- a/libraries/model-baking/src/Baker.h +++ b/libraries/model-baking/src/Baker.h @@ -18,14 +18,15 @@ class Baker : public QObject { Q_OBJECT public: - virtual void bake() = 0; - bool hasErrors() const { return !_errorList.isEmpty(); } QStringList getErrors() const { return _errorList; } bool hasWarnings() const { return !_warningList.isEmpty(); } QStringList getWarnings() const { return _warningList; } +public slots: + virtual void bake() = 0; + signals: void finished(); diff --git a/libraries/model-baking/src/FBXBaker.cpp b/libraries/model-baking/src/FBXBaker.cpp index ddf68b74c7..5b47f5023b 100644 --- a/libraries/model-baking/src/FBXBaker.cpp +++ b/libraries/model-baking/src/FBXBaker.cpp @@ -30,9 +30,11 @@ std::once_flag onceFlag; FBXSDKManagerUniquePointer FBXBaker::_sdkManager { nullptr }; -FBXBaker::FBXBaker(const QUrl& fbxURL, const QString& baseOutputPath, bool copyOriginals) : +FBXBaker::FBXBaker(const QUrl& fbxURL, const QString& baseOutputPath, + TextureBakerThreadGetter textureThreadGetter, bool copyOriginals) : _fbxURL(fbxURL), _baseOutputPath(baseOutputPath), + _textureThreadGetter(textureThreadGetter), _copyOriginals(copyOriginals) { std::call_once(onceFlag, [](){ @@ -402,8 +404,9 @@ void FBXBaker::bakeTexture(const QUrl& textureURL, gpu::TextureType textureType, // keep a shared pointer to the baking texture _bakingTextures.insert(bakingTexture); - // start baking the texture on our thread pool - QtConcurrent::run(bakingTexture.data(), &TextureBaker::bake); + // start baking the texture on one of our available worker threads + bakingTexture->moveToThread(_textureThreadGetter()); + QMetaObject::invokeMethod(bakingTexture.data(), "bake"); } void FBXBaker::handleBakedTexture() { diff --git a/libraries/model-baking/src/FBXBaker.h b/libraries/model-baking/src/FBXBaker.h index cca4878308..8ad42d6de8 100644 --- a/libraries/model-baking/src/FBXBaker.h +++ b/libraries/model-baking/src/FBXBaker.h @@ -32,21 +32,23 @@ namespace fbxsdk { static const QString BAKED_FBX_EXTENSION = ".baked.fbx"; using FBXSDKManagerUniquePointer = std::unique_ptr>; +using TextureBakerThreadGetter = std::function; + class FBXBaker : public Baker { Q_OBJECT public: - FBXBaker(const QUrl& fbxURL, const QString& baseOutputPath, bool copyOriginals = true); - - // all calls to bake must be from the same thread, because the Autodesk SDK will cause - // a crash if it is called from multiple threads - Q_INVOKABLE virtual void bake() override; + FBXBaker(const QUrl& fbxURL, const QString& baseOutputPath, + TextureBakerThreadGetter textureThreadGetter, bool copyOriginals = true); QUrl getFBXUrl() const { return _fbxURL; } QString getBakedFBXRelativePath() const { return _bakedFBXRelativePath; } -signals: - void allTexturesBaked(); +public slots: + // all calls to FBXBaker::bake for FBXBaker instances must be from the same thread + // because the Autodesk SDK will cause a crash if it is called from multiple threads + virtual void bake() override; +signals: void sourceCopyReadyToLoad(); private slots: @@ -92,6 +94,8 @@ private: QSet> _bakingTextures; QFutureSynchronizer _textureBakeSynchronizer; + TextureBakerThreadGetter _textureThreadGetter; + bool _copyOriginals { true }; bool _pendingErrorEmission { false }; diff --git a/libraries/model-baking/src/TextureBaker.cpp b/libraries/model-baking/src/TextureBaker.cpp index 82f42a5776..0ebde00bde 100644 --- a/libraries/model-baking/src/TextureBaker.cpp +++ b/libraries/model-baking/src/TextureBaker.cpp @@ -33,24 +33,11 @@ TextureBaker::TextureBaker(const QUrl& textureURL, gpu::TextureType textureType, } void TextureBaker::bake() { + // once our texture is loaded, kick off a the processing + connect(this, &TextureBaker::originalTextureLoaded, this, &TextureBaker::processTexture); + // first load the texture (either locally or remotely) loadTexture(); - - if (hasErrors()) { - return; - } - - qCDebug(model_baking) << "Baking texture at" << _textureURL; - - processTexture(); - - if (hasErrors()) { - return; - } - - qCDebug(model_baking) << "Baked texture at" << _textureURL; - - emit finished(); } void TextureBaker::loadTexture() { @@ -65,6 +52,8 @@ void TextureBaker::loadTexture() { } _originalTexture = localTexture.readAll(); + + emit originalTextureLoaded(); } else { // remote file, kick off a download auto& networkAccessManager = NetworkAccessManager::getInstance(); @@ -79,23 +68,22 @@ void TextureBaker::loadTexture() { qCDebug(model_baking) << "Downloading" << _textureURL; + // kickoff the download, wait for slot to tell us it is done auto networkReply = networkAccessManager.get(networkRequest); - - // use an event loop to process events while we wait for the network reply - QEventLoop eventLoop; - connect(networkReply, &QNetworkReply::finished, &eventLoop, &QEventLoop::quit); - eventLoop.exec(); - - handleTextureNetworkReply(networkReply); + connect(networkReply, &QNetworkReply::finished, this, &TextureBaker::handleTextureNetworkReply); } } -void TextureBaker::handleTextureNetworkReply(QNetworkReply* requestReply) { +void TextureBaker::handleTextureNetworkReply() { + auto requestReply = qobject_cast(sender()); + if (requestReply->error() == QNetworkReply::NoError) { - qCDebug(model_baking) << "Downloaded texture at" << _textureURL; + qCDebug(model_baking) << "Downloaded texture" << _textureURL; // store the original texture so it can be passed along for the bake _originalTexture = requestReply->readAll(); + + emit originalTextureLoaded(); } else { // add an error to our list stating that this texture could not be downloaded handleError("Error downloading " + _textureURL.toString() + " - " + requestReply->errorString()); @@ -111,6 +99,13 @@ void TextureBaker::processTexture() { return; } + // the baked textures need to have the source hash added for cache checks in Interface + // so we add that to the processed texture before handling it off to be serialized + QCryptographicHash hasher(QCryptographicHash::Md5); + hasher.addData(_originalTexture); + std::string hash = hasher.result().toHex().toStdString(); + processedTexture->setSourceHash(hash); + auto memKTX = gpu::Texture::serialize(*processedTexture); if (!memKTX) { @@ -127,4 +122,7 @@ void TextureBaker::processTexture() { if (!bakedTextureFile.open(QIODevice::WriteOnly) || bakedTextureFile.write(data, length) == -1) { handleError("Could not write baked texture for " + _textureURL.toString()); } + + qCDebug(model_baking) << "Baked texture" << _textureURL; + emit finished(); } diff --git a/libraries/model-baking/src/TextureBaker.h b/libraries/model-baking/src/TextureBaker.h index 17c725b57d..c0cb8a377a 100644 --- a/libraries/model-baking/src/TextureBaker.h +++ b/libraries/model-baking/src/TextureBaker.h @@ -27,18 +27,23 @@ class TextureBaker : public Baker { public: TextureBaker(const QUrl& textureURL, gpu::TextureType textureType, const QString& destinationFilePath); - - void bake(); const QByteArray& getOriginalTexture() const { return _originalTexture; } const QUrl& getTextureURL() const { return _textureURL; } +public slots: + virtual void bake() override; + +signals: + void originalTextureLoaded(); + +private slots: + void processTexture(); + private: void loadTexture(); - void handleTextureNetworkReply(QNetworkReply* requestReply); - - void processTexture(); + void handleTextureNetworkReply(); QUrl _textureURL; QByteArray _originalTexture; diff --git a/tools/oven/src/DomainBaker.cpp b/tools/oven/src/DomainBaker.cpp index 5589df1645..a8fd464790 100644 --- a/tools/oven/src/DomainBaker.cpp +++ b/tools/oven/src/DomainBaker.cpp @@ -18,6 +18,8 @@ #include "Gzip.h" +#include "Oven.h" + #include "DomainBaker.h" DomainBaker::DomainBaker(const QUrl& localModelFileURL, const QString& domainName, @@ -47,40 +49,14 @@ void DomainBaker::bake() { return; } - setupBakerThread(); - - if (hasErrors()) { - return; - } - enumerateEntities(); if (hasErrors()) { return; } - if (!_entitiesNeedingRewrite.isEmpty()) { - // use a QEventLoop to wait for all entity rewrites to be completed before writing the final models file - QEventLoop eventLoop; - connect(this, &DomainBaker::allModelsFinished, &eventLoop, &QEventLoop::quit); - eventLoop.exec(); - } - - if (hasErrors()) { - return; - } - - writeNewEntitiesFile(); - - if (hasErrors()) { - return; - } - - // stop the FBX baker thread now that all our bakes have completed - _fbxBakerThread->quit(); - - // we've now written out our new models file - time to say that we are finished up - emit finished(); + // in case we've baked and re-written all of our entities already, check if we're done + checkIfRewritingComplete(); } void DomainBaker::setupOutputFolder() { @@ -158,15 +134,6 @@ void DomainBaker::loadLocalFile() { } } -void DomainBaker::setupBakerThread() { - // This is a real bummer, but the FBX SDK is not thread safe - even with separate FBXManager objects. - // This means that we need to put all of the FBX importing/exporting on the same thread. - // We'll setup that thread now and then move the FBXBaker objects to the thread later when enumerating entities. - _fbxBakerThread = std::unique_ptr(new QThread); - _fbxBakerThread->setObjectName("Domain FBX Baker Thread"); - _fbxBakerThread->start(); -} - static const QString ENTITY_MODEL_URL_KEY = "modelURL"; void DomainBaker::enumerateEntities() { @@ -190,12 +157,15 @@ void DomainBaker::enumerateEntities() { if (BAKEABLE_MODEL_EXTENSIONS.contains(completeLowerExtension)) { // grab a clean version of the URL without a query or fragment - modelURL.setFragment(QString()); - modelURL.setQuery(QString()); + modelURL = modelURL.adjusted(QUrl::RemoveQuery | QUrl::RemoveFragment); // setup an FBXBaker for this URL, as long as we don't already have one if (!_bakers.contains(modelURL)) { - QSharedPointer baker { new FBXBaker(modelURL, _contentOutputPath), &FBXBaker::deleteLater }; + QSharedPointer baker { + new FBXBaker(modelURL, _contentOutputPath, []() -> QThread* { + return qApp->getNextWorkerThread(); + }), &FBXBaker::deleteLater + }; // make sure our handler is called when the baker is done connect(baker.data(), &Baker::finished, this, &DomainBaker::handleFinishedBaker); @@ -205,7 +175,7 @@ void DomainBaker::enumerateEntities() { // move the baker to the baker thread // and kickoff the bake - baker->moveToThread(_fbxBakerThread.get()); + baker->moveToThread(qApp->getFBXBakerThread()); QMetaObject::invokeMethod(baker.data(), "bake"); } @@ -228,6 +198,7 @@ void DomainBaker::handleFinishedBaker() { if (baker) { if (!baker->hasErrors()) { // this FBXBaker is done and everything went according to plan + qDebug() << "Re-writing entity references to" << baker->getFBXUrl(); // enumerate the QJsonRef values for the URL of this FBX from our multi hash of // entity objects needing a URL re-write @@ -242,12 +213,42 @@ void DomainBaker::handleFinishedBaker() { // setup a new URL using the prefix we were passed QUrl newModelURL = _destinationPath.resolved(baker->getBakedFBXRelativePath()); - // copy the fragment and query from the old model URL + // copy the fragment and query, and user info from the old model URL newModelURL.setQuery(oldModelURL.query()); newModelURL.setFragment(oldModelURL.fragment()); + newModelURL.setUserInfo(oldModelURL.userInfo()); // set the new model URL as the value in our temp QJsonObject entity[ENTITY_MODEL_URL_KEY] = newModelURL.toString(); + + // check if the entity also had an animation at the same URL + // in which case it should be replaced with our baked model URL too + const QString ENTITY_ANIMATION_KEY = "animation"; + const QString ENTITIY_ANIMATION_URL_KEY = "url"; + + if (entity.contains(ENTITY_ANIMATION_KEY) + && entity[ENTITY_ANIMATION_KEY].toObject().contains(ENTITIY_ANIMATION_URL_KEY)) { + auto animationValue = entity[ENTITY_ANIMATION_KEY]; + auto animationObject = animationValue.toObject(); + + // grab the old animation URL + QUrl oldAnimationURL { animationObject[ENTITIY_ANIMATION_URL_KEY].toString() }; + + // check if its stripped down version matches our stripped down model URL + if (oldAnimationURL.matches(oldModelURL, QUrl::RemoveUserInfo | QUrl::RemoveQuery | QUrl::RemoveFragment)) { + // the animation URL matched the old model URL, so make the animation URL point to the baked FBX + // with its original query and fragment + auto newAnimationURL = _destinationPath.resolved(baker->getBakedFBXRelativePath()); + newAnimationURL.setQuery(oldAnimationURL.query()); + newAnimationURL.setFragment(oldAnimationURL.fragment()); + newAnimationURL.setUserInfo(oldAnimationURL.userInfo()); + + animationObject[ENTITIY_ANIMATION_URL_KEY] = newAnimationURL.toString(); + + // replace the animation object referenced by the QJsonValueRef + animationValue = animationObject; + } + } // replace our temp object with the value referenced by our QJsonValueRef entityValue = entity; @@ -267,9 +268,21 @@ void DomainBaker::handleFinishedBaker() { // emit progress to tell listeners how many models we have baked emit bakeProgress(_totalNumberOfEntities - _entitiesNeedingRewrite.keys().size(), _totalNumberOfEntities); - if (_entitiesNeedingRewrite.isEmpty()) { - emit allModelsFinished(); + // check if this was the last model we needed to re-write and if we are done now + checkIfRewritingComplete(); + } +} + +void DomainBaker::checkIfRewritingComplete() { + if (_entitiesNeedingRewrite.isEmpty()) { + writeNewEntitiesFile(); + + if (hasErrors()) { + return; } + + // we've now written out our new models file - time to say that we are finished up + emit finished(); } } @@ -308,6 +321,5 @@ void DomainBaker::writeNewEntitiesFile() { } qDebug() << "Exported entities file with baked model URLs to" << bakedEntitiesFilePath; - qDebug() << "WARNINGS:" << _warningList; } diff --git a/tools/oven/src/DomainBaker.h b/tools/oven/src/DomainBaker.h index 23c332abab..ddcb3cd006 100644 --- a/tools/oven/src/DomainBaker.h +++ b/tools/oven/src/DomainBaker.h @@ -23,24 +23,25 @@ class DomainBaker : public Baker { Q_OBJECT public: + // This is a real bummer, but the FBX SDK is not thread safe - even with separate FBXManager objects. + // This means that we need to put all of the FBX importing/exporting from the same process on the same thread. + // That means you must pass a usable running QThread when constructing a domain baker. DomainBaker(const QUrl& localEntitiesFileURL, const QString& domainName, const QString& baseOutputPath, const QUrl& destinationPath); -public: - virtual void bake() override; - signals: void allModelsFinished(); void bakeProgress(int modelsBaked, int modelsTotal); private slots: + virtual void bake() override; void handleFinishedBaker(); private: void setupOutputFolder(); void loadLocalFile(); - void setupBakerThread(); void enumerateEntities(); + void checkIfRewritingComplete(); void writeNewEntitiesFile(); QUrl _localEntitiesFileURL; @@ -52,11 +53,10 @@ private: QJsonArray _entities; - std::unique_ptr _fbxBakerThread; QHash> _bakers; QMultiHash _entitiesNeedingRewrite; - int _totalNumberOfEntities; + int _totalNumberOfEntities { 0 }; }; #endif // hifi_DomainBaker_h diff --git a/tools/oven/src/Oven.cpp b/tools/oven/src/Oven.cpp index 60754759f4..ac8ef505ba 100644 --- a/tools/oven/src/Oven.cpp +++ b/tools/oven/src/Oven.cpp @@ -10,6 +10,7 @@ // #include +#include #include @@ -33,5 +34,67 @@ Oven::Oven(int argc, char* argv[]) : // setup the GUI _mainWindow = new OvenMainWindow; _mainWindow->show(); + + // setup our worker threads + setupWorkerThreads(QThread::idealThreadCount() - 1); + + // Autodesk's SDK means that we need a single thread for all FBX importing/exporting in the same process + // setup the FBX Baker thread + setupFBXBakerThread(); +} + +Oven::~Oven() { + // cleanup the worker threads + for (auto i = 0; i < _workerThreads.size(); ++i) { + _workerThreads[i]->quit(); + _workerThreads[i]->wait(); + } + + // cleanup the FBX Baker thread + _fbxBakerThread->quit(); + _fbxBakerThread->wait(); +} + +void Oven::setupWorkerThreads(int numWorkerThreads) { + for (auto i = 0; i < numWorkerThreads; ++i) { + // setup a worker thread yet and add it to our concurrent vector + auto newThread = new QThread(this); + newThread->setObjectName("Oven Worker Thread " + QString::number(i + 1)); + + _workerThreads.push_back(newThread); + } +} + +void Oven::setupFBXBakerThread() { + // we're being asked for the FBX baker thread, but we don't have one yet + // so set that up now + _fbxBakerThread = new QThread(this); + _fbxBakerThread->setObjectName("Oven FBX Baker Thread"); +} + +QThread* Oven::getFBXBakerThread() { + if (!_fbxBakerThread->isRunning()) { + // start the FBX baker thread if it isn't running yet + _fbxBakerThread->start(); + } + + return _fbxBakerThread; +} + +QThread* Oven::getNextWorkerThread() { + // Here we replicate some of the functionality of QThreadPool by giving callers an available worker thread to use. + // We can't use QThreadPool because we want to put QObjects with signals/slots on these threads. + // So instead we setup our own list of threads, up to one less than the ideal thread count + // (for the FBX Baker Thread to have room), and cycle through them to hand a usable running thread back to our callers. + + auto nextIndex = ++_nextWorkerThreadIndex; + auto nextThread = _workerThreads[nextIndex % _workerThreads.size()]; + + // start the thread if it isn't running yet + if (!nextThread->isRunning()) { + nextThread->start(); + } + + return nextThread; } diff --git a/tools/oven/src/Oven.h b/tools/oven/src/Oven.h index 3fc9a4c0f6..bf7f478b83 100644 --- a/tools/oven/src/Oven.h +++ b/tools/oven/src/Oven.h @@ -14,6 +14,8 @@ #include +#include + #if defined(qApp) #undef qApp #endif @@ -26,11 +28,23 @@ class Oven : public QApplication { public: Oven(int argc, char* argv[]); + ~Oven(); OvenMainWindow* getMainWindow() const { return _mainWindow; } + QThread* getFBXBakerThread(); + QThread* getNextWorkerThread(); + private: + void setupWorkerThreads(int numWorkerThreads); + void setupFBXBakerThread(); + OvenMainWindow* _mainWindow; + QThread* _fbxBakerThread; + QList _workerThreads; + + std::atomic _nextWorkerThreadIndex; + int _numWorkerThreads; }; diff --git a/tools/oven/src/ui/DomainBakeWidget.cpp b/tools/oven/src/ui/DomainBakeWidget.cpp index 9f33dbcc29..cd2d9f8e3c 100644 --- a/tools/oven/src/ui/DomainBakeWidget.cpp +++ b/tools/oven/src/ui/DomainBakeWidget.cpp @@ -216,8 +216,12 @@ void DomainBakeWidget::bakeButtonClicked() { // watch the baker's progress so that we can put its progress in the results table connect(domainBaker.get(), &DomainBaker::bakeProgress, this, &DomainBakeWidget::handleBakerProgress); - // run the baker in our thread pool - QtConcurrent::run(domainBaker.get(), &DomainBaker::bake); + // move the baker to the next available Oven worker thread + auto nextThread = qApp->getNextWorkerThread(); + domainBaker->moveToThread(nextThread); + + // kickoff the domain baker on its thread + QMetaObject::invokeMethod(domainBaker.get(), "bake"); // add a pending row to the results window to show that this bake is in process auto resultsWindow = qApp->getMainWindow()->showResultsWindow(); @@ -232,7 +236,7 @@ void DomainBakeWidget::bakeButtonClicked() { void DomainBakeWidget::handleBakerProgress(int modelsBaked, int modelsTotal) { if (auto baker = qobject_cast(sender())) { // add the results of this bake to the results window - auto it = std::remove_if(_bakers.begin(), _bakers.end(), [baker](const BakerRowPair& value) { + auto it = std::find_if(_bakers.begin(), _bakers.end(), [baker](const BakerRowPair& value) { return value.first.get() == baker; }); @@ -251,7 +255,7 @@ void DomainBakeWidget::handleBakerProgress(int modelsBaked, int modelsTotal) { void DomainBakeWidget::handleFinishedBaker() { if (auto baker = qobject_cast(sender())) { // add the results of this bake to the results window - auto it = std::remove_if(_bakers.begin(), _bakers.end(), [baker](const BakerRowPair& value) { + auto it = std::find_if(_bakers.begin(), _bakers.end(), [baker](const BakerRowPair& value) { return value.first.get() == baker; }); @@ -260,12 +264,21 @@ void DomainBakeWidget::handleFinishedBaker() { auto resultsWindow = qApp->getMainWindow()->showResultsWindow(); if (baker->hasErrors()) { - resultsWindow->changeStatusForRow(resultRow, baker->getErrors().join("\n")); + auto errors = baker->getErrors(); + errors.removeDuplicates(); + + resultsWindow->changeStatusForRow(resultRow, errors.join("\n")); } else if (baker->hasWarnings()) { - resultsWindow->changeStatusForRow(resultRow, baker->getWarnings().join("\n")); + auto warnings = baker->getWarnings(); + warnings.removeDuplicates(); + + resultsWindow->changeStatusForRow(resultRow, warnings.join("\n")); } else { resultsWindow->changeStatusForRow(resultRow, "Success"); } + + // remove the DomainBaker now that it has completed + _bakers.erase(it); } } } diff --git a/tools/oven/src/ui/ModelBakeWidget.cpp b/tools/oven/src/ui/ModelBakeWidget.cpp index 08b5402821..2841262fee 100644 --- a/tools/oven/src/ui/ModelBakeWidget.cpp +++ b/tools/oven/src/ui/ModelBakeWidget.cpp @@ -183,7 +183,11 @@ void ModelBakeWidget::bakeButtonClicked() { } // everything seems to be in place, kick off a bake for this model now - auto baker = std::unique_ptr { new FBXBaker(modelToBakeURL, outputDirectory.absolutePath(), false) }; + auto baker = std::unique_ptr { + new FBXBaker(modelToBakeURL, outputDirectory.absolutePath(), []() -> QThread* { + return qApp->getNextWorkerThread(); + }, false) + }; // move the baker to the baker thread baker->moveToThread(_bakerThread); From cbd6f6417c21c05f13437bc2b1a7b98aca145fa4 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 17 Apr 2017 13:29:44 -0700 Subject: [PATCH 41/71] allow clicking on results row to show dir --- tools/oven/src/ui/DomainBakeWidget.cpp | 2 +- tools/oven/src/ui/ModelBakeWidget.cpp | 2 +- tools/oven/src/ui/ResultsWindow.cpp | 48 ++++++++++++++++++++++++-- tools/oven/src/ui/ResultsWindow.h | 7 +++- 4 files changed, 54 insertions(+), 5 deletions(-) diff --git a/tools/oven/src/ui/DomainBakeWidget.cpp b/tools/oven/src/ui/DomainBakeWidget.cpp index cd2d9f8e3c..34ae0680af 100644 --- a/tools/oven/src/ui/DomainBakeWidget.cpp +++ b/tools/oven/src/ui/DomainBakeWidget.cpp @@ -226,7 +226,7 @@ void DomainBakeWidget::bakeButtonClicked() { // add a pending row to the results window to show that this bake is in process auto resultsWindow = qApp->getMainWindow()->showResultsWindow(); auto resultsRowName = _domainNameLineEdit->text().isEmpty() ? fileToBakeURL.fileName() : _domainNameLineEdit->text(); - auto resultsRow = resultsWindow->addPendingResultRow(resultsRowName); + auto resultsRow = resultsWindow->addPendingResultRow(resultsRowName, outputDirectory); // keep the unique ptr to the domain baker and the index to the row representing it in the results table _bakers.emplace_back(std::move(domainBaker), resultsRow); diff --git a/tools/oven/src/ui/ModelBakeWidget.cpp b/tools/oven/src/ui/ModelBakeWidget.cpp index 2841262fee..f5204020da 100644 --- a/tools/oven/src/ui/ModelBakeWidget.cpp +++ b/tools/oven/src/ui/ModelBakeWidget.cpp @@ -205,7 +205,7 @@ void ModelBakeWidget::bakeButtonClicked() { // add a pending row to the results window to show that this bake is in process auto resultsWindow = qApp->getMainWindow()->showResultsWindow(); - auto resultsRow = resultsWindow->addPendingResultRow(modelToBakeURL.fileName()); + auto resultsRow = resultsWindow->addPendingResultRow(modelToBakeURL.fileName(), outputDirectory); // keep a unique_ptr to this baker // and remember the row that represents it in the results table diff --git a/tools/oven/src/ui/ResultsWindow.cpp b/tools/oven/src/ui/ResultsWindow.cpp index 36b7e83177..387e3698b8 100644 --- a/tools/oven/src/ui/ResultsWindow.cpp +++ b/tools/oven/src/ui/ResultsWindow.cpp @@ -9,7 +9,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include +#include #include #include #include @@ -50,12 +50,53 @@ void ResultsWindow::setupUI() { _resultsTable->horizontalHeader()->resizeSection(0, 0.25 * FIXED_WINDOW_WIDTH); _resultsTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::ResizeToContents); + // make sure we hear about cell clicks so that we can show the output directory for the given row + connect(_resultsTable, &QTableWidget::cellClicked, this, &ResultsWindow::handleCellClicked); + // set the layout of this widget to the created layout setLayout(resultsLayout); } +void revealDirectory(const QDir& dirToReveal) { -int ResultsWindow::addPendingResultRow(const QString& fileName) { + // See http://stackoverflow.com/questions/3490336/how-to-reveal-in-finder-or-show-in-explorer-with-qt + // for details + + // Mac, Windows support folder or file. +#if defined(Q_OS_WIN) + const QString explorer = Environment::systemEnvironment().searchInPath(QLatin1String("explorer.exe")); + if (explorer.isEmpty()) { + QMessageBox::warning(parent, + tr("Launching Windows Explorer failed"), + tr("Could not find explorer.exe in path to launch Windows Explorer.")); + return; + } + + QString param = QLatin1String("/select,") + QDir::toNativeSeparators(dirToReveal.absolutePath()); + + QString command = explorer + " " + param; + QProcess::startDetached(command); + +#elif defined(Q_OS_MAC) + QStringList scriptArgs; + scriptArgs << QLatin1String("-e") + << QString::fromLatin1("tell application \"Finder\" to reveal POSIX file \"%1\"").arg(dirToReveal.absolutePath()); + QProcess::execute(QLatin1String("/usr/bin/osascript"), scriptArgs); + + scriptArgs.clear(); + scriptArgs << QLatin1String("-e") << QLatin1String("tell application \"Finder\" to activate"); + QProcess::execute("/usr/bin/osascript", scriptArgs); +#endif + +} + +void ResultsWindow::handleCellClicked(int rowIndex, int columnIndex) { + // use revealDirectory to show the output directory for this row + revealDirectory(_outputDirectories[rowIndex]); +} + + +int ResultsWindow::addPendingResultRow(const QString& fileName, const QDir& outputDirectory) { int rowIndex = _resultsTable->rowCount(); _resultsTable->insertRow(rowIndex); @@ -69,6 +110,9 @@ int ResultsWindow::addPendingResultRow(const QString& fileName) { statusItem->setFlags(statusItem->flags() & ~Qt::ItemIsEditable); _resultsTable->setItem(rowIndex, 1, statusItem); + // push an output directory to our list so we can show it if the user clicks on this bake in the results table + _outputDirectories.push_back(outputDirectory); + return rowIndex; } diff --git a/tools/oven/src/ui/ResultsWindow.h b/tools/oven/src/ui/ResultsWindow.h index b7e380a631..ae7bb0e327 100644 --- a/tools/oven/src/ui/ResultsWindow.h +++ b/tools/oven/src/ui/ResultsWindow.h @@ -12,6 +12,7 @@ #ifndef hifi_ResultsWindow_h #define hifi_ResultsWindow_h +#include #include class QTableWidget; @@ -24,11 +25,15 @@ public: void setupUI(); - int addPendingResultRow(const QString& fileName); + int addPendingResultRow(const QString& fileName, const QDir& outputDirectory); void changeStatusForRow(int rowIndex, const QString& result); +private slots: + void handleCellClicked(int rowIndex, int columnIndex); + private: QTableWidget* _resultsTable { nullptr }; + QList _outputDirectories; }; #endif // hifi_ResultsWindow_h From 7bc69e6eda61c0a8ea57b123e0fc2d72a0a73b1d Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 17 Apr 2017 14:11:46 -0700 Subject: [PATCH 42/71] add skybox baking to DomainBaker --- libraries/model-baking/src/TextureBaker.h | 2 + tools/oven/src/DomainBaker.cpp | 165 ++++++++++++++++++---- tools/oven/src/DomainBaker.h | 13 +- tools/oven/src/ui/DomainBakeWidget.cpp | 6 +- tools/oven/src/ui/DomainBakeWidget.h | 2 +- 5 files changed, 152 insertions(+), 36 deletions(-) diff --git a/libraries/model-baking/src/TextureBaker.h b/libraries/model-baking/src/TextureBaker.h index c0cb8a377a..65623c96c4 100644 --- a/libraries/model-baking/src/TextureBaker.h +++ b/libraries/model-baking/src/TextureBaker.h @@ -32,6 +32,8 @@ public: const QUrl& getTextureURL() const { return _textureURL; } + const QString& getDestinationFilePath() const { return _destinationFilePath; } + public slots: virtual void bake() override; diff --git a/tools/oven/src/DomainBaker.cpp b/tools/oven/src/DomainBaker.cpp index a8fd464790..f869e1cded 100644 --- a/tools/oven/src/DomainBaker.cpp +++ b/tools/oven/src/DomainBaker.cpp @@ -134,7 +134,9 @@ void DomainBaker::loadLocalFile() { } } -static const QString ENTITY_MODEL_URL_KEY = "modelURL"; +const QString ENTITY_MODEL_URL_KEY = "modelURL"; +const QString ENTITY_SKYBOX_KEY = "skybox"; +const QString ENTITY_SKYBOX_URL_KEY = "url"; void DomainBaker::enumerateEntities() { qDebug() << "Enumerating" << _entities.size() << "entities from domain"; @@ -144,7 +146,7 @@ void DomainBaker::enumerateEntities() { if (it->isObject()) { auto entity = it->toObject(); - // check if this is an entity with a model URL + // check if this is an entity with a model URL or is a skybox texture if (entity.contains(ENTITY_MODEL_URL_KEY)) { // grab a QUrl for the model URL QUrl modelURL { entity[ENTITY_MODEL_URL_KEY].toString() }; @@ -160,7 +162,7 @@ void DomainBaker::enumerateEntities() { modelURL = modelURL.adjusted(QUrl::RemoveQuery | QUrl::RemoveFragment); // setup an FBXBaker for this URL, as long as we don't already have one - if (!_bakers.contains(modelURL)) { + if (!_modelBakers.contains(modelURL)) { QSharedPointer baker { new FBXBaker(modelURL, _contentOutputPath, []() -> QThread* { return qApp->getNextWorkerThread(); @@ -168,31 +170,76 @@ void DomainBaker::enumerateEntities() { }; // make sure our handler is called when the baker is done - connect(baker.data(), &Baker::finished, this, &DomainBaker::handleFinishedBaker); + connect(baker.data(), &Baker::finished, this, &DomainBaker::handleFinishedModelBaker); // insert it into our bakers hash so we hold a strong pointer to it - _bakers.insert(modelURL, baker); + _modelBakers.insert(modelURL, baker); // move the baker to the baker thread // and kickoff the bake baker->moveToThread(qApp->getFBXBakerThread()); QMetaObject::invokeMethod(baker.data(), "bake"); + + // keep track of the total number of baking entities + ++_totalNumberOfSubBakes; } // add this QJsonValueRef to our multi hash so that we can easily re-write // the model URL to the baked version once the baker is complete _entitiesNeedingRewrite.insert(modelURL, *it); - ++_totalNumberOfEntities; + } + } else if (entity.contains(ENTITY_SKYBOX_KEY) + && entity[ENTITY_SKYBOX_KEY].toObject().contains(ENTITY_SKYBOX_URL_KEY)) { + // we have a URL to a skybox, grab it + QUrl skyboxURL { entity[ENTITY_SKYBOX_KEY].toObject()[ENTITY_SKYBOX_URL_KEY].toString() }; + + auto skyboxFileName = skyboxURL.fileName(); + + static const QStringList BAKEABLE_SKYBOX_EXTENSIONS { ".jpg" }; + auto completeLowerExtension = skyboxFileName.mid(skyboxFileName.indexOf('.')).toLower(); + + if (BAKEABLE_SKYBOX_EXTENSIONS.contains(completeLowerExtension)) { + // grab a clean version of the URL without a query or fragment + skyboxURL = skyboxURL.adjusted(QUrl::RemoveQuery | QUrl::RemoveFragment); + + // setup a texture baker for this URL, as long as we aren't baking a skybox already + if (!_skyboxBakers.contains(skyboxURL)) { + // figure out the path for this baked skybox + auto skyboxFileName = skyboxURL.fileName(); + auto bakedSkyboxFileName = skyboxFileName.left(skyboxFileName.indexOf('.')) + BAKED_TEXTURE_EXT; + auto bakedTextureDestination = QDir(_contentOutputPath).absoluteFilePath(bakedSkyboxFileName); + + QSharedPointer skyboxBaker { + new TextureBaker(skyboxURL, gpu::CUBE_TEXTURE, bakedTextureDestination) + }; + + // make sure our handler is called when the skybox baker is done + connect(skyboxBaker.data(), &TextureBaker::finished, this, &DomainBaker::handleFinishedSkyboxBaker); + + // insert it into our bakers hash so we hold a strong pointer to it + _skyboxBakers.insert(skyboxURL, skyboxBaker); + + // move the baker to a worker thread and kickoff the bake + skyboxBaker->moveToThread(qApp->getNextWorkerThread()); + QMetaObject::invokeMethod(skyboxBaker.data(), "bake"); + + // keep track of the total number of baking entities + ++_totalNumberOfSubBakes; + } + + // add this QJsonValueRef to our multi hash so that it can re-write the skybox URL + // to the baked version once the baker is complete + _entitiesNeedingRewrite.insert(skyboxURL, *it); } } } } // emit progress now to say we're just starting - emit bakeProgress(0, _totalNumberOfEntities); + emit bakeProgress(0, _totalNumberOfSubBakes); } -void DomainBaker::handleFinishedBaker() { +void DomainBaker::handleFinishedModelBaker() { auto baker = qobject_cast(sender()); if (baker) { @@ -226,27 +273,27 @@ void DomainBaker::handleFinishedBaker() { const QString ENTITY_ANIMATION_KEY = "animation"; const QString ENTITIY_ANIMATION_URL_KEY = "url"; - if (entity.contains(ENTITY_ANIMATION_KEY) - && entity[ENTITY_ANIMATION_KEY].toObject().contains(ENTITIY_ANIMATION_URL_KEY)) { - auto animationValue = entity[ENTITY_ANIMATION_KEY]; - auto animationObject = animationValue.toObject(); + if (entity.contains(ENTITY_ANIMATION_KEY)) { + auto animationObject = entity[ENTITY_ANIMATION_KEY].toObject(); - // grab the old animation URL - QUrl oldAnimationURL { animationObject[ENTITIY_ANIMATION_URL_KEY].toString() }; + if (animationObject.contains(ENTITIY_ANIMATION_URL_KEY)) { + // grab the old animation URL + QUrl oldAnimationURL { animationObject[ENTITIY_ANIMATION_URL_KEY].toString() }; - // check if its stripped down version matches our stripped down model URL - if (oldAnimationURL.matches(oldModelURL, QUrl::RemoveUserInfo | QUrl::RemoveQuery | QUrl::RemoveFragment)) { - // the animation URL matched the old model URL, so make the animation URL point to the baked FBX - // with its original query and fragment - auto newAnimationURL = _destinationPath.resolved(baker->getBakedFBXRelativePath()); - newAnimationURL.setQuery(oldAnimationURL.query()); - newAnimationURL.setFragment(oldAnimationURL.fragment()); - newAnimationURL.setUserInfo(oldAnimationURL.userInfo()); + // check if its stripped down version matches our stripped down model URL + if (oldAnimationURL.matches(oldModelURL, QUrl::RemoveUserInfo | QUrl::RemoveQuery | QUrl::RemoveFragment)) { + // the animation URL matched the old model URL, so make the animation URL point to the baked FBX + // with its original query and fragment + auto newAnimationURL = _destinationPath.resolved(baker->getBakedFBXRelativePath()); + newAnimationURL.setQuery(oldAnimationURL.query()); + newAnimationURL.setFragment(oldAnimationURL.fragment()); + newAnimationURL.setUserInfo(oldAnimationURL.userInfo()); - animationObject[ENTITIY_ANIMATION_URL_KEY] = newAnimationURL.toString(); + animationObject[ENTITIY_ANIMATION_URL_KEY] = newAnimationURL.toString(); - // replace the animation object referenced by the QJsonValueRef - animationValue = animationObject; + // replace the animation object in the entity object + entity[ENTITY_ANIMATION_KEY] = animationObject; + } } } @@ -255,7 +302,7 @@ void DomainBaker::handleFinishedBaker() { } } else { // this model failed to bake - this doesn't fail the entire bake but we need to add - // the errors from the model to our errors + // the errors from the model to our warnings _warningList << baker->getErrors(); } @@ -263,16 +310,78 @@ void DomainBaker::handleFinishedBaker() { _entitiesNeedingRewrite.remove(baker->getFBXUrl()); // drop our shared pointer to this baker so that it gets cleaned up - _bakers.remove(baker->getFBXUrl()); + _modelBakers.remove(baker->getFBXUrl()); // emit progress to tell listeners how many models we have baked - emit bakeProgress(_totalNumberOfEntities - _entitiesNeedingRewrite.keys().size(), _totalNumberOfEntities); + emit bakeProgress(++_completedSubBakes, _totalNumberOfSubBakes); // check if this was the last model we needed to re-write and if we are done now checkIfRewritingComplete(); } } +void DomainBaker::handleFinishedSkyboxBaker() { + auto baker = qobject_cast(sender()); + + if (baker) { + if (!baker->hasErrors()) { + // this FBXBaker is done and everything went according to plan + qDebug() << "Re-writing entity references to" << baker->getTextureURL(); + + // enumerate the QJsonRef values for the URL of this FBX from our multi hash of + // entity objects needing a URL re-write + for (QJsonValueRef entityValue : _entitiesNeedingRewrite.values(baker->getTextureURL())) { + // convert the entity QJsonValueRef to a QJsonObject so we can modify its URL + auto entity = entityValue.toObject(); + + if (entity.contains(ENTITY_SKYBOX_KEY)) { + auto skyboxObject = entity[ENTITY_SKYBOX_KEY].toObject(); + + if (skyboxObject.contains(ENTITY_SKYBOX_URL_KEY)) { + // grab the old skybox URL + QUrl oldSkyboxURL { skyboxObject[ENTITY_SKYBOX_URL_KEY].toString() }; + + // the animation URL matched the old model URL, so make the animation URL point to the baked FBX + // with its original query and fragment + + auto bakedSkyboxFileName = QFileInfo(baker->getDestinationFilePath()).fileName(); + + auto newSkyboxURL = _destinationPath.resolved(bakedSkyboxFileName); + newSkyboxURL.setQuery(oldSkyboxURL.query()); + newSkyboxURL.setFragment(oldSkyboxURL.fragment()); + newSkyboxURL.setUserInfo(oldSkyboxURL.userInfo()); + + skyboxObject[ENTITY_SKYBOX_URL_KEY] = newSkyboxURL.toString(); + + // replace the skybox object referenced by the entity object + entity[ENTITY_SKYBOX_KEY] = skyboxObject; + } + } + + // replace our temp object with the value referenced by our QJsonValueRef + entityValue = entity; + } + } else { + // this skybox failed to bake - this doesn't fail the entire bake but we need to add the errors from + // the model to our warnings + _warningList << baker->getWarnings(); + } + + // remove the baked URL from the multi hash of entities needing a re-write + _entitiesNeedingRewrite.remove(baker->getTextureURL()); + + // drop our shared pointer to this baker so that it gets cleaned up + _skyboxBakers.remove(baker->getTextureURL()); + + // emit progress to tell listeners how many models we have baked + emit bakeProgress(++_completedSubBakes, _totalNumberOfSubBakes); + + // check if this was the last model we needed to re-write and if we are done now + checkIfRewritingComplete(); + } + +} + void DomainBaker::checkIfRewritingComplete() { if (_entitiesNeedingRewrite.isEmpty()) { writeNewEntitiesFile(); diff --git a/tools/oven/src/DomainBaker.h b/tools/oven/src/DomainBaker.h index ddcb3cd006..1a100d2184 100644 --- a/tools/oven/src/DomainBaker.h +++ b/tools/oven/src/DomainBaker.h @@ -19,6 +19,7 @@ #include #include +#include class DomainBaker : public Baker { Q_OBJECT @@ -31,11 +32,12 @@ public: signals: void allModelsFinished(); - void bakeProgress(int modelsBaked, int modelsTotal); + void bakeProgress(int baked, int total); private slots: virtual void bake() override; - void handleFinishedBaker(); + void handleFinishedModelBaker(); + void handleFinishedSkyboxBaker(); private: void setupOutputFolder(); @@ -53,10 +55,13 @@ private: QJsonArray _entities; - QHash> _bakers; + QHash> _modelBakers; + QHash> _skyboxBakers; + QMultiHash _entitiesNeedingRewrite; - int _totalNumberOfEntities { 0 }; + int _totalNumberOfSubBakes { 0 }; + int _completedSubBakes { 0 }; }; #endif // hifi_DomainBaker_h diff --git a/tools/oven/src/ui/DomainBakeWidget.cpp b/tools/oven/src/ui/DomainBakeWidget.cpp index 34ae0680af..7a8c5fc6a2 100644 --- a/tools/oven/src/ui/DomainBakeWidget.cpp +++ b/tools/oven/src/ui/DomainBakeWidget.cpp @@ -233,7 +233,7 @@ void DomainBakeWidget::bakeButtonClicked() { } } -void DomainBakeWidget::handleBakerProgress(int modelsBaked, int modelsTotal) { +void DomainBakeWidget::handleBakerProgress(int baked, int total) { if (auto baker = qobject_cast(sender())) { // add the results of this bake to the results window auto it = std::find_if(_bakers.begin(), _bakers.end(), [baker](const BakerRowPair& value) { @@ -244,9 +244,9 @@ void DomainBakeWidget::handleBakerProgress(int modelsBaked, int modelsTotal) { auto resultRow = it->second; auto resultsWindow = qApp->getMainWindow()->showResultsWindow(); - int percentage = roundf(float(modelsBaked) / float(modelsTotal) * 100.0f); + int percentage = roundf(float(baked) / float(total) * 100.0f); - auto statusString = QString("Baking - %1 of %2 models baked - %3%").arg(modelsBaked).arg(modelsTotal).arg(percentage); + auto statusString = QString("Baking - %1 of %2 - %3%").arg(baked).arg(total).arg(percentage); resultsWindow->changeStatusForRow(resultRow, statusString); } } diff --git a/tools/oven/src/ui/DomainBakeWidget.h b/tools/oven/src/ui/DomainBakeWidget.h index ea9e6f7049..16b0c76c11 100644 --- a/tools/oven/src/ui/DomainBakeWidget.h +++ b/tools/oven/src/ui/DomainBakeWidget.h @@ -34,7 +34,7 @@ private slots: void outputDirectoryChanged(const QString& newDirectory); - void handleBakerProgress(int modelsBaked, int modelsTotal); + void handleBakerProgress(int baked, int total); void handleFinishedBaker(); private: From 25d24c445db9373323f37ecedb8992fc0876709f Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 17 Apr 2017 14:57:11 -0700 Subject: [PATCH 43/71] handle ambient skybox textures for zones in domain bake --- tools/oven/src/DomainBaker.cpp | 152 +++++++++++++++++++++------------ tools/oven/src/DomainBaker.h | 3 + 2 files changed, 100 insertions(+), 55 deletions(-) diff --git a/tools/oven/src/DomainBaker.cpp b/tools/oven/src/DomainBaker.cpp index f869e1cded..01dbaca3e6 100644 --- a/tools/oven/src/DomainBaker.cpp +++ b/tools/oven/src/DomainBaker.cpp @@ -137,6 +137,8 @@ void DomainBaker::loadLocalFile() { const QString ENTITY_MODEL_URL_KEY = "modelURL"; const QString ENTITY_SKYBOX_KEY = "skybox"; const QString ENTITY_SKYBOX_URL_KEY = "url"; +const QString ENTITY_KEYLIGHT_KEY = "keyLight"; +const QString ENTITY_KEYLIGHT_AMBIENT_URL_KEY = "ambientURL"; void DomainBaker::enumerateEntities() { qDebug() << "Enumerating" << _entities.size() << "entities from domain"; @@ -188,48 +190,28 @@ void DomainBaker::enumerateEntities() { // the model URL to the baked version once the baker is complete _entitiesNeedingRewrite.insert(modelURL, *it); } - } else if (entity.contains(ENTITY_SKYBOX_KEY) - && entity[ENTITY_SKYBOX_KEY].toObject().contains(ENTITY_SKYBOX_URL_KEY)) { - // we have a URL to a skybox, grab it - QUrl skyboxURL { entity[ENTITY_SKYBOX_KEY].toObject()[ENTITY_SKYBOX_URL_KEY].toString() }; + } else { + // We check now to see if we have either a texture for a skybox or a keylight, or both. + if (entity.contains(ENTITY_SKYBOX_KEY)) { + auto skyboxObject = entity[ENTITY_SKYBOX_KEY].toObject(); + if (skyboxObject.contains(ENTITY_SKYBOX_URL_KEY)) { + // we have a URL to a skybox, grab it + QUrl skyboxURL { skyboxObject[ENTITY_SKYBOX_URL_KEY].toString() }; - auto skyboxFileName = skyboxURL.fileName(); - - static const QStringList BAKEABLE_SKYBOX_EXTENSIONS { ".jpg" }; - auto completeLowerExtension = skyboxFileName.mid(skyboxFileName.indexOf('.')).toLower(); - - if (BAKEABLE_SKYBOX_EXTENSIONS.contains(completeLowerExtension)) { - // grab a clean version of the URL without a query or fragment - skyboxURL = skyboxURL.adjusted(QUrl::RemoveQuery | QUrl::RemoveFragment); - - // setup a texture baker for this URL, as long as we aren't baking a skybox already - if (!_skyboxBakers.contains(skyboxURL)) { - // figure out the path for this baked skybox - auto skyboxFileName = skyboxURL.fileName(); - auto bakedSkyboxFileName = skyboxFileName.left(skyboxFileName.indexOf('.')) + BAKED_TEXTURE_EXT; - auto bakedTextureDestination = QDir(_contentOutputPath).absoluteFilePath(bakedSkyboxFileName); - - QSharedPointer skyboxBaker { - new TextureBaker(skyboxURL, gpu::CUBE_TEXTURE, bakedTextureDestination) - }; - - // make sure our handler is called when the skybox baker is done - connect(skyboxBaker.data(), &TextureBaker::finished, this, &DomainBaker::handleFinishedSkyboxBaker); - - // insert it into our bakers hash so we hold a strong pointer to it - _skyboxBakers.insert(skyboxURL, skyboxBaker); - - // move the baker to a worker thread and kickoff the bake - skyboxBaker->moveToThread(qApp->getNextWorkerThread()); - QMetaObject::invokeMethod(skyboxBaker.data(), "bake"); - - // keep track of the total number of baking entities - ++_totalNumberOfSubBakes; + // setup a bake of the skybox + bakeSkybox(skyboxURL, *it); } + } - // add this QJsonValueRef to our multi hash so that it can re-write the skybox URL - // to the baked version once the baker is complete - _entitiesNeedingRewrite.insert(skyboxURL, *it); + if (entity.contains(ENTITY_KEYLIGHT_KEY)) { + auto keyLightObject = entity[ENTITY_KEYLIGHT_KEY].toObject(); + if (keyLightObject.contains(ENTITY_KEYLIGHT_AMBIENT_URL_KEY)) { + // we have a URL to a skybox, grab it + QUrl skyboxURL { keyLightObject[ENTITY_KEYLIGHT_AMBIENT_URL_KEY].toString() }; + + // setup a bake of the skybox + bakeSkybox(skyboxURL, *it); + } } } } @@ -239,6 +221,48 @@ void DomainBaker::enumerateEntities() { emit bakeProgress(0, _totalNumberOfSubBakes); } +void DomainBaker::bakeSkybox(QUrl skyboxURL, QJsonValueRef entity) { + + auto skyboxFileName = skyboxURL.fileName(); + + static const QStringList BAKEABLE_SKYBOX_EXTENSIONS { ".jpg" }; + auto completeLowerExtension = skyboxFileName.mid(skyboxFileName.indexOf('.')).toLower(); + + if (BAKEABLE_SKYBOX_EXTENSIONS.contains(completeLowerExtension)) { + // grab a clean version of the URL without a query or fragment + skyboxURL = skyboxURL.adjusted(QUrl::RemoveQuery | QUrl::RemoveFragment); + + // setup a texture baker for this URL, as long as we aren't baking a skybox already + if (!_skyboxBakers.contains(skyboxURL)) { + // figure out the path for this baked skybox + auto skyboxFileName = skyboxURL.fileName(); + auto bakedSkyboxFileName = skyboxFileName.left(skyboxFileName.indexOf('.')) + BAKED_TEXTURE_EXT; + auto bakedTextureDestination = QDir(_contentOutputPath).absoluteFilePath(bakedSkyboxFileName); + + QSharedPointer skyboxBaker { + new TextureBaker(skyboxURL, gpu::CUBE_TEXTURE, bakedTextureDestination) + }; + + // make sure our handler is called when the skybox baker is done + connect(skyboxBaker.data(), &TextureBaker::finished, this, &DomainBaker::handleFinishedSkyboxBaker); + + // insert it into our bakers hash so we hold a strong pointer to it + _skyboxBakers.insert(skyboxURL, skyboxBaker); + + // move the baker to a worker thread and kickoff the bake + skyboxBaker->moveToThread(qApp->getNextWorkerThread()); + QMetaObject::invokeMethod(skyboxBaker.data(), "bake"); + + // keep track of the total number of baking entities + ++_totalNumberOfSubBakes; + } + + // add this QJsonValueRef to our multi hash so that it can re-write the skybox URL + // to the baked version once the baker is complete + _entitiesNeedingRewrite.insert(skyboxURL, entity); + } +} + void DomainBaker::handleFinishedModelBaker() { auto baker = qobject_cast(sender()); @@ -281,7 +305,7 @@ void DomainBaker::handleFinishedModelBaker() { QUrl oldAnimationURL { animationObject[ENTITIY_ANIMATION_URL_KEY].toString() }; // check if its stripped down version matches our stripped down model URL - if (oldAnimationURL.matches(oldModelURL, QUrl::RemoveUserInfo | QUrl::RemoveQuery | QUrl::RemoveFragment)) { + if (oldAnimationURL.matches(oldModelURL, QUrl::RemoveQuery | QUrl::RemoveFragment)) { // the animation URL matched the old model URL, so make the animation URL point to the baked FBX // with its original query and fragment auto newAnimationURL = _destinationPath.resolved(baker->getBakedFBXRelativePath()); @@ -338,23 +362,21 @@ void DomainBaker::handleFinishedSkyboxBaker() { auto skyboxObject = entity[ENTITY_SKYBOX_KEY].toObject(); if (skyboxObject.contains(ENTITY_SKYBOX_URL_KEY)) { - // grab the old skybox URL - QUrl oldSkyboxURL { skyboxObject[ENTITY_SKYBOX_URL_KEY].toString() }; + if (rewriteSkyboxURL(skyboxObject[ENTITY_SKYBOX_URL_KEY], baker)) { + // we re-wrote the URL, replace the skybox object referenced by the entity object + entity[ENTITY_SKYBOX_KEY] = skyboxObject; + } + } + } - // the animation URL matched the old model URL, so make the animation URL point to the baked FBX - // with its original query and fragment + if (entity.contains(ENTITY_KEYLIGHT_KEY)) { + auto ambientObject = entity[ENTITY_KEYLIGHT_KEY].toObject(); - auto bakedSkyboxFileName = QFileInfo(baker->getDestinationFilePath()).fileName(); - - auto newSkyboxURL = _destinationPath.resolved(bakedSkyboxFileName); - newSkyboxURL.setQuery(oldSkyboxURL.query()); - newSkyboxURL.setFragment(oldSkyboxURL.fragment()); - newSkyboxURL.setUserInfo(oldSkyboxURL.userInfo()); - - skyboxObject[ENTITY_SKYBOX_URL_KEY] = newSkyboxURL.toString(); - - // replace the skybox object referenced by the entity object - entity[ENTITY_SKYBOX_KEY] = skyboxObject; + if (ambientObject.contains(ENTITY_KEYLIGHT_AMBIENT_URL_KEY)) { + if (rewriteSkyboxURL(ambientObject[ENTITY_KEYLIGHT_AMBIENT_URL_KEY], baker)) { + // we re-wrote the URL, replace the ambient object referenced by the entity object + entity[ENTITY_KEYLIGHT_KEY] = ambientObject; + } } } @@ -379,7 +401,27 @@ void DomainBaker::handleFinishedSkyboxBaker() { // check if this was the last model we needed to re-write and if we are done now checkIfRewritingComplete(); } +} +bool DomainBaker::rewriteSkyboxURL(QJsonValueRef urlValue, TextureBaker* baker) { + // grab the old skybox URL + QUrl oldSkyboxURL { urlValue.toString() }; + + if (oldSkyboxURL.matches(baker->getTextureURL(), QUrl::RemoveQuery | QUrl::RemoveFragment)) { + // change the URL to point to the baked texture with its original query and fragment + auto bakedSkyboxFileName = QFileInfo(baker->getDestinationFilePath()).fileName(); + + auto newSkyboxURL = _destinationPath.resolved(bakedSkyboxFileName); + newSkyboxURL.setQuery(oldSkyboxURL.query()); + newSkyboxURL.setFragment(oldSkyboxURL.fragment()); + newSkyboxURL.setUserInfo(oldSkyboxURL.userInfo()); + + urlValue = newSkyboxURL.toString(); + + return true; + } else { + return false; + } } void DomainBaker::checkIfRewritingComplete() { diff --git a/tools/oven/src/DomainBaker.h b/tools/oven/src/DomainBaker.h index 1a100d2184..54cbb18b06 100644 --- a/tools/oven/src/DomainBaker.h +++ b/tools/oven/src/DomainBaker.h @@ -46,6 +46,9 @@ private: void checkIfRewritingComplete(); void writeNewEntitiesFile(); + void bakeSkybox(QUrl skyboxURL, QJsonValueRef entity); + bool rewriteSkyboxURL(QJsonValueRef urlValue, TextureBaker* baker); + QUrl _localEntitiesFileURL; QString _domainName; QString _baseOutputPath; From 980de595a9b62eb92bbc026de46055cda1ed064c Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 17 Apr 2017 16:02:49 -0700 Subject: [PATCH 44/71] handle skybox baking from oven menu --- libraries/model-baking/src/FBXBaker.cpp | 6 +- libraries/model-baking/src/FBXBaker.h | 2 +- libraries/model-baking/src/TextureBaker.cpp | 10 +- libraries/model-baking/src/TextureBaker.h | 10 +- tools/oven/src/DomainBaker.cpp | 14 +- tools/oven/src/ui/ModelBakeWidget.cpp | 26 +-- tools/oven/src/ui/ModelBakeWidget.h | 3 - tools/oven/src/ui/ModesWidget.cpp | 29 ++- tools/oven/src/ui/ModesWidget.h | 1 + tools/oven/src/ui/SkyboxBakeWidget.cpp | 232 ++++++++++++++++++++ tools/oven/src/ui/SkyboxBakeWidget.h | 53 +++++ 11 files changed, 335 insertions(+), 51 deletions(-) create mode 100644 tools/oven/src/ui/SkyboxBakeWidget.cpp create mode 100644 tools/oven/src/ui/SkyboxBakeWidget.h diff --git a/libraries/model-baking/src/FBXBaker.cpp b/libraries/model-baking/src/FBXBaker.cpp index 5b47f5023b..fe48cc8372 100644 --- a/libraries/model-baking/src/FBXBaker.cpp +++ b/libraries/model-baking/src/FBXBaker.cpp @@ -378,7 +378,7 @@ void FBXBaker::rewriteAndBakeSceneTextures() { _unbakedTextures.insert(urlToTexture, bakedTextureFileName); // bake this texture asynchronously - bakeTexture(urlToTexture, textureType, bakedTextureFilePath); + bakeTexture(urlToTexture, textureType, _uniqueOutputPath + BAKED_OUTPUT_SUBFOLDER); } } } @@ -391,10 +391,10 @@ void FBXBaker::rewriteAndBakeSceneTextures() { } } -void FBXBaker::bakeTexture(const QUrl& textureURL, gpu::TextureType textureType, const QString& destinationFilePath) { +void FBXBaker::bakeTexture(const QUrl& textureURL, gpu::TextureType textureType, const QDir& outputDir) { // start a bake for this texture and add it to our list to keep track of QSharedPointer bakingTexture { - new TextureBaker(textureURL, textureType, destinationFilePath), + new TextureBaker(textureURL, textureType, outputDir), &TextureBaker::deleteLater }; diff --git a/libraries/model-baking/src/FBXBaker.h b/libraries/model-baking/src/FBXBaker.h index 8ad42d6de8..903720a0a9 100644 --- a/libraries/model-baking/src/FBXBaker.h +++ b/libraries/model-baking/src/FBXBaker.h @@ -74,7 +74,7 @@ private: QString createBakedTextureFileName(const QFileInfo& textureFileInfo); QUrl getTextureURL(const QFileInfo& textureFileInfo, fbxsdk::FbxFileTexture* fileTexture); - void bakeTexture(const QUrl& textureURL, gpu::TextureType textureType, const QString& destinationFilePath); + void bakeTexture(const QUrl& textureURL, gpu::TextureType textureType, const QDir& outputDir); QString pathToCopyOfOriginal() const; diff --git a/libraries/model-baking/src/TextureBaker.cpp b/libraries/model-baking/src/TextureBaker.cpp index 0ebde00bde..f0136fb454 100644 --- a/libraries/model-baking/src/TextureBaker.cpp +++ b/libraries/model-baking/src/TextureBaker.cpp @@ -24,12 +24,14 @@ const QString BAKED_TEXTURE_EXT = ".ktx"; -TextureBaker::TextureBaker(const QUrl& textureURL, gpu::TextureType textureType, const QString& destinationFilePath) : +TextureBaker::TextureBaker(const QUrl& textureURL, gpu::TextureType textureType, const QDir& outputDirectory) : _textureURL(textureURL), _textureType(textureType), - _destinationFilePath(destinationFilePath) + _outputDirectory(outputDirectory) { - + // figure out the baked texture filename + auto originalFilename = textureURL.fileName(); + _bakedTextureFileName = originalFilename.left(originalFilename.indexOf('.')) + BAKED_TEXTURE_EXT; } void TextureBaker::bake() { @@ -117,7 +119,7 @@ void TextureBaker::processTexture() { const size_t length = memKTX->_storage->size(); // attempt to write the baked texture to the destination file path - QFile bakedTextureFile { _destinationFilePath }; + QFile bakedTextureFile { _outputDirectory.absoluteFilePath(_bakedTextureFileName) }; if (!bakedTextureFile.open(QIODevice::WriteOnly) || bakedTextureFile.write(data, length) == -1) { handleError("Could not write baked texture for " + _textureURL.toString()); diff --git a/libraries/model-baking/src/TextureBaker.h b/libraries/model-baking/src/TextureBaker.h index 65623c96c4..7a6d1d404b 100644 --- a/libraries/model-baking/src/TextureBaker.h +++ b/libraries/model-baking/src/TextureBaker.h @@ -26,13 +26,14 @@ class TextureBaker : public Baker { Q_OBJECT public: - TextureBaker(const QUrl& textureURL, gpu::TextureType textureType, const QString& destinationFilePath); + TextureBaker(const QUrl& textureURL, gpu::TextureType textureType, const QDir& outputDirectory); const QByteArray& getOriginalTexture() const { return _originalTexture; } - const QUrl& getTextureURL() const { return _textureURL; } + QUrl getTextureURL() const { return _textureURL; } - const QString& getDestinationFilePath() const { return _destinationFilePath; } + QString getDestinationFilePath() const { return _outputDirectory.absoluteFilePath(_bakedTextureFileName); } + QString getBakedTextureFileName() const { return _bakedTextureFileName; } public slots: virtual void bake() override; @@ -51,7 +52,8 @@ private: QByteArray _originalTexture; gpu::TextureType _textureType; - QString _destinationFilePath; + QDir _outputDirectory; + QString _bakedTextureFileName; }; #endif // hifi_TextureBaker_h diff --git a/tools/oven/src/DomainBaker.cpp b/tools/oven/src/DomainBaker.cpp index 01dbaca3e6..eea73bb1b6 100644 --- a/tools/oven/src/DomainBaker.cpp +++ b/tools/oven/src/DomainBaker.cpp @@ -225,7 +225,9 @@ void DomainBaker::bakeSkybox(QUrl skyboxURL, QJsonValueRef entity) { auto skyboxFileName = skyboxURL.fileName(); - static const QStringList BAKEABLE_SKYBOX_EXTENSIONS { ".jpg" }; + static const QStringList BAKEABLE_SKYBOX_EXTENSIONS { + ".jpg", ".png", ".gif", ".bmp", ".pbm", ".pgm", ".ppm", ".xbm", ".xpm", ".svg" + }; auto completeLowerExtension = skyboxFileName.mid(skyboxFileName.indexOf('.')).toLower(); if (BAKEABLE_SKYBOX_EXTENSIONS.contains(completeLowerExtension)) { @@ -234,13 +236,10 @@ void DomainBaker::bakeSkybox(QUrl skyboxURL, QJsonValueRef entity) { // setup a texture baker for this URL, as long as we aren't baking a skybox already if (!_skyboxBakers.contains(skyboxURL)) { - // figure out the path for this baked skybox - auto skyboxFileName = skyboxURL.fileName(); - auto bakedSkyboxFileName = skyboxFileName.left(skyboxFileName.indexOf('.')) + BAKED_TEXTURE_EXT; - auto bakedTextureDestination = QDir(_contentOutputPath).absoluteFilePath(bakedSkyboxFileName); + // setup a baker for this skybox QSharedPointer skyboxBaker { - new TextureBaker(skyboxURL, gpu::CUBE_TEXTURE, bakedTextureDestination) + new TextureBaker(skyboxURL, gpu::CUBE_TEXTURE, _contentOutputPath) }; // make sure our handler is called when the skybox baker is done @@ -409,9 +408,8 @@ bool DomainBaker::rewriteSkyboxURL(QJsonValueRef urlValue, TextureBaker* baker) if (oldSkyboxURL.matches(baker->getTextureURL(), QUrl::RemoveQuery | QUrl::RemoveFragment)) { // change the URL to point to the baked texture with its original query and fragment - auto bakedSkyboxFileName = QFileInfo(baker->getDestinationFilePath()).fileName(); - auto newSkyboxURL = _destinationPath.resolved(bakedSkyboxFileName); + auto newSkyboxURL = _destinationPath.resolved(baker->getBakedTextureFileName()); newSkyboxURL.setQuery(oldSkyboxURL.query()); newSkyboxURL.setFragment(oldSkyboxURL.fragment()); newSkyboxURL.setUserInfo(oldSkyboxURL.userInfo()); diff --git a/tools/oven/src/ui/ModelBakeWidget.cpp b/tools/oven/src/ui/ModelBakeWidget.cpp index f5204020da..f87210dbee 100644 --- a/tools/oven/src/ui/ModelBakeWidget.cpp +++ b/tools/oven/src/ui/ModelBakeWidget.cpp @@ -25,24 +25,17 @@ #include "ModelBakeWidget.h" -static const QString EXPORT_DIR_SETTING_KEY = "model_export_directory"; -static const QString MODEL_START_DIR_SETTING_KEY = "model_search_directory"; +static const auto EXPORT_DIR_SETTING_KEY = "model_export_directory"; +static const auto MODEL_START_DIR_SETTING_KEY = "model_search_directory"; ModelBakeWidget::ModelBakeWidget(QWidget* parent, Qt::WindowFlags flags) : QWidget(parent, flags), _exportDirectory(EXPORT_DIR_SETTING_KEY), - _modelStartDirectory(MODEL_START_DIR_SETTING_KEY), - _bakerThread(new QThread(this)) + _modelStartDirectory(MODEL_START_DIR_SETTING_KEY) { setupUI(); } -ModelBakeWidget::~ModelBakeWidget() { - // before we go down, stop the baker thread and make sure it's done - _bakerThread->quit(); - _bakerThread->wait(); -} - void ModelBakeWidget::setupUI() { // setup a grid layout to hold everything QGridLayout* gridLayout = new QGridLayout; @@ -189,13 +182,8 @@ void ModelBakeWidget::bakeButtonClicked() { }, false) }; - // move the baker to the baker thread - baker->moveToThread(_bakerThread); - - // make sure we start the baker thread if it isn't already running - if (!_bakerThread->isRunning()) { - _bakerThread->start(); - } + // move the baker to the FBX baker thread + baker->moveToThread(qApp->getFBXBakerThread()); // invoke the bake method on the baker thread QMetaObject::invokeMethod(baker.get(), "bake"); @@ -216,7 +204,7 @@ void ModelBakeWidget::bakeButtonClicked() { void ModelBakeWidget::handleFinishedBaker() { if (auto baker = qobject_cast(sender())) { // add the results of this bake to the results window - auto it = std::remove_if(_bakers.begin(), _bakers.end(), [baker](const BakerRowPair& value) { + auto it = std::find_if(_bakers.begin(), _bakers.end(), [baker](const BakerRowPair& value) { return value.first.get() == baker; }); @@ -229,6 +217,8 @@ void ModelBakeWidget::handleFinishedBaker() { } else { resultsWindow->changeStatusForRow(resultRow, "Success"); } + + _bakers.erase(it); } } } diff --git a/tools/oven/src/ui/ModelBakeWidget.h b/tools/oven/src/ui/ModelBakeWidget.h index 9b7a2fed20..9a9394c386 100644 --- a/tools/oven/src/ui/ModelBakeWidget.h +++ b/tools/oven/src/ui/ModelBakeWidget.h @@ -26,7 +26,6 @@ class ModelBakeWidget : public QWidget { public: ModelBakeWidget(QWidget* parent = nullptr, Qt::WindowFlags flags = Qt::WindowFlags()); - ~ModelBakeWidget(); private slots: void chooseFileButtonClicked(); @@ -50,8 +49,6 @@ private: Setting::Handle _exportDirectory; Setting::Handle _modelStartDirectory; - - QThread* _bakerThread; }; #endif // hifi_ModelBakeWidget_h diff --git a/tools/oven/src/ui/ModesWidget.cpp b/tools/oven/src/ui/ModesWidget.cpp index 867f89b4c4..624aa949cc 100644 --- a/tools/oven/src/ui/ModesWidget.cpp +++ b/tools/oven/src/ui/ModesWidget.cpp @@ -15,6 +15,7 @@ #include "DomainBakeWidget.h" #include "ModelBakeWidget.h" +#include "SkyboxBakeWidget.h" #include "ModesWidget.h" @@ -28,19 +29,20 @@ void ModesWidget::setupUI() { // setup a horizontal box layout to hold our mode buttons QHBoxLayout* horizontalLayout = new QHBoxLayout; - // add a button for model baking - QPushButton* modelsButton = new QPushButton("Bake Models"); - connect(modelsButton, &QPushButton::clicked, this, &ModesWidget::showModelBakingWidget); - horizontalLayout->addWidget(modelsButton); - // add a button for domain baking QPushButton* domainButton = new QPushButton("Bake Domain"); connect(domainButton, &QPushButton::clicked, this, &ModesWidget::showDomainBakingWidget); horizontalLayout->addWidget(domainButton); - // add a button for texture baking - QPushButton* textureButton = new QPushButton("Bake Textures"); - horizontalLayout->addWidget(textureButton); + // add a button for model baking + QPushButton* modelsButton = new QPushButton("Bake Models"); + connect(modelsButton, &QPushButton::clicked, this, &ModesWidget::showModelBakingWidget); + horizontalLayout->addWidget(modelsButton); + + // add a button for skybox baking + QPushButton* skyboxButton = new QPushButton("Bake Skyboxes"); + connect(skyboxButton, &QPushButton::clicked, this, &ModesWidget::showSkyboxBakingWidget); + horizontalLayout->addWidget(skyboxButton); setLayout(horizontalLayout); } @@ -48,13 +50,20 @@ void ModesWidget::setupUI() { void ModesWidget::showModelBakingWidget() { auto stackedWidget = qobject_cast(parentWidget()); - // add a new widget for making baking to the stack, and switch to it + // add a new widget for model baking to the stack, and switch to it stackedWidget->setCurrentIndex(stackedWidget->addWidget(new ModelBakeWidget)); } void ModesWidget::showDomainBakingWidget() { auto stackedWidget = qobject_cast(parentWidget()); - // add a new widget for making baking to the stack, and switch to it + // add a new widget for domain baking to the stack, and switch to it stackedWidget->setCurrentIndex(stackedWidget->addWidget(new DomainBakeWidget)); } + +void ModesWidget::showSkyboxBakingWidget() { + auto stackedWidget = qobject_cast(parentWidget()); + + // add a new widget for skybox baking to the stack, and switch to it + stackedWidget->setCurrentIndex(stackedWidget->addWidget(new SkyboxBakeWidget)); +} diff --git a/tools/oven/src/ui/ModesWidget.h b/tools/oven/src/ui/ModesWidget.h index e7e239d63e..fd660923f2 100644 --- a/tools/oven/src/ui/ModesWidget.h +++ b/tools/oven/src/ui/ModesWidget.h @@ -22,6 +22,7 @@ public: private slots: void showModelBakingWidget(); void showDomainBakingWidget(); + void showSkyboxBakingWidget(); private: void setupUI(); diff --git a/tools/oven/src/ui/SkyboxBakeWidget.cpp b/tools/oven/src/ui/SkyboxBakeWidget.cpp new file mode 100644 index 0000000000..c6bca5819e --- /dev/null +++ b/tools/oven/src/ui/SkyboxBakeWidget.cpp @@ -0,0 +1,232 @@ +// +// SkyboxBakeWidget.cpp +// tools/oven/src/ui +// +// Created by Stephen Birarda on 4/17/17. +// Copyright 2017 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 +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "../Oven.h" +#include "OvenMainWindow.h" + +#include "SkyboxBakeWidget.h" + +static const auto EXPORT_DIR_SETTING_KEY = "skybox_export_directory"; +static const auto SELECTION_START_DIR_SETTING_KEY = "skybox_search_directory"; + +SkyboxBakeWidget::SkyboxBakeWidget(QWidget* parent, Qt::WindowFlags flags) : + QWidget(parent, flags), + _exportDirectory(EXPORT_DIR_SETTING_KEY), + _selectionStartDirectory(SELECTION_START_DIR_SETTING_KEY) +{ + setupUI(); +} + +void SkyboxBakeWidget::setupUI() { + // setup a grid layout to hold everything + QGridLayout* gridLayout = new QGridLayout; + + int rowIndex = 0; + + // setup a section to choose the file being baked + QLabel* skyboxFileLabel = new QLabel("Skybox File(s)"); + + _selectionLineEdit = new QLineEdit; + _selectionLineEdit->setPlaceholderText("File or URL"); + + QPushButton* chooseFileButton = new QPushButton("Browse..."); + connect(chooseFileButton, &QPushButton::clicked, this, &SkyboxBakeWidget::chooseFileButtonClicked); + + // add the components for the model file picker to the layout + gridLayout->addWidget(skyboxFileLabel, rowIndex, 0); + gridLayout->addWidget(_selectionLineEdit, rowIndex, 1, 1, 3); + gridLayout->addWidget(chooseFileButton, rowIndex, 4); + + // start a new row for next component + ++rowIndex; + + // setup a section to choose the output directory + QLabel* outputDirectoryLabel = new QLabel("Output Directory"); + + _outputDirLineEdit = new QLineEdit; + + // set the current export directory to whatever was last used + _outputDirLineEdit->setText(_exportDirectory.get()); + + // whenever the output directory line edit changes, update the value in settings + connect(_outputDirLineEdit, &QLineEdit::textChanged, this, &SkyboxBakeWidget::outputDirectoryChanged); + + QPushButton* chooseOutputDirectoryButton = new QPushButton("Browse..."); + connect(chooseOutputDirectoryButton, &QPushButton::clicked, this, &SkyboxBakeWidget::chooseOutputDirButtonClicked); + + // add the components for the output directory picker to the layout + gridLayout->addWidget(outputDirectoryLabel, rowIndex, 0); + gridLayout->addWidget(_outputDirLineEdit, rowIndex, 1, 1, 3); + gridLayout->addWidget(chooseOutputDirectoryButton, rowIndex, 4); + + // start a new row for the next component + ++rowIndex; + + // add a horizontal line to split the bake/cancel buttons off + QFrame* lineFrame = new QFrame; + lineFrame->setFrameShape(QFrame::HLine); + lineFrame->setFrameShadow(QFrame::Sunken); + gridLayout->addWidget(lineFrame, rowIndex, 0, 1, -1); + + // start a new row for the next component + ++rowIndex; + + // add a button that will kickoff the bake + QPushButton* bakeButton = new QPushButton("Bake"); + connect(bakeButton, &QPushButton::clicked, this, &SkyboxBakeWidget::bakeButtonClicked); + gridLayout->addWidget(bakeButton, rowIndex, 3); + + // add a cancel button to go back to the modes page + QPushButton* cancelButton = new QPushButton("Cancel"); + connect(cancelButton, &QPushButton::clicked, this, &SkyboxBakeWidget::cancelButtonClicked); + gridLayout->addWidget(cancelButton, rowIndex, 4); + + setLayout(gridLayout); +} + +void SkyboxBakeWidget::chooseFileButtonClicked() { + // pop a file dialog so the user can select the skybox file(s) + + // if we have picked a skybox before, start in the folder that matches the last path + // otherwise start in the home directory + auto startDir = _selectionStartDirectory.get(); + if (startDir.isEmpty()) { + startDir = QDir::homePath(); + } + + auto selectedFiles = QFileDialog::getOpenFileNames(this, "Choose Skybox", startDir); + + if (!selectedFiles.isEmpty()) { + // set the contents of the file select text box to be the path to the selected file + _selectionLineEdit->setText(selectedFiles.join(',')); + + if (_outputDirLineEdit->text().isEmpty()) { + auto directoryOfSkybox = QFileInfo(selectedFiles[0]).absolutePath(); + + // if our output directory is not yet set, set it to the directory of this skybox + _outputDirLineEdit->setText(directoryOfSkybox); + } + } +} + +void SkyboxBakeWidget::chooseOutputDirButtonClicked() { + // pop a file dialog so the user can select the output directory + + // if we have a previously selected output directory, use that as the initial path in the choose dialog + // otherwise use the user's home directory + auto startDir = _exportDirectory.get(); + if (startDir.isEmpty()) { + startDir = QDir::homePath(); + } + + auto selectedDir = QFileDialog::getExistingDirectory(this, "Choose Output Directory", startDir); + + if (!selectedDir.isEmpty()) { + // set the contents of the output directory text box to be the path to the directory + _outputDirLineEdit->setText(selectedDir); + } +} + +void SkyboxBakeWidget::outputDirectoryChanged(const QString& newDirectory) { + // update the export directory setting so we can re-use it next time + _exportDirectory.set(newDirectory); +} + +void SkyboxBakeWidget::bakeButtonClicked() { + // make sure we have a valid output directory + QDir outputDirectory(_outputDirLineEdit->text()); + + if (!outputDirectory.exists()) { + return; + } + + // make sure we have a non empty URL to a skybox to bake + if (_selectionLineEdit->text().isEmpty()) { + return; + } + + // split the list from the model line edit to see how many models we need to bake + auto fileURLStrings = _selectionLineEdit->text().split(','); + foreach (QString fileURLString, fileURLStrings) { + // construct a URL from the path in the model file text box + QUrl skyboxToBakeURL(fileURLString); + + // if the URL doesn't have a scheme, assume it is a local file + if (skyboxToBakeURL.scheme().isEmpty()) { + skyboxToBakeURL.setScheme("file"); + } + + // everything seems to be in place, kick off a bake for this model now + auto baker = std::unique_ptr { + new TextureBaker(skyboxToBakeURL, gpu::CUBE_TEXTURE, outputDirectory.absolutePath()) + }; + + // move the baker to a worker thread + baker->moveToThread(qApp->getNextWorkerThread()); + + // invoke the bake method on the baker thread + QMetaObject::invokeMethod(baker.get(), "bake"); + + // make sure we hear about the results of this baker when it is done + connect(baker.get(), &TextureBaker::finished, this, &SkyboxBakeWidget::handleFinishedBaker); + + // add a pending row to the results window to show that this bake is in process + auto resultsWindow = qApp->getMainWindow()->showResultsWindow(); + auto resultsRow = resultsWindow->addPendingResultRow(skyboxToBakeURL.fileName(), outputDirectory); + + // keep a unique_ptr to this baker + // and remember the row that represents it in the results table + _bakers.emplace_back(std::move(baker), resultsRow); + } +} + +void SkyboxBakeWidget::handleFinishedBaker() { + if (auto baker = qobject_cast(sender())) { + // add the results of this bake to the results window + auto it = std::find_if(_bakers.begin(), _bakers.end(), [baker](const BakerRowPair& value) { + return value.first.get() == baker; + }); + + if (it != _bakers.end()) { + auto resultRow = it->second; + auto resultsWindow = qApp->getMainWindow()->showResultsWindow(); + + if (baker->hasErrors()) { + resultsWindow->changeStatusForRow(resultRow, baker->getErrors().join("\n")); + } else { + resultsWindow->changeStatusForRow(resultRow, "Success"); + } + + // drop our strong pointer to the baker now that we are done with it + _bakers.erase(it); + } + } +} + +void SkyboxBakeWidget::cancelButtonClicked() { + // the user wants to go back to the mode selection screen + // remove ourselves from the stacked widget and call delete later so we'll be cleaned up + auto stackedWidget = qobject_cast(parentWidget()); + stackedWidget->removeWidget(this); + + this->deleteLater(); +} diff --git a/tools/oven/src/ui/SkyboxBakeWidget.h b/tools/oven/src/ui/SkyboxBakeWidget.h new file mode 100644 index 0000000000..3dfd2fa4f8 --- /dev/null +++ b/tools/oven/src/ui/SkyboxBakeWidget.h @@ -0,0 +1,53 @@ +// +// SkyboxBakeWidget.h +// tools/oven/src/ui +// +// Created by Stephen Birarda on 4/17/17. +// Copyright 2017 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_SkyboxBakeWidget_h +#define hifi_SkyboxBakeWidget_h + +#include + +#include + +#include + +class QLineEdit; + +class SkyboxBakeWidget : public QWidget { + Q_OBJECT + +public: + SkyboxBakeWidget(QWidget* parent = nullptr, Qt::WindowFlags flags = Qt::WindowFlags()); + +private slots: + void chooseFileButtonClicked(); + void chooseOutputDirButtonClicked(); + void bakeButtonClicked(); + void cancelButtonClicked(); + + void outputDirectoryChanged(const QString& newDirectory); + + void handleFinishedBaker(); + +private: + void setupUI(); + + using BakerRowPair = std::pair, int>; + using BakerRowPairList = std::list; + BakerRowPairList _bakers; + + QLineEdit* _selectionLineEdit; + QLineEdit* _outputDirLineEdit; + + Setting::Handle _exportDirectory; + Setting::Handle _selectionStartDirectory; +}; + +#endif // hifi_SkyboxBakeWidget_h From 19aa05281e8760982ce1a9c97b7b42cb2d83749f Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 17 Apr 2017 16:32:14 -0700 Subject: [PATCH 45/71] fix build errors for oven on windows --- libraries/model-baking/src/FBXBaker.cpp | 2 ++ tools/oven/CMakeLists.txt | 4 ++++ tools/oven/src/Oven.h | 2 ++ 3 files changed, 8 insertions(+) diff --git a/libraries/model-baking/src/FBXBaker.cpp b/libraries/model-baking/src/FBXBaker.cpp index fe48cc8372..e7dffea158 100644 --- a/libraries/model-baking/src/FBXBaker.cpp +++ b/libraries/model-baking/src/FBXBaker.cpp @@ -9,6 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include // need this include so we don't get an error looking for std::isnan + #include #include diff --git a/tools/oven/CMakeLists.txt b/tools/oven/CMakeLists.txt index 1e644a2c62..c9afc7660f 100644 --- a/tools/oven/CMakeLists.txt +++ b/tools/oven/CMakeLists.txt @@ -3,3 +3,7 @@ set(TARGET_NAME oven) setup_hifi_project(Widgets Gui Concurrent) link_hifi_libraries(model-baking shared image gpu ktx) + +if (WIN32) + package_libraries_for_deployment() +endif () diff --git a/tools/oven/src/Oven.h b/tools/oven/src/Oven.h index bf7f478b83..350c615ce0 100644 --- a/tools/oven/src/Oven.h +++ b/tools/oven/src/Oven.h @@ -16,6 +16,8 @@ #include +#include + #if defined(qApp) #undef qApp #endif From a072f940858d75816a5a9f0e53743ebc7b191ce8 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 17 Apr 2017 16:44:11 -0700 Subject: [PATCH 46/71] leverage QDesktopServices to show output directory --- tools/oven/src/ui/ResultsWindow.cpp | 45 +++++------------------------ 1 file changed, 8 insertions(+), 37 deletions(-) diff --git a/tools/oven/src/ui/ResultsWindow.cpp b/tools/oven/src/ui/ResultsWindow.cpp index 387e3698b8..cfbd07090f 100644 --- a/tools/oven/src/ui/ResultsWindow.cpp +++ b/tools/oven/src/ui/ResultsWindow.cpp @@ -9,7 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include +#include +#include #include #include #include @@ -57,45 +58,15 @@ void ResultsWindow::setupUI() { setLayout(resultsLayout); } -void revealDirectory(const QDir& dirToReveal) { - - // See http://stackoverflow.com/questions/3490336/how-to-reveal-in-finder-or-show-in-explorer-with-qt - // for details - - // Mac, Windows support folder or file. -#if defined(Q_OS_WIN) - const QString explorer = Environment::systemEnvironment().searchInPath(QLatin1String("explorer.exe")); - if (explorer.isEmpty()) { - QMessageBox::warning(parent, - tr("Launching Windows Explorer failed"), - tr("Could not find explorer.exe in path to launch Windows Explorer.")); - return; - } - - QString param = QLatin1String("/select,") + QDir::toNativeSeparators(dirToReveal.absolutePath()); - - QString command = explorer + " " + param; - QProcess::startDetached(command); - -#elif defined(Q_OS_MAC) - QStringList scriptArgs; - scriptArgs << QLatin1String("-e") - << QString::fromLatin1("tell application \"Finder\" to reveal POSIX file \"%1\"").arg(dirToReveal.absolutePath()); - QProcess::execute(QLatin1String("/usr/bin/osascript"), scriptArgs); - - scriptArgs.clear(); - scriptArgs << QLatin1String("-e") << QLatin1String("tell application \"Finder\" to activate"); - QProcess::execute("/usr/bin/osascript", scriptArgs); -#endif - -} - void ResultsWindow::handleCellClicked(int rowIndex, int columnIndex) { - // use revealDirectory to show the output directory for this row - revealDirectory(_outputDirectories[rowIndex]); + // make sure this click was on the file/domain being baked + if (columnIndex == 0) { + // use QDesktopServices to show the output directory for this row + auto directory = _outputDirectories[rowIndex]; + QDesktopServices::openUrl(QUrl::fromLocalFile(directory.absolutePath())); + } } - int ResultsWindow::addPendingResultRow(const QString& fileName, const QDir& outputDirectory) { int rowIndex = _resultsTable->rowCount(); From 0bb3f1c9dc56aedd32919dc6bafebed544325c9a Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 17 Apr 2017 16:55:31 -0700 Subject: [PATCH 47/71] assume local file if scheme is not remote --- tools/oven/src/ui/ModelBakeWidget.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/oven/src/ui/ModelBakeWidget.cpp b/tools/oven/src/ui/ModelBakeWidget.cpp index f87210dbee..9e2ab842b4 100644 --- a/tools/oven/src/ui/ModelBakeWidget.cpp +++ b/tools/oven/src/ui/ModelBakeWidget.cpp @@ -171,7 +171,7 @@ void ModelBakeWidget::bakeButtonClicked() { QUrl modelToBakeURL(fileURLString); // if the URL doesn't have a scheme, assume it is a local file - if (modelToBakeURL.scheme().isEmpty()) { + if (modelToBakeURL.scheme() != "http" && modelToBakeURL.scheme() != "https" && modelToBakeURL.scheme() != "ftp") { modelToBakeURL.setScheme("file"); } From 3216202a8b436c4aca9f81e31803ec6f6f3ba6bb Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 17 Apr 2017 16:57:14 -0700 Subject: [PATCH 48/71] fix local skybox file reference on windows --- tools/oven/src/ui/SkyboxBakeWidget.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/oven/src/ui/SkyboxBakeWidget.cpp b/tools/oven/src/ui/SkyboxBakeWidget.cpp index c6bca5819e..1a5a53de21 100644 --- a/tools/oven/src/ui/SkyboxBakeWidget.cpp +++ b/tools/oven/src/ui/SkyboxBakeWidget.cpp @@ -171,7 +171,7 @@ void SkyboxBakeWidget::bakeButtonClicked() { QUrl skyboxToBakeURL(fileURLString); // if the URL doesn't have a scheme, assume it is a local file - if (skyboxToBakeURL.scheme().isEmpty()) { + if (skyboxToBakeURL.scheme() != "http" && skyboxToBakeURL.scheme() != "https" && skyboxToBakeURL.scheme() != "ftp") { skyboxToBakeURL.setScheme("file"); } From 9d8e493c2039f2e8332ac09ce6702d2308037153 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 17 Apr 2017 16:58:16 -0700 Subject: [PATCH 49/71] remove suggestion that domain baker can load from URL --- tools/oven/src/ui/DomainBakeWidget.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/oven/src/ui/DomainBakeWidget.cpp b/tools/oven/src/ui/DomainBakeWidget.cpp index 7a8c5fc6a2..baea4c80a2 100644 --- a/tools/oven/src/ui/DomainBakeWidget.cpp +++ b/tools/oven/src/ui/DomainBakeWidget.cpp @@ -67,7 +67,7 @@ void DomainBakeWidget::setupUI() { QLabel* entitiesFileLabel = new QLabel("Entities File"); _entitiesFileLineEdit = new QLineEdit; - _entitiesFileLineEdit->setPlaceholderText("File or URL"); + _entitiesFileLineEdit->setPlaceholderText("File"); QPushButton* chooseFileButton = new QPushButton("Browse..."); connect(chooseFileButton, &QPushButton::clicked, this, &DomainBakeWidget::chooseFileButtonClicked); From 7a5bfb8c1945849ccae3c822306d438940937927 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 17 Apr 2017 17:19:18 -0700 Subject: [PATCH 50/71] add cancellation handling for domain bake widget --- libraries/model-baking/src/FBXBaker.cpp | 18 +++++++----------- libraries/model-baking/src/FBXBaker.h | 5 +---- tools/oven/src/DomainBaker.cpp | 3 ++- tools/oven/src/ui/DomainBakeWidget.cpp | 14 ++++++++++++++ tools/oven/src/ui/DomainBakeWidget.h | 3 ++- 5 files changed, 26 insertions(+), 17 deletions(-) diff --git a/libraries/model-baking/src/FBXBaker.cpp b/libraries/model-baking/src/FBXBaker.cpp index e7dffea158..bbd490447b 100644 --- a/libraries/model-baking/src/FBXBaker.cpp +++ b/libraries/model-baking/src/FBXBaker.cpp @@ -374,11 +374,7 @@ void FBXBaker::rewriteAndBakeSceneTextures() { // figure out the URL to this texture, embedded or external auto urlToTexture = getTextureURL(textureFileInfo, fileTexture); - if (!_unbakedTextures.contains(urlToTexture)) { - // add the deduced url to the texture, associated with the resulting baked texture file name, - // to our hash of textures needing to be baked - _unbakedTextures.insert(urlToTexture, bakedTextureFileName); - + if (!_bakingTextures.contains(urlToTexture)) { // bake this texture asynchronously bakeTexture(urlToTexture, textureType, _uniqueOutputPath + BAKED_OUTPUT_SUBFOLDER); } @@ -404,7 +400,7 @@ void FBXBaker::bakeTexture(const QUrl& textureURL, gpu::TextureType textureType, connect(bakingTexture.data(), &Baker::finished, this, &FBXBaker::handleBakedTexture); // keep a shared pointer to the baking texture - _bakingTextures.insert(bakingTexture); + _bakingTextures.insert(textureURL, bakingTexture); // start baking the texture on one of our available worker threads bakingTexture->moveToThread(_textureThreadGetter()); @@ -456,8 +452,8 @@ void FBXBaker::handleBakedTexture() { } - // now that this texture has been baked and handled, we can remove that TextureBaker from our list - _unbakedTextures.remove(bakedTexture->getTextureURL()); + // now that this texture has been baked and handled, we can remove that TextureBaker from our hash + _bakingTextures.remove(bakedTexture->getTextureURL()); checkIfTexturesFinished(); } else { @@ -468,7 +464,7 @@ void FBXBaker::handleBakedTexture() { _pendingErrorEmission = true; // now that this texture has been baked, even though it failed, we can remove that TextureBaker from our list - _unbakedTextures.remove(bakedTexture->getTextureURL()); + _bakingTextures.remove(bakedTexture->getTextureURL()); checkIfTexturesFinished(); } @@ -476,7 +472,7 @@ void FBXBaker::handleBakedTexture() { // we have errors to attend to, so we don't do extra processing for this texture // but we do need to remove that TextureBaker from our list // and then check if we're done with all textures - _unbakedTextures.remove(bakedTexture->getTextureURL()); + _bakingTextures.remove(bakedTexture->getTextureURL()); checkIfTexturesFinished(); } @@ -525,7 +521,7 @@ void FBXBaker::checkIfTexturesFinished() { // check if we're done everything we need to do for this FBX // and emit our finished signal if we're done - if (_unbakedTextures.isEmpty()) { + if (_bakingTextures.isEmpty()) { // remove the embedded media folder that the FBX SDK produces when reading the original removeEmbeddedMediaFolder(); diff --git a/libraries/model-baking/src/FBXBaker.h b/libraries/model-baking/src/FBXBaker.h index 903720a0a9..5ec6427bfb 100644 --- a/libraries/model-baking/src/FBXBaker.h +++ b/libraries/model-baking/src/FBXBaker.h @@ -88,12 +88,9 @@ private: static FBXSDKManagerUniquePointer _sdkManager; fbxsdk::FbxScene* _scene { nullptr }; - QHash _unbakedTextures; + QMultiHash> _bakingTextures; QHash _textureNameMatchCount; - QSet> _bakingTextures; - QFutureSynchronizer _textureBakeSynchronizer; - TextureBakerThreadGetter _textureThreadGetter; bool _copyOriginals { true }; diff --git a/tools/oven/src/DomainBaker.cpp b/tools/oven/src/DomainBaker.cpp index eea73bb1b6..07df9870aa 100644 --- a/tools/oven/src/DomainBaker.cpp +++ b/tools/oven/src/DomainBaker.cpp @@ -239,7 +239,8 @@ void DomainBaker::bakeSkybox(QUrl skyboxURL, QJsonValueRef entity) { // setup a baker for this skybox QSharedPointer skyboxBaker { - new TextureBaker(skyboxURL, gpu::CUBE_TEXTURE, _contentOutputPath) + new TextureBaker(skyboxURL, gpu::CUBE_TEXTURE, _contentOutputPath), + &TextureBaker::deleteLater }; // make sure our handler is called when the skybox baker is done diff --git a/tools/oven/src/ui/DomainBakeWidget.cpp b/tools/oven/src/ui/DomainBakeWidget.cpp index baea4c80a2..382e99f14e 100644 --- a/tools/oven/src/ui/DomainBakeWidget.cpp +++ b/tools/oven/src/ui/DomainBakeWidget.cpp @@ -41,6 +41,20 @@ DomainBakeWidget::DomainBakeWidget(QWidget* parent, Qt::WindowFlags flags) : setupUI(); } +DomainBakeWidget::~DomainBakeWidget() { + // if we're going down, our bakers are about to too + // enumerate them, send a cancelled status to the results table, and remove them + auto it = _bakers.begin(); + while (it != _bakers.end()) { + auto resultRow = it->second; + auto resultsWindow = qApp->getMainWindow()->showResultsWindow(); + + resultsWindow->changeStatusForRow(resultRow, "Cancelled"); + + it = _bakers.erase(it); + } +} + void DomainBakeWidget::setupUI() { // setup a grid layout to hold everything QGridLayout* gridLayout = new QGridLayout; diff --git a/tools/oven/src/ui/DomainBakeWidget.h b/tools/oven/src/ui/DomainBakeWidget.h index 16b0c76c11..b37ed490bc 100644 --- a/tools/oven/src/ui/DomainBakeWidget.h +++ b/tools/oven/src/ui/DomainBakeWidget.h @@ -25,7 +25,8 @@ class DomainBakeWidget : public QWidget { public: DomainBakeWidget(QWidget* parent = nullptr, Qt::WindowFlags flags = Qt::WindowFlags()); - + ~DomainBakeWidget(); + private slots: void chooseFileButtonClicked(); void chooseOutputDirButtonClicked(); From 8a1eb5f0771f86e5fa592d40ded86cd874823c35 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 17 Apr 2017 17:23:06 -0700 Subject: [PATCH 51/71] add cancellation handling to model bake widget --- tools/oven/src/ui/ModelBakeWidget.cpp | 14 ++++++++++++++ tools/oven/src/ui/ModelBakeWidget.h | 1 + 2 files changed, 15 insertions(+) diff --git a/tools/oven/src/ui/ModelBakeWidget.cpp b/tools/oven/src/ui/ModelBakeWidget.cpp index 9e2ab842b4..7b1adf05a7 100644 --- a/tools/oven/src/ui/ModelBakeWidget.cpp +++ b/tools/oven/src/ui/ModelBakeWidget.cpp @@ -36,6 +36,20 @@ ModelBakeWidget::ModelBakeWidget(QWidget* parent, Qt::WindowFlags flags) : setupUI(); } +ModelBakeWidget::~ModelBakeWidget() { + // if we're about to go down, whatever bakers we're managing are about to as well + // enumerate them, send the results table a cancelled status, and clean them up + auto it = _bakers.begin(); + while (it != _bakers.end()) { + auto resultRow = it->second; + auto resultsWindow = qApp->getMainWindow()->showResultsWindow(); + + resultsWindow->changeStatusForRow(resultRow, "Cancelled"); + + it = _bakers.erase(it); + } +} + void ModelBakeWidget::setupUI() { // setup a grid layout to hold everything QGridLayout* gridLayout = new QGridLayout; diff --git a/tools/oven/src/ui/ModelBakeWidget.h b/tools/oven/src/ui/ModelBakeWidget.h index 9a9394c386..0711417d21 100644 --- a/tools/oven/src/ui/ModelBakeWidget.h +++ b/tools/oven/src/ui/ModelBakeWidget.h @@ -26,6 +26,7 @@ class ModelBakeWidget : public QWidget { public: ModelBakeWidget(QWidget* parent = nullptr, Qt::WindowFlags flags = Qt::WindowFlags()); + ~ModelBakeWidget(); private slots: void chooseFileButtonClicked(); From 95e2cc4eeaba04fce928e4e7b19889f9a2a8732c Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 17 Apr 2017 17:32:23 -0700 Subject: [PATCH 52/71] add BakeWidget, leverage for skybox widget cancellation --- tools/oven/src/ui/BakeWidget.cpp | 46 ++++++++++++++++++++++++++ tools/oven/src/ui/BakeWidget.h | 33 ++++++++++++++++++ tools/oven/src/ui/DomainBakeWidget.cpp | 26 +-------------- tools/oven/src/ui/DomainBakeWidget.h | 9 ++--- tools/oven/src/ui/ModelBakeWidget.cpp | 25 +------------- tools/oven/src/ui/ModelBakeWidget.h | 10 ++---- tools/oven/src/ui/SkyboxBakeWidget.cpp | 11 +----- tools/oven/src/ui/SkyboxBakeWidget.h | 9 ++--- 8 files changed, 90 insertions(+), 79 deletions(-) create mode 100644 tools/oven/src/ui/BakeWidget.cpp create mode 100644 tools/oven/src/ui/BakeWidget.h diff --git a/tools/oven/src/ui/BakeWidget.cpp b/tools/oven/src/ui/BakeWidget.cpp new file mode 100644 index 0000000000..23a4822d82 --- /dev/null +++ b/tools/oven/src/ui/BakeWidget.cpp @@ -0,0 +1,46 @@ +// +// BakeWidget.cpp +// tools/oven/src/ui +// +// Created by Stephen Birarda on 4/17/17. +// Copyright 2017 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 + +#include "../Oven.h" +#include "OvenMainWindow.h" + +#include "BakeWidget.h" + +BakeWidget::BakeWidget(QWidget* parent, Qt::WindowFlags flags) : + QWidget(parent, flags) +{ + +} + +BakeWidget::~BakeWidget() { + // if we're going down, our bakers are about to too + // enumerate them, send a cancelled status to the results table, and remove them + auto it = _bakers.begin(); + while (it != _bakers.end()) { + auto resultRow = it->second; + auto resultsWindow = qApp->getMainWindow()->showResultsWindow(); + + resultsWindow->changeStatusForRow(resultRow, "Cancelled"); + + it = _bakers.erase(it); + } +} + +void BakeWidget::cancelButtonClicked() { + // the user wants to go back to the mode selection screen + // remove ourselves from the stacked widget and call delete later so we'll be cleaned up + auto stackedWidget = qobject_cast(parentWidget()); + stackedWidget->removeWidget(this); + + this->deleteLater(); +} diff --git a/tools/oven/src/ui/BakeWidget.h b/tools/oven/src/ui/BakeWidget.h new file mode 100644 index 0000000000..00996128ed --- /dev/null +++ b/tools/oven/src/ui/BakeWidget.h @@ -0,0 +1,33 @@ +// +// BakeWidget.h +// tools/oven/src/ui +// +// Created by Stephen Birarda on 4/17/17. +// Copyright 2017 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_BakeWidget_h +#define hifi_BakeWidget_h + +#include + +#include + +class BakeWidget : public QWidget { + Q_OBJECT +public: + BakeWidget(QWidget* parent = nullptr, Qt::WindowFlags flags = Qt::WindowFlags()); + ~BakeWidget(); + + void cancelButtonClicked(); + +protected: + using BakerRowPair = std::pair, int>; + using BakerRowPairList = std::list; + BakerRowPairList _bakers; +}; + +#endif // hifi_BakeWidget_h diff --git a/tools/oven/src/ui/DomainBakeWidget.cpp b/tools/oven/src/ui/DomainBakeWidget.cpp index 382e99f14e..27364a54de 100644 --- a/tools/oven/src/ui/DomainBakeWidget.cpp +++ b/tools/oven/src/ui/DomainBakeWidget.cpp @@ -16,7 +16,6 @@ #include #include #include -#include #include #include @@ -32,7 +31,7 @@ static const QString BROWSE_START_DIR_SETTING_KEY = "domain_search_directory"; static const QString DESTINATION_PATH_SETTING_KEY = "destination_path"; DomainBakeWidget::DomainBakeWidget(QWidget* parent, Qt::WindowFlags flags) : - QWidget(parent, flags), + BakeWidget(parent, flags), _domainNameSetting(DOMAIN_NAME_SETTING_KEY), _exportDirectory(EXPORT_DIR_SETTING_KEY), _browseStartDirectory(BROWSE_START_DIR_SETTING_KEY), @@ -41,20 +40,6 @@ DomainBakeWidget::DomainBakeWidget(QWidget* parent, Qt::WindowFlags flags) : setupUI(); } -DomainBakeWidget::~DomainBakeWidget() { - // if we're going down, our bakers are about to too - // enumerate them, send a cancelled status to the results table, and remove them - auto it = _bakers.begin(); - while (it != _bakers.end()) { - auto resultRow = it->second; - auto resultsWindow = qApp->getMainWindow()->showResultsWindow(); - - resultsWindow->changeStatusForRow(resultRow, "Cancelled"); - - it = _bakers.erase(it); - } -} - void DomainBakeWidget::setupUI() { // setup a grid layout to hold everything QGridLayout* gridLayout = new QGridLayout; @@ -296,12 +281,3 @@ void DomainBakeWidget::handleFinishedBaker() { } } } - -void DomainBakeWidget::cancelButtonClicked() { - // the user wants to go back to the mode selection screen - // remove ourselves from the stacked widget and call delete later so we'll be cleaned up - auto stackedWidget = qobject_cast(parentWidget()); - stackedWidget->removeWidget(this); - - this->deleteLater(); -} diff --git a/tools/oven/src/ui/DomainBakeWidget.h b/tools/oven/src/ui/DomainBakeWidget.h index b37ed490bc..cd8c4a012e 100644 --- a/tools/oven/src/ui/DomainBakeWidget.h +++ b/tools/oven/src/ui/DomainBakeWidget.h @@ -17,21 +17,20 @@ #include #include "../DomainBaker.h" +#include "BakeWidget.h" class QLineEdit; -class DomainBakeWidget : public QWidget { +class DomainBakeWidget : public BakeWidget { Q_OBJECT public: DomainBakeWidget(QWidget* parent = nullptr, Qt::WindowFlags flags = Qt::WindowFlags()); - ~DomainBakeWidget(); private slots: void chooseFileButtonClicked(); void chooseOutputDirButtonClicked(); void bakeButtonClicked(); - void cancelButtonClicked(); void outputDirectoryChanged(const QString& newDirectory); @@ -41,10 +40,6 @@ private slots: private: void setupUI(); - using BakerRowPair = std::pair, int>; - using BakerRowPairList = std::list; - BakerRowPairList _bakers; - QLineEdit* _domainNameLineEdit; QLineEdit* _entitiesFileLineEdit; QLineEdit* _outputDirLineEdit; diff --git a/tools/oven/src/ui/ModelBakeWidget.cpp b/tools/oven/src/ui/ModelBakeWidget.cpp index 7b1adf05a7..77f92c82e1 100644 --- a/tools/oven/src/ui/ModelBakeWidget.cpp +++ b/tools/oven/src/ui/ModelBakeWidget.cpp @@ -29,27 +29,13 @@ static const auto EXPORT_DIR_SETTING_KEY = "model_export_directory"; static const auto MODEL_START_DIR_SETTING_KEY = "model_search_directory"; ModelBakeWidget::ModelBakeWidget(QWidget* parent, Qt::WindowFlags flags) : - QWidget(parent, flags), + BakeWidget(parent, flags), _exportDirectory(EXPORT_DIR_SETTING_KEY), _modelStartDirectory(MODEL_START_DIR_SETTING_KEY) { setupUI(); } -ModelBakeWidget::~ModelBakeWidget() { - // if we're about to go down, whatever bakers we're managing are about to as well - // enumerate them, send the results table a cancelled status, and clean them up - auto it = _bakers.begin(); - while (it != _bakers.end()) { - auto resultRow = it->second; - auto resultsWindow = qApp->getMainWindow()->showResultsWindow(); - - resultsWindow->changeStatusForRow(resultRow, "Cancelled"); - - it = _bakers.erase(it); - } -} - void ModelBakeWidget::setupUI() { // setup a grid layout to hold everything QGridLayout* gridLayout = new QGridLayout; @@ -236,12 +222,3 @@ void ModelBakeWidget::handleFinishedBaker() { } } } - -void ModelBakeWidget::cancelButtonClicked() { - // the user wants to go back to the mode selection screen - // remove ourselves from the stacked widget and call delete later so we'll be cleaned up - auto stackedWidget = qobject_cast(parentWidget()); - stackedWidget->removeWidget(this); - - this->deleteLater(); -} diff --git a/tools/oven/src/ui/ModelBakeWidget.h b/tools/oven/src/ui/ModelBakeWidget.h index 0711417d21..b42b8725f6 100644 --- a/tools/oven/src/ui/ModelBakeWidget.h +++ b/tools/oven/src/ui/ModelBakeWidget.h @@ -18,21 +18,21 @@ #include +#include "BakeWidget.h" + class QLineEdit; class QThread; -class ModelBakeWidget : public QWidget { +class ModelBakeWidget : public BakeWidget { Q_OBJECT public: ModelBakeWidget(QWidget* parent = nullptr, Qt::WindowFlags flags = Qt::WindowFlags()); - ~ModelBakeWidget(); private slots: void chooseFileButtonClicked(); void chooseOutputDirButtonClicked(); void bakeButtonClicked(); - void cancelButtonClicked(); void outputDirectoryChanged(const QString& newDirectory); @@ -41,10 +41,6 @@ private slots: private: void setupUI(); - using BakerRowPair = std::pair, int>; - using BakerRowPairList = std::list; - BakerRowPairList _bakers; - QLineEdit* _modelLineEdit; QLineEdit* _outputDirLineEdit; diff --git a/tools/oven/src/ui/SkyboxBakeWidget.cpp b/tools/oven/src/ui/SkyboxBakeWidget.cpp index 1a5a53de21..d6dbb98bb7 100644 --- a/tools/oven/src/ui/SkyboxBakeWidget.cpp +++ b/tools/oven/src/ui/SkyboxBakeWidget.cpp @@ -29,7 +29,7 @@ static const auto EXPORT_DIR_SETTING_KEY = "skybox_export_directory"; static const auto SELECTION_START_DIR_SETTING_KEY = "skybox_search_directory"; SkyboxBakeWidget::SkyboxBakeWidget(QWidget* parent, Qt::WindowFlags flags) : - QWidget(parent, flags), + BakeWidget(parent, flags), _exportDirectory(EXPORT_DIR_SETTING_KEY), _selectionStartDirectory(SELECTION_START_DIR_SETTING_KEY) { @@ -221,12 +221,3 @@ void SkyboxBakeWidget::handleFinishedBaker() { } } } - -void SkyboxBakeWidget::cancelButtonClicked() { - // the user wants to go back to the mode selection screen - // remove ourselves from the stacked widget and call delete later so we'll be cleaned up - auto stackedWidget = qobject_cast(parentWidget()); - stackedWidget->removeWidget(this); - - this->deleteLater(); -} diff --git a/tools/oven/src/ui/SkyboxBakeWidget.h b/tools/oven/src/ui/SkyboxBakeWidget.h index 3dfd2fa4f8..f00ab07f33 100644 --- a/tools/oven/src/ui/SkyboxBakeWidget.h +++ b/tools/oven/src/ui/SkyboxBakeWidget.h @@ -18,9 +18,11 @@ #include +#include "BakeWidget.h" + class QLineEdit; -class SkyboxBakeWidget : public QWidget { +class SkyboxBakeWidget : public BakeWidget { Q_OBJECT public: @@ -30,7 +32,6 @@ private slots: void chooseFileButtonClicked(); void chooseOutputDirButtonClicked(); void bakeButtonClicked(); - void cancelButtonClicked(); void outputDirectoryChanged(const QString& newDirectory); @@ -39,10 +40,6 @@ private slots: private: void setupUI(); - using BakerRowPair = std::pair, int>; - using BakerRowPairList = std::list; - BakerRowPairList _bakers; - QLineEdit* _selectionLineEdit; QLineEdit* _outputDirLineEdit; From 822af3365b296a1acaa7d18686ddbe59a9ff5e6b Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 17 Apr 2017 17:41:34 -0700 Subject: [PATCH 53/71] always bring the results window to front when shown --- tools/oven/src/ui/OvenMainWindow.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tools/oven/src/ui/OvenMainWindow.cpp b/tools/oven/src/ui/OvenMainWindow.cpp index 1cf2986d17..1987bab660 100644 --- a/tools/oven/src/ui/OvenMainWindow.cpp +++ b/tools/oven/src/ui/OvenMainWindow.cpp @@ -41,11 +41,17 @@ ResultsWindow* OvenMainWindow::showResultsWindow() { if (!_resultsWindow) { // we don't have a results window right now, so make a new one _resultsWindow = new ResultsWindow; + + // even though we're about to show the results window, we do it here so that the move below works + _resultsWindow->show(); + + // place the results window initially below our window + _resultsWindow->move(_resultsWindow->x(), this->frameGeometry().bottom()); } - // show the results window, place it right below our window + // show the results window and make sure it is in front _resultsWindow->show(); - _resultsWindow->move(_resultsWindow->x(), this->frameGeometry().bottom()); + _resultsWindow->raise(); // return a pointer to the results window the caller can use return _resultsWindow; From 26d13ce0028472f1ab571c88ad70858a4c8a1817 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 18 Apr 2017 15:13:42 -0700 Subject: [PATCH 54/71] make results window raising optional --- tools/oven/src/ui/DomainBakeWidget.cpp | 4 +++- tools/oven/src/ui/OvenMainWindow.cpp | 7 +++++-- tools/oven/src/ui/OvenMainWindow.h | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/tools/oven/src/ui/DomainBakeWidget.cpp b/tools/oven/src/ui/DomainBakeWidget.cpp index 27364a54de..bfae2d70d4 100644 --- a/tools/oven/src/ui/DomainBakeWidget.cpp +++ b/tools/oven/src/ui/DomainBakeWidget.cpp @@ -241,7 +241,9 @@ void DomainBakeWidget::handleBakerProgress(int baked, int total) { if (it != _bakers.end()) { auto resultRow = it->second; - auto resultsWindow = qApp->getMainWindow()->showResultsWindow(); + + // grab the results window, don't force it to be brought to the top + auto resultsWindow = qApp->getMainWindow()->showResultsWindow(false); int percentage = roundf(float(baked) / float(total) * 100.0f); diff --git a/tools/oven/src/ui/OvenMainWindow.cpp b/tools/oven/src/ui/OvenMainWindow.cpp index 1987bab660..dd40fb1f8f 100644 --- a/tools/oven/src/ui/OvenMainWindow.cpp +++ b/tools/oven/src/ui/OvenMainWindow.cpp @@ -37,7 +37,7 @@ OvenMainWindow::~OvenMainWindow() { } } -ResultsWindow* OvenMainWindow::showResultsWindow() { +ResultsWindow* OvenMainWindow::showResultsWindow(bool shouldRaise) { if (!_resultsWindow) { // we don't have a results window right now, so make a new one _resultsWindow = new ResultsWindow; @@ -51,7 +51,10 @@ ResultsWindow* OvenMainWindow::showResultsWindow() { // show the results window and make sure it is in front _resultsWindow->show(); - _resultsWindow->raise(); + + if (shouldRaise) { + _resultsWindow->raise(); + } // return a pointer to the results window the caller can use return _resultsWindow; diff --git a/tools/oven/src/ui/OvenMainWindow.h b/tools/oven/src/ui/OvenMainWindow.h index 2d5d2aec99..a557d5e8dd 100644 --- a/tools/oven/src/ui/OvenMainWindow.h +++ b/tools/oven/src/ui/OvenMainWindow.h @@ -25,7 +25,7 @@ public: OvenMainWindow(QWidget *parent = Q_NULLPTR, Qt::WindowFlags flags = Qt::WindowFlags()); ~OvenMainWindow(); - ResultsWindow* showResultsWindow(); + ResultsWindow* showResultsWindow(bool shouldRaise = true); private: QPointer _resultsWindow; From 2478ddb379e2fe3cfc6c5b0f3dbf170be92c4370 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 18 Apr 2017 16:41:28 -0700 Subject: [PATCH 55/71] cleanup comments in skybox baking widget --- tools/oven/src/ui/SkyboxBakeWidget.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/oven/src/ui/SkyboxBakeWidget.cpp b/tools/oven/src/ui/SkyboxBakeWidget.cpp index d6dbb98bb7..d0da0a98ba 100644 --- a/tools/oven/src/ui/SkyboxBakeWidget.cpp +++ b/tools/oven/src/ui/SkyboxBakeWidget.cpp @@ -51,7 +51,7 @@ void SkyboxBakeWidget::setupUI() { QPushButton* chooseFileButton = new QPushButton("Browse..."); connect(chooseFileButton, &QPushButton::clicked, this, &SkyboxBakeWidget::chooseFileButtonClicked); - // add the components for the model file picker to the layout + // add the components for the skybox file picker to the layout gridLayout->addWidget(skyboxFileLabel, rowIndex, 0); gridLayout->addWidget(_selectionLineEdit, rowIndex, 1, 1, 3); gridLayout->addWidget(chooseFileButton, rowIndex, 4); @@ -164,10 +164,10 @@ void SkyboxBakeWidget::bakeButtonClicked() { return; } - // split the list from the model line edit to see how many models we need to bake + // split the list from the selection line edit to see how many skyboxes we need to bake auto fileURLStrings = _selectionLineEdit->text().split(','); foreach (QString fileURLString, fileURLStrings) { - // construct a URL from the path in the model file text box + // construct a URL from the path in the skybox file text box QUrl skyboxToBakeURL(fileURLString); // if the URL doesn't have a scheme, assume it is a local file @@ -175,7 +175,7 @@ void SkyboxBakeWidget::bakeButtonClicked() { skyboxToBakeURL.setScheme("file"); } - // everything seems to be in place, kick off a bake for this model now + // everything seems to be in place, kick off a bake for this skybox now auto baker = std::unique_ptr { new TextureBaker(skyboxToBakeURL, gpu::CUBE_TEXTURE, outputDirectory.absolutePath()) }; From 6127b72834bb683d5e5eb0e5d447fe00676bf53a Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 18 Apr 2017 17:09:28 -0700 Subject: [PATCH 56/71] don't build oven and model-baking with default/ALL_BUILD --- libraries/model-baking/CMakeLists.txt | 4 +++- tools/oven/CMakeLists.txt | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/libraries/model-baking/CMakeLists.txt b/libraries/model-baking/CMakeLists.txt index 2e08488d69..d26874e488 100644 --- a/libraries/model-baking/CMakeLists.txt +++ b/libraries/model-baking/CMakeLists.txt @@ -4,6 +4,8 @@ setup_hifi_library(Concurrent) link_hifi_libraries(networking image gpu shared ktx) -find_package(FBXSDK REQUIRED) +find_package(FBXSDK) target_link_libraries(${TARGET_NAME} ${FBX_LIBRARIES}) target_include_directories(${TARGET_NAME} SYSTEM PRIVATE ${FBX_INCLUDE_DIR}) + +set_target_properties(${TARGET_NAME} PROPERTIES EXCLUDE_FROM_ALL TRUE EXCLUDE_FROM_DEFAULT_BUILD TRUE) diff --git a/tools/oven/CMakeLists.txt b/tools/oven/CMakeLists.txt index c9afc7660f..1328d7db5a 100644 --- a/tools/oven/CMakeLists.txt +++ b/tools/oven/CMakeLists.txt @@ -7,3 +7,5 @@ link_hifi_libraries(model-baking shared image gpu ktx) if (WIN32) package_libraries_for_deployment() endif () + +set_target_properties(${TARGET_NAME} PROPERTIES EXCLUDE_FROM_ALL TRUE EXCLUDE_FROM_DEFAULT_BUILD TRUE) From 4863c924d1db3fbf8bef3963751156340f773527 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 18 Apr 2017 17:15:33 -0700 Subject: [PATCH 57/71] don't link or include missing FBX library --- cmake/modules/{FindFBXSDK.cmake => FindFBX.cmake} | 0 libraries/model-baking/CMakeLists.txt | 10 +++++++--- 2 files changed, 7 insertions(+), 3 deletions(-) rename cmake/modules/{FindFBXSDK.cmake => FindFBX.cmake} (100%) diff --git a/cmake/modules/FindFBXSDK.cmake b/cmake/modules/FindFBX.cmake similarity index 100% rename from cmake/modules/FindFBXSDK.cmake rename to cmake/modules/FindFBX.cmake diff --git a/libraries/model-baking/CMakeLists.txt b/libraries/model-baking/CMakeLists.txt index d26874e488..b3698270d7 100644 --- a/libraries/model-baking/CMakeLists.txt +++ b/libraries/model-baking/CMakeLists.txt @@ -4,8 +4,12 @@ setup_hifi_library(Concurrent) link_hifi_libraries(networking image gpu shared ktx) -find_package(FBXSDK) -target_link_libraries(${TARGET_NAME} ${FBX_LIBRARIES}) -target_include_directories(${TARGET_NAME} SYSTEM PRIVATE ${FBX_INCLUDE_DIR}) +# try to find the FBX SDK but fail silently if we don't +# because this library is not built by default +find_package(FBX) +if (FBX_FOUND) + target_link_libraries(${TARGET_NAME} ${FBX_LIBRARIES}) + target_include_directories(${TARGET_NAME} SYSTEM PRIVATE ${FBX_INCLUDE_DIR}) +endif () set_target_properties(${TARGET_NAME} PROPERTIES EXCLUDE_FROM_ALL TRUE EXCLUDE_FROM_DEFAULT_BUILD TRUE) From b520640fef44d04d759e7f37a065c13a8a7f93bf Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 20 Apr 2017 10:30:01 -0700 Subject: [PATCH 58/71] grow status column when there are long results --- tools/oven/src/ui/ResultsWindow.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tools/oven/src/ui/ResultsWindow.cpp b/tools/oven/src/ui/ResultsWindow.cpp index cfbd07090f..35b5160f9b 100644 --- a/tools/oven/src/ui/ResultsWindow.cpp +++ b/tools/oven/src/ui/ResultsWindow.cpp @@ -88,10 +88,13 @@ int ResultsWindow::addPendingResultRow(const QString& fileName, const QDir& outp } void ResultsWindow::changeStatusForRow(int rowIndex, const QString& result) { + const int STATUS_COLUMN = 1; auto statusItem = new QTableWidgetItem(result); statusItem->setFlags(statusItem->flags() & ~Qt::ItemIsEditable); - _resultsTable->setItem(rowIndex, 1, statusItem); + _resultsTable->setItem(rowIndex, STATUS_COLUMN, statusItem); // resize the row for the new contents _resultsTable->resizeRowToContents(rowIndex); + // reszie the column for the new contents + _resultsTable->resizeColumnToContents(STATUS_COLUMN); } From b3f3302f5c9953aae6f439123225a3cc4b798a8f Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 20 Apr 2017 10:34:14 -0700 Subject: [PATCH 59/71] add accepted file types to baker file pickers --- tools/oven/src/ui/DomainBakeWidget.cpp | 3 ++- tools/oven/src/ui/ModelBakeWidget.cpp | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/tools/oven/src/ui/DomainBakeWidget.cpp b/tools/oven/src/ui/DomainBakeWidget.cpp index bfae2d70d4..7d667305bb 100644 --- a/tools/oven/src/ui/DomainBakeWidget.cpp +++ b/tools/oven/src/ui/DomainBakeWidget.cpp @@ -149,7 +149,8 @@ void DomainBakeWidget::chooseFileButtonClicked() { startDir = QDir::homePath(); } - auto selectedFile = QFileDialog::getOpenFileName(this, "Choose Entities File", startDir); + auto selectedFile = QFileDialog::getOpenFileName(this, "Choose Entities File", startDir, + "Entities File (*.json *.gz)"); if (!selectedFile.isEmpty()) { // set the contents of the entities file text box to be the path to the selected file diff --git a/tools/oven/src/ui/ModelBakeWidget.cpp b/tools/oven/src/ui/ModelBakeWidget.cpp index 77f92c82e1..7f2a0c74b5 100644 --- a/tools/oven/src/ui/ModelBakeWidget.cpp +++ b/tools/oven/src/ui/ModelBakeWidget.cpp @@ -113,7 +113,7 @@ void ModelBakeWidget::chooseFileButtonClicked() { startDir = QDir::homePath(); } - auto selectedFiles = QFileDialog::getOpenFileNames(this, "Choose Model", startDir); + auto selectedFiles = QFileDialog::getOpenFileNames(this, "Choose Model", startDir, "Models (*.fbx)"); if (!selectedFiles.isEmpty()) { // set the contents of the model file text box to be the path to the selected file From c71255d5feb42e60bf982e31029028386d8329d1 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 20 Apr 2017 10:42:12 -0700 Subject: [PATCH 60/71] force oven dependency on model-baker since it is excluded --- tools/oven/CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tools/oven/CMakeLists.txt b/tools/oven/CMakeLists.txt index 1328d7db5a..10e2923e35 100644 --- a/tools/oven/CMakeLists.txt +++ b/tools/oven/CMakeLists.txt @@ -9,3 +9,7 @@ if (WIN32) endif () set_target_properties(${TARGET_NAME} PROPERTIES EXCLUDE_FROM_ALL TRUE EXCLUDE_FROM_DEFAULT_BUILD TRUE) + +# because the model-baking library is excluded from all and default builds, we force +# a dependency on it here +add_dependencies(${TARGET_NAME} model-baking) From 38cb998ca15eb5fac13613968703ec3531df759b Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 20 Apr 2017 11:14:15 -0700 Subject: [PATCH 61/71] move model-baking library to oven for build exclusion --- libraries/model-baking/CMakeLists.txt | 15 --------------- tools/oven/CMakeLists.txt | 14 +++++++++----- .../model-baking => tools/oven}/src/Baker.cpp | 0 .../model-baking => tools/oven}/src/Baker.h | 0 tools/oven/src/DomainBaker.h | 6 +++--- .../model-baking => tools/oven}/src/FBXBaker.cpp | 0 .../model-baking => tools/oven}/src/FBXBaker.h | 0 .../oven}/src/ModelBakingLoggingCategory.cpp | 0 .../oven}/src/ModelBakingLoggingCategory.h | 0 .../oven}/src/TextureBaker.cpp | 0 .../oven}/src/TextureBaker.h | 0 tools/oven/src/ui/BakeWidget.h | 2 +- tools/oven/src/ui/ModelBakeWidget.h | 2 +- tools/oven/src/ui/SkyboxBakeWidget.h | 2 +- 14 files changed, 15 insertions(+), 26 deletions(-) delete mode 100644 libraries/model-baking/CMakeLists.txt rename {libraries/model-baking => tools/oven}/src/Baker.cpp (100%) rename {libraries/model-baking => tools/oven}/src/Baker.h (100%) rename {libraries/model-baking => tools/oven}/src/FBXBaker.cpp (100%) rename {libraries/model-baking => tools/oven}/src/FBXBaker.h (100%) rename {libraries/model-baking => tools/oven}/src/ModelBakingLoggingCategory.cpp (100%) rename {libraries/model-baking => tools/oven}/src/ModelBakingLoggingCategory.h (100%) rename {libraries/model-baking => tools/oven}/src/TextureBaker.cpp (100%) rename {libraries/model-baking => tools/oven}/src/TextureBaker.h (100%) diff --git a/libraries/model-baking/CMakeLists.txt b/libraries/model-baking/CMakeLists.txt deleted file mode 100644 index b3698270d7..0000000000 --- a/libraries/model-baking/CMakeLists.txt +++ /dev/null @@ -1,15 +0,0 @@ -set(TARGET_NAME model-baking) - -setup_hifi_library(Concurrent) - -link_hifi_libraries(networking image gpu shared ktx) - -# try to find the FBX SDK but fail silently if we don't -# because this library is not built by default -find_package(FBX) -if (FBX_FOUND) - target_link_libraries(${TARGET_NAME} ${FBX_LIBRARIES}) - target_include_directories(${TARGET_NAME} SYSTEM PRIVATE ${FBX_INCLUDE_DIR}) -endif () - -set_target_properties(${TARGET_NAME} PROPERTIES EXCLUDE_FROM_ALL TRUE EXCLUDE_FROM_DEFAULT_BUILD TRUE) diff --git a/tools/oven/CMakeLists.txt b/tools/oven/CMakeLists.txt index 10e2923e35..24c8a9a0e2 100644 --- a/tools/oven/CMakeLists.txt +++ b/tools/oven/CMakeLists.txt @@ -2,14 +2,18 @@ set(TARGET_NAME oven) setup_hifi_project(Widgets Gui Concurrent) -link_hifi_libraries(model-baking shared image gpu ktx) +link_hifi_libraries(networking shared image gpu ktx) if (WIN32) package_libraries_for_deployment() endif () -set_target_properties(${TARGET_NAME} PROPERTIES EXCLUDE_FROM_ALL TRUE EXCLUDE_FROM_DEFAULT_BUILD TRUE) +# try to find the FBX SDK but fail silently if we don't +# because this tool is not built by default +find_package(FBX) +if (FBX_FOUND) + target_link_libraries(${TARGET_NAME} ${FBX_LIBRARIES}) + target_include_directories(${TARGET_NAME} SYSTEM PRIVATE ${FBX_INCLUDE_DIR}) +endif () -# because the model-baking library is excluded from all and default builds, we force -# a dependency on it here -add_dependencies(${TARGET_NAME} model-baking) +set_target_properties(${TARGET_NAME} PROPERTIES EXCLUDE_FROM_ALL TRUE EXCLUDE_FROM_DEFAULT_BUILD TRUE) diff --git a/libraries/model-baking/src/Baker.cpp b/tools/oven/src/Baker.cpp similarity index 100% rename from libraries/model-baking/src/Baker.cpp rename to tools/oven/src/Baker.cpp diff --git a/libraries/model-baking/src/Baker.h b/tools/oven/src/Baker.h similarity index 100% rename from libraries/model-baking/src/Baker.h rename to tools/oven/src/Baker.h diff --git a/tools/oven/src/DomainBaker.h b/tools/oven/src/DomainBaker.h index 54cbb18b06..5244408115 100644 --- a/tools/oven/src/DomainBaker.h +++ b/tools/oven/src/DomainBaker.h @@ -17,9 +17,9 @@ #include #include -#include -#include -#include +#include "Baker.h" +#include "FBXBaker.h" +#include "TextureBaker.h" class DomainBaker : public Baker { Q_OBJECT diff --git a/libraries/model-baking/src/FBXBaker.cpp b/tools/oven/src/FBXBaker.cpp similarity index 100% rename from libraries/model-baking/src/FBXBaker.cpp rename to tools/oven/src/FBXBaker.cpp diff --git a/libraries/model-baking/src/FBXBaker.h b/tools/oven/src/FBXBaker.h similarity index 100% rename from libraries/model-baking/src/FBXBaker.h rename to tools/oven/src/FBXBaker.h diff --git a/libraries/model-baking/src/ModelBakingLoggingCategory.cpp b/tools/oven/src/ModelBakingLoggingCategory.cpp similarity index 100% rename from libraries/model-baking/src/ModelBakingLoggingCategory.cpp rename to tools/oven/src/ModelBakingLoggingCategory.cpp diff --git a/libraries/model-baking/src/ModelBakingLoggingCategory.h b/tools/oven/src/ModelBakingLoggingCategory.h similarity index 100% rename from libraries/model-baking/src/ModelBakingLoggingCategory.h rename to tools/oven/src/ModelBakingLoggingCategory.h diff --git a/libraries/model-baking/src/TextureBaker.cpp b/tools/oven/src/TextureBaker.cpp similarity index 100% rename from libraries/model-baking/src/TextureBaker.cpp rename to tools/oven/src/TextureBaker.cpp diff --git a/libraries/model-baking/src/TextureBaker.h b/tools/oven/src/TextureBaker.h similarity index 100% rename from libraries/model-baking/src/TextureBaker.h rename to tools/oven/src/TextureBaker.h diff --git a/tools/oven/src/ui/BakeWidget.h b/tools/oven/src/ui/BakeWidget.h index 00996128ed..e7ab8d1840 100644 --- a/tools/oven/src/ui/BakeWidget.h +++ b/tools/oven/src/ui/BakeWidget.h @@ -14,7 +14,7 @@ #include -#include +#include "../Baker.h" class BakeWidget : public QWidget { Q_OBJECT diff --git a/tools/oven/src/ui/ModelBakeWidget.h b/tools/oven/src/ui/ModelBakeWidget.h index b42b8725f6..ed08990ba5 100644 --- a/tools/oven/src/ui/ModelBakeWidget.h +++ b/tools/oven/src/ui/ModelBakeWidget.h @@ -16,7 +16,7 @@ #include -#include +#include "../FBXBaker.h" #include "BakeWidget.h" diff --git a/tools/oven/src/ui/SkyboxBakeWidget.h b/tools/oven/src/ui/SkyboxBakeWidget.h index f00ab07f33..4063a5459b 100644 --- a/tools/oven/src/ui/SkyboxBakeWidget.h +++ b/tools/oven/src/ui/SkyboxBakeWidget.h @@ -16,7 +16,7 @@ #include -#include +#include "../TextureBaker.h" #include "BakeWidget.h" From 46fc69dd32c99fd594c043c7a09a4547a10a49f2 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 20 Apr 2017 12:25:14 -0700 Subject: [PATCH 62/71] fix filename parsing in TextureBaker --- tools/oven/src/TextureBaker.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/oven/src/TextureBaker.cpp b/tools/oven/src/TextureBaker.cpp index f0136fb454..1e22a56b52 100644 --- a/tools/oven/src/TextureBaker.cpp +++ b/tools/oven/src/TextureBaker.cpp @@ -31,7 +31,7 @@ TextureBaker::TextureBaker(const QUrl& textureURL, gpu::TextureType textureType, { // figure out the baked texture filename auto originalFilename = textureURL.fileName(); - _bakedTextureFileName = originalFilename.left(originalFilename.indexOf('.')) + BAKED_TEXTURE_EXT; + _bakedTextureFileName = originalFilename.left(originalFilename.indexOf('.', -1)) + BAKED_TEXTURE_EXT; } void TextureBaker::bake() { From 258533de7b15a625c76bb1c817261ebf8d378dde Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 20 Apr 2017 13:20:20 -0700 Subject: [PATCH 63/71] fix filename references in FBXBaker --- tools/oven/src/FBXBaker.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tools/oven/src/FBXBaker.cpp b/tools/oven/src/FBXBaker.cpp index bbd490447b..f2fc4c2c96 100644 --- a/tools/oven/src/FBXBaker.cpp +++ b/tools/oven/src/FBXBaker.cpp @@ -48,7 +48,7 @@ FBXBaker::FBXBaker(const QUrl& fbxURL, const QString& baseOutputPath, // grab the name of the FBX from the URL, this is used for folder output names auto fileName = fbxURL.fileName(); - _fbxName = fileName.left(fileName.indexOf('.')); + _fbxName = fileName.left(fileName.indexOf('.', -1)); } static const QString BAKED_OUTPUT_SUBFOLDER = "baked/"; @@ -229,7 +229,7 @@ QString FBXBaker::createBakedTextureFileName(const QFileInfo& textureFileInfo) { // in case another texture referenced by this model has the same base name auto nameMatches = _textureNameMatchCount[textureFileInfo.baseName()]; - QString bakedTextureFileName { textureFileInfo.baseName() }; + QString bakedTextureFileName { textureFileInfo.completeBaseName() }; if (nameMatches > 0) { // there are already nameMatches texture with this name @@ -356,7 +356,7 @@ void FBXBaker::rewriteAndBakeSceneTextures() { // make sure this texture points to something and isn't one we've already re-mapped if (!textureFileInfo.filePath().isEmpty() - && textureFileInfo.completeSuffix() != BAKED_TEXTURE_EXT.mid(1)) { + && textureFileInfo.suffix() != BAKED_TEXTURE_EXT.mid(1)) { // construct the new baked texture file name and file path // ensuring that the baked texture will have a unique name @@ -366,7 +366,8 @@ void FBXBaker::rewriteAndBakeSceneTextures() { _uniqueOutputPath + BAKED_OUTPUT_SUBFOLDER + bakedTextureFileName }; - qCDebug(model_baking).noquote() << "Re-mapping" << fileTexture->GetFileName() << "to" << bakedTextureFilePath; + qCDebug(model_baking).noquote() << "Re-mapping" << fileTexture->GetFileName() + << "to" << bakedTextureFilePath; // write the new filename into the FBX scene fileTexture->SetFileName(bakedTextureFilePath.toLocal8Bit()); From c43a4eec86193c82d6d150870f684412070fe72c Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 20 Apr 2017 13:25:11 -0700 Subject: [PATCH 64/71] fix indexOf checks by using lastIndexOf --- tools/oven/src/FBXBaker.cpp | 2 +- tools/oven/src/TextureBaker.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/oven/src/FBXBaker.cpp b/tools/oven/src/FBXBaker.cpp index f2fc4c2c96..9b2a30c109 100644 --- a/tools/oven/src/FBXBaker.cpp +++ b/tools/oven/src/FBXBaker.cpp @@ -48,7 +48,7 @@ FBXBaker::FBXBaker(const QUrl& fbxURL, const QString& baseOutputPath, // grab the name of the FBX from the URL, this is used for folder output names auto fileName = fbxURL.fileName(); - _fbxName = fileName.left(fileName.indexOf('.', -1)); + _fbxName = fileName.left(fileName.lastIndexOf('.')); } static const QString BAKED_OUTPUT_SUBFOLDER = "baked/"; diff --git a/tools/oven/src/TextureBaker.cpp b/tools/oven/src/TextureBaker.cpp index 1e22a56b52..03b9fd669d 100644 --- a/tools/oven/src/TextureBaker.cpp +++ b/tools/oven/src/TextureBaker.cpp @@ -31,7 +31,7 @@ TextureBaker::TextureBaker(const QUrl& textureURL, gpu::TextureType textureType, { // figure out the baked texture filename auto originalFilename = textureURL.fileName(); - _bakedTextureFileName = originalFilename.left(originalFilename.indexOf('.', -1)) + BAKED_TEXTURE_EXT; + _bakedTextureFileName = originalFilename.left(originalFilename.lastIndexOf('.')) + BAKED_TEXTURE_EXT; } void TextureBaker::bake() { From 683985aea9af80cd3a2be2165f2ca5c617184de9 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 20 Apr 2017 15:17:54 -0700 Subject: [PATCH 65/71] correct headers after move from model-baking to oven --- tools/oven/src/Baker.cpp | 2 +- tools/oven/src/Baker.h | 2 +- tools/oven/src/FBXBaker.cpp | 2 +- tools/oven/src/FBXBaker.h | 2 +- tools/oven/src/ModelBakingLoggingCategory.cpp | 2 +- tools/oven/src/ModelBakingLoggingCategory.h | 2 +- tools/oven/src/TextureBaker.cpp | 2 +- tools/oven/src/TextureBaker.h | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tools/oven/src/Baker.cpp b/tools/oven/src/Baker.cpp index 666e59a073..c0cbd8d124 100644 --- a/tools/oven/src/Baker.cpp +++ b/tools/oven/src/Baker.cpp @@ -1,6 +1,6 @@ // // Baker.cpp -// libraries/model-baking/src +// tools/oven/src // // Created by Stephen Birarda on 4/14/17. // Copyright 2017 High Fidelity, Inc. diff --git a/tools/oven/src/Baker.h b/tools/oven/src/Baker.h index 8f73819dfe..d7107428bf 100644 --- a/tools/oven/src/Baker.h +++ b/tools/oven/src/Baker.h @@ -1,6 +1,6 @@ // // Baker.h -// libraries/model-baking/src +// tools/oven/src // // Created by Stephen Birarda on 4/14/17. // Copyright 2017 High Fidelity, Inc. diff --git a/tools/oven/src/FBXBaker.cpp b/tools/oven/src/FBXBaker.cpp index 9b2a30c109..7446c7ec67 100644 --- a/tools/oven/src/FBXBaker.cpp +++ b/tools/oven/src/FBXBaker.cpp @@ -1,6 +1,6 @@ // // FBXBaker.cpp -// libraries/model-baking/src +// tools/oven/src // // Created by Stephen Birarda on 3/30/17. // Copyright 2017 High Fidelity, Inc. diff --git a/tools/oven/src/FBXBaker.h b/tools/oven/src/FBXBaker.h index 5ec6427bfb..7532d587b6 100644 --- a/tools/oven/src/FBXBaker.h +++ b/tools/oven/src/FBXBaker.h @@ -1,6 +1,6 @@ // // FBXBaker.h -// libraries/model-baking/src +// tools/oven/src // // Created by Stephen Birarda on 3/30/17. // Copyright 2017 High Fidelity, Inc. diff --git a/tools/oven/src/ModelBakingLoggingCategory.cpp b/tools/oven/src/ModelBakingLoggingCategory.cpp index c2ad6360d2..f897ddf5ca 100644 --- a/tools/oven/src/ModelBakingLoggingCategory.cpp +++ b/tools/oven/src/ModelBakingLoggingCategory.cpp @@ -1,6 +1,6 @@ // // ModelBakingLoggingCategory.cpp -// libraries/model-baking/src +// tools/oven/src // // Created by Stephen Birarda on 4/5/17. // Copyright 2017 High Fidelity, Inc. diff --git a/tools/oven/src/ModelBakingLoggingCategory.h b/tools/oven/src/ModelBakingLoggingCategory.h index 600618ed5e..6c7d9d5db6 100644 --- a/tools/oven/src/ModelBakingLoggingCategory.h +++ b/tools/oven/src/ModelBakingLoggingCategory.h @@ -1,6 +1,6 @@ // // ModelBakingLoggingCategory.h -// libraries/model-baking/src +// tools/oven/src // // Created by Stephen Birarda on 4/5/17. // Copyright 2017 High Fidelity, Inc. diff --git a/tools/oven/src/TextureBaker.cpp b/tools/oven/src/TextureBaker.cpp index 03b9fd669d..2a6c0f5699 100644 --- a/tools/oven/src/TextureBaker.cpp +++ b/tools/oven/src/TextureBaker.cpp @@ -1,6 +1,6 @@ // // TextureBaker.cpp -// libraries/model-baker/src +// tools/oven/src // // Created by Stephen Birarda on 4/5/17. // Copyright 2017 High Fidelity, Inc. diff --git a/tools/oven/src/TextureBaker.h b/tools/oven/src/TextureBaker.h index 7a6d1d404b..2bc4ffc607 100644 --- a/tools/oven/src/TextureBaker.h +++ b/tools/oven/src/TextureBaker.h @@ -1,6 +1,6 @@ // // TextureBaker.h -// libraries/model-baker/src +// tools/oven/src // // Created by Stephen Birarda on 4/5/17. // Copyright 2017 High Fidelity, Inc. From 3aa6757c326f5f6689a79b46739a92f2f0c31237 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 20 Apr 2017 15:19:13 -0700 Subject: [PATCH 66/71] use QCryptographicHash static for cleaner hashing --- tools/oven/src/TextureBaker.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tools/oven/src/TextureBaker.cpp b/tools/oven/src/TextureBaker.cpp index 2a6c0f5699..783cc426d0 100644 --- a/tools/oven/src/TextureBaker.cpp +++ b/tools/oven/src/TextureBaker.cpp @@ -103,9 +103,8 @@ void TextureBaker::processTexture() { // the baked textures need to have the source hash added for cache checks in Interface // so we add that to the processed texture before handling it off to be serialized - QCryptographicHash hasher(QCryptographicHash::Md5); - hasher.addData(_originalTexture); - std::string hash = hasher.result().toHex().toStdString(); + auto hashData = QCryptographicHash::hash(_originalTexture, QCryptographicHash::Md5); + std::string hash = hashData.toHex().toStdString(); processedTexture->setSourceHash(hash); auto memKTX = gpu::Texture::serialize(*processedTexture); From 93c35314ec22f40f9ed9ef321b9f004f5dab637f Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 25 Apr 2017 10:57:53 -0700 Subject: [PATCH 67/71] use HF user agent for texture and FBX requests --- tools/oven/src/FBXBaker.cpp | 3 +++ tools/oven/src/TextureBaker.cpp | 2 ++ 2 files changed, 5 insertions(+) diff --git a/tools/oven/src/FBXBaker.cpp b/tools/oven/src/FBXBaker.cpp index 7446c7ec67..c185488760 100644 --- a/tools/oven/src/FBXBaker.cpp +++ b/tools/oven/src/FBXBaker.cpp @@ -23,6 +23,7 @@ #include #include +#include #include "ModelBakingLoggingCategory.h" #include "TextureBaker.h" @@ -147,6 +148,8 @@ void FBXBaker::loadSourceFBX() { // setup the request to follow re-directs and always hit the network networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); networkRequest.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork); + networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); + networkRequest.setUrl(_fbxURL); diff --git a/tools/oven/src/TextureBaker.cpp b/tools/oven/src/TextureBaker.cpp index 783cc426d0..02bedeb659 100644 --- a/tools/oven/src/TextureBaker.cpp +++ b/tools/oven/src/TextureBaker.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include "ModelBakingLoggingCategory.h" @@ -65,6 +66,7 @@ void TextureBaker::loadTexture() { // setup the request to follow re-directs and always hit the network networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); networkRequest.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork); + networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); networkRequest.setUrl(_textureURL); From 0b4a8d05aace6762ffd24ca6eef4b469d9a920f1 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 26 Apr 2017 11:07:29 -0700 Subject: [PATCH 68/71] fix references to moved texture type enum --- libraries/image/src/image/Image.h | 3 ++- tools/oven/src/DomainBaker.cpp | 2 +- tools/oven/src/FBXBaker.cpp | 8 ++++---- tools/oven/src/FBXBaker.h | 2 +- tools/oven/src/TextureBaker.cpp | 2 +- tools/oven/src/TextureBaker.h | 6 +++--- tools/oven/src/ui/SkyboxBakeWidget.cpp | 2 +- 7 files changed, 13 insertions(+), 12 deletions(-) diff --git a/libraries/image/src/image/Image.h b/libraries/image/src/image/Image.h index 3e5aa868d2..fd214daa2c 100644 --- a/libraries/image/src/image/Image.h +++ b/libraries/image/src/image/Image.h @@ -37,7 +37,8 @@ enum Type { CUBE_TEXTURE, OCCLUSION_TEXTURE, SCATTERING_TEXTURE = OCCLUSION_TEXTURE, - LIGHTMAP_TEXTURE + LIGHTMAP_TEXTURE, + UNUSED_TEXTURE }; using TextureLoader = std::function; diff --git a/tools/oven/src/DomainBaker.cpp b/tools/oven/src/DomainBaker.cpp index 07df9870aa..b93e852daa 100644 --- a/tools/oven/src/DomainBaker.cpp +++ b/tools/oven/src/DomainBaker.cpp @@ -239,7 +239,7 @@ void DomainBaker::bakeSkybox(QUrl skyboxURL, QJsonValueRef entity) { // setup a baker for this skybox QSharedPointer skyboxBaker { - new TextureBaker(skyboxURL, gpu::CUBE_TEXTURE, _contentOutputPath), + new TextureBaker(skyboxURL, image::TextureUsage::CUBE_TEXTURE, _contentOutputPath), &TextureBaker::deleteLater }; diff --git a/tools/oven/src/FBXBaker.cpp b/tools/oven/src/FBXBaker.cpp index c185488760..26aea7a596 100644 --- a/tools/oven/src/FBXBaker.cpp +++ b/tools/oven/src/FBXBaker.cpp @@ -277,8 +277,8 @@ QUrl FBXBaker::getTextureURL(const QFileInfo& textureFileInfo, FbxFileTexture* f return urlToTexture; } -gpu::TextureType textureTypeForMaterialProperty(FbxProperty& property, FbxSurfaceMaterial* material) { - using namespace gpu; +image::TextureUsage::Type textureTypeForMaterialProperty(FbxProperty& property, FbxSurfaceMaterial* material) { + using namespace image::TextureUsage; // this is a property we know has a texture, we need to match it to a High Fidelity known texture type // since that information is passed to the baking process @@ -347,7 +347,7 @@ void FBXBaker::rewriteAndBakeSceneTextures() { // figure out the type of texture from the material property auto textureType = textureTypeForMaterialProperty(property, material); - if (textureType != gpu::UNUSED_TEXTURE) { + if (textureType != image::TextureUsage::UNUSED_TEXTURE) { int numTextures = property.GetSrcObjectCount(); for (int j = 0; j < numTextures; j++) { @@ -393,7 +393,7 @@ void FBXBaker::rewriteAndBakeSceneTextures() { } } -void FBXBaker::bakeTexture(const QUrl& textureURL, gpu::TextureType textureType, const QDir& outputDir) { +void FBXBaker::bakeTexture(const QUrl& textureURL, image::TextureUsage::Type textureType, const QDir& outputDir) { // start a bake for this texture and add it to our list to keep track of QSharedPointer bakingTexture { new TextureBaker(textureURL, textureType, outputDir), diff --git a/tools/oven/src/FBXBaker.h b/tools/oven/src/FBXBaker.h index 7532d587b6..bcfebbe2a8 100644 --- a/tools/oven/src/FBXBaker.h +++ b/tools/oven/src/FBXBaker.h @@ -74,7 +74,7 @@ private: QString createBakedTextureFileName(const QFileInfo& textureFileInfo); QUrl getTextureURL(const QFileInfo& textureFileInfo, fbxsdk::FbxFileTexture* fileTexture); - void bakeTexture(const QUrl& textureURL, gpu::TextureType textureType, const QDir& outputDir); + void bakeTexture(const QUrl& textureURL, image::TextureUsage::Type textureType, const QDir& outputDir); QString pathToCopyOfOriginal() const; diff --git a/tools/oven/src/TextureBaker.cpp b/tools/oven/src/TextureBaker.cpp index 02bedeb659..70df511d2c 100644 --- a/tools/oven/src/TextureBaker.cpp +++ b/tools/oven/src/TextureBaker.cpp @@ -25,7 +25,7 @@ const QString BAKED_TEXTURE_EXT = ".ktx"; -TextureBaker::TextureBaker(const QUrl& textureURL, gpu::TextureType textureType, const QDir& outputDirectory) : +TextureBaker::TextureBaker(const QUrl& textureURL, image::TextureUsage::Type textureType, const QDir& outputDirectory) : _textureURL(textureURL), _textureType(textureType), _outputDirectory(outputDirectory) diff --git a/tools/oven/src/TextureBaker.h b/tools/oven/src/TextureBaker.h index 2bc4ffc607..ee1e968f20 100644 --- a/tools/oven/src/TextureBaker.h +++ b/tools/oven/src/TextureBaker.h @@ -16,7 +16,7 @@ #include #include -#include +#include #include "Baker.h" @@ -26,7 +26,7 @@ class TextureBaker : public Baker { Q_OBJECT public: - TextureBaker(const QUrl& textureURL, gpu::TextureType textureType, const QDir& outputDirectory); + TextureBaker(const QUrl& textureURL, image::TextureUsage::Type textureType, const QDir& outputDirectory); const QByteArray& getOriginalTexture() const { return _originalTexture; } @@ -50,7 +50,7 @@ private: QUrl _textureURL; QByteArray _originalTexture; - gpu::TextureType _textureType; + image::TextureUsage::Type _textureType; QDir _outputDirectory; QString _bakedTextureFileName; diff --git a/tools/oven/src/ui/SkyboxBakeWidget.cpp b/tools/oven/src/ui/SkyboxBakeWidget.cpp index d0da0a98ba..d5c280aebd 100644 --- a/tools/oven/src/ui/SkyboxBakeWidget.cpp +++ b/tools/oven/src/ui/SkyboxBakeWidget.cpp @@ -177,7 +177,7 @@ void SkyboxBakeWidget::bakeButtonClicked() { // everything seems to be in place, kick off a bake for this skybox now auto baker = std::unique_ptr { - new TextureBaker(skyboxToBakeURL, gpu::CUBE_TEXTURE, outputDirectory.absolutePath()) + new TextureBaker(skyboxToBakeURL, image::TextureUsage::CUBE_TEXTURE, outputDirectory.absolutePath()) }; // move the baker to a worker thread From 2637040f174d99828acac297194a8db57f0e566f Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 26 Apr 2017 12:47:25 -0700 Subject: [PATCH 69/71] remove skybox baking from domain baking --- tools/oven/src/DomainBaker.cpp | 44 +++++++++++++++++----------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/tools/oven/src/DomainBaker.cpp b/tools/oven/src/DomainBaker.cpp index b93e852daa..cb2a6bca29 100644 --- a/tools/oven/src/DomainBaker.cpp +++ b/tools/oven/src/DomainBaker.cpp @@ -191,28 +191,28 @@ void DomainBaker::enumerateEntities() { _entitiesNeedingRewrite.insert(modelURL, *it); } } else { - // We check now to see if we have either a texture for a skybox or a keylight, or both. - if (entity.contains(ENTITY_SKYBOX_KEY)) { - auto skyboxObject = entity[ENTITY_SKYBOX_KEY].toObject(); - if (skyboxObject.contains(ENTITY_SKYBOX_URL_KEY)) { - // we have a URL to a skybox, grab it - QUrl skyboxURL { skyboxObject[ENTITY_SKYBOX_URL_KEY].toString() }; - - // setup a bake of the skybox - bakeSkybox(skyboxURL, *it); - } - } - - if (entity.contains(ENTITY_KEYLIGHT_KEY)) { - auto keyLightObject = entity[ENTITY_KEYLIGHT_KEY].toObject(); - if (keyLightObject.contains(ENTITY_KEYLIGHT_AMBIENT_URL_KEY)) { - // we have a URL to a skybox, grab it - QUrl skyboxURL { keyLightObject[ENTITY_KEYLIGHT_AMBIENT_URL_KEY].toString() }; - - // setup a bake of the skybox - bakeSkybox(skyboxURL, *it); - } - } +// // We check now to see if we have either a texture for a skybox or a keylight, or both. +// if (entity.contains(ENTITY_SKYBOX_KEY)) { +// auto skyboxObject = entity[ENTITY_SKYBOX_KEY].toObject(); +// if (skyboxObject.contains(ENTITY_SKYBOX_URL_KEY)) { +// // we have a URL to a skybox, grab it +// QUrl skyboxURL { skyboxObject[ENTITY_SKYBOX_URL_KEY].toString() }; +// +// // setup a bake of the skybox +// bakeSkybox(skyboxURL, *it); +// } +// } +// +// if (entity.contains(ENTITY_KEYLIGHT_KEY)) { +// auto keyLightObject = entity[ENTITY_KEYLIGHT_KEY].toObject(); +// if (keyLightObject.contains(ENTITY_KEYLIGHT_AMBIENT_URL_KEY)) { +// // we have a URL to a skybox, grab it +// QUrl skyboxURL { keyLightObject[ENTITY_KEYLIGHT_AMBIENT_URL_KEY].toString() }; +// +// // setup a bake of the skybox +// bakeSkybox(skyboxURL, *it); +// } +// } } } } From b422ec3f88af9a9d979611c2d17ffb4783735cdf Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 2 May 2017 18:04:23 -0700 Subject: [PATCH 70/71] also set the relative filename to fix double references --- tools/oven/src/FBXBaker.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tools/oven/src/FBXBaker.cpp b/tools/oven/src/FBXBaker.cpp index 26aea7a596..8a72784d7c 100644 --- a/tools/oven/src/FBXBaker.cpp +++ b/tools/oven/src/FBXBaker.cpp @@ -375,6 +375,10 @@ void FBXBaker::rewriteAndBakeSceneTextures() { // write the new filename into the FBX scene fileTexture->SetFileName(bakedTextureFilePath.toLocal8Bit()); + // write the relative filename to be the baked texture file name since it will + // be right beside the FBX + fileTexture->SetRelativeFileName(bakedTextureFileName.toLocal8Bit().constData()); + // figure out the URL to this texture, embedded or external auto urlToTexture = getTextureURL(textureFileInfo, fileTexture); From fe280ab103abb7a5594e61ed9f2aaf5886d5e5d7 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 3 May 2017 14:24:23 -0700 Subject: [PATCH 71/71] remember the browse directory for model baker --- tools/oven/src/ui/ModelBakeWidget.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tools/oven/src/ui/ModelBakeWidget.cpp b/tools/oven/src/ui/ModelBakeWidget.cpp index 7f2a0c74b5..c696fbad26 100644 --- a/tools/oven/src/ui/ModelBakeWidget.cpp +++ b/tools/oven/src/ui/ModelBakeWidget.cpp @@ -119,12 +119,15 @@ void ModelBakeWidget::chooseFileButtonClicked() { // set the contents of the model file text box to be the path to the selected file _modelLineEdit->setText(selectedFiles.join(',')); - if (_outputDirLineEdit->text().isEmpty()) { - auto directoryOfModel = QFileInfo(selectedFiles[0]).absolutePath(); + auto directoryOfModel = QFileInfo(selectedFiles[0]).absolutePath(); + if (_outputDirLineEdit->text().isEmpty()) { // if our output directory is not yet set, set it to the directory of this model _outputDirLineEdit->setText(directoryOfModel); } + + // save the directory containing the file(s) so we can default to it next time we show the file dialog + _modelStartDirectory.set(directoryOfModel); } }