前言
在使用rviz库编写自己的工具的时候,需要使用这样的语句,将工具注册到pluginlib。
#include <pluginlib/class_list_macros.h>
PLUGINLIB_EXPORT_CLASS(robot_upper_plugins::RouteGoalTool, rviz::Tool )
需要启用工具时,需要用注册时的名称来添加到tool_manager,类似于:
addTool("robot_upper_plugins/RouteGoalTool");
让我们来看看librviz是怎么从插件库中加载和启用工具类的。
1 Tool类的添加和鼠标事件处理
1.1 已注册工具类的添加
工具类添加到插件库后,当我们在rviz中点击添加工具,对应到代码里,就是调用了ToolManager类的addTool方法,用插件库内的唯一命名来创建工具,像这样:
void ToolManager::initialize()
{
// 这些都是rviz中的默认插件
addTool( "rviz/MoveCamera" );
addTool( "rviz/Interact" );
addTool( "rviz/Select" );
addTool( "rviz/SetInitialPose" );
addTool( "rviz/SetGoal" );
}
当VisualizationManager实例化ToolManager后,成为其上下文;
tool_manager_ = new ToolManager( this );
addTool():根据插件class_id从工厂创建一个tool,将VisualizationManager作为context传入tool->initialize()。最后发出toolAdded(tool)信号;
ToolManager::ToolManager( DisplayContext* context ) : context_( context ) {}
Tool* ToolManager::addTool( const QString& class_id )
{
Tool* tool = factory_->make( class_id, &error );
tools_.append( tool );
tool->initialize( context_ );
Q_EMIT toolAdded( tool );
}
addTool( Tool* tool ):toolAdded()的槽,当接收到toolAdded()信号,创建一个qaction到toolbar_actions_组里;
当这个qaction被触发时,调用ToolManager()->setCurrentToo();
connect( tool_man, SIGNAL( toolAdded( Tool* )), this, SLOT( addTool( Tool* )));
void VisualizationFrame::addTool( Tool* tool )
{
QAction* action = new QAction( tool->getName(), toolbar_actions_ );
toolbar_->insertAction( add_tool_action_, action );
tool_to_action_map_[ tool ] = action;
}
toolbar_actions_ = new QActionGroup( this );
connect( toolbar_actions_, SIGNAL( triggered( QAction* )), this, SLOT( onToolbarActionTriggered( QAction* )));
void VisualizationFrame::onToolbarActionTriggered( QAction* action )
{
Tool* tool = action_to_tool_map_[ action ];
manager_->getToolManager()->setCurrentTool( tool );
}
setCurrentTool( Tool* tool ):设置当前工具后,发出toolChanged信号通知VisualizationManager;
void ToolManager::setCurrentTool( Tool* tool )
{
...
Q_EMIT toolChanged( current_tool_ );
}
connect( tool_manager_, SIGNAL( toolChanged( Tool* ) ), this, SLOT( onToolChanged( Tool* ) ));
void VisualizationManager::onToolChanged( Tool* tool ) {} /* 然鹅未启用 */
1.2 鼠标事件的传递和处理
我们知道,rviz::Tool类中有一个processMouseEvent( ViewportMouseEvent& event ),用于处理工具的鼠标事件,这是Tool类的核心功能,让我们来看一下它是怎么被调用的。
fake_mouse_move_event_timer_:定时器,定时触发sendMouseMoveEvent();
sendMouseMoveEvent():创建虚拟的鼠标事件fake_event并作为参数调用onRenderWindowMouseEvents( &fake_event ),这也就是按照一定周期(33ms)发送鼠标事件;
onRenderWindowMouseEvents( QMouseEvent* event ):获取鼠标的当前位置,创建ViewportMouseEvent,传递给上下文(就是VisualizationManager)的handleMouseEvent( QMouseEvent* event )进行处理;
connect( fake_mouse_move_event_timer_, SIGNAL( timeout() ), this, SLOT( sendMouseMoveEvent() ));
fake_mouse_move_event_timer_->start( 33 /*milliseconds*/ );
void RenderPanel::sendMouseMoveEvent()
{
...
QMouseEvent fake_event( QEvent::MouseMove,
mouse_rel_widget,
QApplication::mouseButtons(),
QApplication::keyboardModifiers() );
onRenderWindowMouseEvents( &fake_event );
}
void RenderPanel::onRenderWindowMouseEvents( QMouseEvent* event )
{
int last_x = mouse_x_;
int last_y = mouse_y_;
if (context_)
{
setFocus( Qt::MouseFocusReason );
ViewportMouseEvent vme(this, getViewport(), event, last_x, last_y);
context_->handleMouseEvent(vme);
event->accept();
}
...
}
handleMouseEvent( vme ):从ToolManager获取当前工具,调用current_tool->processMouseEvent( _vme );(终于找到你了!)
void VisualizationManager::handleMouseEvent( const ViewportMouseEvent& vme )
{
//process pending mouse events
Tool* current_tool = tool_manager_->getCurrentTool();
if( current_tool )
{
ViewportMouseEvent _vme = vme;
flags = current_tool->processMouseEvent( _vme );
vme.panel->setCursor( current_tool->getCursor() );
}
...
}
1.3 实现工具类的无注册调用
上面我们了解了已注册的工具类如何加载和调用,那么能不能在程序中引入插件类的头文件,实现我们自己实例化的插件类的添加和调用呢,当然是可以的,而且基于librviz优秀的代码设计,这实现起来很容易。
在tool.h中:
虚函数,提供给子类重新实现,实现多样的鼠标事件处理。
virtual int processMouseEvent( ViewportMouseEvent& event ) { return 0; }
举个例子,在move_tool.cpp中:
int MoveTool::processKeyEvent( QKeyEvent* event, RenderPanel* panel )
{
if( context_->getViewManager()->getCurrent() )
{
context_->getViewManager()->getCurrent()->handleKeyEvent( event, panel );
}
return Render;
}
自己创建不被注册的工具类,想要其被启用,并且处理鼠标事件,关键在于这两点:
- Tool::initialize( VisualizationManager ); // 实例化你的工具类,调用initialize函数,把render_panel的VisualizationManager传进去。
- ToolManager::setCurrentTool( Tool ); // 设置为ToolManager的当前工具后,VisualizationManager便可以传递鼠标事件给工具了,让它进行处理了!
举个例子:
void initialize()
{
// 创建3D面板、中央管理器和tool管理器
render_panel_ = new rviz::RenderPanel();
visualization_manager_ = new rviz::VisualizationManager(render_panel_);
tool_manager_ = visualization_manager_->getToolManager();
render_panel_->initialize(visualization_manager_->getSceneManager(), visualization_manager_);
visualization_manager_->initialize();
visualization_manager_->startUpdate();
// 初始化工具
route_goal_tool_ = new rviz_plugins::RouteGoalTool();
route_goal_tool_->initialize(visualization_manager_);
}
// 启动工具
void startTool()
{
tool_manager_->setCurrentTool(route_goal_tool_);
}
当然,这样虽然可以实现,但也有很明显的缺点,ToolManager::addTool()中除了创建工具,还会维护tools_和shortkey_to_tool_map_列表,一些操作如删除工具、属性更改等都是依赖tools_列表实现,快捷键映射则依赖于shortkey_to_tool_map_列表。而没有注册的插件需要自己管理,容易造成混乱。
Tool* ToolManager::addTool( const QString& class_id )
{
Tool* tool = factory_->make( class_id, &error );
tools_.append( tool );
tool->setName( addSpaceToCamelCase( factory_->getClassName( class_id )));
tool->setIcon( factory_->getIcon( class_id ) );
tool->initialize( context_ );
if( tool->getShortcutKey() != '\0' )
{
uint key;
QString str = QString( tool->getShortcutKey() );
if( toKey( str, key ) )
{
shortkey_to_tool_map_[ key ] = tool;
}
}
Property* container = tool->getPropertyContainer();
connect( container, SIGNAL( childListChanged( Property* )), this, SLOT( updatePropertyVisibility( Property* )));
updatePropertyVisibility( container );
Q_EMIT configChanged();
return tool;
}
总结
第一次比较深入地去看源码,希望有一天也能写出这样的好代码~