-
Notifications
You must be signed in to change notification settings - Fork 13
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Save workcell as URDF #155
Conversation
Signed-off-by: Audrow Nash <audrow@intrinsic.ai>
Very nice! The general approach looks good I see some of the parts are still left to do where:
The first point should be pretty much doable in the current implementation.
The third point will be tricky, first of all the Now the tricky part is that some of these points will be hard to do without a functional The only suggestion I have on the current implementation is on the By contrast, if you had your own conversion function you could return either an |
In trying to implement this, it seems that there isn't a reason that the code would fail here. As long as the workcell is correctly defined, it should just be a data mapping, and I think that Rust will stop us if there is a mismatch in the data structures. Otherwise, am I missing something? |
Signed-off-by: Audrow Nash <audrow@intrinsic.ai>
Signed-off-by: Audrow Nash <audrow@intrinsic.ai>
I added visuals in bb09860 and collisions in ccb7915. The |
Let's add inertias and joint data in a separate PR. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for this meaty PR, Audrow! It's great to think that we'll be able to export URDFs soon.
I'm leaving a few comments about best practices within Rust that I think will help tighten up the implementation. I'll do a more careful review of the conversion logic after I've gotten through reviewing down some of the earlier site editor PRs.
rmf_site_format/src/workcell.rs
Outdated
Angle::Rad(v) => urdf_rs::Vec3([0.0, 0.0, v as f64]), | ||
Angle::Deg(v) => urdf_rs::Vec3([0.0, 0.0, v.to_radians() as f64]), | ||
}, | ||
_ => todo!("Unsupported rotation type for conversion to urdf pose"), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will cause the code to panic if pose.rot
is a Rotation::Quat
. That's a big problem since it would be perfectly reasonable for someone to use quaternions to describe a pose.
Another issue with this pattern is if we ever add another variant to Rotation
then we'll never notice that we need to update this pattern matching block. We should prefer for this block to produce a compilation error if we ever add another Rotation
variant.
So let's get rid of this line and add a pattern matcher for Rotation::Quat(_)
. We can draw inspiration from the implementation here, except I believe that was implemented before we accepted glam
as a dependency. glam
is the crate that bevy uses for math, and it's now a dependency for rmf_site_format
so we can feel free to use glam for things like this.
Another tip is that we don't need to do any pattern matching for Angle
because Angle
has a radians()
method. E.g. consider this alternative for the first matching arm:
Rotation::EulerExtrinsicXYZ(arr) => urdf_rs::Vec3(arr.map(|v| v.radians())),
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in dfbf3d3.
rmf_site_format/src/workcell.rs
Outdated
Geometry::Primitive(MeshPrimitive::Sphere { radius }) => urdf_rs::Geometry::Sphere { | ||
radius: radius as f64, | ||
}, | ||
_ => todo!("Only meshes and primitives are supported for conversion to urdf geometry"), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should never merge code with the todo!
macro in it (nor any form of panic!
unless there's extremely strong justification). This indicates a code path that may contain unhandled errors. Also as mentioned in my other comment it will hide API changes that we want the compiler to catch for us.
If there's any conceivable situation where we might fail to convert a urdf_rs::Geometry
into a Geometry
then there is nothing at all wrong with foregoing the From<T>
trait and writing a custom conversion function that returns a Result<T, E>
where E
is an enum that derives ThisError
(see here for an example). This aligns with what Luca was saying about From<T>
.
If you can write a correct From<Geometry>
implementation without any potential panics then you're right that it's preferable to have From<T>
instead of returning a Result<T, E>
. But if we have to chose between From<T>
with possible panics versus Result<T, E>
without panics, then we absolutely must prefer Result<T, E>
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gotcha, thank you for the clarification.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I made it so that we don't panic in 5e376ea.
rmf_site_format/src/workcell.rs
Outdated
|
||
let pose: urdf_rs::Pose = match frame.anchor { | ||
Anchor::Pose3D(pose) => pose.into(), | ||
_ => todo!(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's make sure we implement this before we merge.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in dfbf3d3.
Signed-off-by: Audrow Nash <audrow@intrinsic.ai>
Signed-off-by: Audrow Nash <audrow@intrinsic.ai>
@mxgrey, thanks for the feedback! I believe that I've implemented the changes. I'm fairly new to Rust (and loving it), so please let me know if you see opportunities for me to do things in a more Rust-like way. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is looking really great. High level design looks good to me, I went into a more detailed review
I also just noticed that the pose of links might be wrong without support for joints. I believe a way to make this work would be to look at each link, its parent, then add a fixed joint between the two at export time, but I'm not sure if there are any other/better ways. I'm OK with dealing with this in the next step of supporting joints but just thought I'd point that out so we keep it in mind. |
@luca-della-vedova, thanks for the feedback - I feel like I'm learning a lot! I'll iterate on this a bit later this week. |
Signed-off-by: Audrow Nash <audrow@intrinsic.ai>
Signed-off-by: Audrow Nash <audrow@intrinsic.ai>
Signed-off-by: Audrow Nash <audrow@intrinsic.ai>
Signed-off-by: Audrow Nash <audrow@intrinsic.ai>
That's a good thing to point out. Let's handle it in another PR. |
rmf_site_format/src/workcell.rs
Outdated
let parent = &f.1.parent; | ||
let collision = parent_to_collisions | ||
.get(parent) | ||
.map(|collisions| collisions.clone()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the hierarchy calculation here might be off.
This line (and the same afterwards for visuals), do a lookup in the visual / collision hash map for the parent which is fine.
However, the parent
variable doesn't contain the link's id but the link's parent id, so I believe this line would assign, to the current link, the visuals / collisions that are children of the link's parent link.
The parent
should be the frame's ID f.0
rather than &f.1.parent
, for readability can just unpack the tuple at the iteration level by changing:
.map(|f| {
Into
.map(|(frame_id, frame) {
With this change you should be able to also simplify the HashMap access to move values and remove the clone.
Since in every loop iteration the key you use to access the map is different, you can just move the visuals / collisions out of the map and avoid the cloning as such:
let visual = parent_to_visuals
.remove(frame_id)
.unwrap_or_default();
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I noticed that hierarchy is also off in the example in your first post, the visual has a parent id of 0
that corresponds to the ID of the root of the workcell, while in the exported urdf
its parent is set to the top_anchor
frame which has id 2.
Now there is a minor challenge in the fact that the root might not be a frame itself but that should be pretty easy to fix by adding a dummy link at the origin
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be fixed now in 6c5999c.
Another small thing I noticed is that the root level tag is |
Signed-off-by: Audrow Nash <audrow@intrinsic.ai>
e1ffff7
to
6c5999c
Compare
You were right. |
I updated the It still doesn't load into Rviz, but I think it will once we add joints. I think that the visual and collisions are being included correctly now. |
It was a one-liner and they already fixed and re-released the crate, |
Signed-off-by: Luca Della Vedova <lucadv@intrinsic.ai>
0e22813
to
9b7391c
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is a good first step, we can tackle the remaining features in followups
@luca-della-vedova, great! I don't have merge access. Feel free to merge if you think it's ready. |
Signed-off-by: Luca Della Vedova <lucadv@intrinsic.ai>
Closing in favor of #177. |
The current workcell is saved as a URDF when you click "Export URDF" in the "File" menu.
Note this doesn't include mass/inertia or joint data. I'll make separate PRs for those.
Output URDF
Workcell