roboapi 0.4.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. roboapi/__init__.py +2 -0
  2. roboapi/calibration/__init__.py +6 -0
  3. roboapi/calibration/profile.py +139 -0
  4. roboapi/calibration/session.py +350 -0
  5. roboapi/cli.py +215 -0
  6. roboapi/config/__init__.py +22 -0
  7. roboapi/config/loader.py +119 -0
  8. roboapi/config/models.py +191 -0
  9. roboapi/embodiment.py +251 -0
  10. roboapi/errors.py +37 -0
  11. roboapi/geometry.py +337 -0
  12. roboapi/hardware/__init__.py +5 -0
  13. roboapi/hardware/base.py +113 -0
  14. roboapi/hardware/camera.py +167 -0
  15. roboapi/hardware/franka/__init__.py +5 -0
  16. roboapi/hardware/franka/arm.py +476 -0
  17. roboapi/hardware/franka/desk.py +555 -0
  18. roboapi/hardware/franka/feedback.py +196 -0
  19. roboapi/hardware/franka/guiding.py +104 -0
  20. roboapi/hardware/franka/ik.py +543 -0
  21. roboapi/hardware/franka/limits.py +110 -0
  22. roboapi/hardware/franka/settings.py +116 -0
  23. roboapi/hardware/robotiq.py +350 -0
  24. roboapi/hardware/ur/__init__.py +16 -0
  25. roboapi/hardware/ur/build.py +92 -0
  26. roboapi/hardware/ur/compliant.py +128 -0
  27. roboapi/hardware/ur/dashboard.py +103 -0
  28. roboapi/hardware/ur/feedback.py +159 -0
  29. roboapi/hardware/ur/ipc.py +206 -0
  30. roboapi/hardware/ur/native/CMakeLists.txt +37 -0
  31. roboapi/hardware/ur/native/dependencies.lock.json +13 -0
  32. roboapi/hardware/ur/native/include/ur_cpp/cartesian_speedl.hpp +170 -0
  33. roboapi/hardware/ur/native/include/ur_cpp/control_loop.hpp +126 -0
  34. roboapi/hardware/ur/native/include/ur_cpp/geom.hpp +72 -0
  35. roboapi/hardware/ur/native/include/ur_cpp/motion_primitives.hpp +350 -0
  36. roboapi/hardware/ur/native/include/ur_cpp/robot_io.hpp +26 -0
  37. roboapi/hardware/ur/native/include/ur_cpp/streaming_pose.hpp +369 -0
  38. roboapi/hardware/ur/native/src/control_loop.cpp +512 -0
  39. roboapi/hardware/ur/native/src/robot_io_urcl.cpp +143 -0
  40. roboapi/hardware/ur/native/tests/cartesian_speedl_unit.cpp +113 -0
  41. roboapi/hardware/ur/native/tests/control_modes_unit.cpp +137 -0
  42. roboapi/hardware/ur/native/tests/streaming_pose_unit.cpp +348 -0
  43. roboapi/hardware/ur/native/tests/velocity_governor_unit.cpp +65 -0
  44. roboapi/hardware/ur/native/worker.cpp +147 -0
  45. roboapi/hardware/ur/position.py +253 -0
  46. roboapi/hardware/ur/settings.py +95 -0
  47. roboapi/images.py +16 -0
  48. roboapi/initialize.py +137 -0
  49. roboapi/mcp.py +497 -0
  50. roboapi/motion.py +125 -0
  51. roboapi/naming.py +7 -0
  52. roboapi/operations.py +45 -0
  53. roboapi/policy/__init__.py +7 -0
  54. roboapi/policy/client.py +277 -0
  55. roboapi/policy/protocol.py +166 -0
  56. roboapi/policy/settings.py +79 -0
  57. roboapi/product-names.json +12 -0
  58. roboapi/records.py +149 -0
  59. roboapi/runtime.py +911 -0
  60. roboapi/semantics.py +6 -0
  61. roboapi/tasks.py +337 -0
  62. roboapi/web/__init__.py +5 -0
  63. roboapi/web/presets.py +44 -0
  64. roboapi/web/server.py +419 -0
  65. roboapi/web/static/assets/Bundler-LICENSE.txt +48 -0
  66. roboapi/web/static/assets/CalibrationScene-BO_spRoL.js +4220 -0
  67. roboapi/web/static/assets/Inter-OFL.txt +93 -0
  68. roboapi/web/static/assets/Inter-Variable-c8O0ljhh.ttf +0 -0
  69. roboapi/web/static/assets/Lucide-LICENSE.txt +43 -0
  70. roboapi/web/static/assets/Meshoptimizer-LICENSE.txt +23 -0
  71. roboapi/web/static/assets/MiSans-Regular-ES4DrbaF.woff2 +0 -0
  72. roboapi/web/static/assets/MiSans-Semibold-B34HIk9C.woff2 +0 -0
  73. roboapi/web/static/assets/PokeBrand-NOTICE.txt +20 -0
  74. roboapi/web/static/assets/React-LICENSE.txt +23 -0
  75. roboapi/web/static/assets/Three-LICENSE.txt +23 -0
  76. roboapi/web/static/assets/home-reference-CTpf8-oV.png +0 -0
  77. roboapi/web/static/assets/index-CGLhpUm0.js +28 -0
  78. roboapi/web/static/assets/index-QXqV-fDC.css +1 -0
  79. roboapi/web/static/assets/robotiq-ace-pro-2-EJuoOXTy.glb +0 -0
  80. roboapi/web/static/index.html +15 -0
  81. roboapi-0.4.2.dist-info/METADATA +205 -0
  82. roboapi-0.4.2.dist-info/RECORD +94 -0
  83. roboapi-0.4.2.dist-info/WHEEL +5 -0
  84. roboapi-0.4.2.dist-info/entry_points.txt +2 -0
  85. roboapi-0.4.2.dist-info/licenses/LICENSE +202 -0
  86. roboapi-0.4.2.dist-info/licenses/NOTICE +16 -0
  87. roboapi-0.4.2.dist-info/licenses/frontend/public/assets/Bundler-LICENSE.txt +48 -0
  88. roboapi-0.4.2.dist-info/licenses/frontend/public/assets/Inter-OFL.txt +93 -0
  89. roboapi-0.4.2.dist-info/licenses/frontend/public/assets/Lucide-LICENSE.txt +43 -0
  90. roboapi-0.4.2.dist-info/licenses/frontend/public/assets/Meshoptimizer-LICENSE.txt +23 -0
  91. roboapi-0.4.2.dist-info/licenses/frontend/public/assets/PokeBrand-NOTICE.txt +20 -0
  92. roboapi-0.4.2.dist-info/licenses/frontend/public/assets/React-LICENSE.txt +23 -0
  93. roboapi-0.4.2.dist-info/licenses/frontend/public/assets/Three-LICENSE.txt +23 -0
  94. roboapi-0.4.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,170 @@
1
+ #pragma once
2
+
3
+ #include <algorithm>
4
+ #include <string>
5
+
6
+ #include "ur_cpp/motion_primitives.hpp"
7
+ #include "ur_cpp/streaming_pose.hpp"
8
+
9
+ struct CartesianTick {
10
+ vec6 reference_{};
11
+ vec6 actual_{};
12
+ vec6 wrench_{};
13
+ double dt_ = 0;
14
+ };
15
+
16
+ struct CartesianCommand {
17
+ vec6 desired_{};
18
+ vec6 twist_{};
19
+ LoopFault fault_ = LoopFault::None;
20
+ };
21
+
22
+ // Reference shaping, admittance, position feedback and final twist shaping.
23
+ class CartesianSpeedlLaw {
24
+ public:
25
+ Args args_;
26
+ AdmittanceLaw law_;
27
+ StreamingPoseShaper shaper_;
28
+ TwistGovernor governor_;
29
+
30
+ explicit CartesianSpeedlLaw(const Args &a)
31
+ : args_(a), law_(a.tun_), shaper_(a, a.pos_kp_),
32
+ governor_(streamingPosePeriod(a), a.cmd_vel_trans_, a.cmd_vel_rot_, a.speedl_acc_trans_,
33
+ a.speedl_acc_rot_, a.speedl_jerk_trans_, a.speedl_jerk_rot_) {
34
+ law_.initNotch(a.freq_);
35
+ }
36
+
37
+ bool validate(std::string &error) const {
38
+ if (!std::isfinite(args_.freq_) || args_.freq_ < 50 || args_.freq_ > 500) {
39
+ error = "cartesian speed control frequency must be within [50, 500]";
40
+ return false;
41
+ }
42
+ for (double v :
43
+ {args_.tun_.zeta_, args_.tun_.filter_tau_, args_.tun_.notch_freq_,
44
+ args_.tun_.deadband_force_, args_.tun_.deadband_torque_, args_.tun_.max_dt_,
45
+ args_.tun_.max_vel_, args_.tun_.relaxation_hz_, args_.pos_kp_, args_.cmd_vel_trans_,
46
+ args_.cmd_vel_rot_, args_.cmd_acc_trans_, args_.cmd_acc_rot_, args_.speedl_acc_trans_,
47
+ args_.speedl_acc_rot_, args_.speedl_jerk_trans_, args_.speedl_jerk_rot_})
48
+ if (!std::isfinite(v) || v < 0) {
49
+ error = "a numeric cartesian speed control parameter is negative or non-finite";
50
+ return false;
51
+ }
52
+ if (args_.tun_.max_dt_ <= 0 || args_.cmd_vel_trans_ <= 0 || args_.cmd_vel_rot_ <= 0 ||
53
+ args_.cmd_acc_trans_ <= 0 || args_.cmd_acc_rot_ <= 0) {
54
+ error = "invalid velocity/acceleration or max_dt";
55
+ return false;
56
+ }
57
+ const double hmax = std::max(1 / args_.freq_, args_.tun_.max_dt_);
58
+ const double wc = 2 * M_PI * args_.tun_.relaxation_hz_;
59
+ if (!std::isfinite(wc)) {
60
+ error = "invalid relaxation_hz";
61
+ return false;
62
+ }
63
+ for (int i = 0; i < 6; ++i) {
64
+ const auto M = args_.tun_.M_[i], K = args_.tun_.K_[i], ratio = args_.tun_.hf_mass_ratio_[i];
65
+ if (!std::isfinite(M) || M <= 0 || !std::isfinite(K) || K < 0 || !std::isfinite(ratio) ||
66
+ ratio < 1) {
67
+ error = "invalid mass/stiffness/hf_mass_ratio";
68
+ return false;
69
+ }
70
+ const double damping = 2 * args_.tun_.zeta_ * std::sqrt(M) * std::sqrt(K);
71
+ if (!std::isfinite(damping)) {
72
+ error = "invalid damping";
73
+ return false;
74
+ }
75
+ if (ratio > 1) {
76
+ const double extra = (ratio - 1) * M;
77
+ const double d0 = damping - extra * wc;
78
+ if (wc <= 0 || !std::isfinite(M + extra) || !std::isfinite(extra * wc * wc) ||
79
+ !std::isfinite(d0) || d0 <= K * hmax / 2) {
80
+ error = "invalid Maxwell tuning: D0 must exceed K*hmax/2";
81
+ return false;
82
+ }
83
+ }
84
+ }
85
+ if (args_.speedl_acc_trans_ <= 0 || args_.speedl_acc_rot_ <= 0 ||
86
+ args_.speedl_jerk_trans_ <= 0 || args_.speedl_jerk_rot_ <= 0) {
87
+ error = "speedl acceleration and jerk limits must be positive";
88
+ return false;
89
+ }
90
+ return true;
91
+ }
92
+
93
+ void reset(const vec6 &actual, const vec6 &velocity) {
94
+ law_.reset();
95
+ shaper_.sync(actual);
96
+ governor_.reset(velocity);
97
+ }
98
+
99
+ void resetAdmittance() { law_.reset(); }
100
+
101
+ bool step(const CartesianTick &in, CartesianCommand &out, bool admittance = true) {
102
+ out = {};
103
+ if (!StreamingPoseShaper::plausiblePose(in.reference_) ||
104
+ !StreamingPoseShaper::plausiblePose(in.actual_) || !finite6(in.wrench_) ||
105
+ !std::isfinite(in.dt_) || in.dt_ <= 0 || std::abs(in.dt_ - 1.0 / args_.freq_) > 1e-9) {
106
+ out.fault_ = LoopFault::VirtualStateInvalid;
107
+ return false;
108
+ }
109
+ const vec6 shaped = shaper_.step(in.reference_);
110
+ if (shaper_.errors_) {
111
+ out.fault_ = LoopFault::VirtualStateInvalid;
112
+ return false;
113
+ }
114
+ if (admittance && !law_.predict(in.wrench_, in.dt_)) {
115
+ out.fault_ = LoopFault::VirtualStateInvalid;
116
+ return false;
117
+ }
118
+ vec6 desired = shaped;
119
+ vec6 correction{}, raw_command{};
120
+ vec6 ref_velocity = shaper_.last_vel_;
121
+ // StreamingPoseShaper already returns spatial angular velocity.
122
+ const vec6 offset = admittance ? law_.off_ : vec6{};
123
+ const auto roff = rotvecToQuat(offset[3], offset[4], offset[5]);
124
+ ref_velocity = rotateAngular(roff, ref_velocity);
125
+ for (int i = 0; i < 3; ++i)
126
+ desired[i] += offset[i];
127
+ const auto rotation = quatMul(roff, rotvecToQuat(shaped[3], shaped[4], shaped[5]));
128
+ quatToRotvec(rotation, desired[3], desired[4], desired[5]);
129
+ const auto er =
130
+ quatMul(rotation, quatConj(rotvecToQuat(in.actual_[3], in.actual_[4], in.actual_[5])));
131
+ vec6 error{};
132
+ for (int i = 0; i < 3; ++i)
133
+ error[i] = desired[i] - in.actual_[i];
134
+ quatToRotvec(er, error[3], error[4], error[5]);
135
+ for (int i = 0; i < 6; ++i) {
136
+ const double cap = (i < 3 ? args_.cmd_vel_trans_ : args_.cmd_vel_rot_) * .25 / std::sqrt(3.);
137
+ const double c = std::clamp(args_.pos_kp_ * error[i], -cap, cap);
138
+ correction[i] = c;
139
+ raw_command[i] = ref_velocity[i] + (admittance ? law_.vel_[i] : 0.0) + correction[i];
140
+ }
141
+ if (!finite6(shaped) || !finite6(desired) || !finite6(correction) || !finite6(raw_command)) {
142
+ out.fault_ = LoopFault::VirtualStateInvalid;
143
+ return false;
144
+ }
145
+ vec6 residual{};
146
+ if (!governor_.step(raw_command)) {
147
+ out.fault_ = LoopFault::VirtualStateInvalid;
148
+ return false;
149
+ }
150
+ for (int i = 0; i < 6; ++i)
151
+ residual[i] = governor_.sent_[i] - raw_command[i];
152
+ if (admittance && !law_.commit(residual, in.dt_)) {
153
+ out.fault_ = LoopFault::VirtualStateInvalid;
154
+ return false;
155
+ }
156
+ for (int i = 0; i < 3; ++i)
157
+ desired[i] = shaped[i] + (admittance ? law_.off_[i] : 0.0);
158
+ const auto accepted_offset = admittance ? law_.off_ : vec6{};
159
+ const auto accepted = quatMul(rotvecToQuat(accepted_offset[3], accepted_offset[4], accepted_offset[5]),
160
+ rotvecToQuat(shaped[3], shaped[4], shaped[5]));
161
+ quatToRotvec(accepted, desired[3], desired[4], desired[5]);
162
+ if (!finite6(desired) || !finite6(governor_.sent_)) {
163
+ out.fault_ = LoopFault::VirtualStateInvalid;
164
+ return false;
165
+ }
166
+ out.desired_ = desired;
167
+ out.twist_ = governor_.sent_;
168
+ return true;
169
+ }
170
+ };
@@ -0,0 +1,126 @@
1
+ #pragma once
2
+
3
+ #include <array>
4
+ #include <atomic>
5
+ #include <cstdint>
6
+ #include <mutex>
7
+ #include <string>
8
+ #include <thread>
9
+
10
+ using vec6 = std::array<double, 6>;
11
+
12
+ struct Tuning {
13
+ double M_[6] = {4, 4, 4, .05, .05, .05};
14
+ double K_[6] = {50, 50, 50, 2.8, 2.8, 2.8};
15
+ double zeta_ = 3.5, filter_tau_ = .005, notch_freq_ = 0;
16
+ double deadband_force_ = 2.5, deadband_torque_ = .075, max_dt_ = .015;
17
+ double max_vel_ = .35;
18
+ double hf_mass_ratio_[6] = {1.5, 1.5, 1.5, 2, 2, 2};
19
+ double relaxation_hz_ = 5;
20
+ };
21
+
22
+ struct Args {
23
+ std::string robot_ip_;
24
+ std::string script_file_;
25
+ double freq_ = 500.0;
26
+ int rt_prio_ = 0;
27
+ double cmd_vel_trans_ = 1.0, cmd_vel_rot_ = 2.5;
28
+ double cmd_acc_trans_ = 3.0, cmd_acc_rot_ = 8.0;
29
+ double pos_kp_ = 15;
30
+ double speedl_acc_trans_ = 25, speedl_acc_rot_ = 30;
31
+ double speedl_jerk_trans_ = 2000, speedl_jerk_rot_ = 2000;
32
+ Tuning tun_;
33
+ };
34
+
35
+ enum class LoopFault : uint32_t {
36
+ None = 0, // No fault
37
+ RobotSafetyStop, // Pendant protective/emergency stop; feedback failed and safety_mode is not Normal/Reduced
38
+ SampleDiscontinuity, // RTDE packet loss or robot_dt differs from the control period by more than 10%
39
+ RobotIoFailed, // Failed speedl/keepalive write or lost data stream, excluding safety stops
40
+ VirtualStateInvalid, // Non-finite force/pose/dt or failed admittance calculation
41
+ ModeSwitchTimeout = 6, // No sustained stationary feedback after exiting the previous mode
42
+ };
43
+
44
+ inline const char *loopFaultName(LoopFault f) {
45
+ switch (f) {
46
+ case LoopFault::None:
47
+ return "";
48
+ case LoopFault::RobotSafetyStop:
49
+ return "robot_safety_stop";
50
+ case LoopFault::SampleDiscontinuity:
51
+ return "sample_discontinuity";
52
+ case LoopFault::RobotIoFailed:
53
+ return "robot_io_failed";
54
+ case LoopFault::VirtualStateInvalid:
55
+ return "virtual_state_invalid";
56
+ case LoopFault::ModeSwitchTimeout:
57
+ return "mode_switch_timeout";
58
+ }
59
+ return "loop_fault";
60
+ }
61
+
62
+ struct Snapshot {
63
+ vec6 q_{}, qd_{}, tcp_{}, tcp_speed_{}, wrench_{};
64
+ uint8_t mode_ = 0;
65
+ LoopFault fault_ = LoopFault::None;
66
+ int32_t safety_mode_ = 1;
67
+ uint64_t ticks_ = 0;
68
+ double dt_mean_ms_ = 0, dt_std_ms_ = 0, dt_p99_ms_ = 0, dt_max_ms_ = 0;
69
+ double rtde_hz_ = 0, host_hz_ = 0;
70
+ bool program_running_ = false;
71
+ bool loop_alive_ = false;
72
+ bool loop_ok_ = true;
73
+ std::string last_error_;
74
+ };
75
+
76
+ bool validateArgs(const Args &a, std::string &error);
77
+
78
+ struct LoopCycle;
79
+
80
+ // RTDE-paced URCL session. IPC commands set the latest intent; this thread
81
+ // is the only writer of reverse-interface commands (keepalive / speedl).
82
+ class ControlLoop {
83
+ public:
84
+ explicit ControlLoop(Args args);
85
+ ~ControlLoop();
86
+
87
+ ControlLoop(const ControlLoop &) = delete;
88
+ ControlLoop &operator=(const ControlLoop &) = delete;
89
+
90
+ bool start();
91
+ void requestStop();
92
+ void join();
93
+
94
+ void setPoseRef(const vec6 &tcp, bool admittance = true);
95
+ void zeroFt();
96
+ void stopMotion();
97
+
98
+ Snapshot snapshot() const;
99
+ bool waitReady(double timeout_s);
100
+
101
+ private:
102
+ friend struct LoopCycle;
103
+
104
+ enum class Kind : uint8_t { Idle, TrackPose, TrackPosition };
105
+
106
+ struct Intent {
107
+ Kind kind_ = Kind::Idle;
108
+ vec6 pose_{};
109
+ bool zero_ft_ = false;
110
+ bool stop_motion_ = false;
111
+ };
112
+
113
+ void threadMain();
114
+ void watchdogMain();
115
+ Intent takeIntent();
116
+
117
+ Args args_;
118
+ std::thread thread_;
119
+ std::atomic<bool> stop_{false};
120
+ std::atomic<bool> watchdog_quit_{false};
121
+ std::atomic<uint64_t> last_tick_ns_{0};
122
+ mutable std::mutex intent_mu_;
123
+ Intent intent_{};
124
+ mutable std::mutex snap_mu_;
125
+ Snapshot snap_{};
126
+ };
@@ -0,0 +1,72 @@
1
+ #pragma once
2
+
3
+ #include <algorithm>
4
+ #include <cmath>
5
+ #include <limits>
6
+
7
+ #include "ur_cpp/control_loop.hpp"
8
+
9
+ inline double norm3(const vec6 &v, int o = 0) { return std::hypot(v[o], v[o + 1], v[o + 2]); }
10
+
11
+ inline bool finite6(const vec6 &v) {
12
+ return std::all_of(v.begin(), v.end(), [](double x) { return std::isfinite(x); });
13
+ }
14
+
15
+ struct Quat {
16
+ double w_, x_, y_, z_;
17
+ };
18
+
19
+ inline Quat rotvecToQuat(double rx, double ry, double rz) {
20
+ const double angle = std::sqrt(rx * rx + ry * ry + rz * rz);
21
+ if (angle < 1e-12)
22
+ return {1.0, 0.0, 0.0, 0.0};
23
+ const double s = std::sin(angle / 2.0) / angle;
24
+ return {std::cos(angle / 2.0), rx * s, ry * s, rz * s};
25
+ }
26
+
27
+ inline Quat quatMul(const Quat &a, const Quat &b) {
28
+ return {a.w_ * b.w_ - a.x_ * b.x_ - a.y_ * b.y_ - a.z_ * b.z_,
29
+ a.w_ * b.x_ + a.x_ * b.w_ + a.y_ * b.z_ - a.z_ * b.y_,
30
+ a.w_ * b.y_ - a.x_ * b.z_ + a.y_ * b.w_ + a.z_ * b.x_,
31
+ a.w_ * b.z_ + a.x_ * b.y_ - a.y_ * b.x_ + a.z_ * b.w_};
32
+ }
33
+
34
+ inline Quat quatConj(const Quat &q) { return {q.w_, -q.x_, -q.y_, -q.z_}; }
35
+
36
+ inline void quatToRotvec(Quat q, double &rx, double &ry, double &rz,
37
+ const vec6 *previous = nullptr) {
38
+ const double n = std::hypot(std::hypot(q.w_, q.x_), std::hypot(q.y_, q.z_));
39
+ if (!std::isfinite(n) || n < 1e-15) {
40
+ rx = ry = rz = std::numeric_limits<double>::quiet_NaN();
41
+ return;
42
+ }
43
+ q.w_ /= n, q.x_ /= n, q.y_ /= n, q.z_ /= n;
44
+ if (std::abs(q.w_) <= 1e-12) {
45
+ double choice = 0;
46
+ if (previous)
47
+ choice = q.x_ * (*previous)[3] + q.y_ * (*previous)[4] + q.z_ * (*previous)[5];
48
+ if (!std::isfinite(choice) || std::abs(choice) < 1e-12) {
49
+ choice = q.x_;
50
+ if (std::abs(q.y_) > std::abs(choice))
51
+ choice = q.y_;
52
+ if (std::abs(q.z_) > std::abs(choice))
53
+ choice = q.z_;
54
+ }
55
+ if (choice < 0) {
56
+ q.x_ = -q.x_;
57
+ q.y_ = -q.y_;
58
+ q.z_ = -q.z_;
59
+ }
60
+ q.w_ = 0;
61
+ } else if (q.w_ < 0)
62
+ q.w_ = -q.w_, q.x_ = -q.x_, q.y_ = -q.y_, q.z_ = -q.z_;
63
+ const double sin_half = std::sqrt(q.x_ * q.x_ + q.y_ * q.y_ + q.z_ * q.z_);
64
+ if (sin_half < 1e-12) {
65
+ rx = ry = rz = 0.0;
66
+ return;
67
+ }
68
+ const double angle = 2.0 * std::atan2(sin_half, q.w_);
69
+ rx = q.x_ / sin_half * angle;
70
+ ry = q.y_ / sin_half * angle;
71
+ rz = q.z_ / sin_half * angle;
72
+ }