Skip to content

Add clutter placement - #1242

Open
zhx06 wants to merge 5 commits into
mainfrom
zxiao/feature/clutter-placement
Open

Add clutter placement#1242
zhx06 wants to merge 5 commits into
mainfrom
zxiao/feature/clutter-placement

Conversation

@zhx06

@zhx06 zhx06 commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator

Summary

Add offline physics-settled clutter placement

Detailed description

  • Add a standalone example to settle ordinary Arena assets, preserving base rotations and generating independent layouts.
  • Reject invalid layouts and save one exact-pose scene YAML per generated environment.
  • Restore cached object and robot poses on reset without changing the relation solver.
  • Validate output filesystem support before settling and publish caches without overwriting existing files.
  • Validation: focused PhysX, graph, and CLI tests; pre-commit --all-files. Newton and CAP/Berkeley reproduction remain unverified.

Signed-off-by: zhx06 <zihaox@nvidia.com>
Signed-off-by: zhx06 <zihaox@nvidia.com>
@zhx06
zhx06 force-pushed the zxiao/feature/clutter-placement branch from 2155f20 to f25f5e4 Compare September 10, 2026 21:43
@zhx06
zhx06 marked this pull request as ready for review September 10, 2026 21:44
@greptile-apps

greptile-apps Bot commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 4/5

The PR is not yet safe to merge because environments without relation solving no longer reset embodiment roots to their declared initial poses.

Findings

  1. P1 Embodiment Pose Stops Resetting
  2. P2 Cache Requires Hard Links

Summary

  • Adds drop-pose sampling, settling, containment validation, and atomic per-file cache publication.
  • Extends graph conversion to support explicit initial poses and retain graph-node-to-asset mappings.
  • Adds a CLI, example scene, documentation, and broad simulation and unit-test coverage.
  • The embodiment initial-pose integration drops its existing reset behavior, and cache publication assumes hard-link support.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  Spec[Concrete scene YAML] --> Build[Build Arena environment]
  Build --> Sample[Sample noninterpenetrating drop poses]
  Sample --> Sim[Run physics settling]
  Sim --> Validate{Rest and containment valid?}
  Validate -->|No| Retry[Retry with deterministic attempt seed]
  Retry --> Sample
  Validate -->|Yes| Cache[Export exact poses to scene YAML]
  Cache --> Runtime[Load ordinary runtime environment]
  Runtime --> Reset[Replay cached asset reset poses]
Loading

Reviews (1) · Last reviewed commit: "keep offline"

Comment thread isaaclab_arena/environment_spec/arena_env_graph_conversion_utils.py Outdated
Comment thread isaaclab_arena_environments/isaac_cap/clutter/cache.py
Signed-off-by: zhx06 <zihaox@nvidia.com>
@zhx06 zhx06 changed the title Add support for cluttered scene Add offline physics-settled clutter placement Sep 10, 2026
Signed-off-by: zhx06 <zihaox@nvidia.com>
@zhx06 zhx06 changed the title Add offline physics-settled clutter placement Add clutter placement Sep 10, 2026
Signed-off-by: zhx06 <zihaox@nvidia.com>

@alexmillane alexmillane left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This is a good start.

However I have a few opinions on the design:

  • Output: Right now the system outputs a single environment.yaml per placement. Generating lots of graphs in the case that we want many placements. I would suggest instead that we introduce a new type of yaml file that is an optional companion to our usual environment files. If such a file is passed to the run-time (or specified in the environment.yaml) poses are taken from this companion file. This file would be a mapping between object names and a list of poses-per-object. With this design we can get 1000s of layouts with just two files. It also separates specification of the environment from the specification of the placement. Wdyt?
  • Settling specification. Right now we specify which objects are to be settled via command line parameters. Which means that they're not captured in the environment.yaml. What do you think about instead adding a new relation-type called ClutterOn which is specified in the environment.yaml and captures that this object is the target of the clutter placement. This way the placement of the object travels with the environment spec, rather than being captured on a users CLI (and then immediately lost).
  • Before drop initialization: Right now we use a parallel system to our ObjectPlacer to arrange the object prior to dropping them. Could we not implement this inside the object placer. ClutterOn could translate to an object being above a support surface, as low as possible, without colliding with other (ClutterOn) objects. This would leave to the objects forming a vertical collumn prior to the settling. It also would mean that ClutterOn would produce valid results even without running through generate_clutter_scene.py. They would just drop at the start of simulation.

"""Apply a fixed YAML pose at construction and on every reset."""
if value is None:
return
assert isinstance(value, dict), "initial_pose must be a mapping"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Can we correct the type annotation above. At the input we say Any but then check that it's None | dict

Comment on lines +185 to +205
def _apply_initial_pose(asset: Asset, value: Any) -> None:
"""Apply a fixed YAML pose at construction and on every reset."""
if value is None:
return
assert isinstance(value, dict), "initial_pose must be a mapping"
assert not (set(value) - {"position_xyz", "rotation_xyzw"}), "Unknown initial_pose fields"
if "position_xyz" not in value or "rotation_xyzw" not in value:
declared = asset.get_initial_pose()
assert declared is None or isinstance(declared, Pose), "Partial initial_pose requires a fixed default pose"
default = declared if declared is not None else Pose.identity()
position = value.get("position_xyz", default.position_xyz)
rotation = value.get("rotation_xyzw", default.rotation_xyzw)
else:
position, rotation = value["position_xyz"], value["rotation_xyzw"]
for name, values, size in (("position_xyz", position, 3), ("rotation_xyzw", rotation, 4)):
assert isinstance(values, (list, tuple)) and len(values) == size, f"{name} needs {size} numbers"
assert all(
isinstance(v, Real) and not isinstance(v, bool) and math.isfinite(v) for v in values
), f"{name} must contain finite numbers"
assert math.isclose(sum(v * v for v in rotation), 1.0, abs_tol=1e-4), "rotation_xyzw must be a unit quaternion"
asset.set_initial_pose(Pose(tuple(float(v) for v in position), tuple(float(v) for v in rotation)))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

99% of this function is extracting a Pose from a dict.

Then we set it in half the final line.

Suggestion to break this function up. The first function should be

def _get_pose_from_dict(pose_dict: dict) -> Pose | None:

This

def _apply_initial_pose(pose: Pose | None):



def scene_with_cached_poses(
spec: ArenaEnvGraphSpec, poses: Mapping[str, Pose], assets_by_node_id: Mapping[str, Asset]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I'm interested why the agent wants to use Mapping. Do we expect something other than a dict here?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This is a test/example file correct?

I would suggest that this goes into isaaclab_arena_environments or isaaclab_arena_examples.

Generally we try to keep examples out of the core framework.

Comment on lines +74 to +81
def write_scene_cache(spec: ArenaEnvGraphSpec, path: Path) -> None:
"""Publish a complete YAML file atomically without overwriting; requires hard-link support."""
path.parent.mkdir(parents=True, exist_ok=True)
with tempfile.TemporaryDirectory(dir=path.parent, prefix=".clutter-") as directory:
temporary = Path(directory) / path.name
spec.write_yaml(temporary)
# A same-filesystem hard link publishes the complete file without overwriting a raced writer.
os.link(temporary, path)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I'm wondering why we have to go through all this effort to write a file? What's wrong with just writing a file normally?

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