Skip to content

Split object provenance from rooted transforms - #1112

Open
qianl-nv wants to merge 1 commit into
mainfrom
qianl/refactor/split-object-behavior-axes
Open

Split object provenance from rooted transforms#1112
qianl-nv wants to merge 1 commit into
mainfrom
qianl/refactor/split-object-behavior-axes

Conversation

@qianl-nv

Copy link
Copy Markdown
Collaborator

Summary

Separate object provenance and transform behavior.

Detailed description

  • Compose Object and ObjectReference from provenance-specific behavior and shared RootedTransform.
  • Centralize rooted configuration, pose, reset, and contact-sensor handling.
  • Support contact sensors for spawner-backed objects without source USDs.
  • Breaking: remove ObjectBase.set_prim_path() and require direct subclasses to provide transform behavior.

Separate spawned and referenced prim behavior from root-transform state handling
while preserving the existing Object and ObjectReference API names.

BREAKING CHANGE:
- Direct ObjectBase subclasses must implement get_object_pose
and set_object_pose, or compose RootedTransform.
- ObjectBase.set_prim_path() has been removed. Prim paths must
now be supplied during object construction and remain immutable afterward.

Signed-off-by: Qian Lin <qianl@nvidia.com>
@greptile-apps

greptile-apps Bot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR separates object provenance from shared rooted-transform behavior while preserving the scene-object configuration lifecycle.

  • Introduces SpawnPrim, ReferencedPrim, and RootedTransform mixins.
  • Centralizes pose, reset-event, velocity, configuration, and contact-sensor behavior.
  • Adds root-path contact-sensor support for spawner-backed objects without source USD files.
  • Makes transform behavior an explicit responsibility of concrete ObjectBase subclasses.

Confidence Score: 5/5

The PR appears safe to merge, with no concrete changed-code failures identified.

The new mixin composition retains concrete configuration initialization and existing parent-relative reference behavior while adding the intended spawner-backed contact-sensor path.

Important Files Changed

Filename Overview
isaaclab_arena/assets/object.py Splits spawned-prim provenance from shared rooted-transform configuration, pose, reset, and contact-sensor behavior.
isaaclab_arena/assets/object_base.py Narrows the abstract base to immutable identity, configuration access, and required transform/contact interfaces.
isaaclab_arena/assets/object_reference.py Composes referenced-prim provenance with rooted-transform behavior while retaining parent-relative geometry and configuration initialization.
isaaclab_arena/tests/test_object_configuration.py Adds coverage confirming root contact-sensor paths for spawner-backed rigid objects.

Class Diagram

%%{init: {'theme': 'neutral'}}%%
classDiagram
  class ObjectBase {
    <<abstract>>
    +prim_path
    +object_type
    +get_object_pose()
    +set_object_pose()
  }
  class RootedTransform {
    +set_initial_velocity()
    +get_contact_sensor_cfg()
    +get_object_pose()
    +set_object_pose()
    -_build_reset_event()
    -_init_object_cfg()
  }
  class SpawnPrim {
    +usd_path
    +spawner_cfg
    -_get_cfg_source_kwargs()
    -_get_contact_sensor_prim_path()
  }
  class ReferencedPrim {
    +parent_asset
    -_get_cfg_source_kwargs()
    -_resolve_prim_path_in_parent_usd()
  }
  class Object
  class ObjectReference
  ObjectBase <|-- Object
  RootedTransform <|-- Object
  SpawnPrim <|-- Object
  ObjectBase <|-- ObjectReference
  RootedTransform <|-- ObjectReference
  ReferencedPrim <|-- ObjectReference
Loading

Reviews (1): Last reviewed commit: "Split object provenance from rooted tran..." | Re-trigger Greptile


def set_prim_path(self, prim_path: str) -> None:
self.prim_path = prim_path
def resolve_object_cfg(self, physics_preset: object | None = None):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 resolve_object_cfg ignores its physics_preset argument

This new method accepts physics_preset but never reads it, and I couldn't find any caller — the scene builder still pulls configs via get_object_cfg(). Is it needed for this PR, or could it wait for the MR that actually consumes a preset? A parameter that's silently dropped is easy to mis-call, and it looks a bit outside the "split provenance from transform" scope.


def _resolve_prim_path_in_parent_usd(self, parent_stage: Usd.Stage) -> str:
"""Return the cached referenced path, resolving it for partially initialized test objects."""
return getattr(self, "_prim_path_in_parent_usd", None) or self.isaaclab_prim_path_to_original_prim_path(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 getattr fallback for a constructor-set attribute

_init_referenced_prim always sets _prim_path_in_parent_usd, so for a fully built reference this is just self._prim_path_in_parent_usd. The getattr(..., None) default and the recompute branch exist only for the "partially initialized test objects" the docstring mentions. Could those tests construct the object fully (or __init__ seed the attribute) so production code doesn't carry the fallback?

@arena-review-bot

Copy link
Copy Markdown
Contributor

🤖 Isaac Lab-Arena Review Bot

Summary

Clean refactor that splits object provenance (SpawnPrim / ReferencedPrim) from the shared RootedTransform behavior and folds the duplicated _generate_*_cfg / pose-reset logic into one place. I traced the MRO, the positional set_object_pose(env, env_ids, asset_cfg, pose) delegation (matches the term signature), and the subclasses (RigidObjectSet, LibraryObject, ProceduralTable/Cube, the lights) — they all inherit through Object, so the moved APIs stay available, and the removed set_prim_path has no real callers. The new contact-sensor-for-spawner-backed-objects path is covered by a test. Two small, non-blocking questions below.

Findings

🟡 Warning: object_base.py:48resolve_object_cfg(physics_preset=...) accepts a preset it never reads and has no callers; looks speculative / out of scope for this PR. Remove it or wait for the MR that consumes a preset.
🔵 Improvement: object_reference.py:59getattr(self, "_prim_path_in_parent_usd", None) falls back for "partially initialized test objects," but the constructor always sets that attribute; prefer plain access and construct the test objects fully.

Test Coverage

The added Phase-1 case (spawner-backed source/destination, get_contact_sensor_cfg) exercises the new no-USD contact-sensor path and uses the inner/outer pattern with deferred imports — good. No regression test is needed for the set_prim_path removal (no callers).

Verdict

Minor fixes needed

@qianl-nv qianl-nv left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

self review 1


def set_prim_path(self, prim_path: str) -> None:
self.prim_path = prim_path
def resolve_object_cfg(self, physics_preset: object | None = None):

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

this is a new API introduced in this MR? remove it

"""Return the root prim used for contact sensing by default."""
return self.prim_path

def get_object_cfg(self) -> tuple[str, object]:

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

should we still use the explicit type RigidObjectCfg | ArticulationCfg | AssetBaseCfg here?

self, contact_against_object: ObjectBase | None = None, usd_path: str | None = None
) -> ContactSensorCfg:
assert self.object_type == ObjectType.RIGID, "Contact sensor is only supported for rigid objects"
# We override this function from the parent class because in some assets, the rigid body

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

check if comment still applies

rigid_body_relative_path is not None
), f"No rigid body found in {self.name} USD file: {usd_path}. Can't add contact sensor."
contact_sensor_prim_path = self.prim_path + rigid_body_relative_path
# There are also cases where the contact against object does not have its rigid body at the root.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

check if comment still applies

return extract_trimesh_from_prim(parent_stage, prim_path_in_usd, self._parent_scale)

def get_contact_sensor_cfg(self, contact_against_object: ObjectBase | None = None) -> ContactSensorCfg:
# NOTE(alexmillane): Right now this requires that the object

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

check if comment still applies

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.

1 participant