Skip to content

Commit

Permalink
Add binding documentation API and use it (#1711)
Browse files Browse the repository at this point in the history
 - Add an interaction_bind_t
 - Add a std::function binding documentation API in interactor::addBinding
 - Use it for each libf3d binding
 - Use it for F3D binding
 - Adapt vtkF3DRenderer to new logic and clean up unused code
 - Add `--bindings-list` CLI option to show interactions
 - Add/Improve tests accordingly
 - Rework animation name logic to make it work with the cheatsheet logic
 - Add/Improve doc
  • Loading branch information
mwestphal authored Nov 27, 2024
1 parent d036a4e commit f3d7627
Show file tree
Hide file tree
Showing 45 changed files with 772 additions and 431 deletions.
2 changes: 1 addition & 1 deletion .clang-tidy
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Checks: '-*,performance-*,modernize-*,-modernize-use-trailing-return-type,-modernize-use-nodiscard,-modernize-avoid-c-arrays,-modernize-use-auto'
Checks: '-*,performance-*,modernize-*,-modernize-use-trailing-return-type,-modernize-use-nodiscard,-modernize-avoid-c-arrays,-modernize-use-auto,-modernize-avoid-bind'
WarningsAsErrors: '*'
UseColor: true
FormatStyle: none
2 changes: 1 addition & 1 deletion _config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ defaults:

-
scope:
path: "doc/libf3d/BINDINGS.md"
path: "doc/libf3d/LANGUAGE_BINDINGS.md"
values:
parent: libf3d Documentation
nav_order: 3
Expand Down
37 changes: 16 additions & 21 deletions application/F3DOptionsTools.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ static inline const std::array<CLIGroup, 8> CLIOptions = {{
{ "no-background", "", "No background when render to file", "<bool>", "1" },
{ "help", "h", "Print help", "", "" }, { "version", "", "Print version details", "", "" },
{ "readers-list", "", "Print the list of readers", "", "" },
{ "bindings-list", "", "Print the list of interaction bindings, ignored with `--no-render`", "", "" },
{ "config", "", "Specify the configuration file to use. absolute/relative path or filename/filestem to search in configuration file locations", "<filePath/filename/fileStem>", "" },
{ "dry-run", "", "Do not read the configuration file", "<bool>", "1" },
{ "no-render", "", "Do not read the configuration file", "<bool>", "1" },
Expand Down Expand Up @@ -188,20 +189,6 @@ std::string CollapseName(const std::string_view& longName, const std::string_vie
return ss.str();
}

//----------------------------------------------------------------------------
void PrintHelpPair(
std::string_view key, std::string_view help, int keyWidth = 10, int helpWidth = 70)
{
std::stringstream ss;
ss << " " << std::left << std::setw(keyWidth) << key;
if (key.size() > static_cast<size_t>(keyWidth))
{
ss << "\n " << std::setw(keyWidth) << " ";
}
ss << " " << std::setw(helpWidth) << help;
f3d::log::info(ss.str());
}

//----------------------------------------------------------------------------
void PrintHelp(const std::string& execName, const cxxopts::Options& cxxOptions)
{
Expand All @@ -224,16 +211,10 @@ void PrintHelp(const std::string& execName, const cxxopts::Options& cxxOptions)
orderedCLIGroupNames.emplace_back(optionGroup.GroupName);
}
f3d::log::info(cxxOptions.help(orderedCLIGroupNames));
f3d::log::info("Keys:");
for (const auto& [key, desc] : f3d::interactor::getDefaultInteractionsInfo())
{
::PrintHelpPair(key, desc);
}

f3d::log::info("\nExamples:");
for (const auto& [cmd, desc] : examples)
{
::PrintHelpPair(cmd, desc, 50);
F3DOptionsTools::PrintHelpPair(cmd, desc, 50);
}
f3d::log::info("\nReport bugs to https://github.com/f3d-app/f3d/issues");
f3d::log::setUseColoring(true);
Expand Down Expand Up @@ -570,3 +551,17 @@ F3DOptionsTools::OptionsDict F3DOptionsTools::ParseCLIOptions(
throw F3DExFailure("Could not parse command line arguments");
}
}

//----------------------------------------------------------------------------
void F3DOptionsTools::PrintHelpPair(
std::string_view key, std::string_view help, int keyWidth, int helpWidth)
{
std::stringstream ss;
ss << " " << std::left << std::setw(keyWidth) << key;
if (key.size() > static_cast<size_t>(keyWidth))
{
ss << "\n " << std::setw(keyWidth) << " ";
}
ss << " " << std::setw(helpWidth) << help;
f3d::log::info(ss.str());
}
7 changes: 7 additions & 0 deletions application/F3DOptionsTools.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ using OptionsEntries = std::vector<OptionsEntry>;
static inline const OptionsDict DefaultAppOptions = {
{ "input", "" },
{ "output", "" },
{ "bindings-list", "false" },
{ "no-background", "false" },
{ "config", "" },
{ "dry-run", "false" },
Expand Down Expand Up @@ -140,6 +141,12 @@ std::pair<std::string, int> GetClosestOption(const std::string& option, bool che
*/
F3DOptionsTools::OptionsDict ParseCLIOptions(
int argc, char** argv, std::vector<std::string>& positionals);

/**
* Log provided key and help with nice formatting
*/
void PrintHelpPair(
std::string_view key, std::string_view help, int keyWidth = 10, int helpWidth = 70);
};

#endif
58 changes: 47 additions & 11 deletions application/F3DStarter.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include "F3DColorMapTools.h"
#include "F3DConfig.h"
#include "F3DConfigFileTools.h"
#include "F3DException.h"
#include "F3DIcon.h"
#include "F3DNSDelegate.h"
#include "F3DOptionsTools.h"
Expand Down Expand Up @@ -67,6 +68,7 @@ class F3DStarter::F3DInternals
struct F3DAppOptions
{
std::string Output;
bool BindingsList;
bool NoBackground;
bool NoRender;
std::string RenderingBackend;
Expand Down Expand Up @@ -556,6 +558,7 @@ class F3DStarter::F3DInternals
{
// Update typed app options from app options
this->AppOptions.Output = f3d::options::parse<std::string>(appOptions.at("output"));
this->AppOptions.BindingsList = f3d::options::parse<bool>(appOptions.at("bindings-list"));
this->AppOptions.NoBackground = f3d::options::parse<bool>(appOptions.at("no-background"));
this->AppOptions.NoRender = f3d::options::parse<bool>(appOptions.at("no-render"));
this->AppOptions.RenderingBackend =
Expand Down Expand Up @@ -766,7 +769,8 @@ int F3DStarter::Start(int argc, char** argv)
}
else
{
bool offscreen = !reference.empty() || !output.empty();
bool offscreen =
!reference.empty() || !output.empty() || this->Internals->AppOptions.BindingsList;

if (this->Internals->AppOptions.RenderingBackend == "egl")
{
Expand Down Expand Up @@ -901,18 +905,32 @@ int F3DStarter::Start(int argc, char** argv)
}
});

interactor.addBinding("Left", f3d::interactor::ModifierKeys::NONE, "load_previous_file_group");
interactor.addBinding("Right", f3d::interactor::ModifierKeys::NONE, "load_next_file_group");
interactor.addBinding("Up", f3d::interactor::ModifierKeys::NONE, "reload_current_file_group");
interactor.addBinding("Down", f3d::interactor::ModifierKeys::NONE, "add_current_directories");
interactor.addBinding("F11", f3d::interactor::ModifierKeys::NONE, "take_minimal_screenshot");
interactor.addBinding("F12", f3d::interactor::ModifierKeys::NONE, "take_screenshot");
// "doc"
auto docString = [](const std::string& doc) -> std::pair<std::string, std::string>
{ return std::make_pair(doc, ""); };

using mod_t = f3d::interaction_bind_t::ModifierKeys;
interactor.addBinding({ mod_t::NONE, "Left" }, "load_previous_file_group", "Others",
std::bind(docString, "Load previous file group"));
interactor.addBinding({ mod_t::NONE, "Right" }, "load_next_file_group", "Others",
std::bind(docString, "Load next file group"));
interactor.addBinding({ mod_t::NONE, "Up" }, "reload_current_file_group", "Others",
std::bind(docString, "Reload current file group"));
interactor.addBinding({ mod_t::NONE, "Down" }, "add_current_directories", "Others",
std::bind(docString, "Add files from dir of current file"));
interactor.addBinding({ mod_t::NONE, "F11" }, "take_minimal_screenshot", "Others",
std::bind(docString, "Take a minimal screenshot"));
interactor.addBinding({ mod_t::NONE, "F12" }, "take_screenshot", "Others",
std::bind(docString, "Take a screenshot"));

// This replace an existing default interaction command in the libf3d
interactor.removeBinding("Drop", f3d::interactor::ModifierKeys::NONE);
interactor.addBinding("Drop", f3d::interactor::ModifierKeys::NONE, "add_files_or_set_hdri");
interactor.addBinding("Drop", f3d::interactor::ModifierKeys::CTRL, "add_files");
interactor.addBinding("Drop", f3d::interactor::ModifierKeys::SHIFT, "set_hdri");
interactor.removeBinding({ mod_t::NONE, "Drop" });
interactor.addBinding({ mod_t::NONE, "Drop" }, "add_files_or_set_hdri", "Others",
std::bind(docString, "Load dropped files, folder or HDRI"));
interactor.addBinding({ mod_t::CTRL, "Drop" }, "add_files", "Others",
std::bind(docString, "Load dropped files or folder"));
interactor.addBinding(
{ mod_t::SHIFT, "Drop" }, "set_hdri", "Others", std::bind(docString, "Set HDRI and use it"));
}

this->Internals->Engine->setOptions(this->Internals->LibOptions);
Expand All @@ -932,6 +950,24 @@ int F3DStarter::Start(int argc, char** argv)
f3d::window& window = this->Internals->Engine->getWindow();
f3d::interactor& interactor = this->Internals->Engine->getInteractor();

// Print bindings list and exits if needed
if (this->Internals->AppOptions.BindingsList)
{
f3d::log::info("Bindings:");
for (const std::string& group : interactor.getBindGroups())
{
f3d::log::info(" ", group, ":");
for (const f3d::interaction_bind_t& bind : interactor.getBindsForGroup(group))
{
// XXX: Formatting could be improved here
auto [doc, val] = interactor.getBindingDocumentation(bind);
F3DOptionsTools::PrintHelpPair(bind.format(), doc, 12);
}
f3d::log::info("");
}
throw F3DExNoProcess("bindings list requested");
}

// Play recording if any
const std::string& interactionTestPlayFile =
this->Internals->AppOptions.InteractionTestPlayFile;
Expand Down
16 changes: 11 additions & 5 deletions application/testing/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -394,14 +394,16 @@ if(F3D_MODULE_RAYTRACING)
f3d_test(NAME TestInteractionCheatsheetRaytracing DATA cow.vtp INTERACTION) #H
f3d_test(NAME TestInteractionCheatsheetWhiteBGRaytracing DATA cow.vtp ARGS --bg-color=1,1,1 INTERACTION) #H
f3d_test(NAME TestInteractionCheatsheetBlackBGRaytracing DATA cow.vtp ARGS --bg-color=0,0,0 INTERACTION) #H
f3d_test(NAME TestInteractionCheatsheetScalarsRaytracing DATA dragon.vtu ARGS --scalar-coloring --comp=-2 INTERACTION LONG_TIMEOUT) #HSSS
f3d_test(NAME TestInteractionNoFileCheatsheetRaytracing INTERACTION NO_DATA_FORCE_RENDER) #HXZM
f3d_test(NAME TestInteractionCheatsheetScalarsRaytracing DATA dragon.vtu ARGS --scalar-coloring --comp=-2 RESOLUTION 800,800 INTERACTION LONG_TIMEOUT) #HSSS
f3d_test(NAME TestInteractionCheatsheetNoFileRaytracing INTERACTION NO_DATA_FORCE_RENDER) #HXZM
f3d_test(NAME TestInteractionCheatsheetAnimationNameRaytracing DATA InterpolationTest.glb ARGS --animation-index=6 RESOLUTION 800,800 INTERACTION) #HWWW
else()
f3d_test(NAME TestInteractionCheatsheet DATA cow.vtp INTERACTION) #H
f3d_test(NAME TestInteractionCheatsheetWhiteBG DATA cow.vtp ARGS --bg-color=1,1,1 INTERACTION) #H
f3d_test(NAME TestInteractionCheatsheetBlackBG DATA cow.vtp ARGS --bg-color=0,0,0 INTERACTION) #H
f3d_test(NAME TestInteractionCheatsheetScalars DATA dragon.vtu ARGS --scalar-coloring --comp=-2 INTERACTION LONG_TIMEOUT) #HSSS
f3d_test(NAME TestInteractionNoFileCheatsheet INTERACTION NO_DATA_FORCE_RENDER) #HXZM
f3d_test(NAME TestInteractionCheatsheetScalars DATA dragon.vtu ARGS --scalar-coloring --comp=-2 RESOLUTION 800,800 INTERACTION LONG_TIMEOUT) #HSSS
f3d_test(NAME TestInteractionCheatsheetNoFile INTERACTION NO_DATA_FORCE_RENDER) #HXZM
f3d_test(NAME TestInteractionCheatsheetAnimationName DATA InterpolationTest.glb ARGS --animation-index=6 RESOLUTION 800,800 INTERACTION) #HWWW
endif()

f3d_test(NAME TestCameraPersp DATA Cameras.gltf ARGS --camera-index=0)
Expand Down Expand Up @@ -518,7 +520,8 @@ if(F3D_MODULE_RAYTRACING)
f3d_test(NAME TestRaytracingNoBackground DATA suzanne.ply ARGS -rd --samples=4 --no-background)

else(F3D_MODULE_RAYTRACING)
f3d_test(NAME TestInteractionRaytracingDenoiseNoRaytracing DATA suzanne.ply INTERACTION NO_BASELINE REGEXP "Raytracing options can't be used if F3D has not been built with raytracing") #RD
# XXX: This test is disabled until this code can be reached again using `--command-script`
# f3d_test(NAME TestInteractionRaytracingDenoiseNoRaytracing DATA suzanne.ply INTERACTION NO_BASELINE REGEXP "Raytracing options can't be used if F3D has not been built with raytracing") #RD
endif()

if(F3D_MODULE_EXR)
Expand Down Expand Up @@ -1020,6 +1023,9 @@ if(F3D_PLUGIN_BUILD_ALEMBIC AND F3D_PLUGIN_BUILD_ASSIMP)
f3d_test(NAME TestReadersListMultiplePlugins ARGS --readers-list --load-plugins=assimp,alembic NO_BASELINE REGEXP_FAIL "Plugin failed to load")
endif()

# Test bindings-list display
f3d_test(NAME TestBindingsList ARGS --bindings-list REGEXP "Any.Question Print scene descr to terminal")

# Test rendering backends
# For some reason the sanitizer detects leaks because of EGL and OSMesa
f3d_test(NAME TestRenderingBackendAuto DATA cow.vtp RENDERING_BACKEND auto)
Expand Down
2 changes: 1 addition & 1 deletion doc/dev/BUILD.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ Here is some CMake options of interest:
* `F3D_PLUGINS_STATIC_BUILD`: Build all plugins as static library (embedded into `libf3d`) and automatically loaded by the application. Incompatible with `F3D_MACOS_BUNDLE`.
* `BUILD_SHARED_LIBS`: Build the libf3d and all plugins as static library (embedded into `f3d` executable). The `library` and `plugin_sdk` component will not be installed.

Some modules, plugins and bindings depending on external libraries can be optionally enabled with the following CMake variables:
Some modules, plugins and language bindings depending on external libraries can be optionally enabled with the following CMake variables:

* `F3D_MODULE_RAYTRACING`: Support for raytracing rendering. Requires that VTK has been built with `OSPRay` and `VTK_MODULE_ENABLE_VTK_RenderingRayTracing` turned on. Disabled by default.
* `F3D_MODULE_EXR`: Support for OpenEXR images. Requires `OpenEXR`. Disabled by default.
Expand Down
1 change: 1 addition & 0 deletions doc/libf3d/CLASSES.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ The commands let you interact with the libf3d in a very flexible manner.

Interactor also lets you add and remove bindings in order to modify how
the libf3d react to different interactions, eg. when a key is pressed or when a file is dropped.
This API also lets you control the content of the cheatsheet.

Use `log::setVerboseLevel(log::VerboseLevel::DEBUG)` to print debug information on interaction and command use.

Expand Down
2 changes: 1 addition & 1 deletion doc/libf3d/BINDINGS.md → doc/libf3d/LANGUAGE_BINDINGS.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Bindings
# Language Bindings

## Python

Expand Down
2 changes: 1 addition & 1 deletion doc/libf3d/README_LIBF3D.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
- [Overview of the libf3d.](OVERVIEW.md)
- [libf3d classes introduction.](CLASSES.md)
- [Exhaustive list of libf3d options.](OPTIONS.md)
- [How to use libf3d bindings.](BINDINGS.md)
- [How to use libf3d language bindings.](LANGUAGE_BINDINGS.md)
- [How to create a plugin.](PLUGINS.md)
12 changes: 7 additions & 5 deletions doc/user/INTERACTIONS.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Interactions

## Mouse Interactions
## 3D Mouse Interactions

Simple interaction with the displayed data is possible directly within the window. It is as follows:

Expand All @@ -12,11 +12,10 @@ Simple interaction with the displayed data is possible directly within the windo
* Hold <kbd>Ctrl</kbd>, then *Move the mouse wheel* to zoom in/out to mouse position.
* *Click and drag* with the *middle* mouse button to translate the camera.
* *Click* with the *middle* mouse button to center the camera on the point under the cursor (hold <kbd>Shift</kbd> to allow forward or backward movement).
* Drag and drop a file, directory or HDRI into the F3D window to load it. F3D relies on the extension (`.hdr` or `.exr`) to detect if the dropped file is an HDRI.

> Note: When playing an animation with a scene camera, camera interactions are locked.
## Hotkeys
## Bindings

The coloring can be controlled directly by pressing the following hotkeys:

Expand Down Expand Up @@ -52,7 +51,7 @@ Other options can be toggled directly by pressing the following hotkeys:
* <kbd>L</kbd>: increase lights intensity.
* <kbd>Shift</kbd>+<kbd>L</kbd>: decrease lights intensity.

Note that some hotkeys can be available or not depending on the file being loaded and the F3D configuration.
Note that the raytracing hotkeys are only available if F3D is build with raytracing enabled.

Camera Hotkeys:
* <kbd>1</kbd>: front view camera.
Expand All @@ -64,7 +63,7 @@ Camera Hotkeys:
* <kbd>9</kbd>: isometric view camera.
* <kbd>Enter</kbd>: reset the camera to its initial parameters.

Other hotkeys are available:
Other hotkeys and interactions are available:

* <kbd>H</kbd>: key to toggle the display of a cheat sheet showing all these hotkeys and their statuses.
* <kbd>?</kbd>: key to print scene description to the terminal.
Expand All @@ -76,6 +75,9 @@ Other hotkeys are available:
* <kbd>&darr;</kbd>: add all current files parent directories to the list of files, reload the currently loaded files and reset the camera.
* <kbd>F12</kbd>: take a screenshot, ie. render the current view to an image file.
* <kbd>F11</kbd>: take a "minimal" screenshot, ie. render the current view with no grid and no overlays to an image file with a transparent background.
* *Drop* a file, directory or HDRI into the F3D window to load it. F3D relies on the extension (`.hdr` or `.exr`) to detect if the dropped file is an HDRI.
* <kbd>Ctrl</kbd> + *Drop* a file, directory and load it.
* <kbd>Shift</kbd> + *Drop* a HDRI and use it, no extensions checks performed.

When loading another file or reloading, options that have been changed interactively are kept as is.

Expand Down
6 changes: 3 additions & 3 deletions library/private/animationManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,16 @@ class vtkRenderWindow;
namespace f3d
{
class options;
class window;

namespace detail
{
class interactor_impl;
class window_impl;

class animationManager
{
public:
animationManager(const options& options, window& window);
animationManager(const options& options, window_impl& window);
~animationManager() = default;

/**
Expand Down Expand Up @@ -107,7 +107,7 @@ class animationManager
void Tick();

const options& Options;
window& Window;
window_impl& Window;
vtkImporter* Importer = nullptr;
interactor_impl* Interactor = nullptr;

Expand Down
18 changes: 12 additions & 6 deletions library/private/interactor_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,18 @@ class interactor_impl : public interactor
bool triggerCommand(std::string_view command) override;

interactor& initBindings() override;
interactor& addBinding(const std::string& interaction, ModifierKeys modifiers,
std::vector<std::string> commands) override;
interactor& addBinding(
const std::string& interaction, ModifierKeys modifiers, std::string command) override;
interactor& removeBinding(std::string interaction, ModifierKeys modifiers) override;
std::vector<std::pair<std::string, ModifierKeys>> getBindingInteractions() const override;
interactor& addBinding(const interaction_bind_t& bind, std::vector<std::string> commands,
std::string group = std::string(),
documentation_callback_t documentationCallback = nullptr) override;
interactor& addBinding(const interaction_bind_t& bind, std::string command,
std::string group = std::string(),
documentation_callback_t documentationCallback = nullptr) override;
interactor& removeBinding(const interaction_bind_t& bind) override;
std::vector<std::string> getBindGroups() const override;
std::vector<interaction_bind_t> getBindsForGroup(std::string group) const override;
std::vector<interaction_bind_t> getBinds() const override;
std::pair<std::string, std::string> getBindingDocumentation(
const interaction_bind_t& bind) const override;

unsigned long createTimerCallBack(double time, std::function<void()> callBack) override;
void removeTimerCallBack(unsigned long id) override;
Expand Down
Loading

0 comments on commit f3d7627

Please sign in to comment.