Skip to content

feat(market_sim): add a Mobile FR3 Duo supermarket sim with AMCL and Nav2 - #1046

Draft
sanchit-badamikar wants to merge 7 commits into
mainfrom
fm/market-sim-config
Draft

sanchit-badamikar wants to merge 7 commits into
mainfrom
fm/market-sim-config

Conversation

@sanchit-badamikar

@sanchit-badamikar sanchit-badamikar commented Oct 9, 2026 •

Copy link
Copy Markdown

[written by AI]

Motivation

The workspace has no mobile manipulator example at store scale. This PR adds market_sim: a Franka Mobile FR3 Duo in a MuJoCo supermarket, with Beluga AMCL localization and Nav2 navigation.

Part of the split of #1031 (see it for the history). Based on #1033 (its assets), which sits on #1045 (kitchen_sim and dual_arm_sim) and #1032 (franka_description 2.9.0). Replaces the closed drafts #1034 and #1035.

Brief description

  • Standalone config. The robot description, SRDF, MoveIt parameters, ros2_control files, the odometry, scan and base-velocity scripts with their tests, and the Objectives are copied from the Franka Mobile FR3 Duo configuration. The robot macros come from the vendored franka_description. No other config package is needed.
  • Spine: the spine joint and its controllers stay, so the spine can be joint-jogged. There are no spine Objectives.
  • scripts/generate_market.py builds the store, the occupancy map and the Navigate to Aisle Objectives from a text floor plan. A test fails if the committed files differ from its output.
  • Three keyframes (default, loose_a_d, loose_e_i) move the loose shelf units between aisles. scripts/reset_relocalizer.py re-seeds AMCL at the true pose after a reset.
  • Nav2 uses the rotation shim around MPPI and a stowed-arms footprint. Every Objective that drives the base stows the arms first.
  • Objectives: Navigate to Aisle (7 goals), Navigate with NavFn, Navigate with SMAC, Localize Robot, Refine Localization In Place, Reset Market Keyframe, plus the base, gripper and teleoperation Objectives.
  • AGENTS.md adds two known issues: Nav2 Jazzy can keep driving the base after a navigation aborts, and AMCL can drift along the long A to D aisles.

How it was tested

  • Live, on MoveIt Pro main, with the inherited version on feat(market_sim): add a Mobile FR3 Duo supermarket sim with AMCL and Nav2 #1031 head: localization, navigation to every aisle with no rack contacts, both loose-product keyframes, and arm and base teleoperation.
    • This PR's config is the same, except: no spine Objectives and no spine Behavior loader; the per-arm gripper Objectives are no longer favourites (the repo allows 8); prettier formatting.
  • Offline in picknikciuser/moveit-pro:main-jazzy: the build and 34 tests pass, the config loads with no parent, the expanded URDF matches the live one, and the scene compiles with its keyframes.
  • bin/validate_workspace_dependencies.py --verify-upstream and pre-commit pass.
  • Not run live in this standalone form.

Notes for reviewers

Release notes

  • Enhancement: New market_sim example: a Franka Mobile FR3 Duo in a simulated supermarket with AMCL localization and Nav2 navigation.

@coderabbitai

coderabbitai Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Tip

🎯 Customize your risk assessments

Guide how CodeRabbit assesses merge risk and security architecture risk. Highlight sensitive changes and explain your security boundaries using Merge Risk Instructions and Security Risk Instructions in your review settings. Security architecture risk assessments require the Advanced plan or above.

✅ Automatic approvals, with your rules

Let CodeRabbit approve pull requests once required threads are resolved, pre-merge checks pass, and your risk limits are met. Enable Automatic approvals in review settings, with overrides for teams or file paths.

@github-actions

github-actions Bot commented Oct 9, 2026

Copy link
Copy Markdown

⚠️ This PR modifies 1 file(s) that also exist in PickNikRobotics/moveit_pro_empty_ws.

Consider whether the change should land upstream in moveit_pro_empty_ws first so downstream forks pick it up on the next sync.

Overlapping files
  • README.md

@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.

[written by AI]

Requesting changes, mostly for duplication: much of this package is copied from moveit_pro_franka_ws or hangar_sim; details inline. I built it and ran its 34 tests locally (all pass); I did not run it live.

Comment thread src/market_sim/README.md
Comment on lines +8 to +10
`market_sim` is a standalone configuration. Its robot description, planning groups, controllers,
odometry bridge and scan and base-velocity nodes are copied from the Franka Mobile FR3 Duo
configuration in `moveit_pro_franka_ws` (`mobile_fr3_duo_mock` and `mobile_fr3_duo_sim`, at commit

@bkanator bkanator Oct 13, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[written by AI] The SRDF, MoveIt and ros2_control config, scripts, tests and several Objectives are verbatim copies of moveit_pro_franka_ws, and nothing here tracks that upstream, so upstream fixes will never show up as drift. Please vendor it under src/external_dependencies/ with an UPSTREAM.yaml and overlay market_sim on it, as the earlier split did. If a standalone copy is deliberate, please say why in the PR description.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

you don't have to do this, this is mainly just questioning on how you want to handle upstream changes. Keeping separate copies are fine as long as maybe you state that decision somewhere

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]

Stated rather than vendored, in e3d6592: README.md now says the copy is deliberate, names the upstream commit it was taken at, and says this package does not track that source so an upstream fix has to be copied here by hand. That follows your follow-up that keeping separate copies is fine as long as the decision is written down somewhere.

ID="GetLatestTransform"
name="Read the refined pose"
target_frame_id="map"
source_frame_id="base_link"

@bkanator bkanator Oct 13, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[written by AI] This subtree and localize_robot, localize_robot_if_needed and refine_localization_in_place are hangar_sim's (objectives) with only the base frame, scan topics, max_range and one default changed. Adding ports for those would let one copy serve both packages. A shared location is fine as a follow-up; it shouldn't block this PR.

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]

Left as the follow-up you allowed. e3d6592 added xy_variance and yaw_variance ports, but the base frame, the two scan topics, the map topic and max_range are still fixed here, so one copy does not yet serve both packages. Sharing it is a cross-package move and this package is the only consumer today, so it is not folded into this PR.

Both simulated SICK TIM571s are <camera> sensors with DEPTH_TYPE=THREE_D_LIDAR, so
picknik_mujoco_ros publishes them as an organized PointCloud2 rather than as a LaserScan.
Everything downstream of /scan_front and /scan_rear -- the two laser_filters chains, then
dual_laser_merger, then beluga_amcl on /scan_merged and both Nav2 costmap obstacle layers

@bkanator bkanator Oct 13, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[written by AI] This package has no dual_laser_merger and no /scan_merged; it uses scan_merger.py and publishes /scan.

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]

Fixed in e3d6592 and 10f4239. The merger and /scan_merged are gone: e3d6592 deleted that node and feeds AMCL from two topic_tools relays onto /scan_interleaved, and 10f4239 rewrote the docstring that still described the old path.

<param name="lidar_publish_rate">10</param>
<!-- The URDF root odom is not a MuJoCo body, so the base body is named explicitly. -->
<param name="base_link_name">${base_link_name}</param>
<!-- world -> mj_world comes from launch/additional_driver_nodes.launch.py, so the plugin must not publish it. -->

@bkanator bkanator Oct 13, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[written by AI] Stale: world -> mj_world is published by launch/sim/robot_drivers_to_persist_sim.launch.py (line 147); additional_driver_nodes.launch.py doesn't exist here.

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]

Fixed in e3d6592. That line now names launch/sim/robot_drivers_to_persist_sim.launch.py, which is the file that publishes world -> mj_world, and the additional_driver_nodes.launch.py reference is gone.

Comment thread src/market_sim/params/nav2_params.yaml Resolved
Comment on lines +6 to +17
install(
DIRECTORY
config
description
launch
maps
mjcf
objectives
params
waypoints
DESTINATION
share/${PROJECT_NAME}

@bkanator bkanator Oct 13, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[written by AI] 15 meshes in mjcf/assets/ are byte-identical to franka_description's, and it is already an exec_depend here. Please install-copy them from its share directory at build time (add a build_depend and find_package), as factory_sim/CMakeLists.txt:19-31 does, instead of committing copies.

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]

In part: #1087 goes one step further for kitchen_sim and dual_arm_sim: the meshes that are byte-identical to franka_description are loaded from the vendored copy with package://franka_description/... paths, the copies are removed, and nothing is copied at build time. The reworked version of this PR will do the same for market_sim's 14 meshes, so market_sim needs no install-copy rule.

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]

Gone further than asked, in e3d6592: the byte-identical meshes are no longer committed at all. The MJCF loads them at run time from the vendored franka_description over package://franka_description/..., so there is no install-copy rule to add and no copies to drift.

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]

Follow-up now that the mesh handling has moved on, in ca5ea7d and c8953dd on this branch. The package still commits no franka_description copies. It now also loads 29 convex collision parts and the 13 real visual meshes from a vendored franka_meshes over package://, so the MuJoCo view shows the visual geometry rather than the collision stand-ins, and the five concave links stop colliding as their filled-in hulls. Still no install-copy rule and still nothing duplicated.

Comment thread src/market_sim/mjcf/NOTICE Resolved
lib/${PROJECT_NAME}
)

if(BUILD_TESTING)

@bkanator bkanator Oct 13, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[written by AI] Unlike the other sims, this has no ament_lint_auto and no objectives_integration_test. Please add lint at least.

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]

Fixed in e3d6592: CMakeLists.txt now has ament_lint_auto with ament_lint_auto_find_test_dependencies(), the matching lint test_depends are in package.xml, and objectives_integration_test is registered alongside the offline pytest targets.

sanchit-badamikar and others added 4 commits November 3, 2026 16:30
Adds market_sim: a Franka Mobile FR3 Duo in a MuJoCo supermarket, localizing
with Beluga AMCL and navigating with Nav2.

The store is generated rather than drawn. One text floor plan gives the rows,
the checkouts and the walls, and the generator writes the scene, both maps and
the per-aisle navigation Objectives from it, so the world and the goals cannot
drift apart.

Also brings in the shelf and robot mesh assets the generator builds from.
mjcf/mobile_fr3_duo.xml is copied from moveit_pro_franka_ws (940aa42) and
built from franka_description 2.9.0 (FR3 v2, spine, head and TMR base
kinematics, inertials and dynamics), MuJoCo Menagerie franka_fr3 (actuator
settings) and Menagerie franka_emika_panda (finger coupling from hand.xml).

- mjcf/mobile_fr3_duo.xml says so in a comment at its top and that it is
  modified, as Apache-2.0 section 4(b) asks.
- mjcf/NOTICE lists those sources; it sits in mjcf/ so the mesh attribution
  for mjcf/assets/ can be added separately.
- mjcf/LICENSE-franka_description and mjcf/LICENSE-mujoco_menagerie_franka_fr3
  are the upstream LICENSE files (Apache-2.0, each with its BSD section).
- package.xml declares Apache-2.0 alongside BSD-3-Clause, and the README
  names the moveit_pro_franka_ws commit the files were copied from.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e lidar plane

Review rework of the supermarket config, plus the localization fix the first
simulator runs turned up.

The robot's meshes that are byte-identical to franka_description now load
through package:// instead of being copied, and the base's visual mesh is
rebuilt as per-material parts with UVs. Licensing collapses to one NOTICE at
the package root alongside the upstream licence texts. The store ships empty,
so the first change is navigation only. Every base drive goes through one
"Drive Base using Nav2" SubTree with the planner as a port, rather than each
Objective repeating the call. Both lidar scans reach AMCL through two relays.
Comments and docstrings were corrected throughout, and the package now has
lint and an Objectives integration test.

The localization fix matters most. Both lidars scan a plane just above the
bottom deck, so in an empty store that plane passes over the deck and strikes
the back panel. The map marked the whole gondola footprint solid, which let a
scan point score well anywhere inside it and let the estimate slide by the
deck's depth with no penalty. The generator now also writes a map of only what
the lidars can see at their height, AMCL and the in-place refinement read that
one, and the costmaps keep the full map so the base still never plans onto a
deck. Two tests cover it, one of which ray-casts the lidar plane in the
simulator and fails against the old map.
…e drives

Work from running every localization and navigation Objective against the
simulator, which is what turned up the faults below.

The store was built on 1.114 m gondolas with 1.6 m aisles, leaving the stowed
robot 4 mm of lateral clearance once both shelf faces are inflated by the
footprint's inscribed radius. A centre-point planner never notices; one that
checks the whole footprint cannot plan in it. Gondola depth is a layout choice
rather than a property of the shelf asset, so the asset now stretches in depth
to fill it, and the generator recomputes the world, both maps and the aisle
goals together.

Navigate with SMAC had never worked. The SubTree named its Nav2 tree by a bare
filename and leaned on bt_navigator's working directory; the directory was
right and Nav2 opened the file, but BehaviorTree.CPP refused the relative path.
The driver launch file now writes the stock navigate-to-pose tree with the Smac
planner selected to an absolute path, and the SubTree names it. Generating it
at startup keeps it from drifting from the stock tree, and the selector is
checked so the build fails if that tree stops defaulting to NavFn.

The store was also bare: no ceiling, no lights, nothing loose on the floor, and
a planning scene holding only the racks, so an arm could plan through a wall, a
checkout or the floor. The generator now writes the enclosure and lighting and
two stocking carts, and the planning scene gains a box per rack, wall, register
and cart plus a finite floor slab, each taken from the geom the simulator
collides with. The carts are physical but absent from both maps on purpose, so
the costmaps must see them through the lidars. Collision stays simple boxes.

Three Objective faults besides. Localize Robot relabelled the marker's frame
instead of transforming it, and asserted an input frame the UI need not use.
The in-place refinement seeded the filter without a variance, so it discarded
the estimate it was refining. The Rotation Shim restarted its heading turn on
every replan, making the base stutter.

The package also declared none of the Nav2 nodes its launch file starts,
resolving them only through a bringup package it never launches that drags a
simulator stack into the image. Four nodes caught the wrong exception on
SIGINT and could not shut down cleanly. The Objectives test raced the
controller spawners, and its drive budget predated the wider store.

The floor and walls are textured with layered PBR materials rather than a
procedural checker and a flat colour, so the rendered store reads as a shop.

Every asset the scene references is installed, and a test resolves each
reference against the installed tree rather than the source tree, where a
missing install rule looks fine.

The base's planar travel limits are widened to match the larger store, and
every aisle drive gets the same budget.
Collapses the 2026-11-03 work answering bkanator's review. Most of the review
was already satisfied by the earlier rework; these are the four comments that
still needed a change.

The lidar flattener described hangar_sim's scanner, not this one. Its docstring
and parameter comments named description/ur5e_ridgeback.xml and
params/laser_filter_params.yaml, neither of which exists in this package, and
carried TIM571 and <rangefinder> history plus a column-to-azimuth mapping
measured on an 811-beam sweep when these cameras are 1651-beam nanoScan3s. The
provenance line also claimed only the docstring differs from hangar_sim's copy,
which the added ExternalShutdownException guard contradicts. The comments now
point at mjcf/mobile_fr3_duo.xml's <camera> elements and the launch parameters,
and the claims that cannot be checked here are gone. The QoS comment no longer
cites a laser_filters chain this package does not have.

The package shipped the Apache licence text twice: LICENSE-mujoco_menagerie_franka_fr3
was byte-identical to LICENSE-franka_description. Keep the one named for the
file it actually is, franka_description's own licence, and let NOTICE name every
source it covers. NOTICE now also points at the store textures' own notice,
which are third-party under different terms.

Drive Base using Nav2 did not say who reads its tree path. bt_navigator does,
and the launch file that writes the Smac copy is the one that starts it, so the
file is always where the reader is. README said CMakeLists.txt wrote that copy
at build time; the launch file writes it at startup.

Two stale claims found while checking: AMCL's scan_topic comment called the two
relayed scans merged into one 360 deg scan, and NOTICE said the MJCF records its
widened planar travel in a comment it did not have.

AGENTS.md records that the other configs keep Nav2's default server timeout, so
this config's raise does not read as a workspace-wide setting.
@github-actions

github-actions Bot commented Nov 4, 2026

Copy link
Copy Markdown

MoveIt Pro Example WS - Objectives Integration Test Report

@github-actions

github-actions Bot commented Nov 4, 2026

Copy link
Copy Markdown

MoveIt Pro Example WS - Objectives Integration Test Report

@PickNikRobotics PickNikRobotics deleted a comment from github-actions Bot Nov 4, 2026
@PickNikRobotics PickNikRobotics deleted a comment from github-actions Bot Nov 4, 2026
@PickNikRobotics PickNikRobotics deleted a comment from github-actions Bot Nov 4, 2026
@PickNikRobotics PickNikRobotics deleted a comment from github-actions Bot Nov 4, 2026
@PickNikRobotics PickNikRobotics deleted a comment from github-actions Bot Nov 4, 2026
…franka_meshes

MuJoCo collides a mesh as its convex hull, so a link whose collision mesh is far
from convex collides as the filled-in shape. The model now collides already
convex parts instead, loaded by package:// from the newly vendored
franka_meshes, which is the same package and the same pinned commit dual_arm_sim
uses. The five source meshes and their part counts are franka_head (3),
fr3v2/link5 (6), fr3_duo_mount (4), fr3v2/link3 (8) and fr3v2/link4 (8); the
effective-volume figures are in that package's own README and are its
measurements, not re-measured here.

market_sim is the same Franka variant as dual_arm_sim, a two-arm Mobile FR3 Duo
on fr3v2 arms with the fr3_duo_mount_v0_3 mount and the franka_head_v0_2 head,
so all five source meshes exist here. link3, link4 and link5 appear once per arm
and the mount and head once each, which is eight collision geoms replaced by 51
parts.

Checked without a simulator, before against after: bodies 86 to 86, joints 37 to
37, actuators 20 to 20, sites 7 to 7, cameras 6 to 6, bodies with an explicit
inertial 42 to 42, and the sum of those masses 178.153827 kg unchanged. Mass
cannot move here because every affected body declares its own inertial and the
compiler sets no inertiafromgeom, so geom density is not consulted. Mesh assets
rise by 29 and colliding geoms by 43, which is the 51 parts less the 8 geoms they
replace. All 25 objective XML files and all 41 XML, xacro and urdf files in the
package still parse.

The parts never reach a URDF, because MoveIt collides the mesh itself, so
franka_description's shared robots/common/utils.xacro is untouched, as is
everything else under the franka_config snapshot. The visual geoms keep their
source mesh, their group and density="0".

The model has not yet been loaded through the real package resource path, which
needs a built workspace; that proof is outstanding.
The MJCF rendered franka_description's simplified collision STLs as its visual
geoms, so the MuJoCo view of the arms, spine, mount, cover, head and hands was
coarser than the robot MoveIt Pro draws. Each group-1 geom now carries the
matching visual mesh from the vendored franka_meshes instead, over package://,
which is the same source and the same wiring dual_arm_sim uses.

franka_meshes' snapshot is taken verbatim from the dual-arm branch so the shared
dependency is byte-identical in both lanes. That snapshot un-prunes the 13
visual meshes it had dropped, which also makes its own test true as written:
VISUAL_COUNT is 13 and 13 are now present, so the assertion was corrected by
restoring the files rather than by weakening the number. Its generator also
resolves franka_description in the vendored layout, not only the upstream one.

22 group-1 geoms are repointed across those 13 meshes: the spine, mount, cover
and head once each, and link0 to link7 and the hand once per arm. The finger
meshes and the five tmrv0_2 base meshes are left alone, because those are
already real visual meshes rather than collision stand-ins.

Collision is provably untouched, before against after: the collision fields of
all 82 colliding geoms, taken together as an ordered fingerprint of name, mesh,
type, contype, conaffinity, condim, friction, margin, solref, solimp, pos and
quat, are unchanged. So are bodies 86, joints 37, actuators 20, sites 7, cameras
6, bodies with an explicit inertial 42, and the sum of those masses at
178.153827 kg. Mesh assets rise by 13 and nothing else moves. All 25 objective
XML files and all 41 XML, xacro and urdf files still parse, and all 42
franka_meshes references resolve on disk.

The model has not yet been loaded through the real package resource path; that
proof is outstanding.
@github-actions

github-actions Bot commented Nov 4, 2026

Copy link
Copy Markdown

MoveIt Pro Example WS - Objectives Integration Test Report

This branch has not been deployed

No deployments
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.

3 participants