VertexDataWidget.cpp
Go to the documentation of this file.
1/*
2 * This file is part of ArmarX.
3 *
4 * ArmarX is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2 as
6 * published by the Free Software Foundation.
7 *
8 * ArmarX is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * @package MemoryX::ArmarXObjects::GraphImportExport
17 * @author Rainer Kartmann ( rainer dot kartmann at kit dot edu )
18 * @date 2021
19 * @copyright http://www.gnu.org/licenses/gpl-2.0.txt
20 * GNU General Public License
21 */
22
23#include "VertexDataWidget.h"
24
25#include <functional>
26#include <optional>
27#include <string>
28#include <vector>
29
30#include <QComboBox>
31#include <QDoubleSpinBox>
32#include <QFormLayout>
33#include <QHBoxLayout>
34#include <QLineEdit>
35#include <QList>
36#include <QPushButton>
37#include <QRadioButton>
38#include <QSignalBlocker>
39#include <qboxlayout.h>
40#include <qformlayout.h>
41#include <qglobal.h>
42#include <qnamespace.h>
43#include <qobject.h>
44#include <qobjectdefs.h>
45#include <qsizepolicy.h>
46
47#include <SimoxUtility/math/convert/deg_to_rad.h>
48#include <SimoxUtility/math/convert/mat4f_to_rpy.h>
49#include <SimoxUtility/math/convert/rad_to_deg.h>
50#include <SimoxUtility/math/convert/rpy_to_mat3f.h>
51#include <SimoxUtility/math/pose/pose.h>
52#include <VirtualRobot/XML/BaseIO.h>
53#include <VirtualRobot/XML/RobotIO.h>
54
57
64
68
70#include "RobotVisuWidget.h"
71
73{
74
76 {
77 setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum);
78
79 name = new QLineEdit(this);
80 name->setReadOnly(true);
81 locationID = new QLineEdit(this);
82 locationID->setReadOnly(true);
83
84 agent = new QComboBox(this);
85 agent->setSizeAdjustPolicy(QComboBox::AdjustToContents);
86
87 frame = new QComboBox(this);
88 frame->setSizeAdjustPolicy(QComboBox::AdjustToContents);
89
90 removeInstanceButton = new QPushButton("Remove Instance");
91 removeInstanceButton->setToolTip(
92 "Remove the instance id from the agent and make this location relative to an object "
93 "class instead of a specific object instance.");
94
95 x = new QDoubleSpinBox(this);
96 y = new QDoubleSpinBox(this);
97 z = new QDoubleSpinBox(this);
98
99 roll = new QDoubleSpinBox(this);
100 pitch = new QDoubleSpinBox(this);
101 yaw = new QDoubleSpinBox(this);
102
103 angleUnitDeg = new QRadioButton("Degrees");
104 angleUnitRad = new QRadioButton("Radians");
105
106 _useCurrentRobotPose = new QPushButton("Use Current Robot Pose");
107 _useCurrentRobotPose->setEnabled(false);
108 _useCurrentRobotPose->setToolTip(
109 "Use the robot's current global pose (requires connection to robot state).");
110
111
112 for (QDoubleSpinBox* pos : _positionSpinBoxes())
113 {
114 pos->setSuffix(" mm");
115 pos->setMinimum(-1e6);
116 pos->setMaximum(+1e6);
117 pos->setSingleStep(50);
118 }
119 for (QDoubleSpinBox* spinBox : _allSpinBoxes())
120 {
121 spinBox->setValue(0);
122 spinBox->setAlignment(Qt::AlignRight);
123 }
124
125 QHBoxLayout* angleUnitLayout = new QHBoxLayout();
126 for (QRadioButton* angleUnit : {angleUnitDeg, angleUnitRad})
127 {
128 angleUnitLayout->addWidget(angleUnit);
129 }
130
131 QFormLayout* layout = new QFormLayout();
132 this->setLayout(layout);
133
134 layout->addRow("Name", name);
135 layout->addRow("Entity ID", locationID);
136 layout->addRow("Agent", agent);
137 layout->addRow("Frame", frame);
138 layout->addRow(removeInstanceButton);
139 layout->addRow("X", x);
140 layout->addRow("Y", y);
141 layout->addRow("Z", z);
142 layout->addRow("Roll", roll);
143 layout->addRow("Pitch", pitch);
144 layout->addRow("Yaw", yaw);
145 layout->addRow("Angle Units", angleUnitLayout);
146 layout->addRow(_useCurrentRobotPose);
147
148
149 // Connect
150
151 for (QRadioButton* angleUnit : {angleUnitDeg, angleUnitRad})
152 {
153 connect(angleUnit, &QRadioButton::toggled, this, &This::_updateAngleUnit);
154 }
155
156 connect(agent,
157 QOverload<int>::of(&QComboBox::currentIndexChanged),
158 this,
159 &This::_updateVertexAgent);
160 connect(frame,
161 QOverload<int>::of(&QComboBox::currentIndexChanged),
162 this,
163 &This::_updateVertexFrame);
164 connect(removeInstanceButton, &QPushButton::pressed, this, &This::_removeInstance);
165
166 for (QDoubleSpinBox* spinBox : _allSpinBoxes())
167 {
168 connect(spinBox,
169 QOverload<qreal>::of(&QDoubleSpinBox::valueChanged),
170 this,
171 &This::_updateVertexAttribs);
172 }
173
174 connect(_useCurrentRobotPose, &QPushButton::pressed, this, &This::_setFromCurrentRobotPose);
175
176
177 angleUnitDeg->click();
178 }
179
180 std::optional<GuiGraph::Vertex>
182 {
183 return _vertex;
184 }
185
186 void
188 {
189 QSignalBlocker blocker(this);
190
191 _vertex = vertex;
192 _setFromVertex(vertex);
193 setEnabled(true);
194 }
195
196 void
198 {
199 _vertex = std::nullopt;
200 setEnabled(false);
201 }
202
203 Eigen::Vector3d
205 {
206 return {x->value(), y->value(), z->value()};
207 }
208
209 Eigen::Vector3d
211 {
212 Eigen::Vector3d raw = _rpyRaw();
213 if (angleUnitRad->isChecked())
214 {
215 return {}; // return simox::math::rad_to_deg(raw);
216 }
217 else
218 {
219 return raw;
220 }
221 }
222
223 Eigen::Vector3d
225 {
226 Eigen::Vector3d raw = _rpyRaw();
227 if (angleUnitDeg->isChecked())
228 {
229 return simox::math::deg_to_rad(raw);
230 }
231 else
232 {
233 return raw;
234 }
235 }
236
239 {
240 return core::Pose_d(simox::math::pose(xyz(), simox::math::rpy_to_mat3f(rpyRad())));
241 }
242
243 void
245 {
246 this->_robotConnection = connection;
247 _useCurrentRobotPose->setEnabled(_robotConnection != nullptr);
248 }
249
250 void
252 {
253 this->_objectPosesConnection = connection;
254 if (_vertex.has_value())
255 {
256 QSignalBlocker blocker(this);
257
258 _loadFramedPoseFromVertex(_vertex.value());
259 }
260 }
261
262 void
263 VertexDataWidget::_setFromVertex(const GuiGraph::Vertex& vertex)
264 {
265 const VertexData& attrib = vertex.attrib();
266
267 name->setText(QString::fromStdString(attrib.getLocationName()));
268 locationID->setText(
269 QString::fromStdString(aron::fromAron<armem::MemoryID>(attrib.aron.locationID).str()));
270
271 _loadFramedPoseFromVertex(vertex);
272
273 _setPose(core::Pose_d(attrib.getPose().toEigen().cast<qreal>()));
274 }
275
276 void
277 VertexDataWidget::_getToVertex(GuiGraph::Vertex& vertex)
278 {
279 VertexData& attrib = vertex.attrib();
280
281 // name is read-only
282 // locationID is read-only
283
284 FramedPose framed(pose().cast<float>().matrix(),
285 frame->currentText().toStdString(),
286 agent->currentText().toStdString());
287 attrib.setPose(framed);
288 }
289
290 void
291 VertexDataWidget::_updateAngleUnit()
292 {
293 std::function<double(double)> convertValue;
294 QString suffix = " \u00b0";
295 double min = -360;
296 double max = +360;
297 double step = 5.;
298 int decimals = 2;
299
300 if (angleUnitRad->isChecked())
301 {
302 convertValue = [](double deg) { return simox::math::deg_to_rad(deg); };
303 suffix = " rad";
304 min = simox::math::deg_to_rad(min);
305 max = simox::math::deg_to_rad(max);
306 step = simox::math::deg_to_rad(step);
307 decimals = 3;
308 }
309 else
310 {
311 ARMARX_CHECK(angleUnitDeg->isChecked());
312 convertValue = [](double rad) { return simox::math::rad_to_deg(rad); };
313 }
314
315 std::vector<QSignalBlocker> blockers;
316 for (QDoubleSpinBox* angle : _angleSpinBoxes())
317 {
318 blockers.emplace_back(angle);
319
320 angle->setSuffix(suffix);
321 angle->setMinimum(min);
322 angle->setMaximum(max);
323 angle->setSingleStep(step);
324 angle->setDecimals(decimals);
325 }
326 if (_vertex.has_value())
327 {
328 _setRpyRad(
329 simox::math::mat4f_to_rpy(_vertex->attrib().getPose().toEigen().cast<qreal>()));
330 }
331
332 // blockers will disable blocking for all spin boxes on destruction.
333 }
334
335 void
336 VertexDataWidget::_updateVertexAttribs()
337 {
338 if (not signalsBlocked() and _vertex.has_value())
339 {
340 _getToVertex(_vertex.value());
341 _vertex->attrib().changed = true;
342 emit vertexDataChanged();
343 }
344 }
345
346 void
347 VertexDataWidget::_updateVertexAgent()
348 {
349 if (not signalsBlocked() and _vertex.has_value() and _objectPosesConnection != nullptr)
350 {
351 {
352 QSignalBlocker blocker(this);
353
354 _updateFrameMenu();
355 frame->setCurrentIndex(0);
356
357 _framedPoseCoordinateConversion();
358 }
359 _updateVertexAttribs();
360 }
361 }
362
363 void
364 VertexDataWidget::_updateVertexFrame()
365 {
366 if (not signalsBlocked() and _vertex.has_value() and _objectPosesConnection != nullptr)
367 {
368 {
369 QSignalBlocker blocker(this);
370
371 _framedPoseCoordinateConversion();
372 }
373 _updateVertexAttribs();
374 }
375 }
376
377 void
378 VertexDataWidget::_removeInstance()
379 {
380 if (not signalsBlocked() and _vertex.has_value())
381 {
382 // when no agent is set, we don't have to do anything
383 if (agent->currentText().isEmpty())
384 {
385 return;
386 }
387
388 {
389 QSignalBlocker blocker(this);
390
391 ObjectID currentAgent(agent->currentText().toStdString());
392 QString newAgent = QString::fromStdString(currentAgent.getClassID().str());
393 if (agent->findText(newAgent) < 0)
394 {
395 agent->insertItem(0, newAgent);
396 }
397 agent->setCurrentText(newAgent);
398 }
399 _updateVertexAttribs();
400 }
401 }
402
403 std::vector<QDoubleSpinBox*>
404 VertexDataWidget::_positionSpinBoxes()
405 {
406 return {x, y, z};
407 }
408
409 std::vector<QDoubleSpinBox*>
410 VertexDataWidget::_angleSpinBoxes()
411 {
412 return {roll, pitch, yaw};
413 }
414
415 std::vector<QDoubleSpinBox*>
416 VertexDataWidget::_allSpinBoxes()
417 {
418 return {x, y, z, roll, pitch, yaw};
419 }
420
421 void
422 VertexDataWidget::_setPose(const core::Pose_d& pose)
423 {
424 _setXyz(simox::math::position(pose.matrix()));
425 _setRpyRad(simox::math::mat4f_to_rpy(pose.matrix()));
426 }
427
428 void
429 VertexDataWidget::_setXyz(const Eigen::Vector3d& xyz)
430 {
431 x->setValue(xyz.x());
432 y->setValue(xyz.y());
433 z->setValue(xyz.z());
434 }
435
436 Eigen::Vector3d
437 VertexDataWidget::_rpyRaw() const
438 {
439 return {roll->value(), pitch->value(), yaw->value()};
440 }
441
442 void
443 VertexDataWidget::_setRpyRaw(const Eigen::Vector3d& rpy) const
444 {
445 roll->setValue(rpy(0));
446 pitch->setValue(rpy(1));
447 yaw->setValue(rpy(2));
448 }
449
450 void
451 VertexDataWidget::_setRpyDeg(const Eigen::Vector3d& rpyDeg) const
452 {
453 if (angleUnitDeg->isChecked())
454 {
455 _setRpyRaw(rpyDeg);
456 }
457 else if (angleUnitRad->isChecked())
458 {
459 _setRpyRaw(simox::math::deg_to_rad(rpyDeg));
460 }
461 }
462
463 void
464 VertexDataWidget::_setRpyRad(const Eigen::Vector3d& rpyRad) const
465 {
466 if (angleUnitDeg->isChecked())
467 {
468 _setRpyRaw(simox::math::rad_to_deg(rpyRad));
469 }
470 else if (angleUnitRad->isChecked())
471 {
472 _setRpyRaw(rpyRad);
473 }
474 }
475
476 void
477 VertexDataWidget::_setFromCurrentRobotPose()
478 {
479 ARMARX_CHECK_NOT_NULL(_robotConnection);
480 {
481 QSignalBlocker blocker(this);
482 _setPose(_robotConnection->getGlobalPose().cast<qreal>());
483 }
484 _updateVertexAttribs();
485 }
486
487 void
488 VertexDataWidget::_loadFramedPoseFromVertex(const GuiGraph::Vertex& vertex)
489 {
490 _updateAgentMenu();
491 QString currAgent = QString::fromStdString(vertex.attrib().getPose().agent);
492 if (agent->findText(currAgent) < 0)
493 {
494 agent->insertItem(0, currAgent);
495 }
496 agent->setCurrentText(currAgent);
497
498 _updateFrameMenu();
499
500 QString currFrame = QString::fromStdString(vertex.attrib().getPose().frame);
501 if (frame->findText(currFrame) < 0)
502 {
503 frame->insertItem(0, currFrame);
504 }
505 frame->setCurrentText(currFrame);
506 }
507
508 void
509 VertexDataWidget::_updateAgentMenu()
510 {
511 agent->clear();
512
513 if (_objectPosesConnection == nullptr)
514 {
515 return;
516 }
517
518 agent->addItem(QString::fromStdString(""));
519 objpose::ObjectPoseMap map = _objectPosesConnection->getObjectPoseMap();
520 for (const auto& [id, object] : map)
521 {
522 agent->addItem(QString::fromStdString(id.str()));
523 }
524 }
525
526 void
527 VertexDataWidget::_updateFrameMenu()
528 {
529 frame->clear();
530
531 if (_objectPosesConnection == nullptr)
532 {
533 return;
534 }
535
536 const auto nodes = [&]() -> std::optional<std::vector<std::string>>
537 {
538 const auto& currentObj = agent->currentText().toStdString();
539 if (currentObj.empty())
540 {
541 return std::vector<std::string>({armarx::GlobalFrame});
542 }
543 else
544 {
545 const auto& finder = _objectPosesConnection->getObjectClient().getObjectFinder();
546 std::optional<ObjectInfo> info = finder.findObject(currentObj);
547 if (!info.has_value())
548 {
549 return std::nullopt;
550 }
551
552 std::optional<PackageFileLocation> articulatedModel = info->articulatedSimoxXML();
553 if (!articulatedModel.has_value())
554 {
555 return std::nullopt;
556 }
557
558 const auto modelPath = armarx::PackagePath(articulatedModel.value().package,
559 articulatedModel.value().relativePath);
560 const auto robot = VirtualRobot::RobotIO::loadRobot(
561 modelPath.toSystemPath(), VirtualRobot::BaseIO::eStructure);
562 if (robot == nullptr)
563 {
564 return std::nullopt;
565 }
566
567 return robot->getRobotNodeNames();
568 }
569 };
570 std::vector<std::string> frames = nodes().value_or(std::vector<std::string>{"root"});
571
572 for (const auto& i : frames)
573 {
574 frame->addItem(QString::fromStdString(i));
575 };
576 }
577
578 void
579 VertexDataWidget::_framedPoseCoordinateConversion()
580 {
581 objpose::ObjectPoseMap objects = _objectPosesConnection->getObjectPoseMap();
582 std::vector<ObjectInfo> objectInfo = _objectPosesConnection->getObjectInfo();
583
584 // convert current location into the global frame
585 const auto globalPose =
586 core::resolveLocation(objects, objectInfo, _vertex->attrib().getPose());
587 if (!globalPose.pose.has_value())
588 {
589 return;
590 }
591 const core::Pose_d global_T_location = globalPose.pose.value().cast<double>();
592
593 // convert from the global frame into the new object relative frame
594 const std::string newAgent = agent->currentText().toStdString();
595 const std::string newFrame = frame->currentText().toStdString();
596
597 const auto object = core::resolveLocation(
598 objects, objectInfo, FramedPose(core::Pose::Identity().matrix(), newFrame, newAgent));
599 if (!object.pose.has_value())
600 {
601 return;
602 }
603 const core::Pose_d global_T_object = object.pose.value().cast<double>();
604 const core::Pose_d object_T_global = global_T_object.inverse();
605
606 const core::Pose_d object_T_location = object_T_global * global_T_location;
607 _setPose(object_T_location);
608 }
609
610} // namespace armarx::navigation::qt_plugins::location_graph_editor
std::string str(const T &t)
The FramedPose class.
Definition FramedPose.h:281
virtual Eigen::Matrix4f toEigen() const
Definition Pose.cpp:334
#define ARMARX_CHECK(expression)
Shortcut for ARMARX_CHECK_EXPRESSION.
#define ARMARX_CHECK_NOT_NULL(ptr)
This macro evaluates whether ptr is not null and if it turns out to be false it will throw an Express...
std::string const GlobalFrame
Variable of the global coordinate system.
Definition FramedPose.h:65
const VariantTypeId FramedPose
Definition FramedPose.h:36
armem::MemoryID ObjectID
Definition types.h:79
void fromAron(const T &dto, T &bo)
void resolveLocation(Graph::Vertex &vertex, const aron::data::DictPtr &locationData)
Definition Graph.cpp:267
Eigen::Isometry3d Pose_d
Definition basic_types.h:32
std::map< ObjectID, ObjectPose > ObjectPoseMap
This file offers overloads of toIce() and fromIce() functions for STL container types.
std::vector< T > max(const std::vector< T > &v1, const std::vector< T > &v2)
std::vector< T > min(const std::vector< T > &v1, const std::vector< T > &v2)
double angle(const Point &a, const Point &b, const Point &c)
Definition point.hpp:109
armarx::navigation::core::arondto::Vertex aron
Definition Graph.h:46
std::string getLocationName() const
Definition Graph.cpp:48
The NodeData struct holds data required for the node.
Definition GuiGraph.h:45