moveit2
The MoveIt Motion Planning Framework for ROS 2.
Loading...
Searching...
No Matches
motion_planning_frame_joints_widget.cpp
Go to the documentation of this file.
1/*********************************************************************
2 * Software License Agreement (BSD License)
3 *
4 * Copyright (c) 2019, CITEC, Bielefeld University
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 *
11 * * Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * * Redistributions in binary form must reproduce the above
14 * copyright notice, this list of conditions and the following
15 * disclaimer in the documentation and/or other materials provided
16 * with the distribution.
17 * * Neither the name of CITEC / Bielefeld University nor the names of
18 * its contributors may be used to endorse or promote products derived
19 * from this software without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32 * POSSIBILITY OF SUCH DAMAGE.
33 *********************************************************************/
34
35/* Author: Robert Haschke */
36
39
40#include "ui_motion_planning_rviz_plugin_frame_joints.h"
41#include <QPainter>
42#include <QSlider>
43#include <QEvent>
44#include <QMouseEvent>
45
46namespace moveit_rviz_plugin
47{
48
49JMGItemModel::JMGItemModel(const moveit::core::RobotState& robot_state, const std::string& group_name, QObject* parent)
50 : QAbstractTableModel(parent), robot_state_(robot_state), jmg_(nullptr)
51{
52 if (robot_state_.getRobotModel()->hasJointModelGroup(group_name))
53 jmg_ = robot_state_.getRobotModel()->getJointModelGroup(group_name);
54}
55
56int JMGItemModel::rowCount(const QModelIndex& /*parent*/) const
57{
58 if (!jmg_)
59 {
60 return robot_state_.getVariableCount();
61 }
62 else
63 {
64 return jmg_->getVariableCount();
65 }
66}
67
68int JMGItemModel::columnCount(const QModelIndex& /*parent*/) const
69{
70 return 2;
71}
72
73Qt::ItemFlags JMGItemModel::flags(const QModelIndex& index) const
74{
75 if (!index.isValid())
76 return Qt::ItemFlags();
77
78 Qt::ItemFlags f = QAbstractTableModel::flags(index);
79
80 const moveit::core::JointModel* jm = getJointModel(index);
81 bool is_editable = !jm->isPassive() && !jm->getMimic();
82 f.setFlag(Qt::ItemIsEnabled, is_editable);
83 if (index.column() == 1)
84 f.setFlag(Qt::ItemIsEditable, is_editable);
85 return f;
86}
87
88QVariant JMGItemModel::data(const QModelIndex& index, int role) const
89{
90 if (!index.isValid())
91 return QVariant();
92
93 int idx = jmg_ ? jmg_->getVariableIndexList()[index.row()] : index.row();
94 switch (index.column())
95 {
96 case 0: // joint name column
97 switch (role)
98 {
99 case Qt::DisplayRole:
100 return QString::fromStdString(robot_state_.getVariableNames()[idx]);
101 case Qt::TextAlignmentRole:
102 return Qt::AlignLeft;
103 }
104 break;
105 case 1: // joint value column
106 {
107 double value = robot_state_.getVariablePosition(idx);
108 const moveit::core::JointModel* jm = robot_state_.getRobotModel()->getJointOfVariable(idx);
109 switch (role)
110 {
111 case Qt::DisplayRole:
112 return value;
113 case Qt::EditRole:
114 if (jm)
115 return jm->getType() == moveit::core::JointModel::REVOLUTE ? value * 180 / M_PI : value;
116 break;
118 if (jm)
119 return jm->getType();
120 break;
122 if (const moveit::core::VariableBounds* bounds = getVariableBounds(jm, index))
123 return QPointF(bounds->min_position_, bounds->max_position_);
124 break;
125 case Qt::TextAlignmentRole:
126 return Qt::AlignLeft;
127 }
128 }
129 }
130 return QVariant();
131}
132
133QVariant JMGItemModel::headerData(int section, Qt::Orientation orientation, int role) const
134{
135 if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
136 return section == 0 ? "Joint Name" : "Value";
137 return QAbstractTableModel::headerData(section, orientation, role);
138}
139
140bool JMGItemModel::setData(const QModelIndex& index, const QVariant& value, int role)
141{
142 if (index.column() != 1 || role != Qt::EditRole)
143 return false;
144
145 int var_idx = jmg_ ? jmg_->getVariableIndexList()[index.row()] : index.row();
146 const moveit::core::JointModel* jm = robot_state_.getRobotModel()->getJointOfVariable(var_idx);
147 if (!value.canConvert<double>())
148 return false;
149
150 bool ok;
151 double v = value.toDouble(&ok);
152 if (!ok)
153 return false;
154
155 // for revolute joints, we convert degrees to radians
157 v *= M_PI / 180;
158
159 robot_state_.setVariablePosition(var_idx, v);
161 dataChanged(index, index);
162 return true;
163}
164
165const moveit::core::JointModel* JMGItemModel::getJointModel(const QModelIndex& index) const
166{
167 if (!index.isValid())
168 return nullptr;
169 int var_idx = jmg_ ? jmg_->getVariableIndexList()[index.row()] : index.row();
170 return robot_state_.getRobotModel()->getJointOfVariable(var_idx);
171}
172
173const moveit::core::VariableBounds* JMGItemModel::getVariableBounds(const moveit::core::JointModel* jm,
174 const QModelIndex& index) const
175{
176 if (!jm)
177 return nullptr;
178 int var_idx = jmg_ ? jmg_->getVariableIndexList()[index.row()] : index.row();
179 const moveit::core::VariableBounds* bounds = &jm->getVariableBounds()[var_idx - jm->getFirstVariableIndex()];
180 return bounds->position_bounded_ ? bounds : nullptr;
181}
182
183// copy positions from new_state and notify about these changes
185{
186 if (robot_state_.getRobotModel() != state.getRobotModel())
187 return;
188 robot_state_.setVariablePositions(state.getVariablePositions());
189
190 dataChanged(index(0, 1), index(rowCount() - 1, 1));
191}
192
194 : QWidget(parent), ui_(new Ui::MotionPlanningFrameJointsUI()), planning_display_(display)
195{
196 ui_->setupUi(this);
197 // intercept mouse events delivered to joints_view_ to open editor on first mouse press
198 ui_->joints_view_->viewport()->installEventFilter(new JointsWidgetEventFilter(ui_->joints_view_));
199 ui_->joints_view_->setItemDelegateForColumn(1, new ProgressBarDelegate(this));
200 svd_.setThreshold(0.001);
201}
202
207
209{
210 ui_->joints_view_->setModel(nullptr);
211 start_state_handler_.reset();
212 goal_state_handler_.reset();
213 start_state_model_.reset();
214 goal_state_model_.reset();
215}
216
218 const std::string& group_name, const robot_interaction::InteractionHandlerPtr& start_state_handler,
219 const robot_interaction::InteractionHandlerPtr& goal_state_handler)
220{
221 // release previous models (if any)
223 // create new models
224 start_state_handler_ = start_state_handler;
225 goal_state_handler_ = goal_state_handler;
226 start_state_model_ = std::make_unique<JMGItemModel>(*start_state_handler_->getState(), group_name, this);
227 goal_state_model_ = std::make_unique<JMGItemModel>(*goal_state_handler_->getState(), group_name, this);
228
229 // forward model updates to the PlanningDisplay
230 connect(start_state_model_.get(), &JMGItemModel::dataChanged, this, [this]() {
231 if (!ignore_state_changes_)
232 planning_display_->setQueryStartState(start_state_model_->getRobotState());
233 });
234 connect(goal_state_model_.get(), &JMGItemModel::dataChanged, this, [this]() {
235 if (!ignore_state_changes_)
236 planning_display_->setQueryGoalState(goal_state_model_->getRobotState());
237 });
238
239 // show the goal state by default
240 setActiveModel(goal_state_model_.get());
242}
243
245{
246 if (!start_state_model_ || !start_state_handler_)
247 return;
248 ignore_state_changes_ = true;
249 start_state_model_->updateRobotState(*start_state_handler_->getState());
250 ignore_state_changes_ = false;
251 setActiveModel(start_state_model_.get());
253}
254
256{
257 if (!goal_state_model_ || !goal_state_handler_)
258 return;
259 ignore_state_changes_ = true;
260 goal_state_model_->updateRobotState(*goal_state_handler_->getState());
261 ignore_state_changes_ = false;
262 setActiveModel(goal_state_model_.get());
264}
265
267{
268 ui_->joints_view_->setModel(model);
269 ui_->joints_view_label_->setText(
270 QString("Group joints of %1 state").arg(model == start_state_model_.get() ? "start" : "goal"));
271}
272
274{
275 if (model == start_state_model_.get())
276 {
277 planning_display_->setQueryStartState(model->getRobotState());
278 }
279 else
280 {
281 planning_display_->setQueryGoalState(model->getRobotState());
282 }
283}
284
285// Find matching key vector in columns of haystack and return the best-aligned column index.
286// Only consider available indexes (> 0). If no match is found, return largest available index.
287// sign becomes -1 if key has changed sign compared to the matching haystack column.
288static Eigen::Index findMatching(const Eigen::VectorXd& key, const Eigen::MatrixXd& haystack,
289 const Eigen::VectorXi& available, double& sign)
290{
291 Eigen::Index result = available.array().maxCoeff();
292 double best_match = 0.0;
293 for (unsigned int i = 0; i < available.rows(); ++i)
294 {
295 int index = available[i];
296 if (index < 0) // index already taken
297 continue;
298 if (index >= haystack.cols())
299 return result;
300 double match = haystack.col(available[i]).transpose() * key;
301 double abs_match = std::abs(match);
302 if (abs_match > 0.5 && abs_match > best_match)
303 {
304 best_match = abs_match;
305 result = index;
306 sign = match > 0 ? 1.0 : -1.0;
307 }
308 }
309 return result;
310}
311
313{
314 JMGItemModel* model = dynamic_cast<JMGItemModel*>(ui_->joints_view_->model());
315 std::size_t i = 0;
316 if (model && model->getJointModelGroup() && model->getJointModelGroup()->isChain())
317 {
319 Eigen::MatrixXd jacobian;
320 if (!model->getRobotState().getJacobian(model->getJointModelGroup(),
321 model->getJointModelGroup()->getLinkModels().back(),
322 Eigen::Vector3d::Zero(), jacobian, false))
323 goto cleanup;
324
325 svd_.compute(jacobian, Eigen::ComputeFullV);
326 Eigen::Index rank = svd_.rank();
327 std::size_t ns_dim = svd_.cols() - rank;
328 Eigen::MatrixXd ns(svd_.cols(), ns_dim);
329 Eigen::VectorXi available(ns_dim);
330 for (std::size_t j = 0; j < ns_dim; ++j)
331 available[j] = j;
332
333 ns_sliders_.reserve(ns_dim);
334 // create/unhide sliders
335 for (; i < ns_dim; ++i)
336 {
337 if (i >= ns_sliders_.size())
338 ns_sliders_.push_back(createNSSlider(i + 1));
339 ns_sliders_[i]->show();
340
341 // Find matching null-space basis vector in previous nullspace_
342 double sign = 1.0;
343 const Eigen::VectorXd& current = svd_.matrixV().col(rank + i);
344 Eigen::Index index = findMatching(current, nullspace_, available, sign);
345 ns.col(index).noalias() = sign * current;
346 available[index] = -1; // mark index as taken
347 }
348 nullspace_ = ns;
349 }
350
351cleanup:
352 if (i == 0)
353 nullspace_.resize(0, 0);
354
355 // show/hide dummy slider
356 ui_->dummy_ns_slider_->setVisible(i == 0);
357
358 // hide remaining sliders
359 for (; i < ns_sliders_.size(); ++i)
360 ns_sliders_[i]->hide();
361}
362
364{
365 JogSlider* slider = new JogSlider(this);
366 slider->setOrientation(Qt::Horizontal);
367 slider->setMaximum(0.1);
368 slider->setToolTip(QString("Nullspace dim #%1").arg(i));
369 ui_->nullspace_layout_->addWidget(slider);
370 connect(slider, SIGNAL(triggered(double)), this, SLOT(jogNullspace(double)));
371 return slider;
372}
373
375{
376 if (value == 0)
377 return;
378
379 std::size_t index = std::find(ns_sliders_.begin(), ns_sliders_.end(), sender()) - ns_sliders_.begin();
380 if (static_cast<int>(index) >= nullspace_.cols())
381 return;
382
383 JMGItemModel* model = dynamic_cast<JMGItemModel*>(ui_->joints_view_->model());
384 if (!model)
385 return;
386
387 Eigen::VectorXd values;
389 values += value * nullspace_.col(index);
392 triggerUpdate(model);
393}
394
395void ProgressBarDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
396{
397 // copied from QStyledItemDelegate::paint
398 QStyle* style = option.widget ? option.widget->style() : QApplication::style();
399 QStyleOptionViewItem style_option = option;
400 initStyleOption(&style_option, index);
401
402 if (index.column() == 1)
403 {
404 QVariant joint_type = index.data(JointTypeRole);
405 double value = index.data().toDouble();
406 if (joint_type.isValid())
407 {
408 switch (joint_type.toInt())
409 {
411 style_option.text = option.locale.toString(value * 180 / M_PI, 'f', 0).append("°");
412 break;
414 style_option.text = option.locale.toString(value, 'f', 3).append("m");
415 break;
416 default:
417 break;
418 }
419 }
420
421 QVariant vbounds = index.data(VariableBoundsRole);
422 if (vbounds.isValid())
423 {
424 QPointF bounds = vbounds.toPointF();
425 const double min = bounds.x();
426 const double max = bounds.y();
427
428 QStyleOptionProgressBar opt;
429 opt.rect = option.rect;
430 opt.minimum = 0;
431 opt.maximum = 1000;
432 opt.progress = 1000. * (value - min) / (max - min);
433 opt.text = style_option.text;
434 opt.textAlignment = style_option.displayAlignment;
435 opt.textVisible = true;
436 style->drawControl(QStyle::CE_ProgressBar, &opt, painter);
437 return;
438 }
439 }
440
441 style->drawControl(QStyle::CE_ItemViewItem, &style_option, painter, option.widget);
442}
443
444QWidget* ProgressBarDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option,
445 const QModelIndex& index) const
446
447{
448 if (index.column() == 1)
449 {
450 QVariant vbounds = index.data(VariableBoundsRole);
451 if (vbounds.isValid())
452 {
453 QPointF bounds = vbounds.toPointF();
454 double min = bounds.x();
455 double max = bounds.y();
456 bool is_revolute = (index.data(JointTypeRole).toInt() == moveit::core::JointModel::REVOLUTE);
457 if (is_revolute)
458 {
459 min *= 180. / M_PI;
460 max *= 180. / M_PI;
461 }
462 auto* editor = new ProgressBarEditor(parent, min, max, is_revolute ? 0 : 3);
463 connect(editor, &ProgressBarEditor::editingFinished, this, &ProgressBarDelegate::commitAndCloseEditor);
464 connect(editor, &ProgressBarEditor::valueChanged, this, [=](double value) {
465 const_cast<QAbstractItemModel*>(index.model())->setData(index, value, Qt::EditRole);
466 });
467 return editor;
468 }
469 }
470 return QStyledItemDelegate::createEditor(parent, option, index);
471}
472
473void ProgressBarDelegate::commitAndCloseEditor()
474{
475 ProgressBarEditor* editor = qobject_cast<ProgressBarEditor*>(sender());
476 commitData(editor);
477 closeEditor(editor);
478}
479
480JointsWidgetEventFilter::JointsWidgetEventFilter(QAbstractItemView* view) : QObject(view)
481{
482}
483
484bool JointsWidgetEventFilter::eventFilter(QObject* /*target*/, QEvent* event)
485{
486 if (event->type() == QEvent::MouseButtonPress)
487 {
488 QAbstractItemView* view = qobject_cast<QAbstractItemView*>(parent());
489 QModelIndex index = view->indexAt(static_cast<QMouseEvent*>(event)->pos());
490 if (index.flags() & Qt::ItemIsEditable) // mouse event on any editable slider?
491 {
492 view->setCurrentIndex(index);
493 view->edit(index);
494 return true; // event handled
495 }
496 }
497 return false;
498}
499
500ProgressBarEditor::ProgressBarEditor(QWidget* parent, double min, double max, int digits)
501 : QWidget(parent), min_(min), max_(max), digits_(digits)
502{
503 // if left mouse button is pressed, grab all future mouse events until button(s) released
504 if (QApplication::mouseButtons() & Qt::LeftButton)
505 grabMouse();
506}
507
508void ProgressBarEditor::paintEvent(QPaintEvent* /*event*/)
509{
510 QPainter painter(this);
511
512 QStyleOptionProgressBar opt;
513 opt.rect = rect();
514 opt.palette = palette();
515 opt.minimum = 0;
516 opt.maximum = 1000;
517 opt.progress = 1000. * (value_ - min_) / (max_ - min_);
518 opt.text = QLocale().toString(value_, 'f', digits_);
519 opt.textAlignment = Qt::AlignRight;
520 opt.textVisible = true;
521 style()->drawControl(QStyle::CE_ProgressBar, &opt, &painter);
522}
523
525{
526 if (event->button() == Qt::LeftButton)
527 mouseMoveEvent(event);
528}
529
530void ProgressBarEditor::mouseMoveEvent(QMouseEvent* event)
531{
532 double v = std::min(max_, std::max(min_, min_ + event->x() * (max_ - min_) / width()));
533 if (value_ != v)
534 {
535 value_ = v;
536 valueChanged(v);
537 update();
538 }
539 event->accept();
540}
541
543{
544 if (event->button() == Qt::LeftButton)
545 {
546 event->accept();
548 }
549}
550
551JogSlider::JogSlider(QWidget* parent) : QSlider(parent)
552{
554 setResolution(1000);
555 setMaximum(1.0);
556}
557
559{
560 timer_interval_ = ms;
561}
562
563void JogSlider::setResolution(unsigned int resolution)
564{
565 QSlider::setRange(-resolution, +resolution);
566}
567
568void JogSlider::setMaximum(double value)
569{
570 maximum_ = value;
571}
572
573void JogSlider::timerEvent(QTimerEvent* event)
574{
575 QSlider::timerEvent(event);
576 if (event->timerId() == timer_id_)
577 triggered(value());
578}
579
580void JogSlider::mousePressEvent(QMouseEvent* event)
581{
582 QSlider::mousePressEvent(event);
583 timer_id_ = startTimer(timer_interval_);
584}
585
586void JogSlider::mouseReleaseEvent(QMouseEvent* event)
587{
588 killTimer(timer_id_);
589 QSlider::mouseReleaseEvent(event);
590 setValue(0);
591}
592
593} // namespace moveit_rviz_plugin
bool isChain() const
Check if this group is a linear chain.
const std::vector< const LinkModel * > & getLinkModels() const
Get the links that are part of this joint group.
const std::vector< int > & getVariableIndexList() const
Get the index locations in the complete robot state for all the variables in this group.
unsigned int getVariableCount() const
Get the number of variables that describe this joint group. This includes variables necessary for mim...
A joint from the robot. Models the transform that this joint applies in the kinematic chain....
size_t getFirstVariableIndex() const
Get the index of this joint's first variable within the full robot state.
const JointModel * getMimic() const
Get the joint this one is mimicking.
bool isPassive() const
Check if this joint is passive.
bool enforcePositionBounds(double *values) const
Force the specified values to be inside bounds and normalized. Quaternions are normalized,...
const VariableBounds & getVariableBounds(const std::string &variable) const
Get the bounds for a variable. Throw an exception if the variable was not found.
JointType getType() const
Get the type of joint.
Representation of a robot's state. This includes position, velocity, acceleration and effort.
Definition robot_state.h:90
void setVariablePositions(const double *position)
It is assumed positions is an array containing the new positions for all variables in this state....
const std::vector< std::string > & getVariableNames() const
Get the names of the variables that make up this state, in the order they are stored in memory.
bool getJacobian(const JointModelGroup *group, const LinkModel *link, const Eigen::Vector3d &reference_point_position, Eigen::MatrixXd &jacobian, bool use_quaternion_representation=false) const
Compute the Jacobian with reference to a particular point on a given link, for a specified group.
void setVariablePosition(const std::string &variable, double value)
Set the position of a single variable. An exception is thrown if the variable name is not known.
void setJointGroupPositions(const std::string &joint_group_name, const double *gstate)
Given positions for the variables that make up a group, in the order found in the group (including va...
void copyJointGroupPositions(const std::string &joint_group_name, std::vector< double > &gstate) const
For a given group, copy the position values of the variables that make up the group into another loca...
void harmonizePositions()
Call harmonizePosition() for all joints / all joints in group / given joint.
const RobotModelConstPtr & getRobotModel() const
Get the robot model this state is constructed for.
double * getVariablePositions()
Get a raw pointer to the positions of the variables stored in this state. Use carefully....
void updateLinkTransforms()
Update the reference frame transforms for links. This call is needed before using the transforms of l...
double getVariablePosition(const std::string &variable) const
Get the position of a particular variable. An exception is thrown if the variable is not known.
std::size_t getVariableCount() const
Get the number of variables that make up this state.
int columnCount(const QModelIndex &parent=QModelIndex()) const override
const moveit::core::JointModelGroup * getJointModelGroup() const
JMGItemModel(const moveit::core::RobotState &robot_state, const std::string &group_name, QObject *parent=nullptr)
Qt::ItemFlags flags(const QModelIndex &index) const override
QVariant data(const QModelIndex &index, int role) const override
QVariant headerData(int section, Qt::Orientation orientation, int role) const override
void updateRobotState(const moveit::core::RobotState &state)
call this on any external change of the RobotState
int rowCount(const QModelIndex &parent=QModelIndex()) const override
bool setData(const QModelIndex &index, const QVariant &value, int role) override
void triggered(double value)
void mousePressEvent(QMouseEvent *event) override
void mouseReleaseEvent(QMouseEvent *event) override
bool eventFilter(QObject *target, QEvent *event) override
void setQueryGoalState(const moveit::core::RobotState &goal)
void setQueryStartState(const moveit::core::RobotState &start)
void changePlanningGroup(const std::string &group_name, const robot_interaction::InteractionHandlerPtr &start_state_handler, const robot_interaction::InteractionHandlerPtr &goal_state_handler)
MotionPlanningFrameJointsWidget(const MotionPlanningFrameJointsWidget &)=delete
Delegate to show the joint value as with a progress bar indicator between min and max.
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override
QWidget * createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override
ProgressBarEditor(QWidget *parent=nullptr, double min=-1.0, double max=0.0, int digits=0)
Create a progressbar-like slider for editing values in range mix..max.