1 #include "F3DOptions.h"
2 
3 #include "F3DLog.h"
4 #include "F3DException.h"
5 #include "F3DReaderFactory.h"
6 
7 #include <vtk_jsoncpp.h>
8 #include <vtksys/SystemTools.hxx>
9 #include <vtkVersion.h>
10 
11 #include <fstream>
12 #include <regex>
13 #include <sstream>
14 #include <utility>
15 #include <vector>
16 
17 #include "cxxopts.hpp"
18 
19 //----------------------------------------------------------------------------
20 class ConfigurationOptions
21 {
22 public:
ConfigurationOptions(int argc,char ** argv)23   ConfigurationOptions(int argc, char** argv)
24     : Argc(argc)
25     , Argv(argv)
26   {
27   }
28 
29   F3DOptions GetOptionsFromArgs(std::vector<std::string>& inputs);
30   bool InitializeDictionaryFromConfigFile(const std::string& userConfigFile);
31 
SetFilePathForConfigBlock(const std::string & filePath)32   void SetFilePathForConfigBlock(const std::string& filePath) { this->FilePathForConfigBlock = filePath; }
33 
34 protected:
GetOptionConfig(const std::string & option,std::string & configValue) const35   bool GetOptionConfig(const std::string& option, std::string& configValue) const
36   {
37     bool ret = false;
38     for (auto const& it : this->ConfigDic)
39     {
40       std::regex re(it.first);
41       std::smatch matches;
42       if (std::regex_match(this->FilePathForConfigBlock, matches, re))
43       {
44         auto localIt = it.second.find(option);
45         if (localIt != it.second.end())
46         {
47           configValue = localIt->second;
48           ret = true;
49         }
50       }
51     }
52     return ret;
53   }
54 
55   template<class T>
ToString(T currValue)56   static std::string ToString(T currValue)
57   {
58     std::stringstream ss;
59     ss << currValue;
60     return ss.str();
61   }
62 
ToString(bool currValue)63   static std::string ToString(bool currValue)
64   {
65     return currValue ? "true" : "false";
66   }
67 
68   template<class T>
ToString(const std::vector<T> & currValue)69   static std::string ToString(const std::vector<T>& currValue)
70   {
71     std::stringstream ss;
72     for (size_t i = 0; i < currValue.size(); i++)
73     {
74       ss << currValue[i];
75       if (i != currValue.size() - 1)
76       {
77         ss << ",";
78       }
79     }
80     return ss.str();
81   }
82 
CollapseName(const std::string & longName,const std::string & shortName) const83   std::string CollapseName(const std::string& longName, const std::string& shortName) const
84   {
85     std::stringstream ss;
86     if (shortName != "")
87     {
88       ss << shortName << ",";
89     }
90     ss << longName;
91     return ss.str();
92   }
93 
DeclareOption(cxxopts::OptionAdder & group,const std::string & longName,const std::string & shortName,const std::string & doc) const94   void DeclareOption(cxxopts::OptionAdder& group, const std::string& longName,
95     const std::string& shortName, const std::string& doc) const
96   {
97     group(this->CollapseName(longName, shortName), doc);
98   }
99 
100   template<class T>
DeclareOption(cxxopts::OptionAdder & group,const std::string & longName,const std::string & shortName,const std::string & doc,T & var,bool hasDefault=true,bool mayHaveConfig=true,const std::string & argHelp="") const101   void DeclareOption(cxxopts::OptionAdder& group, const std::string& longName,
102     const std::string& shortName, const std::string& doc, T& var, bool hasDefault = true, bool mayHaveConfig = true,
103     const std::string& argHelp = "") const
104   {
105     auto val = cxxopts::value<T>(var);
106     std::string defaultVal;
107     if (hasDefault)
108     {
109       defaultVal = ConfigurationOptions::ToString(var);
110     }
111 
112     if (mayHaveConfig)
113     {
114       hasDefault |= this->GetOptionConfig(longName, defaultVal);
115     }
116 
117     if (hasDefault)
118     {
119       val = val->default_value(defaultVal);
120     }
121     var = {};
122     group(this->CollapseName(longName, shortName), doc, val, argHelp);
123   }
124 
125   template<class T>
DeclareOptionWithImplicitValue(cxxopts::OptionAdder & group,const std::string & longName,const std::string & shortName,const std::string & doc,T & var,const std::string & implicitValue,bool hasDefault=true,bool mayHaveConfig=true,const std::string & argHelp="") const126   void DeclareOptionWithImplicitValue(cxxopts::OptionAdder& group, const std::string& longName,
127     const std::string& shortName, const std::string& doc, T& var, const std::string& implicitValue,
128     bool hasDefault = true, bool mayHaveConfig = true, const std::string& argHelp = "") const
129   {
130     auto val = cxxopts::value<T>(var)->implicit_value(implicitValue);
131     std::string defaultVal;
132     if (hasDefault)
133     {
134       defaultVal = ConfigurationOptions::ToString(var);
135     }
136 
137     if (mayHaveConfig)
138     {
139       hasDefault |= this->GetOptionConfig(longName, defaultVal);
140     }
141 
142     if (hasDefault)
143     {
144       val = val->default_value(defaultVal);
145     }
146 
147     var = {};
148     group(this->CollapseName(longName, shortName), doc, val, argHelp);
149   }
150 
151   std::string GetBinarySettingsDirectory();
152   std::string GetSettingsFilePath();
153 
154   static std::string GetSystemSettingsDirectory();
155   static std::string GetUserSettingsDirectory();
156 
157   void PrintHelpPair(const std::string& key, const std::string& help, int keyWidth = 10, int helpWidth = 70);
158   void PrintHelp(cxxopts::Options& cxxOptions);
159   void PrintVersion();
160   void PrintReadersList();
161   void PrintExtensionsList();
162 
163 private:
164   int Argc;
165   char** Argv;
166 
167   std::string FilePathForConfigBlock;
168 
169   using Dictionnary = std::map<std::string, std::map<std::string, std::string>>;
170   Dictionnary ConfigDic;
171 };
172 
173 //----------------------------------------------------------------------------
GetOptionsFromArgs(std::vector<std::string> & inputs)174 F3DOptions ConfigurationOptions::GetOptionsFromArgs(std::vector<std::string>& inputs)
175 {
176   F3DOptions options;
177   try
178   {
179     cxxopts::Options cxxOptions(f3d::AppName, f3d::AppTitle);
180     cxxOptions.positional_help("file1 file2 ...");
181 
182     // clang-format off
183     auto grp1 = cxxOptions.add_options();
184     this->DeclareOption(grp1, "input", "", "Input file", inputs, false, false, "<files>");
185     this->DeclareOption(grp1, "output", "", "Render to file", options.Output, false, false,"<png file>");
186     this->DeclareOption(grp1, "no-background", "", "No background when render to file", options.NoBackground);
187     this->DeclareOption(grp1, "help", "h", "Print help");
188     this->DeclareOption(grp1, "version", "", "Print version details");
189     this->DeclareOption(grp1, "readers-list", "", "Print the list of file types");
190     this->DeclareOption(grp1, "extensions-list", "", "Print the list of supported extensions");
191     this->DeclareOption(grp1, "verbose", "", "Enable verbose mode, providing more information about the loaded data in the console output", options.Verbose);
192     this->DeclareOption(grp1, "no-render", "", "Verbose mode without any rendering, only for the first file", options.NoRender);
193     this->DeclareOption(grp1, "quiet", "", "Enable quiet mode, which superseed any verbose options and prevent any console output to be generated at all", options.Quiet);
194     this->DeclareOption(grp1, "axis", "x", "Show axes", options.Axis);
195     this->DeclareOption(grp1, "grid", "g", "Show grid", options.Grid);
196     this->DeclareOption(grp1, "edges", "e", "Show cell edges", options.Edges);
197     this->DeclareOption(grp1, "trackball", "k", "Enable trackball interaction", options.Trackball);
198     this->DeclareOption(grp1, "progress", "", "Show progress bar", options.Progress);
199     this->DeclareOption(grp1, "up", "", "Up direction", options.Up, true, "[-X|+X|-Y|+Y|-Z|+Z]");
200     this->DeclareOption(grp1, "animation-index", "", "Select animation to show", options.AnimationIndex, true, true, "<index>");
201 #if VTK_VERSION_NUMBER > VTK_VERSION_CHECK(9, 0, 20210228)
202     this->DeclareOption(grp1, "camera-index", "", "Select the camera to use", options.CameraIndex, true, true, "<index>");
203 #endif
204     this->DeclareOption(grp1, "geometry-only", "", "Do not read materials, cameras and lights from file", options.GeometryOnly);
205     this->DeclareOption(grp1, "dry-run", "", "Do not read the configuration file", options.DryRun, true, false);
206     this->DeclareOption(grp1, "config", "", "Read a provided configuration file instead of default one", options.UserConfigFile, false, false, "<file path>");
207 
208     auto grp2 = cxxOptions.add_options("Material");
209     this->DeclareOption(grp2, "point-sprites", "o", "Show sphere sprites instead of geometry", options.PointSprites);
210     this->DeclareOption(grp2, "point-size", "", "Point size when showing vertices or point sprites", options.PointSize, true, true, "<size>");
211     this->DeclareOption(grp2, "line-width", "", "Line width when showing edges", options.LineWidth, true, true, "<width>");
212     this->DeclareOption(grp2, "color", "", "Solid color", options.SolidColor, true, true, "<R,G,B>");
213     this->DeclareOption(grp2, "opacity", "", "Opacity", options.Opacity, true, true, "<opacity>");
214     this->DeclareOption(grp2, "roughness", "", "Roughness coefficient (0.0-1.0)", options.Roughness, true, true, "<roughness>");
215     this->DeclareOption(grp2, "metallic", "", "Metallic coefficient (0.0-1.0)", options.Metallic, true, true, "<metallic>");
216     this->DeclareOption(grp2, "hdri", "", "Path to an image file that will be used as a light source", options.HDRIFile, false, true, "<file path>");
217     this->DeclareOption(grp2, "texture-base-color", "", "Path to a texture file that sets the color of the object", options.BaseColorTex, false, true, "<file path>");
218     this->DeclareOption(grp2, "texture-material", "", "Path to a texture file that sets the Occlusion, Roughness and Metallic values of the object", options.ORMTex, false, true, "<file path>");
219     this->DeclareOption(grp2, "texture-emissive", "", "Path to a texture file that sets the emitted light of the object", options.EmissiveTex, false, true, "<file path>");
220     this->DeclareOption(grp2, "emissive-factor", "", "Emissive factor. This value is multiplied with the emissive color when an emissive texture is present", options.EmissiveFactor, true, true, "<R,G,B>");
221     this->DeclareOption(grp2, "texture-normal", "", "Path to a texture file that sets the normal map of the object", options.NormalTex, false, true, "<file path>");
222     this->DeclareOption(grp2, "normal-scale", "", "Normal scale affects the strength of the normal deviation from the normal texture", options.NormalScale, true, true, "<normalScale>");
223 
224     auto grp3 = cxxOptions.add_options("Window");
225     this->DeclareOption(grp3, "bg-color", "", "Background color", options.BackgroundColor, true, true, "<R,G,B>");
226     this->DeclareOption(grp3, "resolution", "", "Window resolution", options.WindowSize, true, true, "<width,height>");
227     this->DeclareOption(grp3, "fps", "z", "Display frame per second", options.FPS);
228     this->DeclareOption(grp3, "filename", "n", "Display filename", options.Filename);
229     this->DeclareOption(grp3, "metadata", "m", "Display file metadata", options.MetaData);
230     this->DeclareOption(grp3, "fullscreen", "f", "Full screen", options.FullScreen);
231     this->DeclareOption(grp3, "blur-background", "u", "Blur background", options.BlurBackground);
232 
233     auto grp4 = cxxOptions.add_options("Scientific visualization");
234     this->DeclareOptionWithImplicitValue(grp4, "scalars", "s", "Color by scalars", options.Scalars, std::string(""), true, true, "<array_name>");
235     this->DeclareOptionWithImplicitValue(grp4, "comp", "y", "Component from the scalar array to color with. -1 means magnitude, -2 or the short option, -y, means direct scalars", options.Component, "-2", true, true, "<comp_index>");
236     this->DeclareOption(grp4, "cells", "c", "Use a scalar array from the cells", options.Cells);
237     this->DeclareOption(grp4, "range", "", "Custom range for the coloring by array", options.Range, false, true, "<min,max>");
238     this->DeclareOption(grp4, "bar", "b", "Show scalar bar", options.Bar);
239     this->DeclareOption(grp4, "colormap", "", "Specify a custom colormap", options.LookupPoints,
240       true, "<color_list>");
241     this->DeclareOption(grp4, "volume", "v", "Show volume if the file is compatible", options.Volume);
242     this->DeclareOption(grp4, "inverse", "i", "Inverse opacity function for volume rendering", options.InverseOpacityFunction);
243 
244     auto grpCamera = cxxOptions.add_options("Camera");
245     this->DeclareOption(grpCamera, "camera-position", "", "Camera position", options.CameraPosition, false, true, "<X,Y,Z>");
246     this->DeclareOption(grpCamera, "camera-focal-point", "", "Camera focal point", options.CameraFocalPoint, false, true, "<X,Y,Z>");
247     this->DeclareOption(grpCamera, "camera-view-up", "", "Camera view up", options.CameraViewUp, false, true, "<X,Y,Z>");
248     this->DeclareOption(grpCamera, "camera-view-angle", "", "Camera view angle (non-zero, in degrees)", options.CameraViewAngle, false, true, "<angle>");
249     this->DeclareOption(grpCamera, "camera-azimuth-angle", "", "Camera azimuth angle (in degrees)", options.CameraAzimuthAngle, true, true, "<angle>");
250     this->DeclareOption(grpCamera, "camera-elevation-angle", "", "Camera elevation angle (in degrees)", options.CameraElevationAngle, true, true, "<angle>");
251 
252 #if F3D_MODULE_RAYTRACING
253     auto grp5 = cxxOptions.add_options("Raytracing");
254     this->DeclareOption(grp5, "raytracing", "r", "Enable raytracing", options.Raytracing);
255     this->DeclareOption(grp5, "samples", "", "Number of samples per pixel", options.Samples, true, true, "<samples>");
256     this->DeclareOption(grp5, "denoise", "d", "Denoise the image", options.Denoise);
257 #endif
258 
259     auto grp6 = cxxOptions.add_options("PostFX (OpenGL)");
260     this->DeclareOption(grp6, "depth-peeling", "p", "Enable depth peeling", options.DepthPeeling);
261     this->DeclareOption(grp6, "ssao", "q", "Enable Screen-Space Ambient Occlusion", options.SSAO);
262     this->DeclareOption(grp6, "fxaa", "a", "Enable Fast Approximate Anti-Aliasing", options.FXAA);
263     this->DeclareOption(grp6, "tone-mapping", "t", "Enable Tone Mapping", options.ToneMapping);
264 
265     auto grp7 = cxxOptions.add_options("Testing");
266     this->DeclareOption(grp7, "ref", "", "Reference", options.Reference, false, false, "<png file>");
267     this->DeclareOption(grp7, "ref-threshold", "", "Testing threshold", options.RefThreshold, true, false, "<threshold>");
268     this->DeclareOption(grp7, "interaction-test-record", "", "Path to an interaction log file to record interactions events to", options.InteractionTestRecordFile, false, false, "<file_path>");
269     this->DeclareOption(grp7, "interaction-test-play", "", "Path to an interaction log file to play interaction events from when loading a file", options.InteractionTestPlayFile, false, false,"<file_path>");
270     // clang-format on
271 
272     cxxOptions.parse_positional({ "input" });
273 
274     int argc = this->Argc;
275     auto result = cxxOptions.parse(argc, this->Argv);
276 
277     if (result.count("help") > 0)
278     {
279       this->PrintHelp(cxxOptions);
280       throw F3DExNoProcess();
281     }
282 
283     if (result.count("version") > 0)
284     {
285       this->PrintVersion();
286       throw F3DExNoProcess();
287     }
288 
289     if (result.count("readers-list") > 0)
290     {
291       this->PrintReadersList();
292       throw F3DExNoProcess();
293     }
294 
295     if (result.count("extensions-list") > 0)
296     {
297       this->PrintExtensionsList();
298       throw F3DExNoProcess();
299     }
300   }
301   catch (const cxxopts::OptionException& e)
302   {
303     F3DLog::Print(F3DLog::Severity::Error, "Error parsing options: ", e.what());
304     throw;
305   }
306   return options;
307 }
308 
309 //----------------------------------------------------------------------------
PrintHelpPair(const std::string & key,const std::string & help,int keyWidth,int helpWidth)310 void ConfigurationOptions::PrintHelpPair(const std::string& key, const std::string& help, int keyWidth, int helpWidth)
311 {
312   std::stringstream ss;
313   ss << "  " << std::left << std::setw(keyWidth) << key << " " << std::setw(helpWidth) << help;
314   F3DLog::Print(F3DLog::Severity::Info, ss.str());
315 }
316 
317 //----------------------------------------------------------------------------
PrintHelp(cxxopts::Options & cxxOptions)318 void ConfigurationOptions::PrintHelp(cxxopts::Options& cxxOptions)
319 {
320   const std::vector<std::pair<std::string, std::string> > keys =
321   {
322     { "C", "Cycle point/cell data coloring" },
323     { "S", "Cycle array to color with" },
324     { "Y", "Cycle array component to color with" },
325     { "B", "Toggle the scalar bar display" },
326     { "V", "Toggle volume rendering" },
327     { "I", "Toggle inverse volume opacity" },
328     { "O", "Toggle point sprites rendering" },
329     { "P", "Toggle depth peeling" },
330     { "Q", "Toggle SSAO" },
331     { "A", "Toggle FXAA" },
332     { "T", "Toggle tone mapping" },
333     { "E", "Toggle the edges display" },
334     { "X", "Toggle the axes display" },
335     { "G", "Toggle the grid display" },
336     { "N", "Toggle the filename display" },
337     { "M", "Toggle the metadata display" },
338     { "Z", "Toggle the FPS counter display" },
339     { "R", "Toggle raytracing rendering" },
340     { "D", "Toggle denoising when raytracing" },
341     { "F", "Toggle full screen" },
342     { "U", "Toggle blur background" },
343     { "K", "Toggle trackball interaction" },
344     { "H", "Toggle cheat sheet display" },
345     { "?", "Dump camera state to the terminal" },
346     { "Escape", "Quit" },
347     { "Enter", "Reset camera to initial parameters" },
348     { "Space", "Play animation if any" },
349     { "Left", "Previous file" },
350     { "Right", "Next file" },
351     { "Up", "Reload current file" }
352   };
353 
354   const std::vector<std::pair<std::string, std::string> > examples =
355   {
356     { "f3d file.vtu -xtgans", "View a unstructured mesh in a typical nice looking sciviz style" },
357     { "f3d file.glb -tuqap --hdri=file.hdr", "View a gltf file in a realistic environment" },
358     { "f3d file.ply -so --point-size=0 --comp=-2", "View a point cloud file with direct scalars rendering" },
359     { "f3d folder", "View all files in folder" },
360   };
361 
362   F3DLog::SetUseColoring(false);
363   F3DLog::Print(F3DLog::Severity::Info, cxxOptions.help());
364   F3DLog::Print(F3DLog::Severity::Info, "Keys:");
365   for (const auto& key : keys)
366   {
367     this->PrintHelpPair(key.first, key.second);
368   }
369 
370   F3DLog::Print(F3DLog::Severity::Info, "\nExamples:");
371   for (const auto& example : examples)
372   {
373     this->PrintHelpPair(example.first, example.second, 50);
374   }
375   F3DLog::Print(F3DLog::Severity::Info, "\nReport bugs to https://github.com/f3d-app/f3d/issues");
376   F3DLog::SetUseColoring(true);
377 }
378 
379 //----------------------------------------------------------------------------
PrintVersion()380 void ConfigurationOptions::PrintVersion()
381 {
382   std::string version = f3d::AppName + " " + f3d::AppVersion + "\n\n";
383 
384   version += f3d::AppTitle;
385   version += "\nVersion: ";
386   version += f3d::AppVersion;
387   version += "\nBuild date: ";
388   version += f3d::AppBuildDate;
389   version += "\nSystem: ";
390   version += f3d::AppBuildSystem;
391   version += "\nCompiler: ";
392   version += f3d::AppCompiler;
393   version += "\nRayTracing module: ";
394 #if F3D_MODULE_RAYTRACING
395   version += "ON";
396 #else
397   version += "OFF";
398 #endif
399   version += "\nExodus module: ";
400 #if F3D_MODULE_EXODUS
401   version += "ON";
402 #else
403   version += "OFF";
404 #endif
405   version += "\nOpenCASCADE module: ";
406 #if F3D_MODULE_OCCT
407   version += F3D_OCCT_VERSION;
408 #if F3D_MODULE_OCCT_XCAF
409   version += " (full support)";
410 #else
411   version += " (no metadata)";
412 #endif
413 #else
414   version += "OFF";
415 #endif
416   version += "\nAssimp module: ";
417 #if F3D_MODULE_ASSIMP
418   version += F3D_ASSIMP_VERSION;
419 #else
420   version += "OFF";
421 #endif
422   version += "\nVTK version: ";
423   version += std::string(VTK_VERSION) + std::string(" (build ") +
424     std::to_string(VTK_BUILD_VERSION) + std::string(")");
425 
426   version += "\n\nCopyright (C) 2019-2021 Kitware SAS.";
427   version += "\nCopyright (C) 2021-2022 Michael Migliore, Mathieu Westphal.";
428   version += "\nLicense BSD-3-Clause.";
429   version += "\nWritten by Michael Migliore, Mathieu Westphal and Joachim Pouderoux.";
430 
431   F3DLog::SetUseColoring(false);
432   F3DLog::Print(F3DLog::Severity::Info, version);
433   F3DLog::SetUseColoring(true);
434 }
435 
436 //----------------------------------------------------------------------------
PrintReadersList()437 void ConfigurationOptions::PrintReadersList()
438 {
439   size_t nameColSize = 0;
440   size_t extsColSize = 0;
441   size_t descColSize = 0;
442 
443   const auto& readers = F3DReaderFactory::GetInstance()->GetReaders();
444   if (readers.empty())
445   {
446     F3DLog::Print(F3DLog::Severity::Warning, "No registered reader found!");
447     return;
448   }
449   // Compute the size of the 3 columns
450   for (const auto& reader : readers)
451   {
452     nameColSize = std::max(nameColSize, reader->GetName().length());
453     descColSize = std::max(descColSize, reader->GetLongDescription().length());
454     auto exts = reader->GetExtensions();
455     size_t extLen = 0;
456     int cnt = 0;
457     for (const auto& ext : exts)
458     {
459       if (cnt++ > 0)
460       {
461         extLen++;
462       }
463       extLen += ext.length();
464     }
465     extsColSize = std::max(extsColSize, extLen);
466   }
467   nameColSize++;
468   extsColSize++;
469   descColSize++;
470 
471   // Print the rows split in 3 columns
472   std::stringstream headerLine;
473   headerLine << std::left << std::setw(nameColSize) << "Name" << std::setw(extsColSize)
474              << "Extensions" << std::setw(descColSize) << "Description";
475   F3DLog::Print(F3DLog::Severity::Info, headerLine.str());
476   F3DLog::Print(F3DLog::Severity::Info, std::string(nameColSize + extsColSize + descColSize, '-'));
477 
478   for (const auto& reader : readers)
479   {
480     std::stringstream readerLine;
481     readerLine << std::left << std::setw(nameColSize) << reader->GetName()
482                << std::setw(extsColSize);
483     auto exts = reader->GetExtensions();
484     unsigned int cnt = 0;
485     std::string extLine;
486     for (const auto& ext : exts)
487     {
488       if (cnt++ > 0)
489       {
490         extLine += ";";
491       }
492       extLine += ext;
493     }
494     readerLine << extLine << std::setw(descColSize) << reader->GetLongDescription();
495     F3DLog::Print(F3DLog::Severity::Info, readerLine.str());
496   }
497 }
498 
499 //----------------------------------------------------------------------------
PrintExtensionsList()500 void ConfigurationOptions::PrintExtensionsList()
501 {
502   const auto& readers = F3DReaderFactory::GetInstance()->GetReaders();
503   if (readers.size() == 0)
504   {
505     F3DLog::Print(F3DLog::Severity::Warning, "No registered reader found!");
506     return;
507   }
508   std::stringstream extList;
509   unsigned int cnt = 0;
510   for (const auto& reader : readers)
511   {
512     auto exts = reader->GetExtensions();
513     for (const auto& ext : exts)
514     {
515       if (cnt++ > 0)
516       {
517         extList << ";";
518       }
519       extList << ext;
520     }
521   }
522   F3DLog::Print(F3DLog::Severity::Info, extList.str());
523 }
524 
525 //----------------------------------------------------------------------------
InitializeDictionaryFromConfigFile(const std::string & userConfigFile)526 bool ConfigurationOptions::InitializeDictionaryFromConfigFile(const std::string& userConfigFile)
527 {
528   this->ConfigDic.clear();
529 
530   std::string configFilePath;
531   if (!userConfigFile.empty())
532   {
533     configFilePath = userConfigFile;
534   }
535   else
536   {
537     configFilePath = this->GetSettingsFilePath();
538   }
539   if (configFilePath.empty())
540   {
541     return false;
542   }
543 
544   std::ifstream file;
545   file.open(configFilePath.c_str());
546 
547   if (!file.is_open())
548   {
549     F3DLog::Print(
550       F3DLog::Severity::Error, "Unable to open the configuration file ", configFilePath);
551     return false;
552   }
553 
554   Json::Value root;
555   Json::CharReaderBuilder builder;
556   builder["collectComments"] = false;
557   std::string errs;
558   std::unique_ptr<Json::CharReader> reader(builder.newCharReader());
559   bool success = Json::parseFromStream(builder, file, &root, &errs);
560   if (!success)
561   {
562     F3DLog::Print(
563       F3DLog::Severity::Error, "Unable to parse the configuration file ", configFilePath);
564     F3DLog::Print(F3DLog::Severity::Error, errs);
565     return false;
566   }
567 
568   for (auto const& id : root.getMemberNames())
569   {
570     const Json::Value node = root[id];
571     std::map<std::string, std::string> localDic;
572     for (auto const& nl : node.getMemberNames())
573     {
574       const Json::Value v = node[nl];
575       localDic[nl] = v.asString();
576     }
577     this->ConfigDic[id] = localDic;
578   }
579 
580   return true;
581 }
582 
583 //----------------------------------------------------------------------------
GetUserSettingsDirectory()584 std::string ConfigurationOptions::GetUserSettingsDirectory()
585 {
586   std::string applicationName = "f3d";
587 #if defined(_WIN32)
588   const char* appData = vtksys::SystemTools::GetEnv("APPDATA");
589   if (!appData)
590   {
591     return std::string();
592   }
593   std::string separator("\\");
594   std::string directoryPath(appData);
595   if (directoryPath[directoryPath.size() - 1] != separator[0])
596   {
597     directoryPath.append(separator);
598   }
599   directoryPath += applicationName + separator;
600 #else
601   std::string directoryPath;
602   std::string separator("/");
603 
604   // Implementing XDG specifications
605   const char* xdgConfigHome = vtksys::SystemTools::GetEnv("XDG_CONFIG_HOME");
606   if (xdgConfigHome && strlen(xdgConfigHome) > 0)
607   {
608     directoryPath = xdgConfigHome;
609     if (directoryPath[directoryPath.size() - 1] != separator[0])
610     {
611       directoryPath += separator;
612     }
613   }
614   else
615   {
616     const char* home = vtksys::SystemTools::GetEnv("HOME");
617     if (!home)
618     {
619       return std::string();
620     }
621     directoryPath = home;
622     if (directoryPath[directoryPath.size() - 1] != separator[0])
623     {
624       directoryPath += separator;
625     }
626     directoryPath += ".config/";
627   }
628   directoryPath += applicationName + separator;
629 #endif
630   return directoryPath;
631 }
632 
633 //----------------------------------------------------------------------------
GetSystemSettingsDirectory()634 std::string ConfigurationOptions::GetSystemSettingsDirectory()
635 {
636   std::string directoryPath = "";
637 // No support implemented for system wide settings on Windows yet
638 #ifndef _WIN32
639 #ifdef __APPLE__
640   // Implementing simple /usr/local/etc/ system wide config
641   directoryPath = "/usr/local/etc/f3d/";
642 #else
643   // Implementing simple /etc/ system wide config
644   directoryPath = "/etc/f3d/";
645 #endif
646 #endif
647   return directoryPath;
648 }
649 
650 //----------------------------------------------------------------------------
GetBinarySettingsDirectory()651 std::string ConfigurationOptions::GetBinarySettingsDirectory()
652 {
653   std::string directoryPath = "";
654   std::string errorMsg, programFilePath;
655   if (vtksys::SystemTools::FindProgramPath(this->Argv[0], programFilePath, errorMsg))
656   {
657     // resolve symlinks
658     programFilePath = vtksys::SystemTools::GetRealPath(programFilePath);
659     directoryPath = vtksys::SystemTools::GetProgramPath(programFilePath);
660     std::string separator;
661 #if defined(_WIN32)
662     separator = "\\";
663     if (directoryPath[directoryPath.size() - 1] != separator[0])
664     {
665       directoryPath.append(separator);
666     }
667 #else
668     separator = "/";
669     directoryPath += separator;
670 #endif
671     directoryPath += "..";
672 #if F3D_MACOS_BUNDLE
673     if (vtksys::SystemTools::FileExists(directoryPath + "/Resources"))
674     {
675       directoryPath += "/Resources";
676     }
677 #endif
678     directoryPath = vtksys::SystemTools::CollapseFullPath(directoryPath);
679     directoryPath += separator;
680   }
681   return directoryPath;
682 }
683 
684 //----------------------------------------------------------------------------
GetSettingsFilePath()685 std::string ConfigurationOptions::GetSettingsFilePath()
686 {
687   std::string fileName = "config.json";
688   std::string filePath = ConfigurationOptions::GetUserSettingsDirectory() + fileName;
689   if (!vtksys::SystemTools::FileExists(filePath))
690   {
691     filePath = this->GetBinarySettingsDirectory() + fileName;
692     if (!vtksys::SystemTools::FileExists(filePath))
693     {
694       filePath = ConfigurationOptions::GetSystemSettingsDirectory() + fileName;
695       if (!vtksys::SystemTools::FileExists(filePath))
696       {
697         filePath = "";
698       }
699     }
700   }
701   return filePath;
702 }
703 
704 //----------------------------------------------------------------------------
705 F3DOptionsParser::F3DOptionsParser() = default;
706 
707 //----------------------------------------------------------------------------
708 F3DOptionsParser::~F3DOptionsParser() = default;
709 
710 //----------------------------------------------------------------------------
Initialize(int argc,char ** argv)711 void F3DOptionsParser::Initialize(int argc, char** argv)
712 {
713   this->ConfigOptions = std::unique_ptr<ConfigurationOptions>(new ConfigurationOptions(argc, argv));
714 }
715 
716 //----------------------------------------------------------------------------
InitializeDictionaryFromConfigFile(const std::string & userConfigFile)717 void F3DOptionsParser::InitializeDictionaryFromConfigFile(const std::string& userConfigFile)
718 {
719   this->ConfigOptions->InitializeDictionaryFromConfigFile(userConfigFile);
720 }
721 
722 //----------------------------------------------------------------------------
GetOptionsFromConfigFile(const std::string & filePath)723 F3DOptions F3DOptionsParser::GetOptionsFromConfigFile(const std::string& filePath)
724 {
725   this->ConfigOptions->SetFilePathForConfigBlock(filePath);
726   std::vector<std::string> dummy;
727   return this->ConfigOptions->GetOptionsFromArgs(dummy);
728 }
729 
730 //----------------------------------------------------------------------------
GetOptionsFromCommandLine(std::vector<std::string> & files)731 F3DOptions F3DOptionsParser::GetOptionsFromCommandLine(std::vector<std::string>& files)
732 {
733   this->ConfigOptions->SetFilePathForConfigBlock("");
734   return this->ConfigOptions->GetOptionsFromArgs(files);
735 }
736