Skip to content

[BUG FIX] Apply MeshSet poses to sampled particles. - #3171

Draft
prexhu wants to merge 1 commit into
Genesis-Embodied-AI:mainfrom
prexhu:bugfix/meshset-particle-transform
Draft

[BUG FIX] Apply MeshSet poses to sampled particles.#3171
prexhu wants to merge 1 commit into
Genesis-Embodied-AI:mainfrom
prexhu:bugfix/meshset-particle-transform

Conversation

@prexhu

@prexhu prexhu commented Aug 4, 2026

Copy link
Copy Markdown

Description

Apply each MeshSet member's rotation and translation to its sampled particle positions.

Previously, MeshSet particles were only translated, while the corresponding visual mesh received the complete pose.

Add an MPM regression test using an asymmetric mesh rotated by 90 degrees.

Related Issue

Resolves #3169

Motivation and Context

Hybrid entities instantiate their soft parts through MeshSet. Ignoring each member's rotation caused particles generated from rotated URDF links to remain aligned with the original local axes instead of attaching to the links.

How Has This Been / Can This Be Tested?

pytest -n 0 tests/particles/test_mpm.py

Screenshots (if appropriate):

Checklist:

  • I read the CONTRIBUTING document.
  • I followed the Submitting Code Changes section of CONTRIBUTING document.
  • I tagged the title correctly (including BUG FIX/FEATURE/MISC/BREAKING)
  • I updated the documentation accordingly or no change is needed.
  • I tested my changes and added instructions on how to test it for reviewers.
  • I have added tests to cover my changes.
  • All new and existing tests passed.

particles_i,
np.asarray(morph_i.pos, dtype=gs.np_float),
np.asarray(morph_i.quat, dtype=gs.np_float),
)

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.

The transform is mathematically correct, but please compute pos_i and quat_i once and use the same values for both the particles and self._vmesh[i] in this loop, then remove the later visual-transform loop. This avoids maintaining two derivations of the same member pose and keeps particle placement, rendering, and skinning in the same frame.

These are validated internal tuples, so the repository guidelines also require np.array rather than np.asarray.

pos_i = np.array(morph_i.pos, dtype=gs.np_float)
quat_i = np.array(morph_i.quat, dtype=gs.np_float)
particles.append(gu.transform_by_trans_quat(particles_i, pos_i, quat_i))
self._vmesh[i].apply_transform(gu.trans_quat_to_T(pos_i, quat_i))



@pytest.mark.required
def test_mesh_set_particle_transform(show_viewer):

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.

CODING_GUIDELINES.md sections 12.1–12.2 require bug-fix regression assertions to be added to the existing test covering the capability; bug fixes are explicitly not an exception.

Please fold this MeshSet entity and its assertions into an existing MPM test and reuse that scene build. A separate test incurs another full Genesis initialization for every CI configuration.


@pytest.mark.required
def test_mesh_set_particle_transform(show_viewer):
pos = np.array((0.2, 0.1, 0.2))

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.

Please keep this value as a tuple:

pos = (0.2, 0.1, 0.2)

The scene-construction guidelines prohibit wrapping controlled array-like inputs in np.array, and the project assertion helper accepts tuples directly.

mpm_options=gs.options.MPMOptions(
lower_bound=(-0.5, -0.5, -0.5),
upper_bound=(0.5, 0.5, 0.5),
particle_size=0.01,

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.

Keyword arguments must follow the callee's declaration order, so particle_size must precede lower_bound and upper_bound.

The custom bounds are also unnecessary for this geometry because it already fits inside the default MPM domain. This can be reduced to:

mpm_options=gs.options.MPMOptions(
    particle_size=0.01,
),

poss=(pos,),
eulers=((0.0, 0.0, 90.0),),
),
material=gs.materials.MPM.Elastic(sampler="regular"),

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.

Scene-building calls require one option per line, even when only one option is provided:

material=gs.materials.MPM.Elastic(
    sampler="regular",
),

)

particles = entity.init_particles
np.testing.assert_allclose(particles.mean(axis=0), pos, atol=1e-6)

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.

Please use the repository assertion utility instead of NumPy's testing API:

from ..utils.assertions import assert_allclose

Then call assert_allclose(..., atol=1e-6).

An explicit atol=1e-6 sits at roughly 5x the measured fp32 floor (~2e-7) while still detecting the 0.08 failure by five orders of magnitude, and stays a fixed absolute bound across precision arms.


particles = entity.init_particles
np.testing.assert_allclose(particles.mean(axis=0), pos, atol=1e-6)
np.testing.assert_allclose(np.ptp(particles, axis=0), (0.11, 0.03, 0.03), atol=1e-6)

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.

This catches a completely missing rotation, but it does not fully validate MeshSet pose semantics.

A single centered box cannot detect applying the wrong member's pose, and +90° and −90° rotations have identical means and extents. Please test at least two members with different rotation axes and make one mesh off-center or assert another direction-sensitive quantity so an inverse rotation fails.

Please also derive the expected spans from extents - particle_size and consider asserting that the particle and public visual-mesh AABBs remain aligned.

Note this interacts with the bounds comment above: keep every member inside the default MPM domain (z well above 0, within the safety padding), or the custom bounds become necessary again.

@prexhu
prexhu marked this pull request as draft August 19, 2026 00:55
@jeetrex17

Copy link
Copy Markdown
Contributor

Hi @prexhu, just following up on this draft. Are you planning to continue addressing the review feedback? I’d be happy to help with the implementation or test restructuring if useful.

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.

[Bug]: MeshSet particle sampling ignores member rotations

2 participants