From bd6a347b272cf541f8d530743d577ae85770700a Mon Sep 17 00:00:00 2001 From: snoyer Date: Wed, 24 Apr 2024 19:02:48 +0400 Subject: [PATCH 01/25] add screenshot feature --- application/F3DStarter.cxx | 143 +++++++++++++++++++++++++++++++++++++ application/F3DStarter.h | 5 ++ 2 files changed, 148 insertions(+) diff --git a/application/F3DStarter.cxx b/application/F3DStarter.cxx index 0b32308ad6..3ecd413456 100644 --- a/application/F3DStarter.cxx +++ b/application/F3DStarter.cxx @@ -36,6 +36,7 @@ #include #include #include +#include #include namespace fs = std::filesystem; @@ -291,6 +292,13 @@ int F3DStarter::Start(int argc, char** argv) } return true; } + + if (keySym == "F12") + { + this->SaveScreenshot("{app}/{model}.png"); // TODO read from conf/opts + return true; + } + return false; }); @@ -700,6 +708,141 @@ void F3DStarter::Render() f3d::log::debug("Render done"); } +//---------------------------------------------------------------------------- +void F3DStarter::SaveScreenshot(const std::string& filenameTemplate) +{ + constexpr auto getPlatform = []() + { +#if defined(_WIN32) + return "win32"; +#elif defined(__APPLE__) + return "apple"; +#elif defined(__ANDROID__) + return "android"; +#elif defined(__unix__) + return "unix"; +#else + return "?"; +#endif + }; + + const auto getScreenshotDir = []() + { + for (const std::string& candidate : { "XDG_PICTURES_DIR", "HOME", "USERPROFILE" }) + { + char* val = std::getenv(candidate.c_str()); + if (val != nullptr) + { + const auto path = std::filesystem::path(val); + if (std::filesystem::is_directory(path)) + { + return path; + } + } + } + + return std::filesystem::current_path(); + }; + + const auto applyFilenameTemplate = [&](const std::string& fn_template) + { + std::string filename = fn_template; + + { + std::regex re("\\{app\\}"); + filename = std::regex_replace(filename, re, F3D::AppName); + } + { + std::regex re("\\{version\\}"); + filename = std::regex_replace(filename, re, F3D::AppVersionFull); + } + { + std::regex re("\\{os\\}"); + filename = std::regex_replace(filename, re, getPlatform()); + } + { + std::regex re("\\{model\\}"); + if (std::regex_search(filename, re)) + { + const auto file = this->Internals->FilesList[this->Internals->CurrentFileIndex]; + filename = std::regex_replace(filename, re, file.filename().string()); + } + } + + return std::filesystem::path(filename); + }; + + const auto findAvailableFilename = [&](const std::string& idealFilename) + { + const auto appendFilenameNumber = [](const std::filesystem::path& path, size_t n) + { + std::stringstream ss; + const std::string fn = path.string(); + const std::string::size_type i = fn.find_last_of("."); + if (i != std::string::npos) + { + ss << fn.substr(0, i) << " (" << n << ")" << fn.substr(i); + } + else + { + ss << fn << " (" << n << ")"; + } + return ss.str(); + }; + + std::string fn = idealFilename; + for (size_t i = 2; std::filesystem::exists(fn) && i < 1e8; ++i) + { + fn = appendFilenameNumber(idealFilename, i); + } + if (std::filesystem::exists(fn)) + { + throw std::runtime_error("could not find available filenamne"); + } + return fn; + }; + + std::filesystem::path path = applyFilenameTemplate(filenameTemplate); + if (!path.is_absolute()) + { + path = getScreenshotDir() / path; + } + path = findAvailableFilename(path); + std::filesystem::create_directories(std::filesystem::path(path).parent_path()); + f3d::log::info("saving screenshot to " + path.string()); + + f3d::window& window = this->Internals->Engine->getWindow(); + + std::stringstream cameraMetadata; + { + const auto state = window.getCamera().getState(); + const auto vec3toJson = [](const std::array& v) + { + std::stringstream ss; + ss << "[" << v[0] << ", " << v[1] << ", " << v[2] << "]"; + return ss.str(); + }; + cameraMetadata << "{\n"; + cameraMetadata << " \"pos\": " << vec3toJson(state.pos) << ",\n"; + cameraMetadata << " \"foc\": " << vec3toJson(state.foc) << ",\n"; + cameraMetadata << " \"up\": " << vec3toJson(state.up) << ",\n"; + cameraMetadata << " \"angle\": " << state.angle << "\n"; + cameraMetadata << "}\n"; + } + + window.renderToImage() + .setMetadata("camera", cameraMetadata.str()) + .save(path, f3d::image::SaveFormat::PNG); + + f3d::options& options = this->Internals->Engine->getOptions(); + const std::string light_intensity_key = "render.light.intensity"; + const double intensity = options.getAsDouble(light_intensity_key); + options.set(light_intensity_key, intensity * 5); + this->Render(); + options.set(light_intensity_key, intensity); + this->Render(); +} + //---------------------------------------------------------------------------- int F3DStarter::AddFile(const fs::path& path, bool quiet) { diff --git a/application/F3DStarter.h b/application/F3DStarter.h index 038da83c60..e54a1e26e0 100644 --- a/application/F3DStarter.h +++ b/application/F3DStarter.h @@ -43,6 +43,11 @@ class F3DStarter */ void Render(); + /** + * Trigger a render and save a screenshot to disk according to a filename template. + */ + void SaveScreenshot(const std::string& filenameTemplate); + F3DStarter(); ~F3DStarter(); From 25c899c5893fe06e809c703df03abeba3ba65625 Mon Sep 17 00:00:00 2001 From: snoyer Date: Thu, 25 Apr 2024 17:00:39 +0400 Subject: [PATCH 02/25] add test --- application/F3DOptionsParser.cxx | 6 ++++++ application/F3DOptionsParser.h | 1 + application/F3DStarter.cxx | 24 +++--------------------- application/testing/CMakeLists.txt | 7 +++++++ testing/recordings/TestScreenshot.log | 11 +++++++++++ 5 files changed, 28 insertions(+), 21 deletions(-) create mode 100644 testing/recordings/TestScreenshot.log diff --git a/application/F3DOptionsParser.cxx b/application/F3DOptionsParser.cxx index a685c6e87a..a01349a117 100644 --- a/application/F3DOptionsParser.cxx +++ b/application/F3DOptionsParser.cxx @@ -314,6 +314,7 @@ void ConfigurationOptions::GetOptions(F3DAppOptions& appOptions, f3d::options& o this->DeclareOption(grp0, "watch", "", "Watch current file and automatically reload it whenever it is modified on disk", appOptions.Watch, HasDefault::YES, MayHaveConfig::YES ); this->DeclareOption(grp0, "load-plugins", "", "List of plugins to load separated with a comma", appOptions.Plugins, LocalHasDefaultNo, MayHaveConfig::YES, ""); this->DeclareOption(grp0, "scan-plugins", "", "Scan standard directories for plugins and display available plugins (result can be incomplete)"); + this->DeclareOption(grp0, "screenshot-filename", "", "Screenshot filename", appOptions.ScreenshotFilename, LocalHasDefaultNo, MayHaveConfig::YES, ""); auto grp1 = cxxOptions.add_options("General"); this->DeclareOption(grp1, "verbose", "", "Set verbose level, providing more information about the loaded data in the console output", appOptions.VerboseLevel, HasDefault::YES, MayHaveConfig::YES, "{debug, info, warning, error, quiet}", HasImplicitValue::YES, "debug"); @@ -527,6 +528,11 @@ void ConfigurationOptions::GetOptions(F3DAppOptions& appOptions, f3d::options& o f3d::log::error("Error parsing options: ", ex.what()); throw; } + + if (appOptions.ScreenshotFilename.empty()) + { + appOptions.ScreenshotFilename = "{app}/{model}.png"; + } } //---------------------------------------------------------------------------- diff --git a/application/F3DOptionsParser.h b/application/F3DOptionsParser.h index b085428d2c..d5b8e427e2 100644 --- a/application/F3DOptionsParser.h +++ b/application/F3DOptionsParser.h @@ -25,6 +25,7 @@ struct F3DAppOptions bool GeometryOnly = false; bool GroupGeometries = false; std::string Output = ""; + std::string ScreenshotFilename = ""; std::string Reference = ""; std::string InteractionTestRecordFile = ""; std::string InteractionTestPlayFile = ""; diff --git a/application/F3DStarter.cxx b/application/F3DStarter.cxx index 3ecd413456..57032879f5 100644 --- a/application/F3DStarter.cxx +++ b/application/F3DStarter.cxx @@ -295,7 +295,7 @@ int F3DStarter::Start(int argc, char** argv) if (keySym == "F12") { - this->SaveScreenshot("{app}/{model}.png"); // TODO read from conf/opts + this->SaveScreenshot(this->Internals->AppOptions.ScreenshotFilename); return true; } @@ -711,20 +711,6 @@ void F3DStarter::Render() //---------------------------------------------------------------------------- void F3DStarter::SaveScreenshot(const std::string& filenameTemplate) { - constexpr auto getPlatform = []() - { -#if defined(_WIN32) - return "win32"; -#elif defined(__APPLE__) - return "apple"; -#elif defined(__ANDROID__) - return "android"; -#elif defined(__unix__) - return "unix"; -#else - return "?"; -#endif - }; const auto getScreenshotDir = []() { @@ -756,10 +742,6 @@ void F3DStarter::SaveScreenshot(const std::string& filenameTemplate) std::regex re("\\{version\\}"); filename = std::regex_replace(filename, re, F3D::AppVersionFull); } - { - std::regex re("\\{os\\}"); - filename = std::regex_replace(filename, re, getPlatform()); - } { std::regex re("\\{model\\}"); if (std::regex_search(filename, re)) @@ -781,11 +763,11 @@ void F3DStarter::SaveScreenshot(const std::string& filenameTemplate) const std::string::size_type i = fn.find_last_of("."); if (i != std::string::npos) { - ss << fn.substr(0, i) << " (" << n << ")" << fn.substr(i); + ss << fn.substr(0, i) << "_" << n << fn.substr(i); } else { - ss << fn << " (" << n << ")"; + ss << fn << "_" << n; } return ss.str(); }; diff --git a/application/testing/CMakeLists.txt b/application/testing/CMakeLists.txt index 7cb8b76d27..1945f9f82e 100644 --- a/application/testing/CMakeLists.txt +++ b/application/testing/CMakeLists.txt @@ -215,6 +215,13 @@ if(NOT F3D_MACOS_BUNDLE) f3d_test(NAME TestColorMapFile DATA dragon.vtu ARGS --colormap-file=magma.png --scalars --comp=1 DEFAULT_LIGHTS) endif() +# Screenshot Interaction +set(_screenshot_dir ${CMAKE_BINARY_DIR}/Testing/Temporary/ss) +add_test(NAME f3d::TestClearScreenshots COMMAND ${CMAKE_COMMAND} -E remove_directory ${_screenshot_dir}) +f3d_test(NAME TestScreenshot DATA suzanne.ply ARGS --screenshot-filename=${_screenshot_dir}/{app}-{version}/{model}.png INTERACTION DEFAULT_LIGHTS DEPENDS TestClearScreenshots NO_BASELINE) +f3d_test(NAME TestScreenshotFile1 DATA suzanne.ply ARGS --ref=${_screenshot_dir}/${PROJECT_NAME}-${F3D_VERSION}/suzanne.ply.png DEPENDS TestScreenshot NO_BASELINE) +f3d_test(NAME TestScreenshotFile2 DATA suzanne.ply ARGS --ref=${_screenshot_dir}/${PROJECT_NAME}-${F3D_VERSION}/suzanne.ply_2.png DEPENDS TestScreenshot NO_BASELINE) + if(NOT APPLE OR VTK_VERSION VERSION_GREATER_EQUAL 9.3.0) f3d_test(NAME TestTextureColor DATA WaterBottle.glb ARGS --geometry-only --texture-base-color=${F3D_SOURCE_DIR}/testing/data/albedo_mod.png --translucency-support DEFAULT_LIGHTS) endif() diff --git a/testing/recordings/TestScreenshot.log b/testing/recordings/TestScreenshot.log new file mode 100644 index 0000000000..6b3ae91604 --- /dev/null +++ b/testing/recordings/TestScreenshot.log @@ -0,0 +1,11 @@ +# StreamVersion 1.2 +ExposeEvent 0 599 0 0 0 0 0 +RenderEvent 0 599 0 0 0 0 0 +KeyPressEvent 916 614 0 0 1 F12 0 +RenderEvent 916 614 0 0 1 F12 0 +CharEvent 916 614 0 0 1 F12 0 +KeyReleaseEvent 916 614 0 0 1 F12 0 +KeyPressEvent 916 614 0 0 1 F12 0 +RenderEvent 916 614 0 0 1 F12 0 +CharEvent 916 614 0 0 1 F12 0 +KeyReleaseEvent 916 614 0 0 1 F12 0 \ No newline at end of file From 86333e484edda50bc1a145804629a757c3fa1062 Mon Sep 17 00:00:00 2001 From: snoyer Date: Thu, 25 Apr 2024 18:53:22 +0400 Subject: [PATCH 03/25] fix `range-loop-construct` error --- application/F3DStarter.cxx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/application/F3DStarter.cxx b/application/F3DStarter.cxx index 57032879f5..a0e6df8786 100644 --- a/application/F3DStarter.cxx +++ b/application/F3DStarter.cxx @@ -714,9 +714,9 @@ void F3DStarter::SaveScreenshot(const std::string& filenameTemplate) const auto getScreenshotDir = []() { - for (const std::string& candidate : { "XDG_PICTURES_DIR", "HOME", "USERPROFILE" }) + for (const std::string_view& candidate : { "XDG_PICTURES_DIR", "HOME", "USERPROFILE" }) { - char* val = std::getenv(candidate.c_str()); + char* val = std::getenv(candidate.data()); if (val != nullptr) { const auto path = std::filesystem::path(val); From a310f456ba30b1b7e958f0eed68bd0e5c87fc14f Mon Sep 17 00:00:00 2001 From: snoyer Date: Thu, 25 Apr 2024 18:57:28 +0400 Subject: [PATCH 04/25] actually fix `range-loop-construct` error --- application/F3DStarter.cxx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/application/F3DStarter.cxx b/application/F3DStarter.cxx index a0e6df8786..f3d4873600 100644 --- a/application/F3DStarter.cxx +++ b/application/F3DStarter.cxx @@ -714,9 +714,9 @@ void F3DStarter::SaveScreenshot(const std::string& filenameTemplate) const auto getScreenshotDir = []() { - for (const std::string_view& candidate : { "XDG_PICTURES_DIR", "HOME", "USERPROFILE" }) + for (const char* const& candidate : { "XDG_PICTURES_DIR", "HOME", "USERPROFILE" }) { - char* val = std::getenv(candidate.data()); + char* val = std::getenv(candidate); if (val != nullptr) { const auto path = std::filesystem::path(val); From 5d551acd920f8505c9cc51a1898bef6831d0a4d1 Mon Sep 17 00:00:00 2001 From: snoyer Date: Thu, 25 Apr 2024 19:14:13 +0400 Subject: [PATCH 05/25] bug fix --- application/F3DStarter.cxx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/application/F3DStarter.cxx b/application/F3DStarter.cxx index f3d4873600..68a5eaf91d 100644 --- a/application/F3DStarter.cxx +++ b/application/F3DStarter.cxx @@ -740,7 +740,7 @@ void F3DStarter::SaveScreenshot(const std::string& filenameTemplate) } { std::regex re("\\{version\\}"); - filename = std::regex_replace(filename, re, F3D::AppVersionFull); + filename = std::regex_replace(filename, re, F3D::AppVersion); } { std::regex re("\\{model\\}"); @@ -789,7 +789,7 @@ void F3DStarter::SaveScreenshot(const std::string& filenameTemplate) { path = getScreenshotDir() / path; } - path = findAvailableFilename(path); + path = findAvailableFilename(path.string()); std::filesystem::create_directories(std::filesystem::path(path).parent_path()); f3d::log::info("saving screenshot to " + path.string()); From e6a30da2e1a9578a1b77682d7a3a1c74b150f423 Mon Sep 17 00:00:00 2001 From: snoyer Date: Thu, 25 Apr 2024 19:29:05 +0400 Subject: [PATCH 06/25] fix test --- application/testing/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/testing/CMakeLists.txt b/application/testing/CMakeLists.txt index 1945f9f82e..b671887f85 100644 --- a/application/testing/CMakeLists.txt +++ b/application/testing/CMakeLists.txt @@ -218,7 +218,7 @@ endif() # Screenshot Interaction set(_screenshot_dir ${CMAKE_BINARY_DIR}/Testing/Temporary/ss) add_test(NAME f3d::TestClearScreenshots COMMAND ${CMAKE_COMMAND} -E remove_directory ${_screenshot_dir}) -f3d_test(NAME TestScreenshot DATA suzanne.ply ARGS --screenshot-filename=${_screenshot_dir}/{app}-{version}/{model}.png INTERACTION DEFAULT_LIGHTS DEPENDS TestClearScreenshots NO_BASELINE) +f3d_test(NAME TestScreenshot DATA suzanne.ply ARGS --screenshot-filename=${_screenshot_dir}/{app}-{version}/{model}.png INTERACTION DEPENDS TestClearScreenshots NO_BASELINE) f3d_test(NAME TestScreenshotFile1 DATA suzanne.ply ARGS --ref=${_screenshot_dir}/${PROJECT_NAME}-${F3D_VERSION}/suzanne.ply.png DEPENDS TestScreenshot NO_BASELINE) f3d_test(NAME TestScreenshotFile2 DATA suzanne.ply ARGS --ref=${_screenshot_dir}/${PROJECT_NAME}-${F3D_VERSION}/suzanne.ply_2.png DEPENDS TestScreenshot NO_BASELINE) From 0f5fd1954d3727de30cc03bdc7162def55feb620 Mon Sep 17 00:00:00 2001 From: snoyer Date: Thu, 25 Apr 2024 19:29:30 +0400 Subject: [PATCH 07/25] `path` to `string` again --- application/F3DStarter.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/F3DStarter.cxx b/application/F3DStarter.cxx index 68a5eaf91d..5f987b63b9 100644 --- a/application/F3DStarter.cxx +++ b/application/F3DStarter.cxx @@ -814,7 +814,7 @@ void F3DStarter::SaveScreenshot(const std::string& filenameTemplate) window.renderToImage() .setMetadata("camera", cameraMetadata.str()) - .save(path, f3d::image::SaveFormat::PNG); + .save(path.string(), f3d::image::SaveFormat::PNG); f3d::options& options = this->Internals->Engine->getOptions(); const std::string light_intensity_key = "render.light.intensity"; From 20186113333cfe9bdd5361285996f6e401502487 Mon Sep 17 00:00:00 2001 From: snoyer Date: Thu, 25 Apr 2024 19:54:44 +0400 Subject: [PATCH 08/25] add explicit condition for tests --- application/testing/CMakeLists.txt | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/application/testing/CMakeLists.txt b/application/testing/CMakeLists.txt index b671887f85..7a6366abca 100644 --- a/application/testing/CMakeLists.txt +++ b/application/testing/CMakeLists.txt @@ -216,11 +216,14 @@ if(NOT F3D_MACOS_BUNDLE) endif() # Screenshot Interaction -set(_screenshot_dir ${CMAKE_BINARY_DIR}/Testing/Temporary/ss) -add_test(NAME f3d::TestClearScreenshots COMMAND ${CMAKE_COMMAND} -E remove_directory ${_screenshot_dir}) -f3d_test(NAME TestScreenshot DATA suzanne.ply ARGS --screenshot-filename=${_screenshot_dir}/{app}-{version}/{model}.png INTERACTION DEPENDS TestClearScreenshots NO_BASELINE) -f3d_test(NAME TestScreenshotFile1 DATA suzanne.ply ARGS --ref=${_screenshot_dir}/${PROJECT_NAME}-${F3D_VERSION}/suzanne.ply.png DEPENDS TestScreenshot NO_BASELINE) -f3d_test(NAME TestScreenshotFile2 DATA suzanne.ply ARGS --ref=${_screenshot_dir}/${PROJECT_NAME}-${F3D_VERSION}/suzanne.ply_2.png DEPENDS TestScreenshot NO_BASELINE) + +if(F3D_TESTING_ENABLE_LONG_TIMEOUT_TESTS) + set(_screenshot_dir ${CMAKE_BINARY_DIR}/Testing/Temporary/ss) + add_test(NAME f3d::TestClearScreenshots COMMAND ${CMAKE_COMMAND} -E remove_directory ${_screenshot_dir}) + f3d_test(NAME TestScreenshot DATA suzanne.ply ARGS --screenshot-filename=${_screenshot_dir}/{app}-{version}/{model}.png INTERACTION DEPENDS TestClearScreenshots NO_BASELINE) + f3d_test(NAME TestScreenshotFile1 DATA suzanne.ply ARGS --ref=${_screenshot_dir}/${PROJECT_NAME}-${F3D_VERSION}/suzanne.ply.png DEPENDS TestScreenshot NO_BASELINE LONG_TIMEOUT) + f3d_test(NAME TestScreenshotFile2 DATA suzanne.ply ARGS --ref=${_screenshot_dir}/${PROJECT_NAME}-${F3D_VERSION}/suzanne.ply_2.png DEPENDS TestScreenshot NO_BASELINE LONG_TIMEOUT) +endif() if(NOT APPLE OR VTK_VERSION VERSION_GREATER_EQUAL 9.3.0) f3d_test(NAME TestTextureColor DATA WaterBottle.glb ARGS --geometry-only --texture-base-color=${F3D_SOURCE_DIR}/testing/data/albedo_mod.png --translucency-support DEFAULT_LIGHTS) From b995a7ce95e0a8be0bef1fe21d8a15f9bec5df7f Mon Sep 17 00:00:00 2001 From: snoyer Date: Thu, 25 Apr 2024 20:08:01 +0400 Subject: [PATCH 09/25] static analysis --- application/F3DStarter.cxx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/application/F3DStarter.cxx b/application/F3DStarter.cxx index 5f987b63b9..b68df8dfdf 100644 --- a/application/F3DStarter.cxx +++ b/application/F3DStarter.cxx @@ -719,7 +719,7 @@ void F3DStarter::SaveScreenshot(const std::string& filenameTemplate) char* val = std::getenv(candidate); if (val != nullptr) { - const auto path = std::filesystem::path(val); + std::filesystem::path path(val); if (std::filesystem::is_directory(path)) { return path; @@ -760,7 +760,7 @@ void F3DStarter::SaveScreenshot(const std::string& filenameTemplate) { std::stringstream ss; const std::string fn = path.string(); - const std::string::size_type i = fn.find_last_of("."); + const std::string::size_type i = fn.find_last_of('.'); if (i != std::string::npos) { ss << fn.substr(0, i) << "_" << n << fn.substr(i); From b16d0ae64558f513db0fc077c9289e963db1a2d5 Mon Sep 17 00:00:00 2001 From: snoyer Date: Mon, 29 Apr 2024 10:53:33 +0400 Subject: [PATCH 10/25] improve testing --- application/testing/CMakeLists.txt | 6 +++++- testing/recordings/TestScreenshotUser.log | 11 +++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 testing/recordings/TestScreenshotUser.log diff --git a/application/testing/CMakeLists.txt b/application/testing/CMakeLists.txt index 7a6366abca..4a3b8d1910 100644 --- a/application/testing/CMakeLists.txt +++ b/application/testing/CMakeLists.txt @@ -216,13 +216,17 @@ if(NOT F3D_MACOS_BUNDLE) endif() # Screenshot Interaction - if(F3D_TESTING_ENABLE_LONG_TIMEOUT_TESTS) set(_screenshot_dir ${CMAKE_BINARY_DIR}/Testing/Temporary/ss) add_test(NAME f3d::TestClearScreenshots COMMAND ${CMAKE_COMMAND} -E remove_directory ${_screenshot_dir}) f3d_test(NAME TestScreenshot DATA suzanne.ply ARGS --screenshot-filename=${_screenshot_dir}/{app}-{version}/{model}.png INTERACTION DEPENDS TestClearScreenshots NO_BASELINE) f3d_test(NAME TestScreenshotFile1 DATA suzanne.ply ARGS --ref=${_screenshot_dir}/${PROJECT_NAME}-${F3D_VERSION}/suzanne.ply.png DEPENDS TestScreenshot NO_BASELINE LONG_TIMEOUT) f3d_test(NAME TestScreenshotFile2 DATA suzanne.ply ARGS --ref=${_screenshot_dir}/${PROJECT_NAME}-${F3D_VERSION}/suzanne.ply_2.png DEPENDS TestScreenshot NO_BASELINE LONG_TIMEOUT) + + f3d_test(NAME TestScreenshotUser DATA suzanne.ply ARGS --screenshot-filename={app}/screenshot INTERACTION DEPENDS TestClearScreenshots NO_BASELINE) + set_tests_properties(f3d::TestScreenshotUser PROPERTIES ENVIRONMENT "XDG_PICTURES_DIR=${_screenshot_dir}/user;HOME=${_screenshot_dir}/user;USERPROFILE=${_screenshot_dir}/user") + f3d_test(NAME TestScreenshotUserFile1 DATA suzanne.ply ARGS --ref=${_screenshot_dir}/user/${PROJECT_NAME}/screenshot DEPENDS TestScreenshotUser NO_BASELINE LONG_TIMEOUT) + f3d_test(NAME TestScreenshotUserFile2 DATA suzanne.ply ARGS --ref=${_screenshot_dir}/user/${PROJECT_NAME}/screenshot_2 DEPENDS TestScreenshotUser NO_BASELINE LONG_TIMEOUT) endif() if(NOT APPLE OR VTK_VERSION VERSION_GREATER_EQUAL 9.3.0) diff --git a/testing/recordings/TestScreenshotUser.log b/testing/recordings/TestScreenshotUser.log new file mode 100644 index 0000000000..6b3ae91604 --- /dev/null +++ b/testing/recordings/TestScreenshotUser.log @@ -0,0 +1,11 @@ +# StreamVersion 1.2 +ExposeEvent 0 599 0 0 0 0 0 +RenderEvent 0 599 0 0 0 0 0 +KeyPressEvent 916 614 0 0 1 F12 0 +RenderEvent 916 614 0 0 1 F12 0 +CharEvent 916 614 0 0 1 F12 0 +KeyReleaseEvent 916 614 0 0 1 F12 0 +KeyPressEvent 916 614 0 0 1 F12 0 +RenderEvent 916 614 0 0 1 F12 0 +CharEvent 916 614 0 0 1 F12 0 +KeyReleaseEvent 916 614 0 0 1 F12 0 \ No newline at end of file From 9afc2012bf241e186fe1fc002e40defd7e0a73dc Mon Sep 17 00:00:00 2001 From: snoyer Date: Tue, 30 Apr 2024 10:50:12 +0400 Subject: [PATCH 11/25] refactor after review --- application/F3DOptionsParser.cxx | 2 +- application/F3DStarter.cxx | 182 ++++++++++++---------- application/testing/CMakeLists.txt | 10 +- testing/recordings/TestScreenshot.log | 2 +- testing/recordings/TestScreenshotUser.log | 2 +- 5 files changed, 107 insertions(+), 91 deletions(-) diff --git a/application/F3DOptionsParser.cxx b/application/F3DOptionsParser.cxx index a01349a117..23da89b064 100644 --- a/application/F3DOptionsParser.cxx +++ b/application/F3DOptionsParser.cxx @@ -531,7 +531,7 @@ void ConfigurationOptions::GetOptions(F3DAppOptions& appOptions, f3d::options& o if (appOptions.ScreenshotFilename.empty()) { - appOptions.ScreenshotFilename = "{app}/{model}.png"; + appOptions.ScreenshotFilename = "{app}/{model}_{n}.png"; } } diff --git a/application/F3DStarter.cxx b/application/F3DStarter.cxx index b68df8dfdf..3f50800a9f 100644 --- a/application/F3DStarter.cxx +++ b/application/F3DStarter.cxx @@ -166,6 +166,92 @@ class F3DStarter::F3DInternals } } + void addOutputImageMetadata(f3d::image& image) + { + std::stringstream cameraMetadata; + { + const auto state = Engine->getWindow().getCamera().getState(); + const auto vec3toJson = [](const std::array& v) + { + std::stringstream ss; + ss << "[" << v[0] << ", " << v[1] << ", " << v[2] << "]"; + return ss.str(); + }; + cameraMetadata << "{\n"; + cameraMetadata << " \"pos\": " << vec3toJson(state.pos) << ",\n"; + cameraMetadata << " \"foc\": " << vec3toJson(state.foc) << ",\n"; + cameraMetadata << " \"up\": " << vec3toJson(state.up) << ",\n"; + cameraMetadata << " \"angle\": " << state.angle << "\n"; + cameraMetadata << "}\n"; + } + + image.setMetadata("camera", cameraMetadata.str()); + } + + /** + * Substitute the following variables in a filename template: + * - `{app}`: application name (eg. `F3D`) + * - `{version}`: application version (eg. `2.4.0`) + * - `{version_full}`: full application version (eg. `2.4.0-abcdefgh`) + * - `{model}`: current model filename without extension (eg. `foo` for `/home/user/foo.glb`) + * - `{model.ext}`: current model filename with extension (eg. `foo.glb` for `/home/user/foo.glb`) + * - `{n}`: auto-incremented number to make filename unique + */ + std::filesystem::path applyFilenameTemplate(const std::string& filenameTemplate) + { + std::string filename = filenameTemplate; + + { + std::regex re("\\{app\\}"); + filename = std::regex_replace(filename, re, F3D::AppName); + } + { + std::regex re("\\{version\\}"); + filename = std::regex_replace(filename, re, F3D::AppVersion); + } + { + std::regex re("\\{version_full\\}"); + filename = std::regex_replace(filename, re, F3D::AppVersionFull); + } + + { + std::regex re("\\{model\\}"); + if (std::regex_search(filename, re)) + { + const auto file = FilesList[CurrentFileIndex]; + filename = std::regex_replace(filename, re, file.stem().string()); + } + } + { + std::regex re("\\{model\\.ext\\}"); + if (std::regex_search(filename, re)) + { + const auto file = FilesList[CurrentFileIndex]; + filename = std::regex_replace(filename, re, file.filename().string()); + } + } + + { + /* try and find non-existing filename; must be done last, assumes full path */ + std::regex re("\\{n\\}"); + if (std::regex_search(filename, re)) + { + std::string fn = std::regex_replace(filename, re, "1"); + for (size_t i = 2; std::filesystem::exists(fn) && i < 1000000; ++i) + { + fn = std::regex_replace(filename, re, std::to_string(i)); + } + if (std::filesystem::exists(fn)) + { + throw std::runtime_error("could not find available unique filename"); + } + filename = fn; + } + } + + return std::filesystem::path(filename); + } + F3DOptionsParser Parser; F3DAppOptions AppOptions; f3d::options DynamicOptions; @@ -481,6 +567,8 @@ int F3DStarter::Start(int argc, char** argv) } f3d::image img = window.renderToImage(this->Internals->AppOptions.NoBackground); + this->Internals->addOutputImageMetadata(img); + if (renderToStdout) { const auto buffer = img.saveBuffer(); @@ -489,8 +577,10 @@ int F3DStarter::Start(int argc, char** argv) } else { - img.save(this->Internals->AppOptions.Output); - f3d::log::debug("Output image saved to ", this->Internals->AppOptions.Output); + std::filesystem::path path = + this->Internals->applyFilenameTemplate(this->Internals->AppOptions.Output); + img.save(path); + f3d::log::debug("Output image saved to ", path); } if (this->Internals->FilesList.size() > 1) @@ -730,91 +820,17 @@ void F3DStarter::SaveScreenshot(const std::string& filenameTemplate) return std::filesystem::current_path(); }; - const auto applyFilenameTemplate = [&](const std::string& fn_template) - { - std::string filename = fn_template; - - { - std::regex re("\\{app\\}"); - filename = std::regex_replace(filename, re, F3D::AppName); - } - { - std::regex re("\\{version\\}"); - filename = std::regex_replace(filename, re, F3D::AppVersion); - } - { - std::regex re("\\{model\\}"); - if (std::regex_search(filename, re)) - { - const auto file = this->Internals->FilesList[this->Internals->CurrentFileIndex]; - filename = std::regex_replace(filename, re, file.filename().string()); - } - } - - return std::filesystem::path(filename); - }; - - const auto findAvailableFilename = [&](const std::string& idealFilename) - { - const auto appendFilenameNumber = [](const std::filesystem::path& path, size_t n) - { - std::stringstream ss; - const std::string fn = path.string(); - const std::string::size_type i = fn.find_last_of('.'); - if (i != std::string::npos) - { - ss << fn.substr(0, i) << "_" << n << fn.substr(i); - } - else - { - ss << fn << "_" << n; - } - return ss.str(); - }; + std::filesystem::path pathTemplate = std::filesystem::path(filenameTemplate); + std::filesystem::path fullPathTemplate = + pathTemplate.is_absolute() ? pathTemplate : getScreenshotDir() / pathTemplate; + std::filesystem::path path = this->Internals->applyFilenameTemplate(fullPathTemplate.string()); - std::string fn = idealFilename; - for (size_t i = 2; std::filesystem::exists(fn) && i < 1e8; ++i) - { - fn = appendFilenameNumber(idealFilename, i); - } - if (std::filesystem::exists(fn)) - { - throw std::runtime_error("could not find available filenamne"); - } - return fn; - }; - - std::filesystem::path path = applyFilenameTemplate(filenameTemplate); - if (!path.is_absolute()) - { - path = getScreenshotDir() / path; - } - path = findAvailableFilename(path.string()); std::filesystem::create_directories(std::filesystem::path(path).parent_path()); f3d::log::info("saving screenshot to " + path.string()); - f3d::window& window = this->Internals->Engine->getWindow(); - - std::stringstream cameraMetadata; - { - const auto state = window.getCamera().getState(); - const auto vec3toJson = [](const std::array& v) - { - std::stringstream ss; - ss << "[" << v[0] << ", " << v[1] << ", " << v[2] << "]"; - return ss.str(); - }; - cameraMetadata << "{\n"; - cameraMetadata << " \"pos\": " << vec3toJson(state.pos) << ",\n"; - cameraMetadata << " \"foc\": " << vec3toJson(state.foc) << ",\n"; - cameraMetadata << " \"up\": " << vec3toJson(state.up) << ",\n"; - cameraMetadata << " \"angle\": " << state.angle << "\n"; - cameraMetadata << "}\n"; - } - - window.renderToImage() - .setMetadata("camera", cameraMetadata.str()) - .save(path.string(), f3d::image::SaveFormat::PNG); + f3d::image img = this->Internals->Engine->getWindow().renderToImage(); + this->Internals->addOutputImageMetadata(img); + img.save(path.string(), f3d::image::SaveFormat::PNG); f3d::options& options = this->Internals->Engine->getOptions(); const std::string light_intensity_key = "render.light.intensity"; diff --git a/application/testing/CMakeLists.txt b/application/testing/CMakeLists.txt index 4a3b8d1910..e60c8723a9 100644 --- a/application/testing/CMakeLists.txt +++ b/application/testing/CMakeLists.txt @@ -219,14 +219,14 @@ endif() if(F3D_TESTING_ENABLE_LONG_TIMEOUT_TESTS) set(_screenshot_dir ${CMAKE_BINARY_DIR}/Testing/Temporary/ss) add_test(NAME f3d::TestClearScreenshots COMMAND ${CMAKE_COMMAND} -E remove_directory ${_screenshot_dir}) - f3d_test(NAME TestScreenshot DATA suzanne.ply ARGS --screenshot-filename=${_screenshot_dir}/{app}-{version}/{model}.png INTERACTION DEPENDS TestClearScreenshots NO_BASELINE) - f3d_test(NAME TestScreenshotFile1 DATA suzanne.ply ARGS --ref=${_screenshot_dir}/${PROJECT_NAME}-${F3D_VERSION}/suzanne.ply.png DEPENDS TestScreenshot NO_BASELINE LONG_TIMEOUT) + f3d_test(NAME TestScreenshot DATA suzanne.ply ARGS --screenshot-filename=${_screenshot_dir}/{app}-{version}/{model.ext}_{n}.png INTERACTION DEPENDS TestClearScreenshots NO_BASELINE) + f3d_test(NAME TestScreenshotFile1 DATA suzanne.ply ARGS --ref=${_screenshot_dir}/${PROJECT_NAME}-${F3D_VERSION}/suzanne.ply_1.png DEPENDS TestScreenshot NO_BASELINE LONG_TIMEOUT) f3d_test(NAME TestScreenshotFile2 DATA suzanne.ply ARGS --ref=${_screenshot_dir}/${PROJECT_NAME}-${F3D_VERSION}/suzanne.ply_2.png DEPENDS TestScreenshot NO_BASELINE LONG_TIMEOUT) - f3d_test(NAME TestScreenshotUser DATA suzanne.ply ARGS --screenshot-filename={app}/screenshot INTERACTION DEPENDS TestClearScreenshots NO_BASELINE) + f3d_test(NAME TestScreenshotUser DATA suzanne.ply ARGS --screenshot-filename={app}-{version_full}/{model}.png INTERACTION DEPENDS TestClearScreenshots NO_BASELINE) set_tests_properties(f3d::TestScreenshotUser PROPERTIES ENVIRONMENT "XDG_PICTURES_DIR=${_screenshot_dir}/user;HOME=${_screenshot_dir}/user;USERPROFILE=${_screenshot_dir}/user") - f3d_test(NAME TestScreenshotUserFile1 DATA suzanne.ply ARGS --ref=${_screenshot_dir}/user/${PROJECT_NAME}/screenshot DEPENDS TestScreenshotUser NO_BASELINE LONG_TIMEOUT) - f3d_test(NAME TestScreenshotUserFile2 DATA suzanne.ply ARGS --ref=${_screenshot_dir}/user/${PROJECT_NAME}/screenshot_2 DEPENDS TestScreenshotUser NO_BASELINE LONG_TIMEOUT) + f3d_test(NAME TestScreenshotUserFile1 DATA suzanne.ply ARGS --ref=${_screenshot_dir}/user/${PROJECT_NAME}-${F3D_VERSION_FULL}/suzanne.png DEPENDS TestScreenshotUser NO_BASELINE LONG_TIMEOUT) + f3d_test(NAME TestScreenshotUserFile2 DATA suzanne.ply ARGS --ref=${_screenshot_dir}/user/${PROJECT_NAME}-${F3D_VERSION_FULL}/suzanne.png DEPENDS TestScreenshotUser NO_BASELINE LONG_TIMEOUT) endif() if(NOT APPLE OR VTK_VERSION VERSION_GREATER_EQUAL 9.3.0) diff --git a/testing/recordings/TestScreenshot.log b/testing/recordings/TestScreenshot.log index 6b3ae91604..e5e71ab998 100644 --- a/testing/recordings/TestScreenshot.log +++ b/testing/recordings/TestScreenshot.log @@ -8,4 +8,4 @@ KeyReleaseEvent 916 614 0 0 1 F12 0 KeyPressEvent 916 614 0 0 1 F12 0 RenderEvent 916 614 0 0 1 F12 0 CharEvent 916 614 0 0 1 F12 0 -KeyReleaseEvent 916 614 0 0 1 F12 0 \ No newline at end of file +KeyReleaseEvent 916 614 0 0 1 F12 0 diff --git a/testing/recordings/TestScreenshotUser.log b/testing/recordings/TestScreenshotUser.log index 6b3ae91604..e5e71ab998 100644 --- a/testing/recordings/TestScreenshotUser.log +++ b/testing/recordings/TestScreenshotUser.log @@ -8,4 +8,4 @@ KeyReleaseEvent 916 614 0 0 1 F12 0 KeyPressEvent 916 614 0 0 1 F12 0 RenderEvent 916 614 0 0 1 F12 0 CharEvent 916 614 0 0 1 F12 0 -KeyReleaseEvent 916 614 0 0 1 F12 0 \ No newline at end of file +KeyReleaseEvent 916 614 0 0 1 F12 0 From f3e7450e1e75ed0b0cb4482f87f2b6f1005f1833 Mon Sep 17 00:00:00 2001 From: snoyer Date: Tue, 30 Apr 2024 11:12:25 +0400 Subject: [PATCH 12/25] static analysis --- application/F3DStarter.cxx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/application/F3DStarter.cxx b/application/F3DStarter.cxx index 3f50800a9f..fc9b5b33e0 100644 --- a/application/F3DStarter.cxx +++ b/application/F3DStarter.cxx @@ -223,7 +223,7 @@ class F3DStarter::F3DInternals } } { - std::regex re("\\{model\\.ext\\}"); + std::regex re("\\{model[.]ext\\}"); if (std::regex_search(filename, re)) { const auto file = FilesList[CurrentFileIndex]; @@ -249,7 +249,7 @@ class F3DStarter::F3DInternals } } - return std::filesystem::path(filename); + return { filename }; } F3DOptionsParser Parser; From 921c951b3d2d7c99b2ae6ffba1f5aecba5272191 Mon Sep 17 00:00:00 2001 From: snoyer Date: Tue, 30 Apr 2024 11:23:40 +0400 Subject: [PATCH 13/25] `path` to `string` again, again --- application/F3DStarter.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/F3DStarter.cxx b/application/F3DStarter.cxx index fc9b5b33e0..48e2445746 100644 --- a/application/F3DStarter.cxx +++ b/application/F3DStarter.cxx @@ -579,7 +579,7 @@ int F3DStarter::Start(int argc, char** argv) { std::filesystem::path path = this->Internals->applyFilenameTemplate(this->Internals->AppOptions.Output); - img.save(path); + img.save(path.string()); f3d::log::debug("Output image saved to ", path); } From 864bef7ba4251cf71b5c24a5e55683d56581a7bb Mon Sep 17 00:00:00 2001 From: snoyer Date: Wed, 1 May 2024 18:39:44 +0400 Subject: [PATCH 14/25] fancify templating --- application/F3DStarter.cxx | 170 +++++++++++++++++----- application/testing/CMakeLists.txt | 27 +++- testing/recordings/TestScreenshot.log | 4 - testing/recordings/TestScreenshotUser.log | 11 -- 4 files changed, 156 insertions(+), 56 deletions(-) delete mode 100644 testing/recordings/TestScreenshotUser.log diff --git a/application/F3DStarter.cxx b/application/F3DStarter.cxx index 48e2445746..9ce358b960 100644 --- a/application/F3DStarter.cxx +++ b/application/F3DStarter.cxx @@ -195,61 +195,164 @@ class F3DStarter::F3DInternals * - `{version_full}`: full application version (eg. `2.4.0-abcdefgh`) * - `{model}`: current model filename without extension (eg. `foo` for `/home/user/foo.glb`) * - `{model.ext}`: current model filename with extension (eg. `foo.glb` for `/home/user/foo.glb`) + * - `{model_ext}`: current model filename extension (eg. `glb` for `/home/user/foo.glb`) + * - `{date}`: current date in YYYYMMDD format + * - `{date:format}`: current date as per C++'s `std::put_time` format) * - `{n}`: auto-incremented number to make filename unique + * - `{n:2}`, `{n:3}`, ...: zero-padded auto-incremented number to make filename unique */ - std::filesystem::path applyFilenameTemplate(const std::string& filenameTemplate) + std::filesystem::path applyFilenameTemplate(const std::string& templateString) { - std::string filename = filenameTemplate; + const std::regex numberingRe("\\{(n:?([0-9]*))\\}"); + const std::regex dateRe("date:?([A-Za-z%]*)"); + /* return value for template variable name (eg. `app` -> `F3D`) */ + const auto variableLookup = [&](const std::string& var) { - std::regex re("\\{app\\}"); - filename = std::regex_replace(filename, re, F3D::AppName); - } - { - std::regex re("\\{version\\}"); - filename = std::regex_replace(filename, re, F3D::AppVersion); - } + if (var == "app") + { + return F3D::AppName; + } + else if (var == "version") + { + return F3D::AppVersion; + } + else if (var == "version_full") + { + return F3D::AppVersionFull; + } + else if (var == "model") + { + return FilesList[CurrentFileIndex].stem().string(); + } + else if (var == "model.ext") + { + return FilesList[CurrentFileIndex].filename().string(); + } + else if (var == "model_ext") + { + return FilesList[CurrentFileIndex].extension().string().substr(1); + } + else if (std::regex_match(var, dateRe)) + { + auto fmt = std::regex_replace(var, dateRe, "$1"); + if (fmt.empty()) + { + fmt = "%Y%m%d"; + } + std::time_t t = std::time(nullptr); + std::stringstream joined; + joined << std::put_time(std::localtime(&t), fmt.c_str()); + return joined.str(); + } + throw std::out_of_range(var); + }; + + /* process template as tokens, keeping track of whether they've been + * substituted or left untouched */ + const auto substituteVariables = [&]() { - std::regex re("\\{version_full\\}"); - filename = std::regex_replace(filename, re, F3D::AppVersionFull); - } + const std::string varName = "[\\w_.%:-]+"; + const std::string escapedVar = "(\\{(\\{" + varName + "\\})\\})"; + const std::string substVar = "(\\{(" + varName + ")\\})"; + const std::regex escapedVarRe(escapedVar); + const std::regex substVarRe(substVar); + + std::vector > fragments; + const auto callback = [&](const std::string& m) + { + if (std::regex_match(m, escapedVarRe)) + { + fragments.emplace_back(std::regex_replace(m, escapedVarRe, "$2"), true); + } + else if (std::regex_match(m, substVarRe)) + { + try + { + fragments.emplace_back(variableLookup(std::regex_replace(m, substVarRe, "$2")), true); + } + catch (std::out_of_range& e) + { + fragments.emplace_back(m, false); + } + } + else + { + fragments.emplace_back(m, false); + } + }; + + const std::regex re(escapedVar + "|" + substVar); + std::sregex_token_iterator begin(templateString.begin(), templateString.end(), re, { -1, 0 }); + std::for_each(begin, std::sregex_token_iterator(), callback); + + return fragments; + }; + const auto fragments = substituteVariables(); + + /* check the non-substituted fragments for numbering variables */ + const auto hasNumbering = [&]() { - std::regex re("\\{model\\}"); - if (std::regex_search(filename, re)) + for (const auto& [fragment, processed] : fragments) { - const auto file = FilesList[CurrentFileIndex]; - filename = std::regex_replace(filename, re, file.stem().string()); + if (!processed && std::regex_search(fragment, numberingRe)) + { + return true; + } } - } + return false; + }; + + /* just join and return if there's no numbering to be done */ + if (!hasNumbering()) { - std::regex re("\\{model[.]ext\\}"); - if (std::regex_search(filename, re)) + std::stringstream joined; + for (const auto& fragment : fragments) { - const auto file = FilesList[CurrentFileIndex]; - filename = std::regex_replace(filename, re, file.filename().string()); + joined << fragment.first; } + return { joined.str() }; } + /* apply numbering in the non-substituted fragments and join */ + const auto applyNumbering = [&](const size_t i) { - /* try and find non-existing filename; must be done last, assumes full path */ - std::regex re("\\{n\\}"); - if (std::regex_search(filename, re)) + std::stringstream joined; + for (const auto& [fragment, processed] : fragments) { - std::string fn = std::regex_replace(filename, re, "1"); - for (size_t i = 2; std::filesystem::exists(fn) && i < 1000000; ++i) + if (!processed && std::regex_match(fragment, numberingRe)) { - fn = std::regex_replace(filename, re, std::to_string(i)); + std::stringstream formattedNumber; + try + { + const std::string fmt = std::regex_replace(fragment, numberingRe, "$2"); + formattedNumber << std::setfill('0') << std::setw(std::stoi(fmt)) << i; + } + catch (std::invalid_argument&) + { + formattedNumber << std::setw(0) << i; + } + joined << std::regex_replace(fragment, numberingRe, formattedNumber.str()); } - if (std::filesystem::exists(fn)) + else { - throw std::runtime_error("could not find available unique filename"); + joined << fragment; } - filename = fn; } - } + return joined.str(); + }; - return { filename }; + /* apply incrementing numbering until file doesn't exist already */ + for (size_t i = 1; i < 1000000; ++i) + { + const std::string candidate = applyNumbering(i); + if (!std::filesystem::exists(candidate)) + { + return { candidate }; + } + } + throw std::runtime_error("could not find available unique filename"); } F3DOptionsParser Parser; @@ -828,7 +931,8 @@ void F3DStarter::SaveScreenshot(const std::string& filenameTemplate) std::filesystem::create_directories(std::filesystem::path(path).parent_path()); f3d::log::info("saving screenshot to " + path.string()); - f3d::image img = this->Internals->Engine->getWindow().renderToImage(); + f3d::image img = + this->Internals->Engine->getWindow().renderToImage(this->Internals->AppOptions.NoBackground); this->Internals->addOutputImageMetadata(img); img.save(path.string(), f3d::image::SaveFormat::PNG); diff --git a/application/testing/CMakeLists.txt b/application/testing/CMakeLists.txt index e60c8723a9..742fc63dd2 100644 --- a/application/testing/CMakeLists.txt +++ b/application/testing/CMakeLists.txt @@ -216,17 +216,28 @@ if(NOT F3D_MACOS_BUNDLE) endif() # Screenshot Interaction +function(f3d_ss_test) + cmake_parse_arguments(F3D_SS_TEST "" "NAME;TEMPLATE;EXPECTED;DEPENDS" "ARGS" ${ARGN}) + f3d_test(NAME TestScreenshot${F3D_SS_TEST_NAME} DATA suzanne.ply ARGS --screenshot-filename=${F3D_SS_TEST_TEMPLATE} --dry-run --interaction-test-play=${F3D_SOURCE_DIR}/testing/recordings/TestScreenshot.log NO_BASELINE DEPENDS TestClearScreenshots) + f3d_test(NAME TestScreenshot${F3D_SS_TEST_NAME}File DATA suzanne.ply ARGS --ref=${F3D_SS_TEST_EXPECTED} DEPENDS TestScreenshot${F3D_SS_TEST_NAME} TestScreenshot${F3D_SS_TEST_DEPENDS} NO_BASELINE LONG_TIMEOUT) +endfunction() + if(F3D_TESTING_ENABLE_LONG_TIMEOUT_TESTS) set(_screenshot_dir ${CMAKE_BINARY_DIR}/Testing/Temporary/ss) add_test(NAME f3d::TestClearScreenshots COMMAND ${CMAKE_COMMAND} -E remove_directory ${_screenshot_dir}) - f3d_test(NAME TestScreenshot DATA suzanne.ply ARGS --screenshot-filename=${_screenshot_dir}/{app}-{version}/{model.ext}_{n}.png INTERACTION DEPENDS TestClearScreenshots NO_BASELINE) - f3d_test(NAME TestScreenshotFile1 DATA suzanne.ply ARGS --ref=${_screenshot_dir}/${PROJECT_NAME}-${F3D_VERSION}/suzanne.ply_1.png DEPENDS TestScreenshot NO_BASELINE LONG_TIMEOUT) - f3d_test(NAME TestScreenshotFile2 DATA suzanne.ply ARGS --ref=${_screenshot_dir}/${PROJECT_NAME}-${F3D_VERSION}/suzanne.ply_2.png DEPENDS TestScreenshot NO_BASELINE LONG_TIMEOUT) - - f3d_test(NAME TestScreenshotUser DATA suzanne.ply ARGS --screenshot-filename={app}-{version_full}/{model}.png INTERACTION DEPENDS TestClearScreenshots NO_BASELINE) - set_tests_properties(f3d::TestScreenshotUser PROPERTIES ENVIRONMENT "XDG_PICTURES_DIR=${_screenshot_dir}/user;HOME=${_screenshot_dir}/user;USERPROFILE=${_screenshot_dir}/user") - f3d_test(NAME TestScreenshotUserFile1 DATA suzanne.ply ARGS --ref=${_screenshot_dir}/user/${PROJECT_NAME}-${F3D_VERSION_FULL}/suzanne.png DEPENDS TestScreenshotUser NO_BASELINE LONG_TIMEOUT) - f3d_test(NAME TestScreenshotUserFile2 DATA suzanne.ply ARGS --ref=${_screenshot_dir}/user/${PROJECT_NAME}-${F3D_VERSION_FULL}/suzanne.png DEPENDS TestScreenshotUser NO_BASELINE LONG_TIMEOUT) + + f3d_ss_test(NAME Version TEMPLATE ${_screenshot_dir}/{app}_{version}_{version_full}.png EXPECTED ${_screenshot_dir}/${PROJECT_NAME}_${F3D_VERSION}_${F3D_VERSION_FULL}.png) + f3d_ss_test(NAME Model TEMPLATE ${_screenshot_dir}/{model}_{model.ext}_{model_ext}.png EXPECTED ${_screenshot_dir}/suzanne_suzanne.ply_ply.png) + f3d_ss_test(NAME ModelN1 TEMPLATE ${_screenshot_dir}/{model}_{n}_{n:2}.png EXPECTED ${_screenshot_dir}/suzanne_1_01.png) + f3d_ss_test(NAME ModelN2 TEMPLATE ${_screenshot_dir}/{model}_{n}_{n:2}.png EXPECTED ${_screenshot_dir}/suzanne_2_02.png DEPENDS ModelN1) + string(TIMESTAMP DATE_Y "%Y") + string(TIMESTAMP DATE_Ymd "%Y%m%d") + f3d_ss_test(NAME Date TEMPLATE ${_screenshot_dir}/{model}_{date}_{date:%Y}.png EXPECTED ${_screenshot_dir}/suzanne_${DATE_Ymd}_${DATE_Y}.png) + f3d_ss_test(NAME Esc TEMPLATE ${_screenshot_dir}/{model}_{{model}}_{}.png EXPECTED ${_screenshot_dir}/suzanne_{model}_{}.png) + + set(_screenshot_user_dir ${_screenshot_dir}/user) + f3d_ss_test(NAME UserModelN TEMPLATE {model}_{n}.png EXPECTED ${_screenshot_user_dir}/suzanne_1.png) + set_tests_properties(f3d::TestScreenshotUserModelN PROPERTIES ENVIRONMENT "XDG_PICTURES_DIR=${_screenshot_user_dir};HOME=${_screenshot_user_dir};USERPROFILE=${_screenshot_user_dir}") endif() if(NOT APPLE OR VTK_VERSION VERSION_GREATER_EQUAL 9.3.0) diff --git a/testing/recordings/TestScreenshot.log b/testing/recordings/TestScreenshot.log index e5e71ab998..7ff198e4c7 100644 --- a/testing/recordings/TestScreenshot.log +++ b/testing/recordings/TestScreenshot.log @@ -5,7 +5,3 @@ KeyPressEvent 916 614 0 0 1 F12 0 RenderEvent 916 614 0 0 1 F12 0 CharEvent 916 614 0 0 1 F12 0 KeyReleaseEvent 916 614 0 0 1 F12 0 -KeyPressEvent 916 614 0 0 1 F12 0 -RenderEvent 916 614 0 0 1 F12 0 -CharEvent 916 614 0 0 1 F12 0 -KeyReleaseEvent 916 614 0 0 1 F12 0 diff --git a/testing/recordings/TestScreenshotUser.log b/testing/recordings/TestScreenshotUser.log deleted file mode 100644 index e5e71ab998..0000000000 --- a/testing/recordings/TestScreenshotUser.log +++ /dev/null @@ -1,11 +0,0 @@ -# StreamVersion 1.2 -ExposeEvent 0 599 0 0 0 0 0 -RenderEvent 0 599 0 0 0 0 0 -KeyPressEvent 916 614 0 0 1 F12 0 -RenderEvent 916 614 0 0 1 F12 0 -CharEvent 916 614 0 0 1 F12 0 -KeyReleaseEvent 916 614 0 0 1 F12 0 -KeyPressEvent 916 614 0 0 1 F12 0 -RenderEvent 916 614 0 0 1 F12 0 -CharEvent 916 614 0 0 1 F12 0 -KeyReleaseEvent 916 614 0 0 1 F12 0 From 83fe0597aa6c3b58edcc75df8a198fe27f16e13c Mon Sep 17 00:00:00 2001 From: snoyer Date: Wed, 1 May 2024 18:51:19 +0400 Subject: [PATCH 15/25] unused variable --- application/F3DStarter.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/F3DStarter.cxx b/application/F3DStarter.cxx index 9ce358b960..635ce004c5 100644 --- a/application/F3DStarter.cxx +++ b/application/F3DStarter.cxx @@ -271,7 +271,7 @@ class F3DStarter::F3DInternals { fragments.emplace_back(variableLookup(std::regex_replace(m, substVarRe, "$2")), true); } - catch (std::out_of_range& e) + catch (std::out_of_range&) { fragments.emplace_back(m, false); } From 4e5dddd2b16553107e40853a000b9c9eaa77ea4b Mon Sep 17 00:00:00 2001 From: snoyer Date: Sun, 5 May 2024 13:01:43 +0400 Subject: [PATCH 16/25] fix default value --- application/F3DOptionsParser.cxx | 7 +------ application/F3DOptionsParser.h | 2 +- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/application/F3DOptionsParser.cxx b/application/F3DOptionsParser.cxx index 23da89b064..a26193edc2 100644 --- a/application/F3DOptionsParser.cxx +++ b/application/F3DOptionsParser.cxx @@ -314,7 +314,7 @@ void ConfigurationOptions::GetOptions(F3DAppOptions& appOptions, f3d::options& o this->DeclareOption(grp0, "watch", "", "Watch current file and automatically reload it whenever it is modified on disk", appOptions.Watch, HasDefault::YES, MayHaveConfig::YES ); this->DeclareOption(grp0, "load-plugins", "", "List of plugins to load separated with a comma", appOptions.Plugins, LocalHasDefaultNo, MayHaveConfig::YES, ""); this->DeclareOption(grp0, "scan-plugins", "", "Scan standard directories for plugins and display available plugins (result can be incomplete)"); - this->DeclareOption(grp0, "screenshot-filename", "", "Screenshot filename", appOptions.ScreenshotFilename, LocalHasDefaultNo, MayHaveConfig::YES, ""); + this->DeclareOption(grp0, "screenshot-filename", "", "Screenshot filename", appOptions.ScreenshotFilename, HasDefault::YES, MayHaveConfig::YES, ""); auto grp1 = cxxOptions.add_options("General"); this->DeclareOption(grp1, "verbose", "", "Set verbose level, providing more information about the loaded data in the console output", appOptions.VerboseLevel, HasDefault::YES, MayHaveConfig::YES, "{debug, info, warning, error, quiet}", HasImplicitValue::YES, "debug"); @@ -528,11 +528,6 @@ void ConfigurationOptions::GetOptions(F3DAppOptions& appOptions, f3d::options& o f3d::log::error("Error parsing options: ", ex.what()); throw; } - - if (appOptions.ScreenshotFilename.empty()) - { - appOptions.ScreenshotFilename = "{app}/{model}_{n}.png"; - } } //---------------------------------------------------------------------------- diff --git a/application/F3DOptionsParser.h b/application/F3DOptionsParser.h index d5b8e427e2..6671464085 100644 --- a/application/F3DOptionsParser.h +++ b/application/F3DOptionsParser.h @@ -25,7 +25,7 @@ struct F3DAppOptions bool GeometryOnly = false; bool GroupGeometries = false; std::string Output = ""; - std::string ScreenshotFilename = ""; + std::string ScreenshotFilename = "{app}/{model}_{n}.png"; std::string Reference = ""; std::string InteractionTestRecordFile = ""; std::string InteractionTestPlayFile = ""; From c9d1baf24622e195a85cbac6e67a040066889246 Mon Sep 17 00:00:00 2001 From: snoyer Date: Sun, 5 May 2024 13:02:02 +0400 Subject: [PATCH 17/25] improve testing --- application/testing/CMakeLists.txt | 34 ++++++++++++++---------------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/application/testing/CMakeLists.txt b/application/testing/CMakeLists.txt index 742fc63dd2..3fdae2f4d3 100644 --- a/application/testing/CMakeLists.txt +++ b/application/testing/CMakeLists.txt @@ -219,26 +219,24 @@ endif() function(f3d_ss_test) cmake_parse_arguments(F3D_SS_TEST "" "NAME;TEMPLATE;EXPECTED;DEPENDS" "ARGS" ${ARGN}) f3d_test(NAME TestScreenshot${F3D_SS_TEST_NAME} DATA suzanne.ply ARGS --screenshot-filename=${F3D_SS_TEST_TEMPLATE} --dry-run --interaction-test-play=${F3D_SOURCE_DIR}/testing/recordings/TestScreenshot.log NO_BASELINE DEPENDS TestClearScreenshots) - f3d_test(NAME TestScreenshot${F3D_SS_TEST_NAME}File DATA suzanne.ply ARGS --ref=${F3D_SS_TEST_EXPECTED} DEPENDS TestScreenshot${F3D_SS_TEST_NAME} TestScreenshot${F3D_SS_TEST_DEPENDS} NO_BASELINE LONG_TIMEOUT) + f3d_test(NAME TestScreenshot${F3D_SS_TEST_NAME}File DATA suzanne.ply ARGS --ref=${F3D_SS_TEST_EXPECTED} DEPENDS TestScreenshot${F3D_SS_TEST_NAME} TestScreenshot${F3D_SS_TEST_DEPENDS} NO_BASELINE) endfunction() -if(F3D_TESTING_ENABLE_LONG_TIMEOUT_TESTS) - set(_screenshot_dir ${CMAKE_BINARY_DIR}/Testing/Temporary/ss) - add_test(NAME f3d::TestClearScreenshots COMMAND ${CMAKE_COMMAND} -E remove_directory ${_screenshot_dir}) - - f3d_ss_test(NAME Version TEMPLATE ${_screenshot_dir}/{app}_{version}_{version_full}.png EXPECTED ${_screenshot_dir}/${PROJECT_NAME}_${F3D_VERSION}_${F3D_VERSION_FULL}.png) - f3d_ss_test(NAME Model TEMPLATE ${_screenshot_dir}/{model}_{model.ext}_{model_ext}.png EXPECTED ${_screenshot_dir}/suzanne_suzanne.ply_ply.png) - f3d_ss_test(NAME ModelN1 TEMPLATE ${_screenshot_dir}/{model}_{n}_{n:2}.png EXPECTED ${_screenshot_dir}/suzanne_1_01.png) - f3d_ss_test(NAME ModelN2 TEMPLATE ${_screenshot_dir}/{model}_{n}_{n:2}.png EXPECTED ${_screenshot_dir}/suzanne_2_02.png DEPENDS ModelN1) - string(TIMESTAMP DATE_Y "%Y") - string(TIMESTAMP DATE_Ymd "%Y%m%d") - f3d_ss_test(NAME Date TEMPLATE ${_screenshot_dir}/{model}_{date}_{date:%Y}.png EXPECTED ${_screenshot_dir}/suzanne_${DATE_Ymd}_${DATE_Y}.png) - f3d_ss_test(NAME Esc TEMPLATE ${_screenshot_dir}/{model}_{{model}}_{}.png EXPECTED ${_screenshot_dir}/suzanne_{model}_{}.png) - - set(_screenshot_user_dir ${_screenshot_dir}/user) - f3d_ss_test(NAME UserModelN TEMPLATE {model}_{n}.png EXPECTED ${_screenshot_user_dir}/suzanne_1.png) - set_tests_properties(f3d::TestScreenshotUserModelN PROPERTIES ENVIRONMENT "XDG_PICTURES_DIR=${_screenshot_user_dir};HOME=${_screenshot_user_dir};USERPROFILE=${_screenshot_user_dir}") -endif() +set(_screenshot_dir ${CMAKE_BINARY_DIR}/Testing/Temporary/ss) +add_test(NAME f3d::TestClearScreenshots COMMAND ${CMAKE_COMMAND} -E remove_directory ${_screenshot_dir}) + +f3d_ss_test(NAME Version TEMPLATE ${_screenshot_dir}/{app}_{version}_{version_full}.png EXPECTED ${_screenshot_dir}/${PROJECT_NAME}_${F3D_VERSION}_${F3D_VERSION_FULL}.png) +f3d_ss_test(NAME Model TEMPLATE ${_screenshot_dir}/{model}_{model.ext}_{model_ext}.png EXPECTED ${_screenshot_dir}/suzanne_suzanne.ply_ply.png) +f3d_ss_test(NAME ModelN1 TEMPLATE ${_screenshot_dir}/{model}_{n}_{n:2}.png EXPECTED ${_screenshot_dir}/suzanne_1_01.png) +f3d_ss_test(NAME ModelN2 TEMPLATE ${_screenshot_dir}/{model}_{n}_{n:2}.png EXPECTED ${_screenshot_dir}/suzanne_2_02.png DEPENDS ModelN1) +string(TIMESTAMP DATE_Y "%Y") +string(TIMESTAMP DATE_Ymd "%Y%m%d") +f3d_ss_test(NAME Date TEMPLATE ${_screenshot_dir}/{model}_{date}_{date:%Y}.png EXPECTED ${_screenshot_dir}/suzanne_${DATE_Ymd}_${DATE_Y}.png) +f3d_ss_test(NAME Esc TEMPLATE ${_screenshot_dir}/{model}_{{model}}_{}.png EXPECTED ${_screenshot_dir}/suzanne_{model}_{}.png) + +set(_screenshot_user_dir ${_screenshot_dir}/user) +f3d_ss_test(NAME UserModelN TEMPLATE {model}_{n}.png EXPECTED ${_screenshot_user_dir}/suzanne_1.png) +set_tests_properties(f3d::TestScreenshotUserModelN PROPERTIES ENVIRONMENT "XDG_PICTURES_DIR=${_screenshot_user_dir};HOME=${_screenshot_user_dir};USERPROFILE=${_screenshot_user_dir}") if(NOT APPLE OR VTK_VERSION VERSION_GREATER_EQUAL 9.3.0) f3d_test(NAME TestTextureColor DATA WaterBottle.glb ARGS --geometry-only --texture-base-color=${F3D_SOURCE_DIR}/testing/data/albedo_mod.png --translucency-support DEFAULT_LIGHTS) From c0c5d584621e763f2635054882cda61e33bfd3c4 Mon Sep 17 00:00:00 2001 From: snoyer Date: Sun, 5 May 2024 14:39:41 +0400 Subject: [PATCH 18/25] fix test --- application/testing/CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/application/testing/CMakeLists.txt b/application/testing/CMakeLists.txt index 3fdae2f4d3..7466674de6 100644 --- a/application/testing/CMakeLists.txt +++ b/application/testing/CMakeLists.txt @@ -235,7 +235,8 @@ f3d_ss_test(NAME Date TEMPLATE ${_screenshot_dir}/{model}_{date}_{date:%Y}.png E f3d_ss_test(NAME Esc TEMPLATE ${_screenshot_dir}/{model}_{{model}}_{}.png EXPECTED ${_screenshot_dir}/suzanne_{model}_{}.png) set(_screenshot_user_dir ${_screenshot_dir}/user) -f3d_ss_test(NAME UserModelN TEMPLATE {model}_{n}.png EXPECTED ${_screenshot_user_dir}/suzanne_1.png) +add_test(NAME f3d::TestSetupScreenshotsUser COMMAND ${CMAKE_COMMAND} -E make_directory ${_screenshot_user_dir}) +f3d_ss_test(NAME UserModelN TEMPLATE {model}_{n}.png EXPECTED ${_screenshot_user_dir}/suzanne_1.png DEPENDS TestSetupScreenshotsUser) set_tests_properties(f3d::TestScreenshotUserModelN PROPERTIES ENVIRONMENT "XDG_PICTURES_DIR=${_screenshot_user_dir};HOME=${_screenshot_user_dir};USERPROFILE=${_screenshot_user_dir}") if(NOT APPLE OR VTK_VERSION VERSION_GREATER_EQUAL 9.3.0) From de417b46ab6f04d800d9ce22fb10d9599562c56f Mon Sep 17 00:00:00 2001 From: snoyer Date: Sun, 5 May 2024 15:03:46 +0400 Subject: [PATCH 19/25] fix test --- application/testing/CMakeLists.txt | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/application/testing/CMakeLists.txt b/application/testing/CMakeLists.txt index 7466674de6..357fc95973 100644 --- a/application/testing/CMakeLists.txt +++ b/application/testing/CMakeLists.txt @@ -218,12 +218,13 @@ endif() # Screenshot Interaction function(f3d_ss_test) cmake_parse_arguments(F3D_SS_TEST "" "NAME;TEMPLATE;EXPECTED;DEPENDS" "ARGS" ${ARGN}) - f3d_test(NAME TestScreenshot${F3D_SS_TEST_NAME} DATA suzanne.ply ARGS --screenshot-filename=${F3D_SS_TEST_TEMPLATE} --dry-run --interaction-test-play=${F3D_SOURCE_DIR}/testing/recordings/TestScreenshot.log NO_BASELINE DEPENDS TestClearScreenshots) + f3d_test(NAME TestScreenshot${F3D_SS_TEST_NAME} DATA suzanne.ply ARGS --screenshot-filename=${F3D_SS_TEST_TEMPLATE} --dry-run --interaction-test-play=${F3D_SOURCE_DIR}/testing/recordings/TestScreenshot.log NO_BASELINE DEPENDS TestSetupScreenshots) f3d_test(NAME TestScreenshot${F3D_SS_TEST_NAME}File DATA suzanne.ply ARGS --ref=${F3D_SS_TEST_EXPECTED} DEPENDS TestScreenshot${F3D_SS_TEST_NAME} TestScreenshot${F3D_SS_TEST_DEPENDS} NO_BASELINE) endfunction() set(_screenshot_dir ${CMAKE_BINARY_DIR}/Testing/Temporary/ss) -add_test(NAME f3d::TestClearScreenshots COMMAND ${CMAKE_COMMAND} -E remove_directory ${_screenshot_dir}) +set(_screenshot_user_dir ${_screenshot_dir}/user) +add_test(NAME f3d::TestSetupScreenshots COMMAND ${CMAKE_COMMAND} -E remove_directory ${_screenshot_dir} -E make_directory ${_screenshot_user_dir}) f3d_ss_test(NAME Version TEMPLATE ${_screenshot_dir}/{app}_{version}_{version_full}.png EXPECTED ${_screenshot_dir}/${PROJECT_NAME}_${F3D_VERSION}_${F3D_VERSION_FULL}.png) f3d_ss_test(NAME Model TEMPLATE ${_screenshot_dir}/{model}_{model.ext}_{model_ext}.png EXPECTED ${_screenshot_dir}/suzanne_suzanne.ply_ply.png) @@ -234,9 +235,7 @@ string(TIMESTAMP DATE_Ymd "%Y%m%d") f3d_ss_test(NAME Date TEMPLATE ${_screenshot_dir}/{model}_{date}_{date:%Y}.png EXPECTED ${_screenshot_dir}/suzanne_${DATE_Ymd}_${DATE_Y}.png) f3d_ss_test(NAME Esc TEMPLATE ${_screenshot_dir}/{model}_{{model}}_{}.png EXPECTED ${_screenshot_dir}/suzanne_{model}_{}.png) -set(_screenshot_user_dir ${_screenshot_dir}/user) -add_test(NAME f3d::TestSetupScreenshotsUser COMMAND ${CMAKE_COMMAND} -E make_directory ${_screenshot_user_dir}) -f3d_ss_test(NAME UserModelN TEMPLATE {model}_{n}.png EXPECTED ${_screenshot_user_dir}/suzanne_1.png DEPENDS TestSetupScreenshotsUser) +f3d_ss_test(NAME UserModelN TEMPLATE {model}_{n}.png EXPECTED ${_screenshot_user_dir}/suzanne_1.png) set_tests_properties(f3d::TestScreenshotUserModelN PROPERTIES ENVIRONMENT "XDG_PICTURES_DIR=${_screenshot_user_dir};HOME=${_screenshot_user_dir};USERPROFILE=${_screenshot_user_dir}") if(NOT APPLE OR VTK_VERSION VERSION_GREATER_EQUAL 9.3.0) From e9eafcc8b016db2090e05f1ad2c64b1bc67a881a Mon Sep 17 00:00:00 2001 From: snoyer Date: Mon, 6 May 2024 09:08:00 +0400 Subject: [PATCH 20/25] native path fixes --- application/F3DStarter.cxx | 2 +- application/testing/CMakeLists.txt | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/application/F3DStarter.cxx b/application/F3DStarter.cxx index 635ce004c5..82604b4f15 100644 --- a/application/F3DStarter.cxx +++ b/application/F3DStarter.cxx @@ -923,7 +923,7 @@ void F3DStarter::SaveScreenshot(const std::string& filenameTemplate) return std::filesystem::current_path(); }; - std::filesystem::path pathTemplate = std::filesystem::path(filenameTemplate); + std::filesystem::path pathTemplate = std::filesystem::path(filenameTemplate).make_preferred(); std::filesystem::path fullPathTemplate = pathTemplate.is_absolute() ? pathTemplate : getScreenshotDir() / pathTemplate; std::filesystem::path path = this->Internals->applyFilenameTemplate(fullPathTemplate.string()); diff --git a/application/testing/CMakeLists.txt b/application/testing/CMakeLists.txt index 357fc95973..471858b009 100644 --- a/application/testing/CMakeLists.txt +++ b/application/testing/CMakeLists.txt @@ -222,8 +222,10 @@ function(f3d_ss_test) f3d_test(NAME TestScreenshot${F3D_SS_TEST_NAME}File DATA suzanne.ply ARGS --ref=${F3D_SS_TEST_EXPECTED} DEPENDS TestScreenshot${F3D_SS_TEST_NAME} TestScreenshot${F3D_SS_TEST_DEPENDS} NO_BASELINE) endfunction() -set(_screenshot_dir ${CMAKE_BINARY_DIR}/Testing/Temporary/ss) -set(_screenshot_user_dir ${_screenshot_dir}/user) +cmake_path(SET _screenshot_path ${CMAKE_BINARY_DIR}/Testing/Temporary/ss) +cmake_path(SET _screenshot_user_path ${_screenshot_path}/user) +cmake_path(NATIVE_PATH _screenshot_path _screenshot_dir) +cmake_path(NATIVE_PATH _screenshot_user_path _screenshot_user_dir) add_test(NAME f3d::TestSetupScreenshots COMMAND ${CMAKE_COMMAND} -E remove_directory ${_screenshot_dir} -E make_directory ${_screenshot_user_dir}) f3d_ss_test(NAME Version TEMPLATE ${_screenshot_dir}/{app}_{version}_{version_full}.png EXPECTED ${_screenshot_dir}/${PROJECT_NAME}_${F3D_VERSION}_${F3D_VERSION_FULL}.png) From 888bfa41033d6d7e08907369726267787ef96815 Mon Sep 17 00:00:00 2001 From: snoyer Date: Mon, 6 May 2024 11:31:14 +0400 Subject: [PATCH 21/25] fix tests again --- application/testing/CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/application/testing/CMakeLists.txt b/application/testing/CMakeLists.txt index 471858b009..dea287e5dc 100644 --- a/application/testing/CMakeLists.txt +++ b/application/testing/CMakeLists.txt @@ -226,7 +226,8 @@ cmake_path(SET _screenshot_path ${CMAKE_BINARY_DIR}/Testing/Temporary/ss) cmake_path(SET _screenshot_user_path ${_screenshot_path}/user) cmake_path(NATIVE_PATH _screenshot_path _screenshot_dir) cmake_path(NATIVE_PATH _screenshot_user_path _screenshot_user_dir) -add_test(NAME f3d::TestSetupScreenshots COMMAND ${CMAKE_COMMAND} -E remove_directory ${_screenshot_dir} -E make_directory ${_screenshot_user_dir}) +add_test(NAME f3d::TestClearScreenshots COMMAND ${CMAKE_COMMAND} -E remove_directory ${_screenshot_dir}) +add_test(NAME f3d::TestSetupScreenshots COMMAND ${CMAKE_COMMAND} -E make_directory ${_screenshot_user_dir} DEPENDS TestClearScreenshots) f3d_ss_test(NAME Version TEMPLATE ${_screenshot_dir}/{app}_{version}_{version_full}.png EXPECTED ${_screenshot_dir}/${PROJECT_NAME}_${F3D_VERSION}_${F3D_VERSION_FULL}.png) f3d_ss_test(NAME Model TEMPLATE ${_screenshot_dir}/{model}_{model.ext}_{model_ext}.png EXPECTED ${_screenshot_dir}/suzanne_suzanne.ply_ply.png) From c833658a0767dea3748872a932e9668a1719b8b9 Mon Sep 17 00:00:00 2001 From: snoyer Date: Mon, 6 May 2024 14:29:45 +0400 Subject: [PATCH 22/25] style, comments --- application/F3DOptionsParser.cxx | 2 +- application/F3DStarter.cxx | 9 +++++---- application/F3DStarter.h | 1 + 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/application/F3DOptionsParser.cxx b/application/F3DOptionsParser.cxx index a26193edc2..7baf28a7e1 100644 --- a/application/F3DOptionsParser.cxx +++ b/application/F3DOptionsParser.cxx @@ -314,7 +314,7 @@ void ConfigurationOptions::GetOptions(F3DAppOptions& appOptions, f3d::options& o this->DeclareOption(grp0, "watch", "", "Watch current file and automatically reload it whenever it is modified on disk", appOptions.Watch, HasDefault::YES, MayHaveConfig::YES ); this->DeclareOption(grp0, "load-plugins", "", "List of plugins to load separated with a comma", appOptions.Plugins, LocalHasDefaultNo, MayHaveConfig::YES, ""); this->DeclareOption(grp0, "scan-plugins", "", "Scan standard directories for plugins and display available plugins (result can be incomplete)"); - this->DeclareOption(grp0, "screenshot-filename", "", "Screenshot filename", appOptions.ScreenshotFilename, HasDefault::YES, MayHaveConfig::YES, ""); + this->DeclareOption(grp0, "screenshot-filename", "", "Screenshot filename", appOptions.ScreenshotFilename, HasDefault::YES, MayHaveConfig::YES, ""); auto grp1 = cxxOptions.add_options("General"); this->DeclareOption(grp1, "verbose", "", "Set verbose level, providing more information about the loaded data in the console output", appOptions.VerboseLevel, HasDefault::YES, MayHaveConfig::YES, "{debug, info, warning, error, quiet}", HasImplicitValue::YES, "debug"); diff --git a/application/F3DStarter.cxx b/application/F3DStarter.cxx index 82604b4f15..d74174ca12 100644 --- a/application/F3DStarter.cxx +++ b/application/F3DStarter.cxx @@ -190,18 +190,19 @@ class F3DStarter::F3DInternals /** * Substitute the following variables in a filename template: - * - `{app}`: application name (eg. `F3D`) + * - `{app}`: application name (ie. `F3D`) * - `{version}`: application version (eg. `2.4.0`) * - `{version_full}`: full application version (eg. `2.4.0-abcdefgh`) * - `{model}`: current model filename without extension (eg. `foo` for `/home/user/foo.glb`) * - `{model.ext}`: current model filename with extension (eg. `foo.glb` for `/home/user/foo.glb`) * - `{model_ext}`: current model filename extension (eg. `glb` for `/home/user/foo.glb`) * - `{date}`: current date in YYYYMMDD format - * - `{date:format}`: current date as per C++'s `std::put_time` format) + * - `{date:format}`: current date as per C++'s `std::put_time` format * - `{n}`: auto-incremented number to make filename unique * - `{n:2}`, `{n:3}`, ...: zero-padded auto-incremented number to make filename unique */ - std::filesystem::path applyFilenameTemplate(const std::string& templateString) + std::filesystem::path applyFilenameTemplate( + const std::string& templateString, size_t maxNumberingAttempts = 1000000) { const std::regex numberingRe("\\{(n:?([0-9]*))\\}"); const std::regex dateRe("date:?([A-Za-z%]*)"); @@ -344,7 +345,7 @@ class F3DStarter::F3DInternals }; /* apply incrementing numbering until file doesn't exist already */ - for (size_t i = 1; i < 1000000; ++i) + for (size_t i = 1; i <= maxNumberingAttempts; ++i) { const std::string candidate = applyNumbering(i); if (!std::filesystem::exists(candidate)) diff --git a/application/F3DStarter.h b/application/F3DStarter.h index e54a1e26e0..e7567bac12 100644 --- a/application/F3DStarter.h +++ b/application/F3DStarter.h @@ -45,6 +45,7 @@ class F3DStarter /** * Trigger a render and save a screenshot to disk according to a filename template. + * See `F3DStarter::F3DInternals::applyFilenameTemplate` for template substitution details. */ void SaveScreenshot(const std::string& filenameTemplate); From be2bb75179e9884de8c072ee1a4b31b9c3eee13b Mon Sep 17 00:00:00 2001 From: snoyer Date: Mon, 6 May 2024 14:29:55 +0400 Subject: [PATCH 23/25] doc --- doc/user/INTERACTIONS.md | 8 ++++++++ doc/user/OPTIONS.md | 23 ++++++++++++++++++++++- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/doc/user/INTERACTIONS.md b/doc/user/INTERACTIONS.md index 9c9311b5d6..ee0993cb0c 100644 --- a/doc/user/INTERACTIONS.md +++ b/doc/user/INTERACTIONS.md @@ -72,6 +72,7 @@ Other hotkeys are available: * : load the next file if any and reset the camera. * : reload the current file. * : add current file parent directory to the list of files, reload the current file and reset the camera. +* F12: take a screenshot, ie. render the current view to an image file. When loading another file or reloading, options that have been changed interactively are kept but can be overridden if a dedicated regular expression block in the configuration file is present, see the [configuration file](CONFIGURATION_FILE.md) @@ -91,3 +92,10 @@ component is found. When changing the type of data to color with, the index of the array within the data will be kept if valid with the new data. If not, it will cycle until a valid array is found. After that, the component will be checked as specified above. + +## Taking Screenshots + +The destination filename used to save the screenshots (created by pressing F12) is configurable and can use template variables as described [on the options page](OPTIONS.md#filename-templating). + +Unless the configured filename template is an absolute path, images will be saved into the user's home directory +(using the following environment variables: `XDG_PICTURES_DIR`, `HOME`, or `USERPROFILE`). diff --git a/doc/user/OPTIONS.md b/doc/user/OPTIONS.md index 54d75caa4a..3fd19b34df 100644 --- a/doc/user/OPTIONS.md +++ b/doc/user/OPTIONS.md @@ -6,7 +6,7 @@ F3D behavior can be fully controlled from the command line using the following o Options|Default|Description ------|------|------ -\-\-output=\||Instead of showing a render view and render into it, *render directly into a png file*. When used with \-\-ref option, only outputs on failure. If `-` is specified instead of a filename, the PNG file is streamed to the stdout. +\-\-output=\||Instead of showing a render view and render into it, *render directly into a png file*. When used with \-\-ref option, only outputs on failure. If `-` is specified instead of a filename, the PNG file is streamed to the stdout. Can use [template variables](#filename-templating). \-\-no-background||Use with \-\-output to output a png file with a transparent background. -h, \-\-help||Print *help* and exit. Ignore `--verbose`. \-\-version||Show *version* information and exit. Ignore `--verbose`. @@ -18,6 +18,7 @@ Options|Default|Description \-\-watch||Watch current file and automatically reload it whenever it is modified on disk. \-\-load-plugins=\||List of plugins to load separated with a comma. Official plugins are `alembic`, `assimp`, `draco`, `exodus`, `occt`, `usd`, `vdb`. See [usage](USAGE.md) for more info. \-\-scan-plugins||Scan standard directories for plugins and display their names, results may be incomplete. See [usage](USAGE.md) for more info. +\-\-screenshot-filename||[Screenshot](INTERACTIONS.md#taking-screenshots) filename template. Can use [template variables](#filename-templating). ## General Options @@ -142,3 +143,23 @@ Some rendering options are not compatible between them, here is the precedence o The `--options=value` syntax is used everywhere in this documentation, however, the syntax `--options value` can also be used, with the exception of options that have implicit values, `--verbose`, `--comp` and `--scalars`. + +## Filename templating + +The destination filename used by `--output` or to save screenshots can use the following template variables: + +- `{app}`: application name (ie. `F3D`) +- `{version}`: application version (eg. `2.4.0`) +- `{version_full}`: full application version (eg. `2.4.0-abcdefgh`) +- `{model}`: current model filename without extension (eg. `foo` for `/home/user/foo.glb`) +- `{model.ext}`: current model filename with extension (eg. `foo.glb` for `/home/user/foo.glb`) +- `{model_ext}`: current model filename extension (eg. `glb` for `/home/user/foo.glb`) +- `{date}`: current date in YYYYMMDD format +- `{date:format}`: current date as per C++'s `std::put_time` format +- `{n}`: auto-incremented number to make filename unique +- `{n:2}`, `{n:3}`, ...: zero-padded auto-incremented number to make filename unique +- variable names can be escaped by doubling the braces (eg. use `{{model}}.png` to output `{model}.png` without the model name being substituted) + +For example the screenshot filename is configured as `{app}/{model}_{n}.png` by default, meaning that, assuming the model `hello.glb` is being viewed, +consecutive screenshots are going to be saved as `F3D/hello_1.png`, `F3D/hello_2.png`, `F3D/hello_3.png`, ... + From 0fb310cca54ffe80b904116597f45d66a2c4be7f Mon Sep 17 00:00:00 2001 From: snoyer Date: Mon, 6 May 2024 17:05:17 +0400 Subject: [PATCH 24/25] review --- application/F3DStarter.cxx | 10 ++++++---- doc/user/INTERACTIONS.md | 4 ++-- doc/user/OPTIONS.md | 6 +++--- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/application/F3DStarter.cxx b/application/F3DStarter.cxx index d74174ca12..18b46c80ac 100644 --- a/application/F3DStarter.cxx +++ b/application/F3DStarter.cxx @@ -198,12 +198,13 @@ class F3DStarter::F3DInternals * - `{model_ext}`: current model filename extension (eg. `glb` for `/home/user/foo.glb`) * - `{date}`: current date in YYYYMMDD format * - `{date:format}`: current date as per C++'s `std::put_time` format - * - `{n}`: auto-incremented number to make filename unique + * - `{n}`: auto-incremented number to make filename unique (up to 1000000) * - `{n:2}`, `{n:3}`, ...: zero-padded auto-incremented number to make filename unique + * (up to 1000000) */ - std::filesystem::path applyFilenameTemplate( - const std::string& templateString, size_t maxNumberingAttempts = 1000000) + std::filesystem::path applyFilenameTemplate(const std::string& templateString) { + constexpr size_t maxNumberingAttempts = 1000000; const std::regex numberingRe("\\{(n:?([0-9]*))\\}"); const std::regex dateRe("date:?([A-Za-z%]*)"); @@ -353,7 +354,8 @@ class F3DStarter::F3DInternals return { candidate }; } } - throw std::runtime_error("could not find available unique filename"); + throw std::runtime_error("could not find available unique filename after " + + std::to_string(maxNumberingAttempts) + " attempts"); } F3DOptionsParser Parser; diff --git a/doc/user/INTERACTIONS.md b/doc/user/INTERACTIONS.md index ee0993cb0c..9f149eb871 100644 --- a/doc/user/INTERACTIONS.md +++ b/doc/user/INTERACTIONS.md @@ -95,7 +95,7 @@ as specified above. ## Taking Screenshots -The destination filename used to save the screenshots (created by pressing F12) is configurable and can use template variables as described [on the options page](OPTIONS.md#filename-templating). +The destination filename used to save the screenshots (created by pressing F12) is configurable (using the `screenshot-filename` option) and can use template variables as described [on the options page](OPTIONS.md#filename-templating). Unless the configured filename template is an absolute path, images will be saved into the user's home directory -(using the following environment variables: `XDG_PICTURES_DIR`, `HOME`, or `USERPROFILE`). +(using the following environment variables, if defined and pointing to an existing directory, in that order: `XDG_PICTURES_DIR`, `HOME`, or `USERPROFILE`). diff --git a/doc/user/OPTIONS.md b/doc/user/OPTIONS.md index 3fd19b34df..1a3b32da64 100644 --- a/doc/user/OPTIONS.md +++ b/doc/user/OPTIONS.md @@ -18,7 +18,7 @@ Options|Default|Description \-\-watch||Watch current file and automatically reload it whenever it is modified on disk. \-\-load-plugins=\||List of plugins to load separated with a comma. Official plugins are `alembic`, `assimp`, `draco`, `exodus`, `occt`, `usd`, `vdb`. See [usage](USAGE.md) for more info. \-\-scan-plugins||Scan standard directories for plugins and display their names, results may be incomplete. See [usage](USAGE.md) for more info. -\-\-screenshot-filename||[Screenshot](INTERACTIONS.md#taking-screenshots) filename template. Can use [template variables](#filename-templating). +\-\-screenshot-filename=\||Filename to save [screenshots](INTERACTIONS.md#taking-screenshots) to. Can use [template variables](#filename-templating). ## General Options @@ -156,8 +156,8 @@ The destination filename used by `--output` or to save screenshots can use the f - `{model_ext}`: current model filename extension (eg. `glb` for `/home/user/foo.glb`) - `{date}`: current date in YYYYMMDD format - `{date:format}`: current date as per C++'s `std::put_time` format -- `{n}`: auto-incremented number to make filename unique -- `{n:2}`, `{n:3}`, ...: zero-padded auto-incremented number to make filename unique +- `{n}`: auto-incremented number to make filename unique (up to 1000000) +- `{n:2}`, `{n:3}`, ...: zero-padded auto-incremented number to make filename unique (up to 1000000) - variable names can be escaped by doubling the braces (eg. use `{{model}}.png` to output `{model}.png` without the model name being substituted) For example the screenshot filename is configured as `{app}/{model}_{n}.png` by default, meaning that, assuming the model `hello.glb` is being viewed, From d20b440b09f9ba459ed09c63c7473d9587eedf4d Mon Sep 17 00:00:00 2001 From: snoyer Date: Tue, 7 May 2024 10:55:14 +0400 Subject: [PATCH 25/25] fix doc --- doc/user/OPTIONS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/user/OPTIONS.md b/doc/user/OPTIONS.md index 1a3b32da64..f56a0fce5b 100644 --- a/doc/user/OPTIONS.md +++ b/doc/user/OPTIONS.md @@ -18,7 +18,7 @@ Options|Default|Description \-\-watch||Watch current file and automatically reload it whenever it is modified on disk. \-\-load-plugins=\||List of plugins to load separated with a comma. Official plugins are `alembic`, `assimp`, `draco`, `exodus`, `occt`, `usd`, `vdb`. See [usage](USAGE.md) for more info. \-\-scan-plugins||Scan standard directories for plugins and display their names, results may be incomplete. See [usage](USAGE.md) for more info. -\-\-screenshot-filename=\||Filename to save [screenshots](INTERACTIONS.md#taking-screenshots) to. Can use [template variables](#filename-templating). +\-\-screenshot-filename=\|`{app}/{model}_{n}.png`|Filename to save [screenshots](INTERACTIONS.md#taking-screenshots) to. Can use [template variables](#filename-templating). ## General Options