Topic Description
Is there a way to **directly access the vertex data buffer of a mesh**, modify only the specific vertex indices in-place, and then refresh/commit the changes? I need to update only a small subset of vertices (approximately 200 out of 50,000+) at high frequency (~30 fps), but the current `UsdGeom.Mesh.GetPointsAttr().Set()` method requires passing the complete vertex array, causing significant performance overhead.
Isaac Sim Version
5.1.0
Detailed Description
I’m developing a simulation scenario that requires updating partial vertex positions of a mesh at very high frequency (approximately 30 fps). In my use case:
- **Mesh size**: Contains over **20,000 vertices** (typically 50,000+ vertices)
- **Update pattern**: Only about **200 vertices** need to be updated per frame (less than 1% of total vertices)
- **Update frequency**: Updates occur every frame at ~30 fps
- **Current method**: Using `UsdGeom.Mesh.GetPointsAttr().Set()` which requires passing the complete vertex array
The current implementation forces me to:
1. Retrieve all vertices using `Get()` (even though I only need to modify ~200)
2. Convert the entire numpy array to a `Gf.Vec3f` list (50,000+ conversions per frame)
3. Call `Set()` with the complete array (triggering validation and notifications for all vertices)
This approach creates significant performance overhead, especially the conversion step which becomes a bottleneck at high update frequencies.
**Question:** Is there a way to **directly access the vertex data buffer**, modify only the specific vertex indices in-place, and then refresh/commit the changes? This would eliminate the need to retrieve, convert, and set the entire vertex array when only a small subset needs updating.
Steps to Reproduce
1. Create or load a mesh with a large number of vertices (e.g., 50,000+ vertices)
2. Identify a small subset of vertices to update (e.g., 200 vertices)
3. Attempt to update only those vertices using the current API:
```python
from pxr import UsdGeom, Gf
import omni.usd
import numpy as np
# Get mesh and vertex attributes
stage = omni.usd.get_context().get_stage()
target_prim = stage.GetPrimAtPath(“/World/ground_plane2/ground_plane2/Grid”)
target_mesh = UsdGeom.Mesh(target_prim)
target_points_attr = target_mesh.GetPointsAttr()
# Get all current vertices (local coordinates)
target_local_points = target_points_attr.Get()
target_local_vertices = np.array([(p[0], p[1], p[2]) for p in target_local_points])
# Only want to update partial vertices (e.g., swept_vertex_indices)
swept_vertex_indices = np.array([100, 200, 300, …]) # Only update vertices at these indices
new_local_vertices = … # New vertex positions (only for updated vertices)
# Update partial vertices into the full array
target_local_vertices[swept_vertex_indices] = new_local_vertices
# Problem: Must convert and set the entire array, even though only a few vertices are updated
new_target_points = [Gf.Vec3f(v[0], v[1], v[2]) for v in target_local_vertices]
target_points_attr.Set(new_target_points) # Full update of all vertices
```
4. Observe performance overhead from:
-
Retrieving all 50,000+ vertices when only 200 need updating
-
Converting 50,000+ numpy arrays to `Gf.Vec3f` objects
-
Setting the entire array back, triggering validation for all vertices
What I’ve Tried
Before posting this question, I’ve searched through the existing Isaac Sim and USD APIs:
1. **Standard USD API**: The `UsdGeom.Mesh.GetPointsAttr()` returns a `VtArray`, which is copy-on-write, but the standard workflow still requires:
-
`Get()` to retrieve the entire array
-
Modify elements in the array
-
`Set()` to write back the entire array
2. **Isaac Sim Deformable API**: I found `DeformablePrim.set_simulation_mesh_nodal_positions(new_positions, indices)` method that accepts indices. However:
-
The `indices` parameter refers to **environment indices** (for batch operations across multiple deformable meshes), not vertex indices
-
Each call still requires passing **all vertices** of the selected environments’ meshes
-
This API is specific to deformable meshes (physics-simulated) and not applicable to standard USD meshes
-
It does not support partial vertex updates within a single mesh
3. **No Direct Buffer Access Found**: I couldn’t find any API methods like:
-
`GetWritableBuffer()`
-
`SetIndices()` / `SetPartial()`
-
`Refresh()` / `Commit()` / `Invalidate()`
All existing code examples in the Isaac Sim codebase use the full `Get()` and `Set()` pattern for mesh vertex updates.
### Related Issues
None found. This appears to be a feature request / optimization question rather than a bug.
### Additional Context
Desired API (Hypothetical)
If direct buffer access were available, the implementation would look like:
```python
# Desired API for direct buffer access (hypothetical)
# Get a writable view of the vertex buffer
vertex_buffer = target_points_attr.GetWritableBuffer() # Returns numpy array or similar
# Modify specific indices directly
vertex_buffer[swept_vertex_indices] = new_local_vertices
# Commit/refresh the changes
target_points_attr.Refresh() # or Commit(), Invalidate(), etc.
```
**Complete Code Example:**
```python
from pxr import UsdGeom, Gf
import omni.usd
import numpy as np
import time
def update_mesh_vertices_partial(
target_mesh_path: str,
swept_vertex_indices: np.ndarray,
new_world_vertices: np.ndarray):
"""
Update partial vertices of a mesh
Parameters:
target_mesh_path: Prim path of the mesh
swept_vertex_indices: Array of vertex indices to update
new_world_vertices: New world coordinate vertices (only for updated vertices)
"""
stage = omni.usd.get_context().get_stage()
target_prim = stage.GetPrimAtPath(target_mesh_path)
target_mesh = UsdGeom.Mesh(target_prim)
target_points_attr = target_mesh.GetPointsAttr()
*# Get all current vertices (local coordinates)*
target_local_points = target_points_attr.Get()
target_local_vertices = np.array(\[(p\[0\], p\[1\], p\[2\]) for p in target_local_points\])
*# Get world-to-local transformation matrix*
target_world_transform = omni.usd.get_world_transform_matrix(target_prim)
target_world_transform_matrix = np.array(target_world_transform).reshape(4, 4)
target_world_to_local = np.linalg.inv(target_world_transform_matrix)
*# Convert new world coordinates to local coordinates*
homogeneous_world = np.column_stack(\[
new_world_vertices,
np.ones(len(new_world_vertices))
\])
homogeneous_local = (target_world_to_local @ homogeneous_world.T).T
new_local_vertices = homogeneous_local\[:, :3\]
*# Update partial vertices into the full array*
target_local_vertices\[swept_vertex_indices\] = new_local_vertices
*# Performance bottleneck: Must convert the entire array to Gf.Vec3f format*
t0 = time.time()
new_target_points = \[Gf.Vec3f(v\[0\], v\[1\], v\[2\]) for v in target_local_vertices\]
conversion_time = time.time() - t0
*# Performance bottleneck: Must set all vertices*
t0 = time.time()
target_points_attr.Set(new_target_points)
set_time = time.time() - t0
print(f"Updated {len(swept_vertex_indices)} vertices out of {len(target_local_vertices)}")
print(f" Conversion time: {conversion_time\*1000:.2f} ms")
print(f" Set time: {set_time\*1000:.2f} ms")
print(f" Total time: {(conversion_time + set_time)\*1000:.2f} ms")