diff --git a/application/F3DConfigFileTools.cxx b/application/F3DConfigFileTools.cxx index a686bd5844..43eeb65c7e 100644 --- a/application/F3DConfigFileTools.cxx +++ b/application/F3DConfigFileTools.cxx @@ -104,8 +104,7 @@ std::vector F3DConfigFileTools::GetConfigPaths(const std::string& conf } //---------------------------------------------------------------------------- -// TODO exceptions -F3DConfigFileTools::ConfigEntries F3DConfigFileTools::ReadConfigFiles(const std::string& userConfig) +void F3DConfigFileTools::ReadConfigFiles(const std::string& userConfig, F3DConfigFileTools::ConfigEntries& confEntries) { std::string configSearch = "config"; if (!userConfig.empty()) @@ -148,14 +147,14 @@ F3DConfigFileTools::ConfigEntries F3DConfigFileTools::ReadConfigFiles(const std: } catch (const fs::filesystem_error&) { - f3d::log::error("Configuration file does not exist: ", configPath.string()); - // return false; + f3d::log::error("Configuration file does not exist: ", configPath.string(), " , ignoring it"); + continue; } // Recover all config files if needed if (fs::is_directory(configPath)) { - // TODO add a ordche check test if not alrady + // TODO add a order check test if not alrady f3d::log::debug("Using config directory ", configPath.string()); for (auto& entry : std::filesystem::directory_iterator(configPath)) { @@ -175,11 +174,9 @@ F3DConfigFileTools::ConfigEntries F3DConfigFileTools::ReadConfigFiles(const std: { f3d::log::warn("Configuration file for \"", configSearch, "\" could not be found"); } - // return false; } // Read config files - ConfigEntries entries; for (auto& configFilePath : actualConfigFilePaths) { std::ifstream file; @@ -187,8 +184,8 @@ F3DConfigFileTools::ConfigEntries F3DConfigFileTools::ReadConfigFiles(const std: if (!file.is_open()) { - f3d::log::error("Unable to open the configuration file: ", configFilePath.string()); - // return false; + f3d::log::warn("Unable to open the configuration file: ", configFilePath.string(), " , ignoring it"); + continue; } nlohmann::ordered_json json; @@ -198,9 +195,9 @@ F3DConfigFileTools::ConfigEntries F3DConfigFileTools::ReadConfigFiles(const std: } catch (const std::exception& ex) { - f3d::log::error("Unable to parse the configuration file ", configFilePath.string()); + f3d::log::error("Unable to parse the configuration file ", configFilePath.string(), " , ignoring it"); f3d::log::error(ex.what()); - // return false; + continue; } for (const auto& configBlock : json.items()) @@ -210,7 +207,6 @@ F3DConfigFileTools::ConfigEntries F3DConfigFileTools::ReadConfigFiles(const std: { if (item.value().is_number() || item.value().is_boolean()) { - // TODO options::format ? entry[item.key()] = ::ToString(item.value()); } else if (item.value().is_string()) @@ -219,13 +215,12 @@ F3DConfigFileTools::ConfigEntries F3DConfigFileTools::ReadConfigFiles(const std: } else { - f3d::log::error(item.key(), " must be a string, a boolean or a number"); - // return false; + f3d::log::error(item.key(), " from ", configFilePath.string(), " must be a string, a boolean or a number, ignoring entry"); + continue; } } - entries.emplace_back(entry, configFilePath, configBlock.key()); + confEntries.emplace_back(entry, configFilePath, configBlock.key()); } } - return entries; } diff --git a/application/F3DConfigFileTools.h b/application/F3DConfigFileTools.h index 92b5b1801a..3cb31ede36 100644 --- a/application/F3DConfigFileTools.h +++ b/application/F3DConfigFileTools.h @@ -21,7 +21,7 @@ using ConfigDict = std::map; using ConfigEntry = std::tuple; using ConfigEntries = std::vector; -ConfigEntries ReadConfigFiles(const std::string& userConfig); +void ReadConfigFiles(const std::string& userConfig, ConfigEntries& conEntries); } #endif diff --git a/application/F3DStarter.cxx b/application/F3DStarter.cxx index 5f0676935a..9602f0f870 100644 --- a/application/F3DStarter.cxx +++ b/application/F3DStarter.cxx @@ -1,6 +1,7 @@ #include "F3DStarter.h" #include "F3DCLIOptionsTools.h" +#include "F3DConfigFileTools.h" #include "F3DPluginsTools.h" #include "F3DColorMapTools.h" #include "F3DConfig.h" @@ -60,6 +61,28 @@ class F3DStarter::F3DInternals double CameraElevationAngle; }; + struct F3DAppOptions + { + std::string Output; + bool NoBackground; + bool NoRender; + double MaxSize; + bool Watch; + std::vector Plugins; + std::string ScreenshotFilename; + std::string VerboseLevel; + bool GeometryOnly; + bool GroupGeometries; + std::vector Resolution; + std::vector Position; + std::string ColorMapFile; + CameraConfiguration CamConf; + std::string Reference; + double RefThreshold; + std::string InteractionTestRecordFile; + std::string InteractionTestPlayFile; + }; + void SetupCamera(const CameraConfiguration& camConf) { f3d::camera& cam = this->Engine->getWindow().getCamera(); @@ -372,26 +395,98 @@ class F3DStarter::F3DInternals std::to_string(maxNumberingAttempts) + " attempts"); } -// F3DAppOptions AppOptions; + void UpdateOptions(const std::vector& entriesVector, std::string inputFile) + { + // Initialize libf3dOptions + f3d::options libOptions; + libOptions.ui.dropzone_info = "Drop a file or HDRI to load it\nPress H to show cheatsheet"; + + // Copy appOptions + std::map appOptions = F3DCLIOptionsTools::DefaultAppOptions; + + // For each config entries, ordered by priority + for (const auto& entries : entriesVector) + { + // For each entry (eg: difference config files) + for (auto const& [conf, source, pattern] : entries) + { + std::regex re(pattern, std::regex_constants::icase); + std::smatch matches; + if (source.empty() || std::regex_match(inputFile, matches, re)) + { + // For each option key/value + for(auto const& [key, value]: conf) + { + // Check in appOptions first + auto appIter = appOptions.find(key); + if (appIter != appOptions.end()) + { + appOptions[key] = value; + continue; + } + + // If not an appOption, must be a libf3d option + libOptions.setAsString(key, value); + } + } + } + } + // TODO add logging + this->UpdateTypedAppOptions(appOptions); + this->LibOptions = libOptions; + } + + void UpdateTypedAppOptions(const std::map& appOptions) + { + // Update typed app options from app options + this->AppOptions.Output = f3d::options::parse(appOptions.at("output")); + this->AppOptions.NoBackground = f3d::options::parse(appOptions.at("no-background")); + this->AppOptions.NoRender = f3d::options::parse(appOptions.at("no-render")); + this->AppOptions.MaxSize = f3d::options::parse(appOptions.at("max-size")); + this->AppOptions.Watch = f3d::options::parse(appOptions.at("watch")); + + // TODO use options::parse for vectors + this->AppOptions.Plugins = {f3d::options::parse(appOptions.at("load-plugins"))}; + + this->AppOptions.ScreenshotFilename = f3d::options::parse(appOptions.at("screenshot-filename")); + this->AppOptions.VerboseLevel = f3d::options::parse(appOptions.at("verbose")); + this->AppOptions.GeometryOnly = f3d::options::parse(appOptions.at("geometry-only")); + this->AppOptions.GroupGeometries = f3d::options::parse(appOptions.at("group-geometries")); + + // TODO int vector + this->AppOptions.Resolution = f3d::options::parse>(appOptions.at("resolution")); + this->AppOptions.Position = f3d::options::parse>(appOptions.at("position")); + + this->AppOptions.ColorMapFile = f3d::options::parse(appOptions.at("colormap-file")); + + this->AppOptions.CamConf = { + f3d::options::parse>(appOptions.at("camera-position")), + f3d::options::parse>(appOptions.at("camera-focal-point")), + f3d::options::parse>(appOptions.at("camera-view-up")), + f3d::options::parse(appOptions.at("camera-view-angle")), + f3d::options::parse>(appOptions.at("camera-direction")), + f3d::options::parse(appOptions.at("camera-zoom-factor")), + f3d::options::parse(appOptions.at("camera-azimuth-angle")), + f3d::options::parse(appOptions.at("camera-elevation-angle"))}; + + this->AppOptions.Reference = f3d::options::parse(appOptions.at("ref")); + this->AppOptions.RefThreshold = f3d::options::parse(appOptions.at("ref-threshold")); + this->AppOptions.InteractionTestRecordFile = f3d::options::parse(appOptions.at("interaction-test-record")); + this->AppOptions.InteractionTestPlayFile = f3d::options::parse(appOptions.at("interaction-test-play")); + } + // f3d::options DynamicOptions; // f3d::options FileOptions; + F3DAppOptions AppOptions; + f3d::options LibOptions; + F3DConfigFileTools::ConfigEntries ConfigFilesEntries; + F3DConfigFileTools::ConfigEntries CLIConfigEntries; std::unique_ptr Engine; std::vector FilesList; dmon_watch_id FolderWatchId; bool LoadedFile = false; // bool UpdateWithCommandLineParsing = true; - // Parsed from CLI option, no need to initialize - std::string ScreenshotFilename; - bool NoRender; - bool NoBackground; - bool GeometryOnly; - bool GroupGeometries; - bool Watch; - double MaxSize; - CameraConfiguration CamConf; - std::vector Plugins; - // dmon used atomic and mutex std::atomic CurrentFileIndex = -1; std::mutex FilesListMutex; @@ -421,25 +516,19 @@ int F3DStarter::Start(int argc, char** argv) { // Parse command line options - // Recover CLI Options into a strings vector - std::vector files; - std::vector> cliOptions = F3DCLIOptionsTools::ParseCLIOptions(argc, argv, files); - f3d::options libOptions; - - // Set options outside of command line and config file - libOptions.ui.dropzone_info = "Drop a file or HDRI to load it\nPress H to show cheatsheet"; - - // Copy appOptions - std::map appOptions = F3DCLIOptionsTools::DefaultAppOptions; + // Recover CLI Options into a config dict + std::vector inputFiles; + std::vector> cliOptions = F3DCLIOptionsTools::ParseCLIOptions(argc, argv, inputFiles); + F3DConfigFileTools::ConfigDict cliConfigDict; // Process each CLI option for (auto cliIter = cliOptions.begin(); cliIter != cliOptions.end();) { - // Recover App options - auto appIter = appOptions.find(cliIter->first); - if (appIter != appOptions.end()) + // Recover app options into the cli config dict + auto appIter = F3DCLIOptionsTools::DefaultAppOptions.find(cliIter->first); + if (appIter != F3DCLIOptionsTools::DefaultAppOptions.end()) { - appIter->second = cliIter->second; + cliConfigDict[cliIter->first] = cliIter->second; cliIter = cliOptions.erase(cliIter); } // Set libf3d options @@ -448,7 +537,7 @@ int F3DStarter::Start(int argc, char** argv) auto libIter = F3DCLIOptionsTools::LibOptionsNames.find(cliIter->first); if (libIter != F3DCLIOptionsTools::LibOptionsNames.end()) { - libOptions.setAsString(libIter->second, cliIter->second); + cliConfigDict[libIter->second] = cliIter->second; cliIter = cliOptions.erase(cliIter); } else @@ -459,12 +548,33 @@ int F3DStarter::Start(int argc, char** argv) } } -// this->Internals->Parser.GetOptions( -// this->Internals->AppOptions, this->Internals->DynamicOptions, files); + // Store in a config entrie for easier processing + this->Internals->CLIConfigEntries.emplace_back(cliConfigDict, fs::path(), "CLI"); + + // Check dry-run, config CLI, output and verbose options first + // TODO better initialization ? + bool dryRun = false; + if (cliConfigDict.find("no-render") != cliConfigDict.end()) + { + dryRun = f3d::options::parse(cliConfigDict["no-render"]); + } + std::string config; + if (cliConfigDict.find("config") != cliConfigDict.end()) + { + config = f3d::options::parse(cliConfigDict["config"]); + } + bool renderToStdout = false; + if (cliConfigDict.find("output") != cliConfigDict.end()) + { + renderToStdout = f3d::options::parse(cliConfigDict["output"]) == "-"; + } + std::string verboseLevel = "info"; + if (cliConfigDict.find("verbose") != cliConfigDict.end()) + { + verboseLevel = f3d::options::parse(cliConfigDict["verbose"]); + } // Set verbosity level early from command line - const bool renderToStdout = f3d::options::parse(appOptions["output"]) == "-"; - std::string verboseLevel = f3d::options::parse(appOptions["verbose"]); F3DInternals::SetVerboseLevel(verboseLevel, renderToStdout); if (renderToStdout) { @@ -474,43 +584,22 @@ int F3DStarter::Start(int argc, char** argv) f3d::log::debug("========== Initializing =========="); - // Read config file if needed TODO -/* if (!this->Internals->AppOptions.DryRun) + // Read config files + if (!dryRun) { - // Initialize the config file dictionary - this->Internals->Parser.InitializeDictionaryFromConfigFile( - this->Internals->AppOptions.UserConfigFile); - - // Parse command line options with config file, global section only - this->Internals->Parser.GetOptions( - this->Internals->AppOptions, this->Internals->DynamicOptions, files); + F3DConfigFileTools::ReadConfigFiles(config, this->Internals->ConfigFilesEntries); + } - // Set verbosity level again if it was defined in the configuration file global block - F3DInternals::SetVerboseLevel(this->Internals->AppOptions.VerboseLevel, renderToStdout); - }*/ + // Update app and libf3d options based on config entries, with an empty input file + // config < cli + this->Internals->UpdateOptions({ this->Internals->ConfigFilesEntries, this->Internals->CLIConfigEntries}, ""); - this->Internals->NoRender = f3d::options::parse(appOptions["no-render"]); - this->Internals->NoBackground = f3d::options::parse(appOptions["no-background"]); - this->Internals->GeometryOnly = f3d::options::parse(appOptions["geometry-only"]); - this->Internals->GroupGeometries = f3d::options::parse(appOptions["group-geometries"]); - this->Internals->Watch = f3d::options::parse(appOptions["watch"]); - this->Internals->MaxSize = f3d::options::parse(appOptions["max-size"]); - this->Internals->ScreenshotFilename = f3d::options::parse(appOptions["screenshot-filename"]); - this->Internals->CamConf = { - f3d::options::parse>(appOptions["camera-position"]), - f3d::options::parse>(appOptions["camera-focal-point"]), - f3d::options::parse>(appOptions["camera-view-up"]), - f3d::options::parse(appOptions["camera-view-angle"]), - f3d::options::parse>(appOptions["camera-direction"]), - f3d::options::parse(appOptions["camera-zoom-factor"]), - f3d::options::parse(appOptions["camera-azimuth-angle"]), - f3d::options::parse(appOptions["camera-elevation-angle"])}; - - // TODO use options::parse for vectors - this->Internals->Plugins = {f3d::options::parse(appOptions["load-plugins"])}; + // Set verbosity level again if it was defined in the configuration file global block + renderToStdout = this->Internals->AppOptions.Output == "-"; + F3DInternals::SetVerboseLevel(this->Internals->AppOptions.VerboseLevel, renderToStdout); // Load plugins from the app options TODO - F3DPluginsTools::LoadPlugins(this->Internals->Plugins); + F3DPluginsTools::LoadPlugins(this->Internals->AppOptions.Plugins); #if __APPLE__ // Initialize MacOS delegate @@ -519,20 +608,20 @@ int F3DStarter::Start(int argc, char** argv) f3d::log::debug("========== Configuring engine =========="); - std::string reference = f3d::options::parse(appOptions["ref"]); - std::string output = f3d::options::parse(appOptions["output"]); + const std::string& reference = this->Internals->AppOptions.Reference; + const std::string& output = this->Internals->AppOptions.Output; - if (this->Internals->NoRender) + if (this->Internals->AppOptions.NoRender) { this->Internals->Engine = std::make_unique(f3d::window::Type::NONE); - this->Internals->Engine->setOptions(libOptions); + this->Internals->Engine->setOptions(this->Internals->LibOptions); } else { bool offscreen = !reference.empty() || !output.empty(); this->Internals->Engine = std::make_unique( offscreen ? f3d::window::Type::NATIVE_OFFSCREEN : f3d::window::Type::NATIVE); - this->Internals->Engine->setOptions(libOptions); + this->Internals->Engine->setOptions(this->Internals->LibOptions); f3d::window& window = this->Internals->Engine->getWindow(); f3d::interactor& interactor = this->Internals->Engine->getInteractor(); @@ -566,12 +655,12 @@ int F3DStarter::Start(int argc, char** argv) if (keySym == "F12") { - this->SaveScreenshot(this->Internals->ScreenshotFilename); + this->SaveScreenshot(this->Internals->AppOptions.ScreenshotFilename); return true; } if (keySym == "F11") { - this->SaveScreenshot(this->Internals->ScreenshotFilename, true); + this->SaveScreenshot(this->Internals->AppOptions.ScreenshotFilename, true); return true; } @@ -614,16 +703,15 @@ int F3DStarter::Start(int argc, char** argv) .setWindowName(F3D::AppTitle) .setIcon(F3DIcon, sizeof(F3DIcon)); + // TODO clarify behavior in doc ? // TODO int - std::vector resolution = f3d::options::parse>(appOptions["resolution"]); - if (resolution.size() == 2) + if (this->Internals->AppOptions.Resolution.size() == 2) { - window.setSize(resolution[0], resolution[1]); + window.setSize(this->Internals->AppOptions.Resolution[0], this->Internals->AppOptions.Resolution[1]); } - std::vector position = f3d::options::parse>(appOptions["position"]); - if (position.size() == 2) + if (this->Internals->AppOptions.Position.size() == 2) { - window.setPosition(position[0], position[1]); + window.setPosition(this->Internals->AppOptions.Position[0], this->Internals->AppOptions.Position[1]); } #ifdef __APPLE__ else @@ -637,7 +725,7 @@ int F3DStarter::Start(int argc, char** argv) } // Parse colormap - std::string colorMapFile = f3d::options::parse(appOptions["colormap-file"]); + const std::string& colorMapFile = this->Internals->AppOptions.ColorMapFile; if (!colorMapFile.empty()) { std::string fullPath = F3DColorMapTools::Find(colorMapFile); @@ -657,7 +745,7 @@ int F3DStarter::Start(int argc, char** argv) f3d::log::debug("Engine configured"); // Add all files - for (auto& file : files) + for (auto& file : inputFiles) { this->AddFile(fs::path(file)); } @@ -665,13 +753,13 @@ int F3DStarter::Start(int argc, char** argv) // Load a file this->LoadFile(); - if (!this->Internals->NoRender) + if (!this->Internals->AppOptions.NoRender) { f3d::window& window = this->Internals->Engine->getWindow(); f3d::interactor& interactor = this->Internals->Engine->getInteractor(); // Play recording if any - std::string interactionTestPlayFile = f3d::options::parse(appOptions["interaction-test-play"]); + const std::string& interactionTestPlayFile = this->Internals->AppOptions.InteractionTestPlayFile; if (!interactionTestPlayFile.empty()) { // For better testing, render once before the interaction @@ -683,7 +771,7 @@ int F3DStarter::Start(int argc, char** argv) } // Start recording if needed - std::string interactionTestRecordFile = f3d::options::parse(appOptions["interaction-test-record"]); + const std::string& interactionTestRecordFile = this->Internals->AppOptions.InteractionTestPlayFile; if (!interactionTestRecordFile.empty()) { if (!interactor.recordInteraction(interactionTestRecordFile)) @@ -713,7 +801,7 @@ int F3DStarter::Start(int argc, char** argv) } else { - window.renderToImage(this->Internals->NoBackground) + window.renderToImage(this->Internals->AppOptions.NoBackground) .save(output); f3d::log::error("Reference image " + reference + @@ -723,11 +811,11 @@ int F3DStarter::Start(int argc, char** argv) return EXIT_FAILURE; } - f3d::image img = window.renderToImage(this->Internals->NoBackground); + f3d::image img = window.renderToImage(this->Internals->AppOptions.NoBackground); f3d::image ref(reference); f3d::image diff; double error; - double threshold = f3d::options::parse(appOptions["ref-threshold"]); + const double& threshold = this->Internals->AppOptions.RefThreshold; if (!img.compare(ref, threshold, error)) { if (output.empty()) @@ -764,7 +852,7 @@ int F3DStarter::Start(int argc, char** argv) return EXIT_FAILURE; } - f3d::image img = window.renderToImage(this->Internals->NoBackground); + f3d::image img = window.renderToImage(this->Internals->AppOptions.NoBackground); this->Internals->addOutputImageMetadata(img); if (renderToStdout) @@ -808,7 +896,7 @@ int F3DStarter::Start(int argc, char** argv) void F3DStarter::LoadFile(int index, bool relativeIndex) { // Make sure the animation is stopped before trying to load any file - if (!this->Internals->NoRender) + if (!this->Internals->AppOptions.NoRender) { this->Internals->Engine->getInteractor().stopAnimation(); } @@ -871,7 +959,7 @@ void F3DStarter::LoadFile(int index, bool relativeIndex) if (this->Internals->CurrentFileIndex >= 0) { - if (this->Internals->GroupGeometries) + if (this->Internals->AppOptions.GroupGeometries) { // Group geometries mode, consider the first file configuration file only this->Internals->CurrentFileIndex = 0; @@ -887,19 +975,21 @@ void F3DStarter::LoadFile(int index, bool relativeIndex) // this->Internals->UpdateWithCommandLineParsing = false; // this is done only once // this->Internals->Engine->setOptions(this->Internals->FileOptions); + // Update app and libf3d options based on config entries, with an empty input file + // config < cli + this->Internals->UpdateOptions({ this->Internals->ConfigFilesEntries, this->Internals->CLIConfigEntries}, filePath.string()); + this->Internals->Engine->setOptions(this->Internals->LibOptions); + this->Internals->LoadedFile = false; - // Load any new plugins the updated app options TODO - F3DPluginsTools::LoadPlugins(this->Internals->Plugins); + // Load any new plugins the updated app options + F3DPluginsTools::LoadPlugins(this->Internals->AppOptions.Plugins); // Check the size of the file before loading it // Not considered in the context of GroupGeometries static constexpr int BYTES_IN_MIB = 1048576; - // TODO -// if (fileAppOptions.MaxSize >= 0.0 && -// fs::file_size(filePath) > static_cast(fileAppOptions.MaxSize * BYTES_IN_MIB)) - if (this->Internals->MaxSize >= 0.0 && - fs::file_size(filePath) > static_cast(this->Internals->MaxSize * BYTES_IN_MIB)) + if (this->Internals->AppOptions.MaxSize >= 0.0 && + fs::file_size(filePath) > static_cast(this->Internals->AppOptions.MaxSize * BYTES_IN_MIB)) { f3d::log::info("No file loaded, file is bigger than max size"); } @@ -907,11 +997,8 @@ void F3DStarter::LoadFile(int index, bool relativeIndex) { try { -// TODO -// if (loader.hasSceneReader(filePath.string()) && !fileAppOptions.GeometryOnly && -// !fileAppOptions.GroupGeometries) - if (loader.hasSceneReader(filePath.string()) && !this->Internals->GeometryOnly && - !this->Internals->GroupGeometries) + if (loader.hasSceneReader(filePath.string()) && !this->Internals->AppOptions.GeometryOnly && + !this->Internals->AppOptions.GroupGeometries) { loader.loadScene(filePath.string()); this->Internals->LoadedFile = true; @@ -919,9 +1006,7 @@ void F3DStarter::LoadFile(int index, bool relativeIndex) else if (loader.hasGeometryReader(filePath.string())) { // In GroupGeometries, just load all the files from the list - // TODO -// if (fileAppOptions.GroupGeometries) - if (this->Internals->GroupGeometries) + if (this->Internals->AppOptions.GroupGeometries) { int nGeom = 0; for (size_t i = 0; i < size; i++) @@ -965,10 +1050,10 @@ void F3DStarter::LoadFile(int index, bool relativeIndex) f3d::log::error("Could not load file: ", ex.what()); } - if (!this->Internals->NoRender) + if (!this->Internals->AppOptions.NoRender) { // Setup the camera according to options - this->Internals->SetupCamera(this->Internals->CamConf); + this->Internals->SetupCamera(this->Internals->AppOptions.CamConf); this->Internals->Engine->getWindow().setWindowName( filePath.filename().string() + " - " + F3D::AppName); @@ -978,7 +1063,7 @@ void F3DStarter::LoadFile(int index, bool relativeIndex) if (this->Internals->LoadedFile) { - if (this->Internals->Watch) + if (this->Internals->AppOptions.Watch) { // Always unwatch and watch current folder, even on reload if (this->Internals->FolderWatchId.id > 0) @@ -1048,7 +1133,7 @@ void F3DStarter::SaveScreenshot(const std::string& filenameTemplate, bool minima f3d::options& options = this->Internals->Engine->getOptions(); f3d::options optionsCopy = this->Internals->Engine->getOptions(); - bool noBackground = this->Internals->NoBackground; + bool noBackground = this->Internals->AppOptions.NoBackground; if (minimal) { options.ui.scalar_bar = false;