diff --git a/application/F3DOptionsTools.cxx b/application/F3DOptionsTools.cxx index d706809729..399eea9236 100644 --- a/application/F3DOptionsTools.cxx +++ b/application/F3DOptionsTools.cxx @@ -65,6 +65,7 @@ static inline const std::array CLIOptions = {{ { "no-background", "", "No background when render to file", "", "1" }, { "help", "h", "Print help", "", "" }, { "version", "", "Print version details", "", "" }, { "list-readers", "", "Print the list of readers", "", "" }, + { "force-reader", "", "Force a specific reader to be used, disregarding the file extension", "", "1"}, { "list-bindings", "", "Print the list of interaction bindings and exits, ignored with `--no-render`, only considers the first file group.", "", "1" }, { "config", "", "Specify the configuration file to use. absolute/relative path or filename/filestem to search in configuration file locations", "", "" }, { "no-config", "", "Do not read the configuration file", "", "1" }, diff --git a/application/F3DOptionsTools.h b/application/F3DOptionsTools.h index 5e58f9b753..e9bc8b11f9 100644 --- a/application/F3DOptionsTools.h +++ b/application/F3DOptionsTools.h @@ -80,6 +80,7 @@ static inline const std::map LibOptionsNames { "animation-autoplay", "scene.animation.autoplay" }, { "animation-index", "scene.animation.index" }, { "animation-speed-factor", "scene.animation.speed_factor" }, + { "force-reader", "scene.force_reader" }, { "font-file", "ui.font_file" }, { "font-scale", "ui.scale" }, { "point-sprites", "model.point_sprites.enable" }, diff --git a/application/F3DStarter.cxx b/application/F3DStarter.cxx index ab5bd0e955..7e9d357c7d 100644 --- a/application/F3DStarter.cxx +++ b/application/F3DStarter.cxx @@ -1515,7 +1515,15 @@ void F3DStarter::LoadFileGroup( } else { - f3d::log::warn(tmpPath.string(), " is not a file of a supported file format"); + auto forceReader = this->Internals->LibOptions.scene.force_reader; + if (forceReader) + { + f3d::log::warn("Forced reader ", *forceReader, " doesn't exist"); + } + else + { + f3d::log::warn(tmpPath.string(), " is not a file of a supported file format"); + } unsupported = true; } } diff --git a/application/testing/CMakeLists.txt b/application/testing/CMakeLists.txt index 5d571b652d..f95d42b0b7 100644 --- a/application/testing/CMakeLists.txt +++ b/application/testing/CMakeLists.txt @@ -1303,6 +1303,17 @@ if(F3D_PLUGIN_BUILD_ALEMBIC AND F3D_PLUGIN_BUILD_ASSIMP) f3d_test(NAME TestReadersListMultiplePlugins ARGS --list-readers --load-plugins=assimp,alembic NO_BASELINE REGEXP_FAIL "Plugin failed to load") endif() +if(VTK_VERSION VERSION_GREATER_EQUAL 9.3.20240707) + f3d_test(NAME TestForceReaderFail DATA suzanne.stl ARGS --force-reader=OBJ NO_BASELINE REGEXP "is not supported by the given forced reader") + f3d_test(NAME TestForceReaderPass DATA suzanne.stl ARGS --force-reader=STL NO_BASELINE REGEXP_FAIL "failed to load scene") + f3d_test(NAME TestForceReaderInvalid DATA suzanne.stl ARGS --force-reader=INVALID NO_BASELINE REGEXP "Forced reader .* doesn't exist") + + if(F3D_PLUGIN_BUILD_DRACO) + f3d_test(NAME TestForceReaderGLTFDraco DATA BoxAnimated.gltf ARGS --load-plugins=draco --force-reader=GLTFDraco NO_BASELINE REGEXP_FAIL "failed to load scene") + f3d_test(NAME TestForceReaderGLTFDracoIntoGLTF DATA Box_draco.gltf ARGS --load-plugins=draco --force-reader=GLTF NO_BASELINE REGEXP "failed to load scene") + endif() +endif() + # Test bindings-list display f3d_test(NAME TestBindingsList ARGS --list-bindings REGEXP "Any.5 Toggle Orthographic Projection") diff --git a/doc/libf3d/OPTIONS.md b/doc/libf3d/OPTIONS.md index c1a7cb346d..f0fcf7612f 100644 --- a/doc/libf3d/OPTIONS.md +++ b/doc/libf3d/OPTIONS.md @@ -28,6 +28,7 @@ See the [APIs](#APIs) details below for more info. | scene.animation.time | double
optional
load | Set the animation time to load. | \-\-animation-time | | scene.camera.index | int
optional
load | Select the scene camera to use when available in the file.
The default scene always uses automatic camera. | \-\-camera-index | | scene.up_direction | direction
+Y
load | Define the Up direction. It impacts the grid, the axis, the HDRI and the camera. | \-\-up | +| scene.force_reader | string
optional
load | Force a specific reader to be used, disregarding the file extension. See [user documentation](../user/SUPPORTED_FORMATS.md) | \-\-force-reader | | scene.camera.orthographic | bool
optional
load | Set to true to force orthographic projection. Model specified by default, which is false if not specified. | \-\-camera\-orthographic | ## Interactor Options diff --git a/doc/user/OPTIONS.md b/doc/user/OPTIONS.md index 25ee9eae8e..8de91ad1a4 100644 --- a/doc/user/OPTIONS.md +++ b/doc/user/OPTIONS.md @@ -14,6 +14,7 @@ F3D behavior can be fully controlled from the command line using the following o | -h, \-\-help | | Print _help_ and exit. Ignore `--verbose`. | | \-\-version | | Show _version_ information and exit. Ignore `--verbose`. | | \-\-list-readers | | List available _readers_ and exit. Ignore `--verbose`. | +| \-\-force-reader=\ | string
- | Force a specific [reader](SUPPORTED_FORMATS.md) to be used, disregarding the file extension. | | \-\-list-bindings | | List available _bindings_ and exit. Ignore `--verbose`. | | \-\-list-rendering-backends | | List available _rendering backends_ and exit. Ignore `--verbose`. | | \-\-config=\ | string
config | Specify the [configuration file](CONFIGURATION_FILE.md) to use. Supports absolute/relative path but also filename/filestem to search for in standard configuration file locations. | diff --git a/doc/user/SUPPORTED_FORMATS.md b/doc/user/SUPPORTED_FORMATS.md index d8dc9fb023..a6f61e6a37 100644 --- a/doc/user/SUPPORTED_FORMATS.md +++ b/doc/user/SUPPORTED_FORMATS.md @@ -2,39 +2,41 @@ F3D supports the following file formats: -| Name | File Extension(s) | Full scene | Animation Supported? | Plugin | -| ----------------------------------------- | ---------------------------------------------- | ---------- | -------------------- | --------- | -| Legacy VTK | `.vtk` | No | No | `native` | -| VTK XML | `.vtp`, `.vtu`, `.vtr`, `.vti`, `.vts`, `.vtm` | No | No | `native` | -| VTKHDF | `.vtkhdf` | No | Yes | `hdf` | -| EXODUS II | `.e`, `.ex2`, `.exo`, `.g` | No | Yes | `hdf` | -| Polygon File Format | `.ply` | No | No | `native` | -| Standard Triangle Language | `.stl` | No | No | `native` | -| DICOM | `.dcm` | No | No | `native` | -| NRRD ("nearly raw raster data") | `.nrrd`, `.nhrd` | No | No | `native` | -| MetaHeader MetaIO | `.mhd`, `.mha` | No | No | `native` | -| Tag Image File Format 2D/3D | `.tif`, `.tiff` | No | No | `native` | -| QuakeMDL | `.mdl` | Yes | Yes | `native` | -| CityGML | `.gml` | No | No | `native` | -| Point Cloud | `.pts` | No | No | `native` | -| Standard for the Exchange of Product Data | `.step`, `.stp` | No | No | `occt` | -| Initial Graphics Exchange Specification | `.iges`, `.igs` | No | No | `occt` | -| Open CASCADE Technology BRep format | `.brep` | No | No | `native` | -| Alembic | `.abc` | No | Yes | `alembic` | -| Wavefront OBJ | `.obj` | Yes | Yes | `native` | -| GL Transmission Format | `.gltf`, `.glb` | Yes | Yes | `native` | -| Autodesk 3D Studio | `.3ds` | Yes | No | `native` | -| Virtual Reality Modeling Language | `.wrl` | Yes | No | `native` | -| Autodesk Filmbox | `.fbx` | Yes | Yes | `assimp` | -| COLLADA | `.dae` | Yes | Yes | `assimp` | -| Object File Format | `.off` | Yes | No | `assimp` | -| Drawing Exchange Format | `.dxf` | Yes | No | `assimp` | -| DirectX | `.x` | Yes | Yes | `assimp` | -| 3D Manufacturing Format | `.3mf` | Yes | No | `assimp` | -| Universal Scene Description | `.usd`, `.usda`, `.usdc`, `.usdz` | Yes | Yes | `usd` | -| VDB | `.vdb` | No | No | `vdb` | -| 3D Gaussian splatting | `.splat` | No | No | `native` | -| Compressed 3D Gaussian splatting | `.spz` | No | No | `native` | +| Name | File Extension(s) | Full scene | Animation Supported? | Plugin | Reader Name | +| ----------------------------------------- | ---------------------------------------------- | ---------- | -------------------- | --------- | ----------------------- | +| Legacy VTK | `.vtk` | No | No | `native` | `VTKLegacy` | +| VTK XML | `.vtp`, `.vtu`, `.vtr`, `.vti`, `.vts`, `.vtm` | No | No | `native` | `VTKXMLVT` | +| VTKHDF | `.vtkhdf` | No | Yes | `hdf` | `VTKHDF` | +| EXODUS II | `.e`, `.ex2`, `.exo`, `.g` | No | Yes | `hdf` | `ExodusII` | +| Polygon File Format | `.ply` | No | No | `native` | `PLYReader` | +| Standard Triangle Language | `.stl` | No | No | `native` | `STL` | +| DICOM | `.dcm` | No | No | `native` | `DICOM` | +| NRRD ("nearly raw raster data") | `.nrrd`, `.nhdr` | No | No | `native` | `Nrrd` | +| MetaHeader MetaIO | `.mhd`, `.mha` | No | No | `native` | `MetaImage` | +| Tag Image File Format 2D/3D | `.tif`, `.tiff` | No | No | `native` | `TIFF` | +| QuakeMDL | `.mdl` | Yes | Yes | `native` | `QuakeMDL` | +| CityGML | `.gml` | No | No | `native` | `CityGML` | +| Point Cloud | `.pts` | No | No | `native` | `PTS` | +| Standard for the Exchange of Product Data | `.step`, `.stp`, `.stpnc`, `.p21`, `.210` | No | No | `occt` | `STEP` | +| Initial Graphics Exchange Specification | `.iges`, `.igs` | No | No | `occt` | `IGES` | +| Open CASCADE Technology BRep format | `.brep` | No | No | `native` | `BREP` | +| Open CASCADE Technology XBF format | `.xbf` | No | No | `occt` | `XBF` | +| Alembic | `.abc` | No | Yes | `alembic` | `Alembic` | +| Wavefront OBJ | `.obj` | Yes | Yes | `native` | `OBJ` | +| GL Transmission Format | `.gltf`, `.glb` | Yes | Yes | `native` | `GLTF`, `GLTFDraco` | +| Draco | `.drc` | No | No | `draco` | `Draco` | +| Autodesk 3D Studio | `.3ds` | Yes | No | `native` | `3DS` | +| Virtual Reality Modeling Language | `.wrl`, `.vrml` | Yes | No | `native` | `VRMLReader` | +| Autodesk Filmbox | `.fbx` | Yes | Yes | `assimp` | `FBX` | +| COLLADA | `.dae` | Yes | Yes | `assimp` | `COLLADA` | +| Object File Format | `.off` | Yes | No | `assimp` | `OFF` | +| Drawing Exchange Format | `.dxf` | Yes | No | `assimp` | `DXF` | +| DirectX | `.x` | Yes | Yes | `assimp` | `DirectX` | +| 3D Manufacturing Format | `.3mf` | Yes | No | `assimp` | `3MF` | +| Universal Scene Description | `.usd`, `.usda`, `.usdc`, `.usdz` | Yes | Yes | `usd` | `USD` | +| VDB | `.vdb` | No | No | `vdb` | `VDB` | +| 3D Gaussian splatting | `.splat` | No | No | `native` | `Splat` | +| Compressed 3D Gaussian splatting | `.spz` | No | No | `native` | `SPZ` | ## Reader options diff --git a/library/options.json b/library/options.json index 57384b538f..74bee66c33 100644 --- a/library/options.json +++ b/library/options.json @@ -25,6 +25,9 @@ "orthographic": { "type": "bool" } + }, + "force_reader": { + "type": "string" } }, "render": { diff --git a/library/plugin/plugin.h b/library/plugin/plugin.h index c15e5cfdb8..14efac42ba 100644 --- a/library/plugin/plugin.h +++ b/library/plugin/plugin.h @@ -61,7 +61,7 @@ class plugin /** * Get the list of readers created by this plugin */ - const std::vector>& getReaders() + const std::vector>& getReaders() const { return this->Readers; } diff --git a/library/private/factory.h b/library/private/factory.h index 733d73254a..a3824189dc 100644 --- a/library/private/factory.h +++ b/library/private/factory.h @@ -17,6 +17,7 @@ #include "reader.h" #include +#include #include namespace f3d @@ -44,7 +45,7 @@ class factory /** * Get the reader that can read the given file, nullptr if none */ - reader* getReader(const std::string& fileName); + reader* getReader(const std::string& fileName, std::optional forceReader); /** * Get the list of the registered plugins diff --git a/library/src/factory.cxx.in b/library/src/factory.cxx.in index 6e474d93c7..dfa5da23aa 100644 --- a/library/src/factory.cxx.in +++ b/library/src/factory.cxx.in @@ -40,16 +40,23 @@ factory::plugin_initializer_t factory::getStaticInitializer(const std::string& p } //---------------------------------------------------------------------------- -reader* factory::getReader(const std::string& fileName) +reader* factory::getReader(const std::string& fileName, std::optional forceReader) { int bestScore = -1; reader* bestReader = nullptr; - for (const auto& plugin : this->Plugins) + for (const auto* plugin : this->Plugins) { for (const auto& reader : plugin->getReaders()) { - if (reader->getScore() > bestScore && reader->canRead(fileName)) + if (forceReader) + { + if (reader->getName() == *forceReader) + { + return reader.get(); + } + } + else if (reader->getScore() > bestScore && reader->canRead(fileName)) { bestScore = reader->getScore(); bestReader = reader.get(); diff --git a/library/src/scene_impl.cxx b/library/src/scene_impl.cxx index c34a9bffcd..40ff547acb 100644 --- a/library/src/scene_impl.cxx +++ b/library/src/scene_impl.cxx @@ -4,6 +4,7 @@ #include "interactor_impl.h" #include "log.h" #include "options.h" +#include "scene.h" #include "window_impl.h" #include "factory.h" @@ -11,6 +12,7 @@ #include "vtkF3DMemoryMesh.h" #include "vtkF3DMetaImporter.h" +#include #include #include #include @@ -18,8 +20,6 @@ #include #include -#include -#include #include namespace fs = std::filesystem; @@ -238,19 +238,35 @@ scene& scene_impl::add(const std::vector& filePaths) { throw scene::load_failure_exception(filePath.string() + " does not exists"); } - + std::optional forceReader = this->Internals->Options.scene.force_reader; // Recover the importer for the provided file path - f3d::reader* reader = f3d::factory::instance()->getReader(filePath.string()); + f3d::reader* reader = f3d::factory::instance()->getReader(filePath.string(), forceReader); if (reader) { - log::debug( - "Found a reader for \"" + filePath.string() + "\" : \"" + reader->getName() + "\""); + if (forceReader) + { + if (!reader->canRead(filePath.string())) + { + throw scene::load_failure_exception( + filePath.string() + " is not supported by the given forced reader " + (*forceReader)); + } + log::debug("Forcing reader ", (*forceReader), " for ", filePath.string()); + } + else + { + log::debug("Found a reader for \"", filePath.string(), "\" : \"", reader->getName(), "\""); + } } else { + if (forceReader) + { + throw scene::load_failure_exception(*forceReader + " is not a valid force reader"); + } throw scene::load_failure_exception( filePath.string() + " is not a file of a supported 3D scene file format"); } + vtkSmartPointer importer = reader->createSceneReader(filePath.string()); if (!importer) { @@ -322,7 +338,8 @@ scene& scene_impl::clear() //---------------------------------------------------------------------------- bool scene_impl::supports(const fs::path& filePath) { - return f3d::factory::instance()->getReader(filePath.string()) != nullptr; + return f3d::factory::instance()->getReader( + filePath.string(), this->Internals->Options.scene.force_reader) != nullptr; } //---------------------------------------------------------------------------- diff --git a/library/testing/CMakeLists.txt b/library/testing/CMakeLists.txt index 03984c7bd7..9d4d03fe0a 100644 --- a/library/testing/CMakeLists.txt +++ b/library/testing/CMakeLists.txt @@ -62,6 +62,7 @@ endif() if(VTK_VERSION VERSION_GREATER_EQUAL 9.3.20240707) list(APPEND libf3dSDKTests_list TestSDKSceneInvalid.cxx + TestSDKInvalidForceReader.cxx ) endif() diff --git a/library/testing/TestSDKInvalidForceReader.cxx b/library/testing/TestSDKInvalidForceReader.cxx new file mode 100644 index 0000000000..fcfe580036 --- /dev/null +++ b/library/testing/TestSDKInvalidForceReader.cxx @@ -0,0 +1,41 @@ +#include "PseudoUnitTest.h" + +#include +#include +#include +#include + +namespace fs = std::filesystem; +int TestSDKInvalidForceReader(int argc, char* argv[]) +{ + PseudoUnitTest test; + + f3d::log::setVerboseLevel(f3d::log::VerboseLevel::DEBUG); + f3d::engine::autoloadPlugins(); + + // Test file path setup + std::string monkey = std::string(argv[1]) + "data/red_translucent_monkey.gltf"; + + // Test INVALID reader + { + f3d::engine engine = f3d::engine::create(true); + engine.getOptions().scene.force_reader = "INVALID"; + f3d::scene& scene = engine.getScene(); + test.expect( + "Handling wrong force reader, exception type check", [&]() { scene.add(fs::path(monkey)); }); + try + { + scene.add(fs::path(monkey)); + } + catch (f3d::scene::load_failure_exception& E) + { + std::string expectedMsg = "is not a valid force reader"; + std::string exceptMsg = E.what(); + test("Check exception message size", exceptMsg.size() >= expectedMsg.size()); + test("Check exception message", + exceptMsg.substr(exceptMsg.size() - expectedMsg.size(), expectedMsg.size()) == expectedMsg); + } + } + + return test.result(); +} diff --git a/plugins/assimp/CMakeLists.txt b/plugins/assimp/CMakeLists.txt index cb97b81d3d..62803e777e 100644 --- a/plugins/assimp/CMakeLists.txt +++ b/plugins/assimp/CMakeLists.txt @@ -33,11 +33,11 @@ f3d_plugin_declare_reader( ) f3d_plugin_declare_reader( - NAME Collada + NAME COLLADA EXTENSIONS dae MIMETYPES application/vnd.dae VTK_IMPORTER vtkF3DAssimpImporter - FORMAT_DESCRIPTION "Collada" + FORMAT_DESCRIPTION "COLLADA" CUSTOM_CODE "${CMAKE_CURRENT_SOURCE_DIR}/collada.inl" )