Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions genesis/utils/path_planning.py
Original file line number Diff line number Diff line change
Expand Up @@ -590,6 +590,8 @@ def plan(
self._entity.set_qpos(qpos_cur, envs_idx=envs_idx, zero_velocity=False)
else:
self._entity.set_qpos(qpos_cur, zero_velocity=False)
if is_plan_with_obj:
self.update_object(ee_link_idx, obj_link_idx, _pos, _quat, envs_idx)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Restore the original object pose for non-current starts

When callers pass a qpos_start that differs from the robot's current pose, get_exclude_geom_pairs(...) above has already moved the robot to qpos_start before _pos/_quat are captured, so this new restore applies a start-relative grasp transform after setting the robot back to qpos_cur. In that scenario a failed plan_path(with_entity=...) still teleports the attached object instead of preserving its original world pose; the same pattern was added in the RRTConnect failure branch, so the regression test only covers the qpos_start=None case.

Useful? React with 👍 / 👎.

sol = torch.zeros((num_waypoints, len(envs_idx), sol.shape[-1]), dtype=gs.tc_float, device=gs.device)
return sol, is_invalid

Expand Down Expand Up @@ -994,6 +996,8 @@ def plan(
self._entity.set_qpos(qpos_cur, envs_idx=envs_idx, zero_velocity=False)
else:
self._entity.set_qpos(qpos_cur, zero_velocity=False)
if is_plan_with_obj:
self.update_object(ee_link_idx, obj_link_idx, _pos, _quat, envs_idx)
return torch.zeros(num_waypoints, len(envs_idx), sol.shape[-1], device=gs.device), is_invalid

mask = rrt_connect_valid_mask(res_idx)
Expand Down
37 changes: 37 additions & 0 deletions tests/test_rigid_physics.py
Original file line number Diff line number Diff line change
Expand Up @@ -2992,6 +2992,43 @@ def test_path_planning_avoidance(backend, n_envs, show_viewer, tol):
assert_allclose(theta, 0.0, tol=5e-3)


@pytest.mark.required
@pytest.mark.parametrize("backend", [gs.cpu])
def test_plan_path_with_entity_preserves_state_on_failure(backend, tol):
# A failed plan_path(with_entity=...) must not move the attached object (#2715): the
# failure early-return restored the robot qpos but not the carried object. The goal
# below targets a pose under the floor, so the goal config is in collision and the
# single planning attempt fails deterministically regardless of the planner's RNG.
scene = gs.Scene(
sim_options=gs.options.SimOptions(dt=0.01),
rigid_options=gs.options.RigidOptions(box_box_detection=True),
show_viewer=False,
)
scene.add_entity(gs.morphs.Plane())
cube = scene.add_entity(gs.morphs.Box(size=(0.05, 0.05, 0.05), pos=(0.3, 0.1, 0.35)))
franka = scene.add_entity(gs.morphs.MJCF(file="xml/franka_emika_panda/panda.xml"))
scene.build()

hand = franka.get_link("hand")
qpos_goal = franka.inverse_kinematics(link=hand, pos=np.array([0.4, 0.0, -0.25]), quat=np.array([0, 1, 0, 0]))
qpos_goal[-2:] = 0.0

cube_pos = cube.get_pos().clone()
cube_quat = cube.get_quat().clone()
_, valid = franka.plan_path(
qpos_goal=qpos_goal,
num_waypoints=50,
max_retry=0,
max_nodes=100,
ee_link_name="hand",
with_entity=cube,
return_valid_mask=True,
)
assert not bool(valid) # the scenario under test is a failed plan
assert_allclose(cube.get_pos(), cube_pos, tol=tol)
assert_allclose(cube.get_quat(), cube_quat, tol=tol)


@pytest.mark.required
def test_all_fixed(show_viewer):
scene = gs.Scene(
Expand Down