New in Electric
| Note: This tutorial assumes that you have completed the previous tutorials: ROS Tutorials. |
Interactive Markers: Basic Controls
Description: This tutorial explains how the basic_controls tutorial code works.Tutorial Level: BEGINNER
Contents
The basic_controls tutorial explained
This tutorial shows you most of the common options you have for designing interactive markers. The node providing will print all feedback it gets from RViz on the command line.
All interactive markers contain a grey box, which in most cases will do nothing more than move together with the rest of the controls. It will show you how the coordinate frame of the Interactive Marker moves.
Simple 6-DOF control
This shows how to control all 6 degrees of freedom using 6 separate controls. Use the rings to rotate and the arrows to move the structure.
Simple 6-DOF control (fixed orientation)
Identical to the 6-DOF control, with the exception that the orientation of the controls will stay fixed, independent of the orientation of the frame being controlled.
49 Marker makeBox( InteractiveMarker &msg )
50 {
51 Marker marker;
52
53 marker.type = Marker::CUBE;
54 marker.scale.x = msg.scale * 0.45;
55 marker.scale.y = msg.scale * 0.45;
56 marker.scale.z = msg.scale * 0.45;
57 marker.color.r = 0.5;
58 marker.color.g = 0.5;
59 marker.color.b = 0.5;
60 marker.color.a = 1.0;
61
62 return marker;
63 }
64
65 InteractiveMarkerControl& makeBoxControl( InteractiveMarker &msg )
66 {
67 InteractiveMarkerControl control;
68 control.always_visible = true;
69 control.markers.push_back( makeBox(msg) );
70 msg.controls.push_back( control );
71
72 return msg.controls.back();
73 }
259 void make6DofMarker( bool fixed )
260 {
261 InteractiveMarker int_marker;
262 int_marker.header.frame_id = "/base_link";
263 int_marker.pose.position.y = -3.0 * marker_pos++;;
264 int_marker.scale = 1;
265
266 int_marker.name = "simple_6dof";
267 int_marker.description = "Simple 6-DOF Control";
268
269 // insert a box
270 makeBoxControl(int_marker);
271
272 InteractiveMarkerControl control;
273
274 if ( fixed )
275 {
276 int_marker.name += "_fixed";
277 int_marker.description += "\n(fixed orientation)";
278 control.orientation_mode = InteractiveMarkerControl::FIXED;
279 }
280
281 control.orientation.w = 1;
282 control.orientation.x = 1;
283 control.orientation.y = 0;
284 control.orientation.z = 0;
285 control.name = "rotate_x";
286 control.interaction_mode = InteractiveMarkerControl::ROTATE_AXIS;
287 int_marker.controls.push_back(control);
288 control.name = "move_x";
289 control.interaction_mode = InteractiveMarkerControl::MOVE_AXIS;
290 int_marker.controls.push_back(control);
291
292 control.orientation.w = 1;
293 control.orientation.x = 0;
294 control.orientation.y = 1;
295 control.orientation.z = 0;
296 control.name = "rotate_z";
297 control.interaction_mode = InteractiveMarkerControl::ROTATE_AXIS;
298 int_marker.controls.push_back(control);
299 control.name = "move_z";
300 control.interaction_mode = InteractiveMarkerControl::MOVE_AXIS;
301 int_marker.controls.push_back(control);
302
303 control.orientation.w = 1;
304 control.orientation.x = 0;
305 control.orientation.y = 0;
306 control.orientation.z = 1;
307 control.name = "rotate_y";
308 control.interaction_mode = InteractiveMarkerControl::ROTATE_AXIS;
309 int_marker.controls.push_back(control);
310 control.name = "move_y";
311 control.interaction_mode = InteractiveMarkerControl::MOVE_AXIS;
312 int_marker.controls.push_back(control);
313
314 server->insert(int_marker);
315 server->setCallback(int_marker.name, &processFeedback);
316 }
The code section above shows how to construct the first two interactive markers. After adding the grey box, 6 controls for each degree of freedom are added. No markers are added to these controls, which will result in RViz creating a set of colored rings and arrows as a default visualization.
The only difference between the two is that in the second case, the orientation mode is set to InteractiveMarkerControl::FIXED, while in the first it is left at its default value, which is InteractiveMarkerControl::INHERIT.
NOTE: The orientations in the above code snippet can be confusing. If you compute the rotation matrices corresponding to each of the quaternions, you can verify that the specified orientation is correct.
6-DOF (Arbitrary Axes)
Shows that controls are not limited to the unit axes but can work on any arbitrary orientation.
318 void makeRandomDofMarker( )
319 {
320 InteractiveMarker int_marker;
321 int_marker.header.frame_id = "/base_link";
322 int_marker.pose.position.y = -3.0 * marker_pos++;;
323 int_marker.scale = 1;
324
325 int_marker.name = "6dof_random_axes";
326 int_marker.description = "6-DOF\n(Arbitrary Axes)";
327
328 makeBoxControl(int_marker);
329
330 InteractiveMarkerControl control;
331
332 for ( int i=0; i<3; i++ )
333 {
334 control.orientation.w = rand(-1,1);
335 control.orientation.x = rand(-1,1);
336 control.orientation.y = rand(-1,1);
337 control.orientation.z = rand(-1,1);
338 control.interaction_mode = InteractiveMarkerControl::ROTATE_AXIS;
339 int_marker.controls.push_back(control);
340 control.interaction_mode = InteractiveMarkerControl::MOVE_AXIS;
341 int_marker.controls.push_back(control);
342 }
343
344 server->insert(int_marker);
345 server->setCallback(int_marker.name, &processFeedback);
346 }
The controls in this example are created by assigning random values to the quaternions which determine the orientation of each control. RViz will normalize these quaternions, so you don't have to worry about it when creating an interactive marker.
View-Facing 6-DOF
This interactive marker can move and rotate in all directions. In contrast to the previous examples, it does that using only two controls. The outer ring rotates along the view axis of the camera in RViz. The box moves in the camera plane, although it is not visually aligned with the camera coordinate frame.
349 void makeViewFacingMarker( )
350 {
351 InteractiveMarker int_marker;
352 int_marker.header.frame_id = "/base_link";
353 int_marker.pose.position.y = -3.0 * marker_pos++;;
354 int_marker.scale = 1;
355
356 int_marker.name = "view_facing";
357 int_marker.description = "View Facing 6-DOF";
358
359 InteractiveMarkerControl control;
360
361 // make a control that rotates around the view axis
362 control.orientation_mode = InteractiveMarkerControl::VIEW_FACING;
363 control.interaction_mode = InteractiveMarkerControl::ROTATE_AXIS;
364 control.orientation.w = 1;
365 control.name = "rotate";
366
367 int_marker.controls.push_back(control);
368
369 // create a box in the center which should not be view facing,
370 // but move in the camera plane.
371 control.orientation_mode = InteractiveMarkerControl::VIEW_FACING;
372 control.interaction_mode = InteractiveMarkerControl::MOVE_PLANE;
373 control.independent_marker_orientation = true;
374 control.name = "move";
375
376 control.markers.push_back( makeBox(int_marker) );
377 control.always_visible = true;
378
379 int_marker.controls.push_back(control);
380
381 server->insert(int_marker);
382 server->setCallback(int_marker.name, &processFeedback);
383 }
Quadrocopter
This interactive marker has a constrained set of 4 degrees of freedom. It can rotate around the z axis and move in all 3 dimensions. It it realized using two controls: the green ring moves in the y-z plane and rotates around the z-axis, while the two additional arrows move along z.
Click and drag the green ring to see how the combined movement and rotation works: If the mouse cursor stays close to the ring, it will only rotate. Once you move it further away, it will start following the mouse.
386 void makeQuadrocopterMarker( )
387 {
388 InteractiveMarker int_marker;
389 int_marker.header.frame_id = "/base_link";
390 int_marker.pose.position.y = -3.0 * marker_pos++;;
391 int_marker.scale = 1;
392
393 int_marker.name = "quadrocopter";
394 int_marker.description = "Quadrocopter";
395
396 makeBoxControl(int_marker);
397
398 InteractiveMarkerControl control;
399
400 control.orientation.w = 1;
401 control.orientation.x = 0;
402 control.orientation.y = 1;
403 control.orientation.z = 0;
404 control.interaction_mode = InteractiveMarkerControl::MOVE_ROTATE;
405 int_marker.controls.push_back(control);
406 control.interaction_mode = InteractiveMarkerControl::MOVE_AXIS;
407 int_marker.controls.push_back(control);
408
409 server->insert(int_marker);
410 server->setCallback(int_marker.name, &processFeedback);
411 }
The creation of the interactive marker is analogous to the previous examples, just that the interaction mode for one of the controls is set to MOVE_ROTATE.
Chess Piece
Click and drag the box or the surrounding Ring to move it in the x-y plane. Once you let go of the mouse button, it will snap to one of the grid fields. The way this works is that the basic_controls server running outside of RViz will set the pose of the Interactive Marker to a new value when it receives the pose from RViz. RViz will apply the update once you stop dragging it.
413 void makeChessPieceMarker( )
414 {
415 InteractiveMarker int_marker;
416 int_marker.header.frame_id = "/base_link";
417 int_marker.pose.position.y = -3.0 * marker_pos++;;
418 int_marker.scale = 1;
419
420 int_marker.name = "chess_piece";
421 int_marker.description = "Chess Piece\n(2D Move + Alignment)";
422
423 InteractiveMarkerControl control;
424
425 control.orientation.w = 1;
426 control.orientation.x = 0;
427 control.orientation.y = 1;
428 control.orientation.z = 0;
429 control.interaction_mode = InteractiveMarkerControl::MOVE_PLANE;
430 int_marker.controls.push_back(control);
431
432 // make a box which also moves in the plane
433 control.markers.push_back( makeBox(int_marker) );
434 control.always_visible = true;
435 int_marker.controls.push_back(control);
436
437 // we want to use our special callback function
438 server->insert(int_marker);
439 server->setCallback(int_marker.name, &processFeedback);
440
441 // set different callback for POSE_UPDATE feedback
442 server->setCallback(int_marker.name, &alignMarker, visualization_msgs::InteractiveMarkerFeedback::POSE_UPDATE );
443 }
The major difference to the previous example is that an additional feedback function is specified, which will be called instead of processFeedback() when the pose of the marker gets updated. This function modifies the pose of the marker and sends it back to RViz:
224 void alignMarker( const visualization_msgs::InteractiveMarkerFeedbackConstPtr &feedback )
225 {
226 geometry_msgs::Pose pose = feedback->pose;
227
228 pose.position.x = round(pose.position.x-0.5)+0.5;
229 pose.position.y = round(pose.position.y-0.5)+0.5;
230
231 ROS_INFO_STREAM( feedback->marker_name << ":"
232 << " aligning position = "
233 << feedback->pose.position.x
234 << ", " << feedback->pose.position.y
235 << ", " << feedback->pose.position.z
236 << " to "
237 << pose.position.x
238 << ", " << pose.position.y
239 << ", " << pose.position.z );
240
241 server->setPose( feedback->marker_name, pose );
242 server->applyChanges();
243 }
Pan / Tilt
This example shows that you can combine frame aligned and fixed-orientation controls in one Interactive Marker. The Pan control will always stay in place, while the tilt control will rotate.
445 void makePanTiltMarker( )
446 {
447 InteractiveMarker int_marker;
448 int_marker.header.frame_id = "/base_link";
449 int_marker.pose.position.y = -3.0 * marker_pos++;;
450 int_marker.scale = 1;
451
452 int_marker.name = "pan_tilt";
453 int_marker.description = "Pan / Tilt";
454
455 makeBoxControl(int_marker);
456
457 InteractiveMarkerControl control;
458
459 control.orientation.w = 1;
460 control.orientation.x = 0;
461 control.orientation.y = 1;
462 control.orientation.z = 0;
463 control.interaction_mode = InteractiveMarkerControl::ROTATE_AXIS;
464 control.orientation_mode = InteractiveMarkerControl::FIXED;
465 int_marker.controls.push_back(control);
466
467 control.orientation.w = 1;
468 control.orientation.x = 0;
469 control.orientation.y = 0;
470 control.orientation.z = 1;
471 control.interaction_mode = InteractiveMarkerControl::ROTATE_AXIS;
472 control.orientation_mode = InteractiveMarkerControl::INHERIT;
473 int_marker.controls.push_back(control);
474
475 server->insert(int_marker);
476 server->setCallback(int_marker.name, &processFeedback);
477 }
Context Menu
This example shows how to attach a simple-static menu to an interactive marker. If you do not specify a custom marker for visualization (as in the case of the grey box), RViz will create a text marker floating above the Interactive Marker, which will enables you to open the context menu.
479 void makeMenuMarker()
480 {
481 InteractiveMarker int_marker;
482 int_marker.header.frame_id = "/base_link";
483 int_marker.pose.position.y = -3.0 * marker_pos++;;
484 int_marker.scale = 1;
485
486 int_marker.name = "context_menu";
487 int_marker.description = "Context Menu\n(Right Click)";
488
489 InteractiveMarkerControl control;
490
491 control.interaction_mode = InteractiveMarkerControl::MENU;
492 control.description="Options";
493 control.name = "menu_only_control";
494
495 Marker marker = makeBox( int_marker );
496 control.markers.push_back( marker );
497 control.always_visible = true;
498 int_marker.controls.push_back(control);
499
500 server->insert(int_marker);
501 server->setCallback(int_marker.name, &processFeedback);
502 menu_handler.apply( *server, int_marker.name );
503 }
Marker attached to a moving frame
This example shows what happens if you click on a marker that is attached to a frame which moves relative to the fixed frame specified in RViz. Click on the box to move and on the ring to rotate. As the containing frame moves, the marker will continue moving relative to your mouse even if you are holding it.
505 void makeMovingMarker()
506 {
507 InteractiveMarker int_marker;
508 int_marker.header.frame_id = "/moving_frame";
509 int_marker.pose.position.y = -3.0 * marker_pos++;;
510 int_marker.scale = 1;
511
512 int_marker.name = "moving";
513 int_marker.description = "Marker Attached to a\nMoving Frame";
514
515 InteractiveMarkerControl control;
516
517 control.orientation.w = 1;
518 control.orientation.x = 1;
519 control.orientation.y = 0;
520 control.orientation.z = 0;
521 control.interaction_mode = InteractiveMarkerControl::ROTATE_AXIS;
522 int_marker.controls.push_back(control);
523
524 control.interaction_mode = InteractiveMarkerControl::MOVE_PLANE;
525 control.always_visible = true;
526 control.markers.push_back( makeBox(int_marker) );
527 int_marker.controls.push_back(control);
528
529 server->insert(int_marker);
530 server->setCallback(int_marker.name, &processFeedback);
531 }
The surrounding code
To setup the server node, all that is needed is to create an instance of InteractiveMarkerServer and pass all InteractiveMarker messages to that object.
Note that you have to call applyChanges() after you have added, updated or removed interactive markers, their pose, menus or feedback functions. This will cause the InteractiveMarkerServer to apply all scheduled changes to its internal state and send an update message to all connected clients. This is done to make it possible to maintain a coherent state and minimize data traffic between the server and its clients.
533 int main(int argc, char** argv)
534 {
535 ros::init(argc, argv, "basic_controls");
536 ros::NodeHandle n;
537
538 // create a timer to update the published transforms
539 ros::Timer frame_timer = n.createTimer(ros::Duration(0.01), frameCallback);
540
541 server.reset( new interactive_markers::InteractiveMarkerServer("basic_controls","",false) );
542
543 ros::Duration(0.1).sleep();
544
545 menu_handler.insert( "First Entry", &processFeedback );
546 menu_handler.insert( "Second Entry", &processFeedback );
547 interactive_markers::MenuHandler::EntryHandle sub_menu_handle = menu_handler.insert( "Submenu" );
548 menu_handler.insert( sub_menu_handle, "First Entry", &processFeedback );
549 menu_handler.insert( sub_menu_handle, "Second Entry", &processFeedback );
550
551 make6DofMarker( false );
552 make6DofMarker( true );
553 makeRandomDofMarker( );
554 makeViewFacingMarker( );
555 makeQuadrocopterMarker( );
556 makeChessPieceMarker( );
557 makePanTiltMarker( );
558 makeMenuMarker( );
559 makeMovingMarker( );
560
561 server->applyChanges();
562
563 ros::spin();
564
565 server.reset();
566 }
A timer is set up to update the tf transformation between base_link and moving_frame, which is done in frameCallback():
75 void frameCallback(const ros::TimerEvent&)
76 {
77 static uint32_t counter = 0;
78 static bool make = true;
79
80 static tf::TransformBroadcaster br;
81
82 tf::Transform t;
83
84 ros::Time time = ros::Time::now();
85
86 t.setOrigin(tf::Vector3(0.0, 0.0, sin(float(counter)/140.0) * 2.0));
87 t.setRotation(tf::Quaternion(0.0, 0.0, 0.0, 1.0));
88 br.sendTransform(tf::StampedTransform(t, time, "base_link", "moving_frame"));
89
90 t.setOrigin(tf::Vector3(0.0, 0.0, 0.0));
91 t.setRotation(tf::createQuaternionFromRPY(0.0, float(counter)/140.0, 0.0));
92 br.sendTransform(tf::StampedTransform(t, time, "base_link", "rotating_frame"));
93
94 ++counter;
95 if( (counter % 400) == 0 )
96 {
97 if( make )
98 {
99 InteractiveMarker int_marker;
100 int_marker.header.frame_id = "/base_link";
101 int_marker.pose.position.x = 5;
102 int_marker.scale = 1;
103
104 int_marker.name = "blinky";
105 int_marker.description = "blinking 6-DOF Control";
106
107 {
108 InteractiveMarkerControl control;
109
110 control.interaction_mode = InteractiveMarkerControl::MENU;
111 control.description="Blinky Options";
112 control.name = "menu_only_control";
113 control.always_visible = true;
114
115 Marker marker = makeBox( int_marker );
116 control.markers.push_back( marker );
117
118 int_marker.controls.push_back(control);
119 }
120
121 InteractiveMarkerControl control;
122
123 control.orientation.w = 1;
124 control.orientation.x = 1;
125 control.orientation.y = 0;
126 control.orientation.z = 0;
127 control.name = "rotate_x";
128 control.interaction_mode = InteractiveMarkerControl::ROTATE_AXIS;
129 int_marker.controls.push_back(control);
130 control.name = "move_x";
131 control.interaction_mode = InteractiveMarkerControl::MOVE_AXIS;
132 int_marker.controls.push_back(control);
133
134 control.orientation.w = 1;
135 control.orientation.x = 0;
136 control.orientation.y = 1;
137 control.orientation.z = 0;
138 control.name = "rotate_z";
139 control.interaction_mode = InteractiveMarkerControl::ROTATE_AXIS;
140 int_marker.controls.push_back(control);
141 control.name = "move_z";
142 control.interaction_mode = InteractiveMarkerControl::MOVE_AXIS;
143 int_marker.controls.push_back(control);
144
145 control.orientation.w = 1;
146 control.orientation.x = 0;
147 control.orientation.y = 0;
148 control.orientation.z = 1;
149 control.name = "rotate_y";
150 control.interaction_mode = InteractiveMarkerControl::ROTATE_AXIS;
151 int_marker.controls.push_back(control);
152 control.name = "move_y";
153 control.interaction_mode = InteractiveMarkerControl::MOVE_AXIS;
154 int_marker.controls.push_back(control);
155
156 server->insert(int_marker);
157 menu_handler.apply( *server, int_marker.name );
158
159 server->applyChanges();
160 make = false;
161 }
162 else
163 {
164 server->erase( "blinky" );
165 server->applyChanges();
166 make = true;
167 }
168 }
169 }
Finally, processFeedback() is used to print output to rosconsole when feedback arrives:
171 void processFeedback( const visualization_msgs::InteractiveMarkerFeedbackConstPtr &feedback )
172 {
173 std::ostringstream s;
174 s << "Feedback from marker '" << feedback->marker_name << "' "
175 << " / control '" << feedback->control_name << "'";
176
177 std::ostringstream mouse_point_ss;
178 if( feedback->mouse_point_valid )
179 {
180 mouse_point_ss << " at " << feedback->mouse_point.x
181 << ", " << feedback->mouse_point.y
182 << ", " << feedback->mouse_point.z
183 << " in frame " << feedback->header.frame_id;
184 }
185
186 switch ( feedback->event_type )
187 {
188 case visualization_msgs::InteractiveMarkerFeedback::BUTTON_CLICK:
189 ROS_INFO_STREAM( s.str() << ": button click" << mouse_point_ss.str() << "." );
190 break;
191
192 case visualization_msgs::InteractiveMarkerFeedback::MENU_SELECT:
193 ROS_INFO_STREAM( s.str() << ": menu item " << feedback->menu_entry_id << " clicked" << mouse_point_ss.str() << "." );
194 break;
195
196 case visualization_msgs::InteractiveMarkerFeedback::POSE_UPDATE:
197 ROS_INFO_STREAM( s.str() << ": pose changed"
198 << "\nposition = "
199 << feedback->pose.position.x
200 << ", " << feedback->pose.position.y
201 << ", " << feedback->pose.position.z
202 << "\norientation = "
203 << feedback->pose.orientation.w
204 << ", " << feedback->pose.orientation.x
205 << ", " << feedback->pose.orientation.y
206 << ", " << feedback->pose.orientation.z
207 << "\nframe: " << feedback->header.frame_id
208 << " time: " << feedback->header.stamp.sec << "sec, "
209 << feedback->header.stamp.nsec << " nsec" );
210 break;
211
212 case visualization_msgs::InteractiveMarkerFeedback::MOUSE_DOWN:
213 ROS_INFO_STREAM( s.str() << ": mouse down" << mouse_point_ss.str() << "." );
214 break;
215
216 case visualization_msgs::InteractiveMarkerFeedback::MOUSE_UP:
217 ROS_INFO_STREAM( s.str() << ": mouse up" << mouse_point_ss.str() << "." );
218 break;
219 }
220
221 server->applyChanges();
222 }






