Add named frames to CollisionObjects (v2)#1439
Conversation
jonbinney
left a comment
There was a problem hiding this comment.
I've done a partial review and found that I have opinions on some of the high level choices in the design.
-
There's this constant tension in the design of the planning scene (and world). Adding more specific features makes certain use cases very convenient. But it also means more code that might only be useful to some people. Personally I'd rather keep all the subframe info in my own external data structure, and be able to make that as complex as desired, without putting that into the planning scene itself. But I don't feel strongly about that, and it sounds like others find this basic concept quite useful, so I'll move on.
-
Mixing the child frames into the top level object namespace makes a few parts of the API slightly more convenient for the user, but I think it's too likely to cause problems. I think it would be a lot cleaner API (and less code) if the named frames only makes sense in the context of a specific object. Thoughts?
moveit_core/collision_detection/include/moveit/collision_detection/world.h
Outdated
Show resolved
Hide resolved
| bool World::knowsTransform(const std::string& object_id) const | ||
| { | ||
| auto it = objects_.find(id); | ||
| // Check object names first |
There was a problem hiding this comment.
It looks like this code does what it's meant to, but i'm torn. It seems a bit sketchy to put both "objects" and "child objects" in the same object namespace. Based on the description of the PR, I was expecting these child frames to only make sense in the context of their parent objects. For example "grasp_pose" or "handle_pose" on a teapot aren't the name of an object at the top level. It feels like it would be cleaner to not treat these child frames "objects" at all; so it seems like a top level "knowsTransform" should only check for object transforms (and therefor wouldn't be too useful compared to hasObject().
There was a problem hiding this comment.
See the mainline comment
| throw std::runtime_error("No transform found for object_id"); | ||
| } | ||
|
|
||
| std::string World::getObjectOwningFrame(const std::string& frame_name) const |
There was a problem hiding this comment.
Same comment here - i'd prefer that named frames within an object only make sense in the context of an object. So it wouldn't make sense to call getObjectOwningFrame("handle_frame") because i might have objects "teapot1" and "teapot2" in the scene which both have a "handle_frame"
There was a problem hiding this comment.
See the mainline comment about namespacing
|
Thanks for the second look!
@henningkayser proposed prefixing the named frames/subframes with the object name (which already needs to be unique), so they're namespaced. The named frame I don't know what you mean by "the named frames only make sense in the context of a specific object". Do you mean the user should get the frame transformation from the object to the object part, apply it to the pose they want to move the part to, and then plan with the new pose applied to the end effector? I do think that's too cumbersome to be useful. |
|
@felixvd yes, i was proposing requiring the user to manually lookup the object and child-frame transforms separately and chain them together. I was imagining something like: Which didn't seem so bad; but I can see that it would be even more convenient to have a single function call in the API as you suggest. Prefixing with the object name is interesting, but it doesn't feel very machine-friendly (it's certainly user-friendly). How about having a function like: And then within the world we can do the lookups and multiply the two transforms, and raise a runtime error if one of them doesn't exist? |
|
I can add the function, but it would make things a lot easier to just write "move top of cup under spout" instead of multiplying transforms. Normal users don't handle raw transforms in ROS either, and I don't think they should be expected to. It's something that notoriously goes wrong and that I don't miss doing at all. I'll implement an attempt at namespacing this week though. |
|
Sounds good - having the function that handles the namespacing for the user sounds like a good compromise. |
|
I changed the naming from "named_frames" to "subframes". Subframes are always prefixed by their parent object's name, so the subframe "tip" of an object "cylinder" can be used as "cylinder_tip" in the planning scene. The namespacing is done in In the current commit there is a bug that seems to cause a crash when the subframe name is shorter than the object's name (?), but I should be able to fix that soon. You can see it occurring for "box_top" in my test package, but not for the longer subframe names. Shortening them causes the error to occur for them, too. |
|
Thanks @felixvd ! It is going to be a few days before i have time to do my next review of this, but I will get to it as soon as I can. |
|
Just documenting that this branch contains essentially the same commit that seems to be causing me trouble, except this one is fine. Should be done before the maintainer meeting tomorrow. |
rhaschke
left a comment
There was a problem hiding this comment.
... it would be a lot cleaner API if the named frames are only used in the context of a specific object.
I agree with @jonbinney, that it feels strange to access those transforms in the same flat namespace as all other transforms. This makes it both, more costly (because you need to search all objects) and less transparent. I support Jon's proposal to use a function like
transform = world.getObjectSubFrame(object_name, chlid_frame_name)
| * Use these to define points of interest on the object to plan with | ||
| * (e.g. screwdriver_tip, kettle_spout, mug_base). | ||
| * */ | ||
| std::map<std::string, Eigen::Isometry3d> subframe_poses_; |
There was a problem hiding this comment.
You need to use an aligned allocator for the Eigen transforms!
What about directly using moveit::core::Transforms or at least moveit::core::FixedTransformsMap?
There was a problem hiding this comment.
I don't know what an aligned allocator is, but I can replace the std::map with moveit::core::FixedTransformsMap if that works?
There was a problem hiding this comment.
I had to look this up too: this is the best resource I found, and this issue is some recent discussion on it.
👍 for using FixedTransformsMap
|
Dear Felix, could you please clean up your commit history a little bit? |
@felixvd, what problem(s) are you referring to? Travis failures? |
|
Thanks for the review!
I usually do squash/reorder before pushing, but I am not sure what the right way to do it is when commits were already pushed. Separate everything into multiple new PRs and abandon this one? Force push the branch to a clean version of commits? I assume that would interfere with the PR and review interface here on Github?
I worry that this is too complicated for the common user. I am hoping for this functionality to be readily accessible, and end users should not need to multiply transforms. The Just to confirm: Is it the search through all the subframes that you find problematic, or the access through the top-level namespace? If it is the latter, I don't know that this design decision would be consistent. We already let users specify a collision object name as a target for planning, although that requires a search operation through the scene as well. If we do not value this convenience, why not ask users to do If it is the former, I am not sure that performance would be affected significantly. The search through object subframes always happens last, so robot links, CollisionObjects and AttachedBody objects are retrieved as fast as before. It will also only make a difference in scenes with many objects and frames. It might be premature optimization to favor this rather than an easier interface. This is the current API. I hope we can agree that this is very readable, even for beginners, and that readability is important: geometry_msgs::PoseStamped ps;
tf2::Quaternion quaternion;
moveit::planning_interface::MoveGroupInterface group("panda_arm");
ps.header.frame_id = "box/hole";
quaternion.setRPY(0, (180.0 / 180.0 * M_PI), 0);
ps.pose.orientation = tf2::toMsg(quaternion);
ps.pose.position.z = 0.01;
group.setEndEffectorLink("cylinder/tip");
group.setPoseTarget(ps);
group.go();The only alternative I could imagine (to avoid the top-level namespacing) is something like this: moveit::planning_interface::PlanningSceneInterface planning_scene_interface;
//ps.header.frame_id = "box/hole"
quaternion.setRPY(0, (180.0 / 180.0 * M_PI), 0);
ps.pose.orientation = tf2::toMsg(quaternion);
ps.pose.position.z = 0.01;
ps = planning_scene_interface.transformPoseHeaderFromSubframe(ps, object="box", subframe="hole");
// Returns a transformed ps such that ps.header.frame_id = "box"
group.setEndEffectorLink("cylinder/tip");
group.setPoseTarget(ps);
group.go();But I find this less elegant, and likely much harder to find for new users.
On 8f31daa, planning to the subframe |
|
For the protocol, @rhaschke and I had a quick Skype call to discuss this PR and some ideas.
ps.header.frame_id = "box/hole"
group.setEndEffectorLink("cylinder/tip");
|
|
As a detail that is open to comments, I wondered in the OP if the string copy in
|
Can't you just "return" (via arguments) a const pointer to the LinkModel, like so: |
|
Sounds great, just what I was looking for. I'll do that if it works. |
0dfc7ee to
18c0b19
Compare
|
As it turns out, with the order /home/catkin_ws/src/moveit/moveit_core/robot_state/src/robot_state.cpp:1019:44: error: assignment of read-only reference ‘robot_link’
robot_link = robot_model_->getRootLink();
^
/home/catkin_ws/src/moveit/moveit_core/robot_state/src/robot_state.cpp:1019:43: error: invalid conversion from ‘const moveit::core::LinkModel*’ to ‘moveit::core::LinkModel*’ [-fpermissive]
robot_link = robot_model_->getRootLink();Second, I fixed the confusing issue I had earlier where only std::cout messages changed the program outcome. I had accidentally left the return value undefined after catching an exception in Finally, as requested by @rhaschke, I cleaned up the commit history into hopefully readable chunks. I recommend looking at each of those separately instead of all the changed files together. There are more detailed explanations in the commit messages, as well. Each commit probably does not compile on its own, they are just grouped semantically for easier reading. Thanks in advance! |
BryceStevenWilley
left a comment
There was a problem hiding this comment.
First off, great feature, thanks for putting in the work for it.
General comments from reading the mainline thread:
- I think I agree with @jonbinney in that "the named frames only make sense in the context of a specific object". It seems the discussion came down to the two APIs you mentioned in this comment. I don't see too much of a difference between the two at the moment, but I do find the latter make more sense when extending this idea. For example, if you have an object that you have multiple screw holes in, a call like
std::vector<std::string> frames = planning_scene_interface.getSubframes(ps, object="box");
for (frame : frames)
{
ps = planning_scene_interface.transformPoseHeaderFromSubframe(ps, object="box", subframe=frame);
move_group.setPoseTarget(ps);
move_group.go();
}
flows better with the second API. Although it could still work with the first API, it'd just be a little more awkward, adding strings and what not. At the moment, with the existing functionality, I don't have a preference between the two APIs, and am okay with your proposed one.
- Agree with using
/instead of_as separators, but the code should be strict about what's there, not just skipping any character that's present (what a weird bug for someone to run into: "drill/bitis the same asdrillabit").
| * Use these to define points of interest on the object to plan with | ||
| * (e.g. screwdriver_tip, kettle_spout, mug_base). | ||
| * */ | ||
| std::map<std::string, Eigen::Isometry3d> subframe_poses_; |
There was a problem hiding this comment.
I had to look this up too: this is the best resource I found, and this issue is some recent discussion on it.
👍 for using FixedTransformsMap
moveit_core/collision_detection/include/moveit/collision_detection/world.h
Outdated
Show resolved
Hide resolved
moveit_core/robot_state/include/moveit/robot_state/attached_body.h
Outdated
Show resolved
Hide resolved
moveit_core/kinematic_constraints/include/moveit/kinematic_constraints/utils.h
Outdated
Show resolved
Hide resolved
moveit_core/robot_state/include/moveit/robot_state/attached_body.h
Outdated
Show resolved
Hide resolved
18c0b19 to
a00ae85
Compare
|
I updated each of the commits as suggested by Bryce, and started changing from Compiler error for the WIP commit: Errors << moveit_core:make /home/catkin_ws/logs/moveit_core/build.make.049.log
/home/catkin_ws/src/moveit/moveit_core/collision_detection/src/world.cpp: In member function ‘void collision_detection::World::ensureUnique(collision_detection::World::ObjectPtr&)’:
/home/catkin_ws/src/moveit/moveit_core/collision_detection/src/world.cpp:131:30: error: use of deleted function ‘collision_detection::World::Object::Object(const collision_detection::World::Object&)’
obj.reset(new Object(*obj));
^
In file included from /home/catkin_ws/src/moveit/moveit_core/collision_detection/src/world.cpp:37:0:
/home/catkin_ws/src/moveit/moveit_core/collision_detection/include/moveit/collision_detection/world.h:82:10: note: ‘collision_detection::World::Object::Object(const collision_detection::World::Object&)’ is implicitly deleted because the default definition would be ill-formed:
struct Object
^~~~~~
/home/catkin_ws/src/moveit/moveit_core/collision_detection/include/moveit/collision_detection/world.h:82:10: error: use of deleted function ‘moveit::core::Transforms::Transforms(const moveit::core::Transforms&)’
In file included from /home/catkin_ws/src/moveit/moveit_core/collision_detection/include/moveit/collision_detection/world.h:41:0,
from /home/catkin_ws/src/moveit/moveit_core/collision_detection/src/world.cpp:37:
/home/catkin_ws/src/moveit/moveit_core/transforms/include/moveit/transforms/transforms.h:61:7: note: ‘moveit::core::Transforms::Transforms(const moveit::core::Transforms&)’ is implicitly deleted because the default definition would be ill-formed:
class Transforms : private boost::noncopyable
^~~~~~~~~~
/home/catkin_ws/src/moveit/moveit_core/transforms/include/moveit/transforms/transforms.h:61:7: error: use of deleted function ‘boost::noncopyable_::noncopyable::noncopyable(const boost::noncopyable_::noncopyable&)’
In file included from /usr/include/boost/noncopyable.hpp:15:0,
from /home/catkin_ws/src/moveit/moveit_core/transforms/include/moveit/transforms/transforms.h:43,
from /home/catkin_ws/src/moveit/moveit_core/collision_detection/include/moveit/collision_detection/world.h:41,
from /home/catkin_ws/src/moveit/moveit_core/collision_detection/src/world.cpp:37:
/usr/include/boost/core/noncopyable.hpp:34:7: note: declared here
noncopyable( const noncopyable& ) = delete;
^~~~~~~~~~~I wouldn't know how to fix this (I assume |
|
Looks good to me just reading the code, but can you explain the change in this commit? This seems to change behavior from the original version, and is also not in line with the behavior of |
|
The original code considered object frames to be known only if there is a unique shape associated with it. You changed this semantic in the first place: |
502f2e1 to
da7539f
Compare
|
True. I was thinking of the getTransform logic which just gave a warning. My bad. I force-pushed your branch to let Travis run, but this line displays an error message when using subframes. I would keep using 'hasLinkModel'/'getLinkModel' because there are never that many robot links and creating the motion planning request is not a frequent operation, so this should not be costly. Also do you want the fixup commits combined before merging? |
You are right. I wasn't aware of the verboseness of this method anymore... |
The getFrameInfo function is needed later for validateConstraintFrames. The getFrameTransform functions with "frame_found" parameter allow skipping the redundant searches in the planning_scene that are also part of this commit.
In PlanningScene and collisions.cpp
da7539f to
e5eb0d7
Compare
rhaschke
left a comment
There was a problem hiding this comment.
I think, this is finally ready for merging. 🎉
|
@felixvd, can you file your tutorial examples as a PR, please? |
|
@felixvd, I just noticed that I already filed the tutorial PR: moveit/moveit_tutorials#326 |
|
So glad this major new feature was merged in!! |
... which was broken in 3e8feeb (moveit#1439)
Description
This PR supercedes #1060 and implements #1041. It includes all the changes requested in the last thread and now targets the correct branch. It requires this PR to
moveit_msgs.In short, this PR adds subframes to CollisionObjects so that one can write "move the tip of object A to somewhere on object B". In the animation below, the cylinder tip is moved to each side of the box in this manner:
I made a test package here, which you can run with
rosrun moveit_tutorials subframes_tutorial. You can reproduce the animation above by entering the numbers 2, 3, 4, 5.Small changelog:
I added a version of
getFrameTransformtorobot_state.cppwhich combines the previousgetFrameTransformandknowsFrameTransform, so that retrieval of a frame does not require two subsequent searches. I also addedgetFrameInfo, which returns the name of the robot link that a CollisionObject or named frame is attached to. This is used for changing the frames of constraints invalidateConstraintFrames, because only links that are part of the link model can be used for motion planning.I included the changes to the rest of the codebase where the new
getFrameTransformwould be used. I can take them out and start a separate PR for that, but they did not seem to cause issues.Last questions:
getFrameTransformforward togetFrameInfo, discarding the robot link returned bygetFrameInfo. Should the functions be kept separate? I am not sure if makes a big difference performance-wise. The difference is one string copy.subframe_idsandsubframe_posesinstead offrame_namesandnamed_frame_poses. Is "subframe" misleading?GetConstraintValidityservice. I am not opposed, it is only used internally anyway.validateConstraintFramesis the current version.The Travis build will fail because moveit_msgs is affected. Before merging, I will remove the simple Rviz visualization of the named frames (the red dots in the animation). The visualization will have to be part of a PR addressing #1122 .
Would love to get this merged ASAP, because the GSoC project will affect this area of the code significantly. Thanks for reviewing!
@v4hn @davetcoleman
Checklist