Skip to content

Fix: Derive the hangar_sim base height from the wheel geometry - #882

Open
L4co77 wants to merge 4 commits into
mainfrom
fix/ridgeback-wheel-height
Open

Fix: Derive the hangar_sim base height from the wheel geometry#882
L4co77 wants to merge 4 commits into
mainfrom
fix/ridgeback-wheel-height

Conversation

@L4co77

@L4co77 L4co77 commented Aug 25, 2026

Copy link
Copy Markdown
Screencast.from.2026-08-25.23-20-59.webm

Motivation

hangar_sim hung the robot off the world frame at z = −0.048, which put the wheels 74 mm below the floor and the chassis 44 mm into it. Viewed from under the ground plane the whole base is visibly through it. Addresses PickNikRobotics/moveit_pro#21933.

The anchor was a compensation, and says so:

z matches the MJCF base_platform_rotation anchor (pos 0 0 -0.048), which is tuned so the wheels contact the floor (the sim base has no z DOF).

It was compensating for the MJCF placing the wheel bodies 70 mm higher than the URDF chain does.

Brief description

The meshes decide which model is wrong, so this does not need a judgement call:

mesh z extent what it fixes
wheel.stl ±0.0395, radius 0.0761 in x/y disc centred on its origin — the joint origin is the axle
rocker.stl ±0.0600, symmetric no downward crank; the axle sits mid-rocker
body.stl +0.0037 … +0.2755 the chassis lies entirely above its link origin

With the URDF as authored (axle_offset 0.05, no wheel z) the wheel contacts 0.0259 below the chassis origin and the body bottom is 0.0037 above it — a 29.6 mm ground clearance, internally consistent. Add the MJCF's extra 0.07 and the contact point lands above the chassis bottom, which cannot happen. The URDF chain is sound; the MJCF wheel placement is not.

So the wheel geometry needed no change in either the vendored ridgeback_description (untouched here) or the URDF. What was wrong is the anchor, and it follows from the geometry rather than from tuning:

anchor = a·cos(π/N) + r − axle_offset = 0.0750889 − 0.05 = +0.0250889

a·cos(π/N) + r rather than the a + r outer radius, because the wheel is a ring of N=20 spheres and not a cylinder: the peak is reached only 20× per revolution, and the base carries no z DOF to take up the 0.811 mm ripple between spheres. Anchoring to the peak leaves wheels hanging clear at every other angle. Caught in review — the first version of this PR made exactly that mistake.

Three files change: the anchor in ur5e_ridgeback.xacro; the MJCF brought into agreement (front wheels 0.07 → 0, rear 0.12 → 0.05, anchor likewise, and ham_assem 0.278813 → 0.27365); and the roller comment in hangar_scene.xml, which claimed a tangency the spheres do not have.

The ham_assem correction is pre-existing rather than introduced here, and it is the same bug one link up — 5.163 mm between the models, which lands at the gripper rather than at a wheel. HAM_Assem.stl runs from y=+0.006350 and the mount rotation maps local y to world z, so the URDF's 0.27365 + 0.00635 = 0.280000 = deck_height: the arm sits on the deck, and the URDF is the correct side again.

How it was tested

Measured against live TF on a running hangar_sim, before and after:

before after expected
chassis_link z −0.048 0.025 +0.0250889
wheel link z +0.002 0.075 +0.0750889
wheel contact −0.0739 0.000 … 0.811 mm penetration in contact at every angle
ground clearance −44 mm 28.8 mm 28.8 mm
ham_assem_link z 0.299 0.0250889 + 0.27365

Navigate to Clicked Point succeeds and the wheels hold 0.076 for the whole drive. Launch log carries only the four pre-existing rotational_yaw_joint startup errors.

colcon test --packages-select hangar_sim: the new base_geometry_test is 6/6. One unrelated pre-existing failure in forward_stereo_publisher_test is untouched by this change.

The new test, and why it is trustworthy

test/test_base_geometry.py walks the URDF chain and the MJCF body nesting and asserts they agree, then that the wheels rest on z = 0. It reads the description sources directly rather than expanding the xacro, which would drag in the launch's argument set (visual_parameters_file and friends) for assertions that do not care about it. No simulator, no ROS, 0.03 s.

Every assertion was mutation-checked rather than merely observed to pass:

  • restoring the −0.048 anchor fails 5 of 8, reporting contact z = -0.0739 m
  • raising one MJCF wheel by 0.07 fails only the agreement test — both models still "touch the floor", so nothing but the cross-model comparison catches that drift
  • re-sizing the rollers alone, exactly the edit fix(hangar_sim): round out the mecanum wheels so the base stops juddering #875 made (r 0.010 → 0.0165), fails 2 of 8. The first version of this test read wheel_radius from the URDF on both sides and would have passed that silently
  • restoring ham_assem to 0.278813 fails 1 of 8
  • setting the anchor back to the peak radius fails 1 of 8 — which the first version also missed, because TOLERANCE_M was 1e-3, larger than the 0.811 mm error it was meant to catch. It is 1e-5 now

Effect on base jitter

#21933 raised the possibility that this mismatch was contributing to the hangar_sim base jitter. It was, and correcting it makes the base quieter and far more repeatable. Three nav runs per geometry, same goal, fresh sim each run, one variable changed:

geometry rocker peak-to-peak median spread sd
original (main) 0.292 / 0.348 / 0.314 0.314° 0.056 0.0223–0.0397
peak-radius anchor 0.871 / 0.463 / 1.362 0.871° 0.899 0.0186–0.0251
this PR 0.204 / 0.216 / 0.191 0.204° 0.025 0.0113–0.0177

No overlap with either other condition — the worst run here (0.216°) sits below the best original one (0.292°) — and the spread collapses from 0.899 to 0.025.

The middle row is the instructive one. It is this PR's earlier revision, which anchored to the wheels' peak radius a + r; with no z DOF on the base that left three of four wheels hanging clear of the floor at the keyframe, so the robot started on one corner and the excursions went erratic. Anchoring to the envelope between spheres puts all four in contact and the jitter drops below where it started.

An earlier version of this description read that the correction increases rocker excursion, on the strength of that middle row. That was wrong: the increase belonged to the bad anchor, not to the corrected geometry. Thanks to @bkanator for catching the anchor error, which is what made the difference visible.

This still does not establish how much the real Ridgeback rocks — that needs hardware, or someone who knows the platform.

Release notes

  • Bug Fix: Fixed the hangar_sim Ridgeback base being modelled below the floor, which placed the wheels 74 mm underground and left TF, the navigation footprint and collision checking describing a robot the simulator was not running.
  • Bug Fix: Fixed the hangar_sim UR5e mount sitting 5.2 mm higher in the simulation than in TF, which offset every arm frame between the two. Grasp poses tuned against the old simulation may need re-checking.

@L4co77 L4co77 added this to the 10.1.0 milestone Aug 25, 2026
@coderabbitai

coderabbitai Bot commented Aug 25, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

An error occurred during the review process. Please try again later.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: ea8bfeae-2022-42c6-bcb1-cd44a1c61ea2

📥 Commits

Reviewing files that changed from the base of the PR and between 9edbab3 and d3c608f.

📒 Files selected for processing (3)
  • src/hangar_sim/description/hangar_scene.xml
  • src/hangar_sim/package.xml
  • src/hangar_sim/test/test_base_geometry.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/hangar_sim/description/hangar_scene.xml

Included review availability: 3 reviews are currently available. Your included PR review attempts over the past 7 days set your current allowance at 5 reviews per hour.


📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes

    • Corrected robot base and wheel vertical placement for more accurate geometry and floor contact.
    • Aligned wheel heights and arm-mount positioning between the kinematic and physics models.
  • Tests

    • Added automated geometry checks for roller coverage, ride height, wheel consistency, floor contact, and arm-mount alignment.
    • Improved validation precision and integrated the checks into the standard test configuration.
  • Documentation

    • Clarified roller-sphere dimensions, spacing, overlap behavior, and positioning requirements.

Walkthrough

Changes

The Ridgeback URDF and MJCF now use roller-based vertical geometry. The test module parses roller dimensions, computes static ride height, compares wheel and arm-mount positions, and validates floor contact. CMake registers the test with a 30-second timeout.

Ridgeback base geometry

Layer / File(s) Summary
Align URDF and MJCF geometry
src/hangar_sim/description/hangar_scene.xml, src/hangar_sim/description/ur5e_ridgeback.xacro, src/hangar_sim/description/ur5e_ridgeback.xml
The model uses updated roller-based platform, wheel, and arm-mount heights. Roller geometry comments document the related radii and spacing.
Compute and validate wheel geometry
src/hangar_sim/test/test_base_geometry.py
The tests parse roller geometry, compute static ride height, compare all four wheel heights, check planar ancestry, validate floor contact, and compare arm-mount offsets.
Register the geometry test
src/hangar_sim/CMakeLists.txt, src/hangar_sim/package.xml
CMake adds the pytest target with a 30-second timeout. Package metadata adds the test dependencies.

Suggested reviewers: jwhitleywork

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description check ✅ Passed The pull request description directly explains the Ridgeback base-height correction, MJCF and URDF alignment, geometry tests, validation results, and release notes. It matches the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Human Review Check ✅ Passed The PR is low-risk under the stated criteria. The verified diff contains only six files in src/hangar_sim: simulator geometry data, package test dependencies, and one pure-data pytest target. It doe…
Full details: Human Review Check

Explanation

The PR is low-risk under the stated criteria. The verified diff contains only six files in src/hangar_sim: simulator geometry data, package test dependencies, and one pure-data pytest target. It does not change authentication, permissions, secrets, deployment or infrastructure files, public APIs/SDKs, tutorials, bin/scripts launch scripts, moveit_pro_behavior_interface, or moveit_pro_behavior. The CMake change registers a 30-second package test under BUILD_TESTING; it is not CI/CD or hosting configuration.


Comment @coderabbitai help to get the list of available commands.

@L4co77 L4co77 self-assigned this Aug 25, 2026
@L4co77
L4co77 requested a review from bkanator August 25, 2026 21:54
@L4co77
L4co77 marked this pull request as ready for review August 25, 2026 21:55
@L4co77
L4co77 force-pushed the fix/ridgeback-wheel-height branch from ef562e6 to 1b13a5f Compare August 25, 2026 21:55
coderabbitai[bot]
coderabbitai Bot previously approved these changes Aug 25, 2026
@github-actions

Copy link
Copy Markdown

MoveIt Pro Example WS - Objectives Integration Test Report

@github-actions

Copy link
Copy Markdown

MoveIt Pro Example WS - Objectives Integration Test Report

@bkanator bkanator left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[from firstmate]

I re-derived this from the meshes and both body trees — the geometry argument is correct, including the front/rear asymmetry. Four should-fix items; the first is the one I'd want resolved before merge.

1. The anchor uses the wheel's peak collision radius. Inline on ur5e_ridgeback.xacro. Short version: the MJCF wheel is 20 spheres, not a cylinder, so a+r = 0.0759002 is the top of an envelope that dips to 0.0750889 — a 0.811 mm ripple per 18° sector. With no z DOF on the base, three of four wheels start the keyframe clear of the floor. One-line fix in the comment.

2. test_wheels_rest_on_the_floor doesn't check the physics. Inline on the test. Both terms come from the URDF, so the exact change #875 made three commits ago would pass it silently.

3. ur5e_ridgeback.xml:1519ham_assem is 5.163 mm out from the URDF (0.278813 vs 0.27365). Rotations agree to 2 µm, so it all lands in z: the whole UR5e sits 5.2 mm higher in physics than in TF. HAM_Assem.stl starts at +0.00635 local, and 0.27365 + 0.00635 = 0.280000 = mid_mount exactly, so the URDF is the correct one; 0.278813 looks copied from the ur_mount_point offset two lines down. Pre-existing rather than yours, but it's the same bug class one link up, it survives this PR, and the new test should grow a case for it — 5 mm at the gripper matters more than 5 mm at the wheel.

4. hangar_scene.xml:15-19 — the roller comment is wrong twice, and this PR's anchor leans on it. The spheres aren't tangent: a·sin(π/20) = 0.010309064 against r = 0.010, so adjacent centres are 0.020618128 apart versus 0.020 of summed radii — a 0.618 mm gap. And tangency wouldn't fix the rolling-radius variation anyway; the ripple is a·(1−cos(π/N)), 0.811 mm as built and 0.808 mm if re-sized to exact tangency. It's inherent to approximating a circle with N spheres. Worth correcting while you're in here, because the next person to derive an anchor from "a + r = 75.9 mm" will make the same assumption this one did.

axle_offset (0.05) above chassis_link and the wheel radius is 0.0759, so the
chassis must ride 0.0259 up for the wheels to touch z = 0. Keep in step with
the MJCF base_platform_rotation anchor. -->
<origin xyz="0 0 0.0259" rpy="0 0 0" />

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The derivation is right, but it uses the wrong radius. This wheel has no collision cylinder — it's 20 spheres of r=0.010 on a circle of a=0.0659002081, so a+r = 0.0759002 is reached only 20× per revolution. Along the mid-pitch direction the envelope is a·cos(π/20)+r = 0.0750889.

Pinning the chassis so the wheel sits at exactly 0.0759 gives 0.2 µm of penetration at bottom-dead-centre and up to 0.811 mm of clearance everywhere else — and base_platform_rotation has no z DOF (free joint commented out at line 204), so nothing takes that up.

At the keyframe the config actually loads (mujoco_keyframe: default, which the integration suite resets to between objectives): front_left 0.865 mm clear, rear_left 0.522 mm clear, rear_right 0.535 mm clear, only front_right touching at 0.276 mm. On main all four penetrate 3.0–4.2 mm. The robot now starts on one wheel corner.

That preload is exactly why #875's residual ripple never showed — it went 8 spheres (4.522 mm ripple) → 20 (0.811 mm), and the −0.048 anchor was pressing every wheel 3.9 mm in. Remove the preload and the ripple is exposed again, 5.6× smaller. I'd guess that's also the real source of the rocker-jitter increase, rather than the longer lever in the description — base_platform_rotation has no z/roll/pitch DOF, so the chassis and arm are welded in z and never load the rocker.

One line, still derived rather than tuned:

anchor = a·cos(π/N) + r − axle_offset = 0.0750889 − 0.05 = 0.0251

Contact at every wheel angle (0 → 0.811 mm penetration), costs 0.8 mm of the 29.6 mm clearance, TF and physics unchanged. Raising N doesn't help — you'd need N≈57 to get the ripple under 0.1 mm.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[written by AI]

You're right — reproduced every number from front_left_wheel_link.xml (N=20, a=0.0659002080, r=0.010, ripple 0.811 mm), so the anchor is now 0.0250889 in both models, along with your ham_assem and roller-comment points.

Three runs a side on jitter: corrected 0.204 / 0.216 / 0.191 against 0.292 / 0.348 / 0.314 on main — no overlap, so the increase was the wheels hanging clear, not the longer lever, and the PR description no longer claims otherwise.


def test_wheels_rest_on_the_floor() -> None:
"""Agreeing on a wrong height is still wrong: the axle sits one radius up."""
radius = _xacro_property(RIDGEBACK_XACRO, "wheel_radius")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both terms here come from the URDF — wheel_radius from ridgeback.urdf.xacro, and the chain height from the anchor — so this asserts URDF-internal consistency rather than floor contact in the physics. MuJoCo's contact radius is a + r from the roller bodies in {front,rear}_{left,right}_wheel_link.xml, which this file never reads.

#875 changed exactly those numbers three commits ago (8 spheres a=0.0594/r=0.0165 → 20 spheres a=0.0659002081/r=0.010); the identical edit today passes all six tests silently.

Cheap fix: parse one roller <body pos> and its <geom size> from a wheel include, assert a + r == wheel_radius (0.0759002081 vs 0.0759 today, so 1e-5 is plenty), and take the contact height from the MJCF side. That also gives you the hook for the ripple check above.

@L4co77 L4co77 Aug 26, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[written by AI]

Correct — the contact height now comes from a·cos(π/N) + r on the MJCF side, read from all four wheel includes, instead of the URDF's wheel_radius.

#875 case (r 0.010 → 0.0165, nothing else) now fails 2 of 10; it also exposed that my TOLERANCE_M was 1e-3, larger than the 0.811 mm ripple it was meant to catch, so it is 1e-5 now.

bkanator added a commit that referenced this pull request Aug 26, 2026
kinematics.wheels_radius was 0.0666 in both the whole-body and the body-frame
mecanum controllers, ~11.9% below the wheels' true rolling radius.

The tread is a ring of 20 spheres of r=0.010 centred at a=0.0659002081. Rolling
on that ring traces the convex hull of the spheres — 20 tangent segments of
2*a*sin(pi/20) plus arcs summing to one circle of radius r — giving a rolling
radius of (20*2*a*sin(pi/20) + 2*pi*r) / (2*pi) = 0.07563. This is deliberately
not the 0.0759 outer radius a+r, which is the static ride height PR #882 uses.

The controller divides commanded body velocity by this constant, so the base
drove ~13.6% faster than commanded; wheel odometry multiplies by it, so it
under-reported distance by ~11.9% — about 0.72 m over a 6 m leg, the dominant
source of the localization drift this stack was built to absorb.

Valid only against main's rebuilt 20-roller wheels; the older 8-roller geometry
had a different answer.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0124EJ6ebbmvMJMjyMYPnWHD
coderabbitai[bot]
coderabbitai Bot previously approved these changes Aug 26, 2026
@github-actions

Copy link
Copy Markdown

MoveIt Pro Example WS - Objectives Integration Test Report

  • lab_sim
    • jazzy: no report produced — see run logs
  • hangar_sim
    • jazzy: no report produced — see run logs

2 similar comments
@github-actions

Copy link
Copy Markdown

MoveIt Pro Example WS - Objectives Integration Test Report

  • lab_sim
    • jazzy: no report produced — see run logs
  • hangar_sim
    • jazzy: no report produced — see run logs

@github-actions

Copy link
Copy Markdown

MoveIt Pro Example WS - Objectives Integration Test Report

  • lab_sim
    • jazzy: no report produced — see run logs
  • hangar_sim
    • jazzy: no report produced — see run logs

@github-actions

Copy link
Copy Markdown

MoveIt Pro Example WS - Objectives Integration Test Report

  • lab_sim
    • jazzy: no report produced — see run logs
  • hangar_sim
    • jazzy: no report produced — see run logs

@L4co77
L4co77 requested a review from bkanator August 26, 2026 14:30
L4co77 and others added 4 commits August 26, 2026 21:05
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@L4co77
L4co77 force-pushed the fix/ridgeback-wheel-height branch from d3c608f to 14e49ea Compare August 26, 2026 19:07
@github-actions

Copy link
Copy Markdown

MoveIt Pro Example WS - Objectives Integration Test Report

@github-actions

Copy link
Copy Markdown

MoveIt Pro Example WS - Objectives Integration Test Report

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants