diff --git a/embodichain/gen_sim/action_agent_pipeline/defaults.py b/embodichain/gen_sim/action_agent_pipeline/defaults.py index cce6e074..8dacb828 100644 --- a/embodichain/gen_sim/action_agent_pipeline/defaults.py +++ b/embodichain/gen_sim/action_agent_pipeline/defaults.py @@ -23,6 +23,7 @@ __all__ = [ "ACTION_AGENT_CONFIG_DEFAULTS", + "CONVEX_HULL_DEFAULTS", "DEFAULT_MAX_EPISODES", "DEFAULT_MAX_EPISODE_STEPS", "DEFAULT_SURFACE_RELEASE_CLEARANCE", @@ -58,8 +59,33 @@ def generation_defaults_section(name: str) -> dict[str, Any]: return section +def _load_convex_hull_defaults() -> dict[str, Any]: + physics = generation_defaults_section("physics") + convex_hulls = physics.get("convex_hulls") + if not isinstance(convex_hulls, dict): + raise ValueError("Generation default physics.convex_hulls must be a mapping.") + value_paths = ( + ("target",), + ("container",), + ("moved",), + ("extra_rigid",), + ("table",), + ) + for value_path in value_paths: + value: Any = convex_hulls + for key in value_path: + value = value.get(key) if isinstance(value, dict) else None + if isinstance(value, bool) or not isinstance(value, int) or value < 1: + dotted_path = ".".join(("physics", "convex_hulls", *value_path)) + raise ValueError( + f"Generation default {dotted_path} must be a positive integer." + ) + return convex_hulls + + _TASK_DEFAULTS = generation_defaults_section("task") _GEOMETRY_DEFAULTS = generation_defaults_section("geometry") +CONVEX_HULL_DEFAULTS = _load_convex_hull_defaults() DEFAULT_TASK_NAME = str(_TASK_DEFAULTS["default_name"]) DEFAULT_MAX_EPISODES = int(_TASK_DEFAULTS["max_episodes"]) diff --git a/embodichain/gen_sim/action_agent_pipeline/generation/action_agent_config.py b/embodichain/gen_sim/action_agent_pipeline/generation/action_agent_config.py index d8d6971d..48f55d2c 100644 --- a/embodichain/gen_sim/action_agent_pipeline/generation/action_agent_config.py +++ b/embodichain/gen_sim/action_agent_pipeline/generation/action_agent_config.py @@ -24,6 +24,7 @@ import warnings from embodichain.gen_sim.action_agent_pipeline.defaults import ( + CONVEX_HULL_DEFAULTS, DEFAULT_MAX_EPISODES, DEFAULT_MAX_EPISODE_STEPS, DEFAULT_SURFACE_RELEASE_CLEARANCE, @@ -843,7 +844,7 @@ def _build_arrangement_line_bundle( max_convex_hull_num=( _moved_rigid_object_max_convex_hull_num(obj) if obj.source_uid in moved_source_uids - else 1 + else CONVEX_HULL_DEFAULTS["extra_rigid"] ), mesh_normalizer=mesh_normalizer, ) @@ -1074,7 +1075,7 @@ def _build_stacking_bundle( else ( _container_rigid_object_max_convex_hull_num(obj) if obj.source_uid == spec.anchor_source_uid - else 1 + else CONVEX_HULL_DEFAULTS["extra_rigid"] ) ), mesh_normalizer=mesh_normalizer, diff --git a/embodichain/gen_sim/action_agent_pipeline/generation/action_agent_config_defaults.yaml b/embodichain/gen_sim/action_agent_pipeline/generation/action_agent_config_defaults.yaml index f251a069..19c951b3 100644 --- a/embodichain/gen_sim/action_agent_pipeline/generation/action_agent_config_defaults.yaml +++ b/embodichain/gen_sim/action_agent_pipeline/generation/action_agent_config_defaults.yaml @@ -95,7 +95,6 @@ physics: static_friction: 0.95 dynamic_friction: 0.9 restitution: 0.01 - max_convex_hull_num: 1 rigid_object: mass: 0.2 static_friction: 0.95 @@ -115,6 +114,7 @@ physics: container: 24 moved: 16 extra_rigid: 1 + table: 1 grasp: affordance_stabilization_steps: 10 diff --git a/embodichain/gen_sim/action_agent_pipeline/generation/config_blocks.py b/embodichain/gen_sim/action_agent_pipeline/generation/config_blocks.py index 60b8280a..9de55e1d 100644 --- a/embodichain/gen_sim/action_agent_pipeline/generation/config_blocks.py +++ b/embodichain/gen_sim/action_agent_pipeline/generation/config_blocks.py @@ -21,6 +21,7 @@ from typing import Any from embodichain.gen_sim.action_agent_pipeline.defaults import ( + CONVEX_HULL_DEFAULTS, DEFAULT_TASK_NAME, generation_defaults_section, ) @@ -81,22 +82,12 @@ _VISUAL_MATERIAL_DEFAULTS = generation_defaults_section("visual_material") _BACKGROUND_DEFAULTS = _PHYSICS_DEFAULTS["background"] _RIGID_OBJECT_DEFAULTS = _PHYSICS_DEFAULTS["rigid_object"] -_CONVEX_HULL_DEFAULTS = _PHYSICS_DEFAULTS["convex_hulls"] _TABLE_VISUAL_MATERIAL_DEFAULTS = _VISUAL_MATERIAL_DEFAULTS["table"] -_BACKGROUND_MAX_CONVEX_HULL_NUM = int(_BACKGROUND_DEFAULTS["max_convex_hull_num"]) -_TARGET_MAX_CONVEX_HULL_NUM = int(_CONVEX_HULL_DEFAULTS["target"]) -_CONTAINER_MAX_CONVEX_HULL_NUM = int(_CONVEX_HULL_DEFAULTS["container"]) -_MOVED_MAX_CONVEX_HULL_NUM = int(_CONVEX_HULL_DEFAULTS["moved"]) -_EXTRA_RIGID_MAX_CONVEX_HULL_NUM = int(_CONVEX_HULL_DEFAULTS["extra_rigid"]) _ROBOT_VIEW_LABEL = "robot_view" _AUDIENCE_VIEW_LABEL = "audience_view" _AUDIENCE_VIEW_Z_ROTATION_DEGREES = 180.0 -_BACKGROUND_ATTRS = { - key: float(value) - for key, value in _BACKGROUND_DEFAULTS.items() - if key != "max_convex_hull_num" -} +_BACKGROUND_ATTRS = {key: float(value) for key, value in _BACKGROUND_DEFAULTS.items()} _RIGID_OBJECT_ATTRS = { key: ( @@ -705,10 +696,7 @@ def _make_background_config( "body_type": "kinematic", "init_pos": _clean_vector3(obj.config.get("init_pos", [0.0, 0.0, 0.0])), "init_rot": _clean_vector3(obj.config.get("init_rot", [0.0, 0.0, 0.0])), - "max_convex_hull_num": _role_limited_max_convex_hull_num( - obj, - _BACKGROUND_MAX_CONVEX_HULL_NUM, - ), + "max_convex_hull_num": CONVEX_HULL_DEFAULTS["table"], } @@ -732,10 +720,7 @@ def _make_extra_background_config( "body_type": str(obj.config.get("body_type", "static")), "init_pos": _clean_vector3(obj.config.get("init_pos", [0.0, 0.0, 0.0])), "init_rot": _clean_vector3(obj.config.get("init_rot", [0.0, 0.0, 0.0])), - "max_convex_hull_num": _role_limited_max_convex_hull_num( - obj, - _BACKGROUND_MAX_CONVEX_HULL_NUM, - ), + "max_convex_hull_num": CONVEX_HULL_DEFAULTS["table"], } return config @@ -753,7 +738,7 @@ def _make_target_object_config( obj, runtime_uid, target_scale, - max_convex_hull_num=_TARGET_MAX_CONVEX_HULL_NUM, + max_convex_hull_num=CONVEX_HULL_DEFAULTS["target"], mesh_fpath=replacement.mesh_path if replacement else None, mesh_normalizer=mesh_normalizer, ) @@ -773,10 +758,7 @@ def _make_container_object_config( obj, runtime_uid, body_scale, - max_convex_hull_num=_role_limited_max_convex_hull_num( - obj, - _CONTAINER_MAX_CONVEX_HULL_NUM, - ), + max_convex_hull_num=CONVEX_HULL_DEFAULTS["container"], mesh_normalizer=mesh_normalizer, ) @@ -850,10 +832,7 @@ def _make_extra_rigid_object_config( obj, runtime_uid or _normalize_runtime_uid(obj.source_uid), body_scale, - max_convex_hull_num=_role_limited_max_convex_hull_num( - obj, - _EXTRA_RIGID_MAX_CONVEX_HULL_NUM, - ), + max_convex_hull_num=CONVEX_HULL_DEFAULTS["extra_rigid"], mesh_normalizer=mesh_normalizer, ) config["body_type"] = "dynamic" @@ -869,19 +848,12 @@ def _make_relative_rigid_object_config( max_convex_hull_num: int, mesh_normalizer: GlbGeometryNormalizer, ) -> dict[str, Any]: - if max_convex_hull_num == _TARGET_MAX_CONVEX_HULL_NUM: - resolved_max_convex_hull_num = max_convex_hull_num - else: - resolved_max_convex_hull_num = _role_limited_max_convex_hull_num( - obj, - max_convex_hull_num, - ) config = _make_rigid_object_config( scene_dir, obj, runtime_uid, body_scale, - max_convex_hull_num=resolved_max_convex_hull_num, + max_convex_hull_num=max_convex_hull_num, mesh_normalizer=mesh_normalizer, ) config["body_type"] = "dynamic" @@ -919,24 +891,14 @@ def _make_rigid_object_config( return config -def _role_limited_max_convex_hull_num( - obj: _SceneObject, - role_max_convex_hull_num: int, -) -> int: - source_max_convex_hull_num = obj.config.get("max_convex_hull_num") - if source_max_convex_hull_num is None: - return role_max_convex_hull_num - return max(1, min(int(source_max_convex_hull_num), role_max_convex_hull_num)) - - -def _moved_rigid_object_max_convex_hull_num(obj: _SceneObject) -> int: +def _moved_rigid_object_max_convex_hull_num(_obj: _SceneObject) -> int: """Return the configured convex-decomposition limit for a moved object.""" - return _role_limited_max_convex_hull_num(obj, _MOVED_MAX_CONVEX_HULL_NUM) + return CONVEX_HULL_DEFAULTS["moved"] -def _container_rigid_object_max_convex_hull_num(obj: _SceneObject) -> int: +def _container_rigid_object_max_convex_hull_num(_obj: _SceneObject) -> int: """Return the configured convex-decomposition limit for a container.""" - return _role_limited_max_convex_hull_num(obj, _CONTAINER_MAX_CONVEX_HULL_NUM) + return CONVEX_HULL_DEFAULTS["container"] def _relative_rigid_object_max_convex_hull_num( @@ -948,15 +910,14 @@ def _relative_rigid_object_max_convex_hull_num( placement.relation == "inside" and runtime_uid == placement.reference_runtime_uid ): - return _CONTAINER_MAX_CONVEX_HULL_NUM - task_uids = { - uid - for placement in spec.placements - for uid in (placement.moved_runtime_uid, placement.reference_runtime_uid) - } - if runtime_uid in task_uids: - return _TARGET_MAX_CONVEX_HULL_NUM - return _EXTRA_RIGID_MAX_CONVEX_HULL_NUM + return CONVEX_HULL_DEFAULTS["container"] + if any(runtime_uid == placement.moved_runtime_uid for placement in spec.placements): + return CONVEX_HULL_DEFAULTS["moved"] + if any( + runtime_uid == placement.reference_runtime_uid for placement in spec.placements + ): + return CONVEX_HULL_DEFAULTS["target"] + return CONVEX_HULL_DEFAULTS["extra_rigid"] def _relative_static_background_max_convex_hull_num( @@ -968,8 +929,8 @@ def _relative_static_background_max_convex_hull_num( placement.relation == "inside" and runtime_uid == placement.reference_runtime_uid ): - return _CONTAINER_MAX_CONVEX_HULL_NUM - return _BACKGROUND_MAX_CONVEX_HULL_NUM + return CONVEX_HULL_DEFAULTS["container"] + return CONVEX_HULL_DEFAULTS["table"] def _make_shape_config( diff --git a/tests/gen_sim/action_agent_pipeline/test_generation_defaults.py b/tests/gen_sim/action_agent_pipeline/test_generation_defaults.py index 7a7e0686..cf22f99d 100644 --- a/tests/gen_sim/action_agent_pipeline/test_generation_defaults.py +++ b/tests/gen_sim/action_agent_pipeline/test_generation_defaults.py @@ -17,11 +17,13 @@ from __future__ import annotations from pathlib import Path +from types import SimpleNamespace import pytest from embodichain.gen_sim.action_agent_pipeline.defaults import ( ACTION_AGENT_CONFIG_DEFAULTS, + CONVEX_HULL_DEFAULTS, DEFAULT_MAX_EPISODES, DEFAULT_MAX_EPISODE_STEPS, DEFAULT_SURFACE_RELEASE_CLEARANCE, @@ -30,8 +32,12 @@ generation_defaults_section, ) from embodichain.gen_sim.action_agent_pipeline.generation.config_blocks import ( + _container_rigid_object_max_convex_hull_num, + _make_background_config, + _make_extra_rigid_object_config, _make_target_object_config, _moved_rigid_object_max_convex_hull_num, + _relative_rigid_object_max_convex_hull_num, ) from embodichain.gen_sim.action_agent_pipeline.generation.config_types import ( _SceneObject, @@ -79,7 +85,14 @@ def test_generation_defaults_expose_required_hyperparameter_sections() -> None: } assert expected_sections <= ACTION_AGENT_CONFIG_DEFAULTS.keys() - assert "moved" in ACTION_AGENT_CONFIG_DEFAULTS["physics"]["convex_hulls"] + convex_hulls = ACTION_AGENT_CONFIG_DEFAULTS["physics"]["convex_hulls"] + assert { + "target", + "container", + "moved", + "extra_rigid", + "table", + } == set(convex_hulls) def test_affordance_stabilization_steps_is_a_non_negative_integer() -> None: @@ -111,6 +124,7 @@ def test_generated_rigid_object_uses_complete_physics_defaults( assert { key: config["attrs"][key] for key in _EXPECTED_RIGID_OBJECT_PHYSICS } == _EXPECTED_RIGID_OBJECT_PHYSICS + assert config["max_convex_hull_num"] == CONVEX_HULL_DEFAULTS["target"] def test_generation_defaults_section_rejects_unknown_section() -> None: @@ -118,14 +132,80 @@ def test_generation_defaults_section_rejects_unknown_section() -> None: generation_defaults_section("missing") -def test_moved_convex_hull_limit_is_loaded_from_defaults() -> None: - moved_default = ACTION_AGENT_CONFIG_DEFAULTS["physics"]["convex_hulls"]["moved"] +def test_convex_hull_limits_are_loaded_from_yaml() -> None: + convex_hulls = ACTION_AGENT_CONFIG_DEFAULTS["physics"]["convex_hulls"] + + assert CONVEX_HULL_DEFAULTS["target"] == convex_hulls["target"] + assert CONVEX_HULL_DEFAULTS["container"] == convex_hulls["container"] + assert CONVEX_HULL_DEFAULTS["moved"] == convex_hulls["moved"] + assert CONVEX_HULL_DEFAULTS["extra_rigid"] == convex_hulls["extra_rigid"] + assert CONVEX_HULL_DEFAULTS["table"] == convex_hulls["table"] + + +def test_scene_role_limits_override_source_convex_hull_limit(tmp_path: Path) -> None: moved_object = _SceneObject("cube", "rigid_object", {}) source_limited_object = _SceneObject( "cup", "rigid_object", {"max_convex_hull_num": 4}, ) + table = _SceneObject( + "table", + "background", + {"shape": {"shape_type": "Box"}, "max_convex_hull_num": 4}, + ) + extra = _SceneObject( + "spoon", + "rigid_object", + {"shape": {"shape_type": "Box"}, "max_convex_hull_num": 4}, + ) + normalizer = GlbGeometryNormalizer(output_dir=tmp_path / "normalized") + table_config = _make_background_config(tmp_path, table, normalizer) + extra_config = _make_extra_rigid_object_config( + tmp_path, + extra, + [1.0, 1.0, 1.0], + normalizer, + ) - assert _moved_rigid_object_max_convex_hull_num(moved_object) == moved_default - assert _moved_rigid_object_max_convex_hull_num(source_limited_object) == 4 + assert _moved_rigid_object_max_convex_hull_num(moved_object) == ( + CONVEX_HULL_DEFAULTS["moved"] + ) + assert _moved_rigid_object_max_convex_hull_num(source_limited_object) == ( + CONVEX_HULL_DEFAULTS["moved"] + ) + assert _container_rigid_object_max_convex_hull_num(source_limited_object) == ( + CONVEX_HULL_DEFAULTS["container"] + ) + assert table_config["max_convex_hull_num"] == CONVEX_HULL_DEFAULTS["table"] + assert extra_config["max_convex_hull_num"] == CONVEX_HULL_DEFAULTS["extra_rigid"] + + +def test_relative_object_roles_use_distinct_yaml_limits() -> None: + spec = SimpleNamespace( + placements=( + SimpleNamespace( + relation="inside", + moved_runtime_uid="apple", + reference_runtime_uid="basket", + ), + SimpleNamespace( + relation="on", + moved_runtime_uid="cup", + reference_runtime_uid="pad", + ), + ) + ) + + assert _relative_rigid_object_max_convex_hull_num("basket", spec) == ( + CONVEX_HULL_DEFAULTS["container"] + ) + assert _relative_rigid_object_max_convex_hull_num("apple", spec) == ( + CONVEX_HULL_DEFAULTS["moved"] + ) + assert _relative_rigid_object_max_convex_hull_num("pad", spec) == ( + CONVEX_HULL_DEFAULTS["target"] + ) + assert _relative_rigid_object_max_convex_hull_num("spoon", spec) == ( + CONVEX_HULL_DEFAULTS["extra_rigid"] + ) diff --git a/tests/gen_sim/action_agent_pipeline/test_ur5_basket_config_generation.py b/tests/gen_sim/action_agent_pipeline/test_ur5_basket_config_generation.py index b910d673..8bd7b46c 100644 --- a/tests/gen_sim/action_agent_pipeline/test_ur5_basket_config_generation.py +++ b/tests/gen_sim/action_agent_pipeline/test_ur5_basket_config_generation.py @@ -26,6 +26,8 @@ import struct import pytest + +from embodichain.gen_sim.action_agent_pipeline.defaults import CONVEX_HULL_DEFAULTS import torch from embodichain.lab.sim.cfg import RobotCfg @@ -442,6 +444,18 @@ def test_action_agent_config_generator_uses_parallel_handoff( assert rigid_objects["right_apple"]["body_type"] == "dynamic" assert rigid_objects["wicker_basket"]["body_type"] == "dynamic" assert background_objects["table"]["body_scale"] == [1.0, 1.0, 1.0] + assert rigid_objects["left_apple"]["max_convex_hull_num"] == ( + CONVEX_HULL_DEFAULTS["target"] + ) + assert rigid_objects["right_apple"]["max_convex_hull_num"] == ( + CONVEX_HULL_DEFAULTS["target"] + ) + assert rigid_objects["wicker_basket"]["max_convex_hull_num"] == ( + CONVEX_HULL_DEFAULTS["container"] + ) + assert background_objects["table"]["max_convex_hull_num"] == ( + CONVEX_HULL_DEFAULTS["table"] + ) for obj in rigid_objects.values(): assert obj["acd_method"] == "vhacd" assert obj["shape"]["acd_method"] == "vhacd" @@ -4454,7 +4468,9 @@ def fake_call_stacking_task_llm(**kwargs): ] assert all(target["offset"][:2] == [0.0, 0.0] for target in place_targets) rigid_by_uid = {obj["uid"]: obj for obj in gym_config["rigid_object"]} - assert rigid_by_uid["red_cube"]["max_convex_hull_num"] == 32 + assert rigid_by_uid["red_cube"]["max_convex_hull_num"] == ( + CONVEX_HULL_DEFAULTS["container"] + ) assert { (term["object"], term["support"]) for term in gym_config["env"]["extensions"]["agent_success"]["terms"]