Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
6 changes: 5 additions & 1 deletion embodichain/lab/sim/shapes.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,11 @@ class MeshCollisionCfg:
"""Maximum hull count for ``convex_decomposition``; must be at least two."""

acd_method: Literal["visacd", "coacd", "vhacd"] | None = None
"""Approximate-convex-decomposition implementation."""
"""Approximate-convex-decomposition implementation.

``None`` selects ``visacd`` when compiling mesh collision geometry.
VisACD requires a DexSim build with CUDA/OptiX support.
"""

sdf_resolution: int | None = None
"""Maximum SDF grid resolution; valid only for the ``sdf`` strategy."""
Expand Down
16 changes: 11 additions & 5 deletions embodichain/lab/sim/spawn/descriptors.py
Original file line number Diff line number Diff line change
Expand Up @@ -302,14 +302,18 @@ def rigid_desc_from_cfg(
newton_solver_type=newton_solver_type,
)
)
geometry, approximation, max_hulls = _compile_geometry(cfg)
geometry, approximation, max_hulls, acd_method = _compile_geometry(cfg)
material_ref, material_entry = _compile_visual_material(
uid, cfg.shape.visual_material
)
collision = CollisionDesc.from_geometry(
geometry,
approximation=approximation,
)
if approximation == CollisionApproximation.CONVEX_DECOMPOSITION:
# Construct the declared field rather than attaching a dynamic attribute
# that older DexSim versions would silently ignore during cooking.
collision = replace(collision, decomp_algorithm=acd_method)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Dependency permits incompatible descriptor

If an environment resolves an older dexsim_engine==0.5.0 package without the declared CollisionDesc.decomp_algorithm field, compiling any convex-decomposition mesh now passes that unsupported field to dataclasses.replace. This raises TypeError instead of producing a Spawn descriptor. Publish the companion engine capability under a distinct version and update the dependency constraint so supported installations cannot resolve the incompatible API.

Prompt To Fix With AI
This is a comment left during a code review.
Path: embodichain/lab/sim/spawn/descriptors.py
Line: 316

Comment:
**Dependency permits incompatible descriptor**

If an environment resolves an older `dexsim_engine==0.5.0` package without the declared `CollisionDesc.decomp_algorithm` field, compiling any convex-decomposition mesh now passes that unsupported field to `dataclasses.replace`. This raises `TypeError` instead of producing a Spawn descriptor. Publish the companion engine capability under a distinct version and update the dependency constraint so supported installations cannot resolve the incompatible API.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Codex Fix in Claude Code

collision.enable_collision = physics.collision_enabled
collision.decomp_max_hulls = max_hulls
collision.dexsim = _compile_default_collision(physics)
Expand Down Expand Up @@ -1581,7 +1585,7 @@ def _compile_newton_collision(

def _compile_geometry(
cfg: RigidObjectCfg,
) -> tuple[GeometryDesc, CollisionApproximation, int]:
) -> tuple[GeometryDesc, CollisionApproximation, int, str]:
shape = cfg.shape
if isinstance(shape, MeshCfg):
geometry = _mesh_geometry_from_cfg(shape, segment_name=cfg.uid or "mesh")
Expand All @@ -1604,10 +1608,10 @@ def _compile_geometry(
# RigidObject after Spawn has created the file-backed mesh.
if (
collision_cfg.approximation == "convex_decomposition"
and acd_method not in ("visacd", "coacd")
and acd_method not in ("visacd", "coacd", "vhacd")
):
raise ValueError(
"Spawn supports only acd_method='visacd' or 'coacd' "
"Spawn supports only acd_method='visacd', 'coacd', or 'vhacd' "
"for convex_decomposition."
)
if collision_cfg.sdf_resolution is not None:
Expand All @@ -1620,13 +1624,14 @@ def _compile_geometry(
geometry,
approximation,
max(1, max_hulls),
acd_method,
)

if isinstance(shape, CubeCfg):
size = tuple(float(value) for value in shape.size)
if len(size) != 3 or any(value <= 0 for value in size):
raise ValueError("CubeCfg.size must contain three positive values.")
return GeometryDesc.cube(size), CollisionApproximation.NONE, 1
return GeometryDesc.cube(size), CollisionApproximation.NONE, 1, "coacd"

if isinstance(shape, SphereCfg):
if shape.radius <= 0:
Expand All @@ -1635,6 +1640,7 @@ def _compile_geometry(
GeometryDesc.sphere(float(shape.radius)),
CollisionApproximation.NONE,
1,
"coacd",
)

raise NotImplementedError(
Expand Down
34 changes: 32 additions & 2 deletions tests/sim/spawn/test_descriptors.py
Original file line number Diff line number Diff line change
Expand Up @@ -1010,6 +1010,35 @@ def test_dynamic_triangle_mesh_collision_is_rejected_before_spawn() -> None:
rigid_desc_from_cfg(cfg)


@pytest.mark.parametrize(
("method", "expected"),
[(None, "visacd"), ("visacd", "visacd"), ("coacd", "coacd"), ("vhacd", "vhacd")],
)
def test_spawn_forwards_convex_decomposition_algorithm(
method: str | None, expected: str
) -> None:
cfg = RigidObjectCfg.from_dict(
{
"uid": "mesh",
"shape": {
"shape_type": "Mesh",
"fpath": "mesh.glb",
"collision": {
"approximation": "convex_decomposition",
"max_hulls": 8,
"acd_method": method,
},
},
}
)
descriptor, _ = rigid_desc_from_cfg(cfg)

collision = descriptor.collisions[0]
assert collision.approximation == CollisionApproximation.CONVEX_DECOMPOSITION
assert collision.decomp_max_hulls == 8
assert collision.decomp_algorithm == expected


def test_spawn_rejects_unsupported_convex_decomposition_method() -> None:
cfg = RigidObjectCfg(
uid="mesh",
Expand All @@ -1018,12 +1047,13 @@ def test_spawn_rejects_unsupported_convex_decomposition_method() -> None:
collision=MeshCollisionCfg(
approximation="convex_decomposition",
max_hulls=4,
acd_method="vhacd",
acd_method="coacd",
),
),
)

with pytest.raises(ValueError, match="acd_method='visacd' or 'coacd'"):
cfg.shape.collision.acd_method = "invalid"
with pytest.raises(ValueError, match="acd_method"):
rigid_desc_from_cfg(cfg)


Expand Down
Loading