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__