From f024c25666bf24f033ba8506a214a7669a8dcf23 Mon Sep 17 00:00:00 2001 From: Alexander Bock Date: Tue, 29 Apr 2025 09:50:29 +0200 Subject: [PATCH] Add the ability to run as task file at startup. Add new model conversion task that requires an OpenGL context. Add ability to use FileVerifier and DirectoryVerifier on files that don't exist (#3612) --- apps/OpenSpace/main.cpp | 87 +++++++++++++++++++--- data/tasks/convertmodel.task | 7 ++ include/openspace/engine/openspaceengine.h | 2 + modules/base/CMakeLists.txt | 2 + modules/base/basemodule.cpp | 14 +++- modules/base/task/convertmodeltask.cpp | 80 ++++++++++++++++++++ modules/base/task/convertmodeltask.h | 52 +++++++++++++ 7 files changed, 231 insertions(+), 13 deletions(-) create mode 100644 data/tasks/convertmodel.task create mode 100644 modules/base/task/convertmodeltask.cpp create mode 100644 modules/base/task/convertmodeltask.h diff --git a/apps/OpenSpace/main.cpp b/apps/OpenSpace/main.cpp index 8be29751e7..7e84e5db3b 100644 --- a/apps/OpenSpace/main.cpp +++ b/apps/OpenSpace/main.cpp @@ -29,6 +29,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -100,6 +103,11 @@ glm::ivec2 currentDrawResolution; Window* FirstOpenVRWindow = nullptr; #endif +// This value is specified from the commandline options and kept around to be run after +// everything has been initialized. It's going to be std::nullopt unless a user wants to +// run a task +std::optional taskToRun; + // // SPOUT-support // @@ -401,6 +409,40 @@ void mainInitFunc(GLFWwindow*) { // Query joystick status, those connected before start up checkJoystickStatus(); + if (taskToRun.has_value()) { + // If a task was specified on the commandline line, we are loading that file and + // executing everything within + + TaskLoader loader; + std::vector> tasks = loader.tasksFromFile(*taskToRun); + + size_t nTasks = tasks.size(); + if (nTasks == 1) { + LINFO("Task queue has 1 item"); + } + else { + LINFO(std::format("Task queue has {} items", tasks.size())); + } + + for (size_t i = 0; i < tasks.size(); i++) { + Task& task = *tasks[i].get(); + LINFO(std::format( + "Performing task {} out of {}: {}", + i + 1, tasks.size(), task.description() + )); + ProgressBar progressBar = ProgressBar(100); + auto onProgress = [&progressBar](float progress) { + progressBar.print(static_cast(progress * 100.f)); + }; + task.perform(onProgress); + } + std::cout << "Done performing tasks" << std::endl; + + // Done with the tasks, so we can terminate + Engine::instance().terminate(); + } + + LTRACE("main::mainInitFunc(end)"); } @@ -1142,41 +1184,56 @@ int main(int argc, char* argv[]) { CommandlineArguments commandlineArguments; parser.addCommand(std::make_unique>( - commandlineArguments.configuration, "--file", "-f", + commandlineArguments.configuration, + "--file", + "-f", "Provides the path to the OpenSpace configuration file. Only the '${TEMPORARY}' " "path token is available and any other path has to be specified relative to the " "current working directory" )); parser.addCommand(std::make_unique>( - commandlineArguments.windowConfig, "--config", "-c", + commandlineArguments.windowConfig, + "--config", + "-c", "Specifies the window configuration file that should be used to start OpenSpace " "and that will override whatever is specified in the `openspace.cfg` or the " "settings. This value can include path tokens, so for example " "`${CONFIG}/single.json` is a valid value." )); parser.addCommand(std::make_unique>( - commandlineArguments.profile, "--profile", "-p", + commandlineArguments.profile, + "--profile", + "-p", "Specifies the profile that should be used to start OpenSpace and that overrides " "the profile specified in the `openspace.cfg` and the settings." )); parser.addCommand(std::make_unique>( - commandlineArguments.propertyVisibility, "--propertyVisibility", "", + commandlineArguments.propertyVisibility, + "--propertyVisibility", + "", "Specifies UI visibility settings for properties that this OpenSpace is using. " "This value overrides the values specified in the `openspace.cfg` and the " "settings and also the environment variable, if that value is provided. Allowed " "values for this parameter are: `Developer`, `AdvancedUser`, `User`, and " "`NoviceUser`." )); + parser.addCommand(std::make_unique>( + commandlineArguments.task, + "--task", + "-t", + "Specifies a task that will be run after OpenSpace has been initialized. Once " + "the task finishes, the application will automatically close again. All other " + "commandline arguments are ignored, if a task is specified." + )); parser.addCommand(std::make_unique( - commandlineArguments.bypassLauncher, "--bypassLauncher", "-b", + commandlineArguments.bypassLauncher, + "--bypassLauncher", + "-b", "Specifies whether the Launcher should be shown at startup or not. This value " "overrides the value specified in the `openspace.cfg` and the settings." )); - // setCommandLine returns a reference to the vector that will be filled later - const std::vector& sgctArguments = parser.setCommandLine( - { argv, argv + argc } - ); + parser.setCommandLine({ argv, argv + argc }); try { const bool showHelp = parser.execute(); @@ -1189,8 +1246,16 @@ int main(int argc, char* argv[]) { LFATALC(e.component, e.message); exit(EXIT_FAILURE); } - // Take an actual copy of the arguments - std::vector arguments = sgctArguments; + + if (commandlineArguments.task.has_value()) { + // If a task was specified, we want to overwrite the used window and profile and + // not display the launcher + commandlineArguments.windowConfig = "${CONFIG}/single.json"; + commandlineArguments.profile = "empty"; + commandlineArguments.bypassLauncher = true; + + taskToRun = *commandlineArguments.task; + } // // Set up SGCT functions for window delegate diff --git a/data/tasks/convertmodel.task b/data/tasks/convertmodel.task new file mode 100644 index 0000000000..fdd078c6fd --- /dev/null +++ b/data/tasks/convertmodel.task @@ -0,0 +1,7 @@ +return { + { + Type = "ConvertModelTask", + InputFilePath = "< in path >", + OutputFilePath = "< out path >" + } +} diff --git a/include/openspace/engine/openspaceengine.h b/include/openspace/engine/openspaceengine.h index 51f8b4f742..5770b91877 100644 --- a/include/openspace/engine/openspaceengine.h +++ b/include/openspace/engine/openspaceengine.h @@ -71,6 +71,8 @@ struct CommandlineArguments { std::optional profile; std::optional propertyVisibility; std::optional bypassLauncher; + + std::optional task; }; class OpenSpaceEngine : public properties::PropertyOwner { diff --git a/modules/base/CMakeLists.txt b/modules/base/CMakeLists.txt index 4b0df2c09d..e5bf991e66 100644 --- a/modules/base/CMakeLists.txt +++ b/modules/base/CMakeLists.txt @@ -87,6 +87,7 @@ set(HEADER_FILES scale/staticscale.h scale/timedependentscale.h scale/timelinescale.h + task/convertmodeltask.h timeframe/timeframeinterval.h timeframe/timeframeunion.h translation/globetranslation.h @@ -161,6 +162,7 @@ set(SOURCE_FILES scale/staticscale.cpp scale/timedependentscale.cpp scale/timelinescale.cpp + task/convertmodeltask.cpp timeframe/timeframeinterval.cpp timeframe/timeframeunion.cpp translation/globetranslation.cpp diff --git a/modules/base/basemodule.cpp b/modules/base/basemodule.cpp index 5d0b0a8fbe..4a8addc05f 100644 --- a/modules/base/basemodule.cpp +++ b/modules/base/basemodule.cpp @@ -83,6 +83,7 @@ #include #include #include +#include #include #include #include @@ -227,13 +228,20 @@ void BaseModule::internalInitialize(const ghoul::Dictionary&) { ghoul::TemplateFactory* fTranslation = FactoryManager::ref().factory(); - ghoul_assert(fTranslation, "Ephemeris factory was not created"); + ghoul_assert(fTranslation, "Translation factory was not created"); fTranslation->registerClass("GlobeTranslation"); fTranslation->registerClass("LuaTranslation"); fTranslation->registerClass("MultiTranslation"); fTranslation->registerClass("StaticTranslation"); fTranslation->registerClass("TimelineTranslation"); + + + ghoul::TemplateFactory* fTask = + FactoryManager::ref().factory(); + ghoul_assert(fTask, "Task factory was not created"); + + fTask->registerClass("ConvertModelTask"); } void BaseModule::internalDeinitializeGL() { @@ -316,7 +324,9 @@ std::vector BaseModule::documentations() const { LuaTranslation::Documentation(), MultiTranslation::Documentation(), StaticTranslation::Documentation(), - TimelineTranslation::Documentation() + TimelineTranslation::Documentation(), + + ConvertModelTask::Documentation() }; } diff --git a/modules/base/task/convertmodeltask.cpp b/modules/base/task/convertmodeltask.cpp new file mode 100644 index 0000000000..6288ce7d3b --- /dev/null +++ b/modules/base/task/convertmodeltask.cpp @@ -0,0 +1,80 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2025 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#include + +#include +#include + +namespace { + // This task converts a 3D model format from a format that is natively supported both + // by OpenSpace and common 3D modelling tools and converts it into an OpenSpace + // proprietary format that can be loaded more efficiently, but more important can be + // distributed without violating terms of service for various 3D model hosting + // websites. + // + // The resulting output file can be used everywhere in OpenSpace in place of the + // source material. + // + // The list of supported files can be found here: + // https://github.com/assimp/assimp/blob/master/doc/Fileformats.md + struct [[codegen::Dictionary(ConvertModelTask)]] Parameters { + // The path to the source file + std::filesystem::path inputFilePath; + // The path to the output file + std::filesystem::path outputFilePath [[codegen::mustexist(false)]]; + }; +#include "convertmodeltask_codegen.cpp" +} // namespace + +namespace openspace { + +documentation::Documentation ConvertModelTask::Documentation() { + return codegen::doc("base_convert_model_task"); +} + +ConvertModelTask::ConvertModelTask(const ghoul::Dictionary& dictionary) { + const Parameters p = codegen::bake(dictionary); + + _inFilePath = p.inputFilePath; + _outFilePath = p.outputFilePath; +} + +std::string ConvertModelTask::description() { + return "This task converts a 3D model format from a format that is natively " + "supported both by OpenSpace and common 3D modelling tools and converts it into " + "an OpenSpace proprietary format that can be loaded more efficiently, but more " + "important can be distributed without violating terms of service for various 3D " + "model hosting websites"; +} + +void ConvertModelTask::perform(const Task::ProgressCallback&) { + ghoul::io::ModelReaderAssimp reader; + + std::unique_ptr geometry = + reader.loadModel(_inFilePath, false, true); + geometry->saveToCacheFile(_outFilePath); +} + +} // namespace openspace diff --git a/modules/base/task/convertmodeltask.h b/modules/base/task/convertmodeltask.h new file mode 100644 index 0000000000..289651f1c0 --- /dev/null +++ b/modules/base/task/convertmodeltask.h @@ -0,0 +1,52 @@ +/***************************************************************************************** + * * + * OpenSpace * + * * + * Copyright (c) 2014-2025 * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this * + * software and associated documentation files (the "Software"), to deal in the Software * + * without restriction, including without limitation the rights to use, copy, modify, * + * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * + * permit persons to whom the Software is furnished to do so, subject to the following * + * conditions: * + * * + * The above copyright notice and this permission notice shall be included in all copies * + * or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * + * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A * + * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF * + * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE * + * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * + ****************************************************************************************/ + +#ifndef __OPENSPACE_CORE___CONVERTMODELTASK___H__ +#define __OPENSPACE_CORE___CONVERTMODELTASK___H__ + +#include + +#include +#include + +namespace openspace { + +class ConvertModelTask : public Task { +public: + explicit ConvertModelTask(const ghoul::Dictionary& dictionary); + ~ConvertModelTask() override = default; + + std::string description() override; + void perform(const Task::ProgressCallback& processCallback) override; + + static documentation::Documentation Documentation(); + +private: + std::filesystem::path _inFilePath; + std::filesystem::path _outFilePath; +}; + +} // namespace openspace + +#endif // __OPENSPACE_CORE___CONVERTMODELTASK___H__