SMACC2
Loading...
Searching...
No Matches
cb_circular_pivot_motion.hpp
Go to the documentation of this file.
1// Copyright 2025 Robosoft Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15/*****************************************************************************************************************
16 *
17 * Authors: Pablo Inigo Blasco, Brett Aldrich
18 *
19 *****************************************************************************************************************/
20#pragma once
21
22#include <tf2/transform_datatypes.h>
23#include <tf2_ros/transform_listener.h>
25#include <tf2_geometry_msgs/tf2_geometry_msgs.hpp>
27
28using namespace std::chrono_literals;
29
30namespace cl_moveit2z
31{
32// executes a circular trajectory of the tipLink around one axis defined
33// by the z-axis of the plaenePivotPose
35{
36public:
37 std::optional<double> angularSpeed_rad_s_;
38 std::optional<double> linearSpeed_m_s_;
39
40 // if not specified it would be used the current robot pose
41 std::optional<geometry_msgs::msg::Pose> relativeInitialPose_;
42
43 CbCircularPivotMotion(std::optional<std::string> tipLink = std::nullopt)
45 {
46 }
47
49 const geometry_msgs::msg::PoseStamped & planePivotPose, double deltaRadians,
50 std::optional<std::string> tipLink = std::nullopt)
52 planePivotPose_(planePivotPose),
53 deltaRadians_(deltaRadians)
54 {
55 if (tipLink_) planePivotPose_.header.frame_id = *tipLink;
56 }
57
59 const geometry_msgs::msg::PoseStamped & planePivotPose,
60 const geometry_msgs::msg::Pose & relativeInitialPose, double deltaRadians,
61 std::optional<std::string> tipLink = std::nullopt)
63 relativeInitialPose_(relativeInitialPose),
64 planePivotPose_(planePivotPose),
65 deltaRadians_(deltaRadians)
66 {
67 }
68
69 virtual void generateTrajectory() override
70 {
72 {
74 }
75
76 // project offset into the xy-plane
77 // get the radius
78 double radius = sqrt(
79 relativeInitialPose_->position.z * relativeInitialPose_->position.z +
80 relativeInitialPose_->position.y * relativeInitialPose_->position.y);
81 double initialAngle = atan2(relativeInitialPose_->position.z, relativeInitialPose_->position.y);
82
83 double totallineardist = fabs(radius * deltaRadians_);
84 double totalangulardist = fabs(deltaRadians_);
85
86 // at least 1 sample per centimeter (average)
87 // at least 1 sample per ~1.1 degrees (average)
88
89 const double RADS_PER_SAMPLE = 0.02;
90 const double METERS_PER_SAMPLE = 0.01;
91
92 int totalSamplesCount =
93 std::max(totallineardist / METERS_PER_SAMPLE, totalangulardist / RADS_PER_SAMPLE);
94
95 double linearSecondsPerSample;
96 double angularSecondsPerSamples;
97 double secondsPerSample;
98
100 {
101 linearSecondsPerSample = METERS_PER_SAMPLE / (*linearSpeed_m_s_);
102 }
103 else
104 {
105 linearSecondsPerSample = std::numeric_limits<double>::max();
106 }
107
109 {
110 angularSecondsPerSamples = RADS_PER_SAMPLE / (*angularSpeed_rad_s_);
111 }
112 else
113 {
114 angularSecondsPerSamples = std::numeric_limits<double>::max();
115 }
116
118 {
119 secondsPerSample = 0.5;
120 }
121 else
122 {
123 secondsPerSample = std::min(linearSecondsPerSample, angularSecondsPerSamples);
124 }
125
126 double currentAngle = initialAngle;
127
128 double angleStep = deltaRadians_ / (double)totalSamplesCount;
129
130 tf2::Transform tfBasePose;
131 tf2::fromMsg(planePivotPose_.pose, tfBasePose);
132
133 RCLCPP_INFO_STREAM(
134 getLogger(),
135 "[" << getName() << "] generated trajectory, total samples: " << totalSamplesCount);
136 for (int i = 0; i < totalSamplesCount; i++)
137 {
138 // relativePose i
139 double y = radius * cos(currentAngle);
140 double z = radius * sin(currentAngle);
141
142 geometry_msgs::msg::Pose relativeCurrentPose;
143
144 relativeCurrentPose.position.x = relativeInitialPose_->position.x;
145 relativeCurrentPose.position.y = y;
146 relativeCurrentPose.position.z = z;
147
148 tf2::Quaternion localquat;
149 localquat.setEuler(currentAngle, 0, 0);
150
151 //relativeCurrentPose.orientation = relativeInitialPose_.orientation;
152 //tf2::toMsg(localquat, relativeCurrentPose.orientation);
153 relativeCurrentPose.orientation.w = 1;
154
155 tf2::Transform tfRelativeCurrentPose;
156 tf2::fromMsg(relativeCurrentPose, tfRelativeCurrentPose);
157
158 tf2::Transform tfGlobalPose = tfRelativeCurrentPose * tfBasePose;
159
160 tfGlobalPose.setRotation(tfGlobalPose.getRotation() * localquat);
161
162 geometry_msgs::msg::PoseStamped globalPose;
163 tf2::toMsg(tfGlobalPose, globalPose.pose);
164 globalPose.header.frame_id = planePivotPose_.header.frame_id;
165 globalPose.header.stamp = rclcpp::Time(planePivotPose_.header.stamp) +
166 rclcpp::Duration::from_seconds(i * secondsPerSample);
167 RCLCPP_INFO_STREAM(
168 getLogger(),
169 "[" << getName() << "]" << rclcpp::Time(globalPose.header.stamp).nanoseconds());
170
171 this->endEffectorTrajectory_.push_back(globalPose);
172 currentAngle += angleStep;
173 }
174 }
175
176 virtual void createMarkers() override
177 {
179
180 tf2::Transform localdirection;
181 localdirection.setIdentity();
182 localdirection.setOrigin(tf2::Vector3(0.12, 0, 0));
183 auto frameid = planePivotPose_.header.frame_id;
184
185 visualization_msgs::msg::Marker marker;
186 marker.header.frame_id = frameid;
187 marker.header.stamp = getNode()->now();
188 marker.ns = "trajectory";
189 marker.id = beahiorMarkers_.markers.size();
190 marker.type = visualization_msgs::msg::Marker::ARROW;
191 marker.action = visualization_msgs::msg::Marker::ADD;
192 marker.scale.x = 0.01;
193 marker.scale.y = 0.02;
194 marker.scale.z = 0.02;
195 marker.color.a = 1.0;
196 marker.color.r = 0.0;
197 marker.color.g = 0;
198 marker.color.b = 1.0;
199
200 geometry_msgs::msg::Point start, end;
201 start.x = planePivotPose_.pose.position.x;
202 start.y = planePivotPose_.pose.position.y;
203 start.z = planePivotPose_.pose.position.z;
204
205 tf2::Transform basetransform;
206 tf2::fromMsg(planePivotPose_.pose, basetransform);
207 tf2::Transform endarrow = localdirection * basetransform;
208
209 end.x = endarrow.getOrigin().x();
210 end.y = endarrow.getOrigin().y();
211 end.z = endarrow.getOrigin().z();
212
213 marker.pose.orientation.w = 1;
214 marker.points.push_back(start);
215 marker.points.push_back(end);
216
217 beahiorMarkers_.markers.push_back(marker);
218 }
219
220protected:
221 // the rotation axis (z-axis) and the center of rotation in the space
222 geometry_msgs::msg::PoseStamped planePivotPose_;
223
225
226private:
228 {
229 // components resolved in onStateOrthogonalAllocation (base chain)
230 CpTfListener * tfListener = cpTfListener_;
231
232 tf2::Stamped<tf2::Transform> endEffectorInPivotFrame;
233
234 try
235 {
236 if (!tipLink_ || *tipLink_ == "")
237 {
238 tipLink_ = this->cpMoveGroup_->moveGroupClientInterface->getEndEffectorLink();
239 }
240
241 RCLCPP_INFO_STREAM(
242 getLogger(), "[" << getName() << "] waiting transform, pivot: '"
243 << planePivotPose_.header.frame_id << "' tipLink: '" << *tipLink_ << "'");
244
245 if (tfListener != nullptr)
246 {
247 // Use component-based TF listener (preferred)
248 auto transformOpt =
249 tfListener->lookupTransform(planePivotPose_.header.frame_id, *tipLink_, rclcpp::Time());
250
251 if (transformOpt)
252 {
253 tf2::fromMsg(transformOpt.value(), endEffectorInPivotFrame);
254 }
255 else
256 {
257 RCLCPP_ERROR_STREAM(
258 getLogger(), "[" << getName() << "] Failed to lookup transform from " << *tipLink_
259 << " to " << planePivotPose_.header.frame_id);
260 return;
261 }
262 }
263 else
264 {
265 // Fallback to legacy TF2 usage if component not available
266 RCLCPP_WARN_STREAM(
267 getLogger(), "[" << getName()
268 << "] CpTfListener component not available, using legacy TF2 (consider "
269 "adding CpTfListener component)");
270 tf2_ros::Buffer tfBuffer(getNode()->get_clock());
271 tf2_ros::TransformListener tfListenerLegacy(tfBuffer);
272
273 tf2::fromMsg(
274 tfBuffer.lookupTransform(
275 planePivotPose_.header.frame_id, *tipLink_, rclcpp::Time(), rclcpp::Duration(10s)),
276 endEffectorInPivotFrame);
277 }
278 }
279 catch (const std::exception & e)
280 {
281 RCLCPP_ERROR_STREAM(
282 getLogger(),
283 "[" << getName()
284 << "] Exception in computeCurrentEndEffectorPoseRelativeToPivot: " << e.what());
285 return;
286 }
287
288 // tf2::Transform endEffectorInBaseLinkFrame;
289 // tf2::fromMsg(currentRobotEndEffectorPose.pose, endEffectorInBaseLinkFrame);
290
291 // tf2::Transform endEffectorInPivotFrame = globalBaseLink * endEffectorInBaseLinkFrame; // pose composition
292
293 // now pivot and EndEffector share a common reference frame (let say map)
294 // now get the current pose from the pivot reference frame with inverse composition
295 tf2::Transform pivotTransform;
296 tf2::fromMsg(planePivotPose_.pose, pivotTransform);
297 tf2::Transform invertedNewReferenceFrame = pivotTransform.inverse();
298
299 tf2::Transform currentPoseRelativeToPivot = invertedNewReferenceFrame * endEffectorInPivotFrame;
300
301 geometry_msgs::msg::Pose finalEndEffectorRelativePose;
302 tf2::toMsg(currentPoseRelativeToPivot, finalEndEffectorRelativePose);
303 relativeInitialPose_ = finalEndEffectorRelativePose;
304 }
305};
306
307} // namespace cl_moveit2z
CbCircularPivotMotion(std::optional< std::string > tipLink=std::nullopt)
std::optional< geometry_msgs::msg::Pose > relativeInitialPose_
CbCircularPivotMotion(const geometry_msgs::msg::PoseStamped &planePivotPose, const geometry_msgs::msg::Pose &relativeInitialPose, double deltaRadians, std::optional< std::string > tipLink=std::nullopt)
geometry_msgs::msg::PoseStamped planePivotPose_
CbCircularPivotMotion(const geometry_msgs::msg::PoseStamped &planePivotPose, double deltaRadians, std::optional< std::string > tipLink=std::nullopt)
std::vector< geometry_msgs::msg::PoseStamped > endEffectorTrajectory_
std::shared_ptr< moveit::planning_interface::MoveGroupInterface > moveGroupClientInterface
Component for shared TF2 transform management across all behaviors.
std::optional< geometry_msgs::msg::TransformStamped > lookupTransform(const std::string &target_frame, const std::string &source_frame, const rclcpp::Time &time=rclcpp::Time(0))
Thread-safe transform lookup.
virtual rclcpp::Logger getLogger() const
virtual rclcpp::Node::SharedPtr getNode() const