A ROCK (http://rock-robotics.org) library that provides methods to create 3d drawings and plot data from inside any code for debugging purpose.
-
Create and manipulate simple 3D drawings from inside any code
-
Create and manipulate 2D plots from inside any code
-
Minimal configuration needed
-
Drawings can be enabled/disabled using compile switch
-
No runtime overhead if disabled
-
Three different operation modes:
-
Standalone mode: A standalone window pops up automatically to visualize the drawings
-
Embedded mode: Drawings can be visualized using an existing Vizkit3DWidget and QApplication context.
-
Port mode: Drawings are published using rock ports and can be visualized using rock-display.
-
Available 3d primitives include:
-
Box, Arrow, Ring, Sphere, Cylinder, Line, Polyline, Text, AABB, Axes, etc.
-
New primitives can be added by implementing a simple interface (command pattern)
The package is part of the rock-core packageset (https://github.com/rock-core/rock-package_set). Nothing special has to be done to compile inside ROCK. Just call amake and wait.
- Install all system dependencies
apt update && apt -y install build-essential gcc g++ cmake git wget libboost-filesystem-dev libboost-serialization-dev libboost-system-dev pkg-config libeigen3-dev libopenscenegraph-dev doxygen libqt4-dev ruby-dev libboost-thread-dev libboost-test-dev
- Install local dependencies
./install_dependencies.sh <path_to_prefix>
This will create an env.sh
. Source it before continuing.
- Build V3DD
-
Port support doesn’t make much sense when building outside rock. It can be disabled with WITH_PORTS=OFF. If ports are disabled you may skip the rtt dependency (remove it from the
install_dependencies.sh
script).
Set DROCK_TEST_ENABLED=ON to build the tests.
mkdir build
cmake -DCMAKE_INSTALL_PREFIX=<path_to_prefix> -DWITH_PORTS=ON -DROCK_TEST_ENABLED=ON ..
make -j install
Run draw_test_standalone in build/test to check if everything works.
If you want to contribute code please do so using pull requests. Requests should contain appropriate test cases.
Every component that contains drawing code needs to link against libvizkit3d_debug_drawings.
DEPS_PKGCONFIG
vizkit3d_debug_drawings
If the component only needs to work with the command submission side and wants to avoid actually linking against Qt libraries, they can link against libvizkit3d_debug_drawings-commands instead. (This is generally the case for rock orogen components.)
DEPS_PKGCONFIG
vizkit3d_debug_drawings-commands
By default all drawing code is disabled and will be removed by the compiler. To enable it ENABLE_DEBUG_DRAWINGS needs to be defined for every component containing debug drawing commands.
add_definitions(-DENABLE_DEBUG_DRAWINGS)
If you do not use pkg-config and want to use the port features you have to define USE_PORTS aswell. pkg-config knows about this flag and sets it automatically if the library has been built with port support.
add_definitions(-DUSE_PORTS)
At runtime you need to choose which operation mode should be used to visualize the drawings. This is done by executing one of the following methods at startup:
void CONFIGURE_DEBUG_DRAWINGS_STANDALONE();
void CONFIGURE_DEBUG_DRAWINGS_USE_EXISTING_WIDGET(vizkit3d::Vizkit3DWidget* widget);
void CONFIGURE_DEBUG_DRAWINGS_USE_PORT(RTT::TaskContext* taskContext, const std::vector<std::string>&);
The STANDALONE
and EXISTING_WIDGET
configurations cannot be changed once configured.
The PORT
configuration should be configured exactly once per task (although it can be configured several times without causing problems).
In standalone mode a new QThread will be started containing a new QApplication context. This thread is used to display a Vizkit3DWidget which is used for visualization.
In this mode the application expects that there already is a QApplication context and a Vizkit3DWidget already exists. The existing widget will be used for visualization.
In port mode the application expects to be running inside a rock task. The context of that task has to be provided. For each drawing channel a new port will be added to the task and the corresponding drawing commands will be sent through that port. The drawings can be visualized using rock-display. Additional configuration is needed for this to work. See example below.
For some use cases the drawing channels need to be known at static initialization time. Therefore all drawing channels need to be declared using the V3DD_DECLARE_DEBUG_DRAWING_CHANNEL
macro.
V3DD_DECLARE_DEBUG_DRAWING_CHANNEL("channel_name");
The macro can be placed anywhere inside a cpp file outside of functions.
Once a channel has been declared it can be used anywhere inside your code.
If you have a lot of drawings it makes sense to create a dedicated drawing_declarations.cpp
and link it.
At runtime a list of all declared channel names is available using GET_DECLARED_CHANNELS
.
std::vector<std::string> channels = V3DD::GET_DECLARED_CHANNELS();
Note that GET_DECLARED_CHANNELS
will return all declared channels known to the process. I.e. also the once that might have been defined in different libraries.
It is adviced to use some sort of prefix for your channel names to be able to identify them later on.
Once configured you can start adding drawing commands anywhere inside your code.
The commands will be executed when the corresponding code path is executed.
Take a look at vizkit3d_debug_drawings/DebugDrawing.hpp
for an overview of all available commands.
All drawing commands are part of the V3DD
namespace.
#include <vizkit3d_debug_drawings/DebugDrawing.hpp>
#include <vizkit3d_debug_drawings/DebugDrawingColors.hpp> //only needed for named colors
Example:
Eigen::Vector3d pos(-3, -3, -3);
V3DD::DRAW_SPHERE("some_pos", pos, 1, V3DD::Color::red);
All drawing commands follow the same structure. The first parameter is always the
name of the drawing channel, the last parameter is always the color.
A list of named colors can be found in vizkit3d_debug_drawings/DebugDrawingColors.hpp
. If none of the named colors suits you, you can always define your own. A color is just an Eigen::Vector4d
containing RGBA values.
The drawing channel has special relevance. All drawings that belong to a channel will be visualized by the same instance of a visualizer or send through the same port. Thus a user can enable or disable the visualizations on a per channel basis. Channels are not limited to a certain type of drawing. They can contain any mix of drawing types (even plots).
Sometimes a lot of extra instructions (e.g. coordinate transformations) are needed before a drawing command can be issued. While the drawing command itself would be removed when debug drawings are disabled, the extra instructions would remain.
TO avoid this the COMPLEX_DRAWING
method can be used. This method takes a lambda that should contain the drawing code. When debug drawings are disabled the lambda is never executed.
V3DD::COMPLEX_DRAWING([]()
{
Eigen::Vector3d min, max;
min << -1, -1, -1;
max << 1, 1, 1;
Eigen::AlignedBox3d boundingBox(min, max);
V3DD::DRAW_AABB("Complex", boundingBox, V3DD::Color::alloy_orange);
V3DD::DRAW_SPHERE("Complex", -7, 1, 1, 1, V3DD::Color::magenta);
});
With a lot of drawings the visualization might get cluttered and laggy. To avoid that the user can clear drawings or remove them altogether. This is done by calling one of the following methods:
void V3DD::REMOVE_DRAWING(const std::string& drawingChannel);
void V3DD::CLEAR_DRAWING(const std::string& drawingChannel);
REMOVE_DRAWING
will remove all drawings belonging to the specified channel. It will also unload the corresponding Vizkit3DPlugin. Thus REMOVE_DRAWING
should be called when you want to permanently remove a channel.
CLEAR_DRAWING
will also remove all drawings from the specified channel. But it will not remove the plugin. It should be used when you intended to use the same channelagain (e.g. during a later iteration) but want a clean canvas to draw on.
In addition to 3D debug drawings, it is also possible to create simple 2D plots.
void V3DD::PLOT_2D(const std::string& plotName, const Eigen::Vector2d& dataPoint);
void V3DD::CLEAR_PLOT(const std::string& plotName);
PLOT_2D
will add a data point to an existing plot or create a new plot if
the plot doesn’t exist. Plots show up as docked widgets in the Vizkit3DWidget.
At the time of writing plots can be cleared but not completely removed.
double x = 0.0;
while(true)
{
x += 0.1;
V3DD::PLOT_2D("sin", Eigen::Vector2d{x,std::sin(x)});
}
When sending drawing commands through rock ports the user needs to flush the send queue regularly. This should be done in the update loop of the corresponding task. If you do not flush manually the library will flush for you every 1.5 seconds.
This is only relevant for port mode. In other modes there is no need to flush!
When several tasks use debug drawings they will ultimatly all use the same internal drawing dispatcher. Thus flushing in one task will also flush the drawings of other tasks. This is in itself not a problem but could become a performance bottleneck if a lot of tasks are running.
See example below.
A minimal standlone example can be found in test/draw_test_standalone.cpp
.
Take a look at test/CMakeLists.txt
to learn about the neccessary flags to build the example.
An example attaching to an existing Vizkit3DWidget
can be found in test/draw_test_attach.cpp
.
If you want to output debug drawings through the ports of a ROCK task the following needs to be done:
- Build V3DD with port support
-
For the port output to work you need to enable port support. Compile the V3DD library with
add_definitions(-DUSE_PORTS)
Without this flag the commands for port output will not be available.
- Add dependencies
-
A minimal
manifest.xml
of your tasks should look like this:
<package>
<depend package="base/cmake" />
<depend package="gui/orogen/vizkit3d_debug_drawings" />
<depend package="gui/vizkit3d_debug_drawings" />
</package>
- Modify CMakeLists
-
Modify the
src/CMakeLists.txt
and add the following:
# enable debug drawings
add_definitions(-DENABLE_DEBUG_DRAWINGS)
# find v3dd
find_package(PkgConfig REQUIRED)
pkg_check_modules(V3DD REQUIRED vizkit3d_debug_drawings)
# link v3dd
TARGET_LINK_LIBRARIES(${YOUR_TASKNAME_HERE_TASKLIB_NAME}
#other libs here
${V3DD_LIBRARIES})
# add include directories and linker flags:
target_include_directories(${YOUR_TASKNAME_HERE_TASKLIB_NAME} PUBLIC ${V3DD_INCLUDE_DIRS})
target_compile_options(${YOUR_TASKNAME_HERE_TASKLIB_NAME} PUBLIC ${V3DD_CFLAGS_OTHER})
- Modify orogen file
-
To be able to output data through ports you need to tell orogen to load the typekit. If you do not do this, rock-display will not be able to deserialize the debug messages. It will shown an error instead.
Add the following to the orogen file:
using_library "vizkit3d_debug_drawings"
import_types_from "vizkit3d_debug_drawings"
And add a dynamic port to every Task that outputs debug data:
dynamic_output_port /^debug_/, "/boost/shared_ptr</vizkit3dDebugDrawings/CommandBuffer>"
- Modify Task
-
You have to tell the library the drawing channels that should be associated with the current tasks. For each drawing a
debug_XXX
port will be added to your task. The port will be added at configuration time.
bool Task::configureHook()
{
std::vector<std::string> channels = V3DD::GET_DECLARED_CHANNELS();
// ...
//filter channels somehow to decided which channels you care about in this task
// ...
V3DD::CONFIGURE_DEBUG_DRAWINGS_USE_PORT(this, channels);
if (! TaskBase::configureHook())
return false;
return true;
}
void Task::updateHook()
{
TaskBase::updateHook();
//your code here
V3DD::FLUSH_DRAWINGS();
}
Commands are serialized using boost to send them through rock ports as opaque type containing a binary blob with the serialized data. The Opaque conversion can be found [in this repository](https://github.com/rock-gui/gui-orogen-vizkit3d_debug_drawings).
Boost serialization was chosen over typekit serialization because typekit cannot handle virtual inheritance.
There are two namespaces within V3DD. The user facing namespace is V3DD
. All methods that should be used by the user are in this namespace.
Internal stuff is in vizkit3dDebugDrawings
.
The way rock-display connects ports allows for message loss. I.e. when too may messages are sent, they are dropped. This happens regularly. Thus we have to send the whole drawing state every time. Sending only incremental updates might lead to a corrupt state due to message loss.
This project was heavily inspired by the inline drawing macros that can be found in the [B-Human](https://b-human.de) framework. See: https://github.com/bhuman/BHumanCodeRelease/blob/master/Src/Tools/Debugging/DebugDrawings3D.hpp