Skip to content

Commit b6286ed

Browse files
committed
Merge branch 'main' into dev/tw4/improve-cube-stacking-stability
2 parents d1029ab + 4be1089 commit b6286ed

25 files changed

Lines changed: 2437 additions & 205 deletions

.github/workflows/aws_gpu_benchmarks.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,9 @@ jobs:
137137
run: uvx asv machine --yes
138138

139139
- name: Run Benchmarks
140+
env:
141+
# Keep resolution 128 in the shared benchmark for nightly ASV collection.
142+
NEWTON_ASV_PR_GATE: "1"
140143
run: |
141144
# Gate this PR job on directly measured runtimes only.
142145
uvx --with virtualenv asv continuous \

CHANGELOG.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
- Add `sign_method` argument to `Mesh.build_sdf` and `SDF.create_from_mesh` support for a `"normal"` (angle-weighted pseudo-normal) sign strategy, for selecting the inside/outside sign of the baked SDF (`"auto"`, `"parity"`, `"winding"`, or `"normal"`).
3838
- Add opt-in MuJoCo Warp sleeping support to `SolverMuJoCo` with MJCF sleep configuration, initial tree policies, `sleep_tolerance`, compact `nvmax` storage, and a launchable `mujoco_sleeping` example. (#3725)
3939
- Add `forward_depth_image` output support to `SensorTiledCamera.update()` and `SensorTiledCamera.utils.create_forward_depth_image_output()` for native forward-depth rendering without post-processing `depth_image`.
40+
- Add a single-kernel sparse fused Conjugate Residual linear solver for the Kamino PADMM solver (`linear_solver_type="CRF"`): a matrix-free Delassus solve that runs the full CR iteration in one Warp kernel per world, with optional inexact-ADMM inner-tolerance scheduling via `linear_solver_tolerance_ratio`.
4041
- Add the `basic_conveyor_forces` example: a multi-belt conveyor circuit that transports rigid boxes with per-belt velocity fields, applying Coulomb-limited tangential body forces from reported per-contact normal forces across `SolverXPBD`, `SolverVBD`, and `SolverMuJoCo`.
4142
- Add optional `shear_stiffness`/`shear_damping` and `twist_stiffness`/`twist_damping` controls to `ModelBuilder.add_joint_cable()`, `ModelBuilder.add_rod()`, and `ModelBuilder.add_rod_graph()`; omitted shear defaults to stretch and omitted twist defaults to bend for compatibility.
4243
- Add `newton.utils.CableStiffness` and extend `newton.utils.create_cable_stiffness_from_elastic_moduli()` with `poissons_ratio`/`shear_modulus` inputs that include torsional `GJ/L` stiffness.
@@ -103,17 +104,19 @@
103104

104105
- Make deterministic collision pipelines cover hydroelastic contact generation and reduction, including unique reduced-contact sort keys and overflow-safe fixed-point pressure accumulation.
105106
- Preserve box-box face contact manifolds under sub-microradian solver drift.
107+
- Fix `SolverMuJoCo` retaining an invalid external-contact cache when its first step is captured in a CUDA graph. (#3767)
106108
- Convert `newton:mimicCoef0` from degrees to radians when the mimic follower joint is angular. Assets authored against the old behavior need the value rescaled to degrees.
107109
- Complete Kamino RCM traversal for large and disconnected systems and reuse the resulting permutation by default; set `reuse_permutation=False` to recompute it for changing matrix topology.
108110
- Bound Kamino DVI contact allocation with a per-world geometry heuristic instead of sizing every contact pair simultaneously; set `collision_detector.max_contacts_per_world` to override the inferred capacity.
109111
- Fix panel-parallel RCM-blocked LLT factorization hanging when a matrix ends in a partial tile.
110112
- Fix `ModelBuilder.add_usd()` marking a `guide`-purpose collider visible when it has a bound render material. Such a collider is not viewport geometry, and the extra `VISIBLE` flag left it drawn by the viewer's visual toggle instead of its collision toggle. `force_show_colliders` still reveals it.
111113
- Fix USD capsule, cylinder, and cone visual and site scaling to follow the authored primitive axis.
114+
- Fix MJCF contact pairs ignoring properties inherited from pair default classes.
112115
- Fix USD plane visual width and length to scale along the axes defined by the `UsdGeomPlane` schema, and orient X- and Y-axis plane visuals along the authored axis.
113116
- Validate `ArticulationView` mask shapes and devices before launching selection kernels. (#3448)
114117
- Exclude active particles with non-finite positions from rebuildable `SolverImplicitMPM` sparse-grid packing.
118+
- Fix incorrect hydroelastic contact surfaces for primitive shapes. (#3150, #3239)
115119
- Fix masked `SolverCoupledProxy.reset()` calls clearing proxy feedback history for unselected worlds.
116-
- Fix hydroelastic primitive texture SDF generation to sample analytic primitive distances instead of temporary tessellated meshes. (#3239)
117120
- Fix MJCF, URDF, and USD imports rendering collision-only bodies as visuals when the asset authors visual geometry elsewhere. (#3291)
118121
- Fix `SchemaResolverPhysx` reading every D6 translational limit gain from the `linear` instance instead of its `transX`, `transY`, or `transZ` instance.
119122
- Fix USD capsule, cylinder, and cone visuals and sites without authored `radius`/`height` to use the UsdGeom schema fallbacks, matching collision shapes.
@@ -144,6 +147,7 @@
144147
- Fix `FastKitchenG1` ASV metrics to build the kitchen scene instead of a plain G1 model.
145148
- Fix the `diffsim_bear` example crashing with its default CUDA configuration and diverging after a few training iterations.
146149
- Fix masked PID state reset to execute on the integral-state device. (#3447)
150+
- Fix MJCF imports ignoring `fromto` transforms and lengths on sites.
147151
- Reject invalid hollow primitive shell thickness before computing inertia.
148152
- Fix `ModelBuilder.add_mjcf()` ignoring positive explicit mass on mesh geoms. (#3595)
149153
- Preserve muscles and rigid-body color groups when copying or replicating a `ModelBuilder`.
@@ -307,6 +311,7 @@
307311
- Correct `SolverFeatherstone` Coriolis/centrifugal forces so torque-free bodies conserve angular momentum.
308312
- Correct `newton.eval_fk()` / `newton.eval_ik()` rotations and joint velocities when three angular axes form a left-handed orthonormal basis.
309313
- Fix mesh inertia computation to produce deterministic results across repeated CUDA runs. (#3136)
314+
- Fix convex decomposition of disconnected mesh components so unified multi-part collision meshes preserve separate convex parts.
310315
- Fix `SolverXPBD.step()` rejecting `contacts=None` for particle models with shapes, and align its optional control and contact annotations with `SolverBase.step()`.
311316
- Fix `ModelBuilder.add_builder()` and `ModelBuilder.finalize()` time and memory scaling for large replicated scenes with collision filter pairs. (#1675)
312317
- Fix mesh-SDF contacts with positive contact gaps by making contact reduction prefer margin-depth contacts over gap-only directional fallbacks. (#3490)

asv/benchmarks/setup/bench_sdf.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
# SPDX-License-Identifier: Apache-2.0
33

44
import inspect
5+
import os
56

67
import numpy as np
78
import warp as wp
@@ -48,14 +49,19 @@ def _build_sdf(mesh: "newton.Mesh", *, max_resolution: int) -> "newton.SDF":
4849
# Measuring a batch rather than a single build amortizes GPU boost-clock and
4950
# thermal transients that otherwise make this benchmark bimodal across AWS CI
5051
# runs (see #2534). Counts decrease with resolution so each sample takes
51-
# roughly the same wall time (~0.5 s); each SDF is released immediately after
52+
# roughly the same wall time; each SDF is released immediately after
5253
# construction so peak GPU memory stays bounded to one SDF at a time.
5354
_BUILDS_PER_SAMPLE = {
5455
32: 20,
5556
64: 20,
5657
128: 10,
5758
}
5859

60+
# The PR benchmark workflow sets this variable to keep its gating comparison
61+
# focused on the two lowest-cost resolutions. The default retains resolution
62+
# 128 for the external nightly ASV collection.
63+
_BENCHMARK_RESOLUTIONS = [32, 64] if os.environ.get("NEWTON_ASV_PR_GATE") else [32, 64, 128]
64+
5965
# Number of untimed warm-up builds in ``setup`` to push the GPU into a stable
6066
# boost-clock state before any timed iterations run.
6167
_WARMUP_BUILDS = 3
@@ -153,12 +159,11 @@ class FastBuildSdf:
153159
Each timed call builds :data:`_BUILDS_PER_SAMPLE` SDFs in a loop and
154160
releases each immediately, reporting the total wall time. The batch size
155161
is scaled down at higher resolutions so every sample takes roughly the
156-
same wall time. This amortizes GPU boost-clock/thermal transients on AWS
157-
CI runners that previously made single-build measurements bimodal across
158-
runs (see #2534).
162+
same wall time. This amortizes GPU boost-clock and thermal transients on
163+
AWS CI runners (see #2534).
159164
"""
160165

161-
params = ([32, 64, 128],)
166+
params = (_BENCHMARK_RESOLUTIONS,)
162167
param_names = ["max_resolution"]
163168

164169
rounds = 2

newton/_src/sim/builder.py

Lines changed: 58 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@
5454
from ..usd.schema_resolver import SchemaResolver
5555
from ..utils import compute_world_offsets
5656
from ..utils.deprecation import deprecate_nonkeyword_arguments
57-
from ..utils.mesh import MeshAdjacency
57+
from ..utils.mesh import MeshAdjacency, split_mesh_components
5858
from .enums import (
5959
BodyFlags,
6060
JointTargetMode,
@@ -7385,6 +7385,8 @@ def approximate_meshes(
73857385

73867386
The ``coacd`` and ``vhacd`` methods require additional dependencies (``coacd`` or ``trimesh`` and ``vhacdx`` respectively) to be installed.
73877387
The convex hull approximation requires ``scipy`` to be installed.
7388+
For ``coacd`` and ``vhacd``, each geometrically connected component
7389+
is decomposed separately and may produce one or more convex shapes.
73887390

73897391
The ``raise_on_failure`` parameter controls the behavior when the remeshing fails:
73907392
- If `True`, an exception is raised when the remeshing fails.
@@ -7514,6 +7516,15 @@ def get_shape_custom_attributes(shape: int) -> dict[str, Any] | None:
75147516
import trimesh
75157517

75167518
decompositions = {}
7519+
filtered_shapes_by_shape: dict[int, set[int]] = {}
7520+
convex_parts_by_shape: dict[int, list[int]] = {}
7521+
source_shapes = set(shape_indices)
7522+
# Snapshot source filters before adding convex parts, without materializing compact storage.
7523+
for shape_a, shape_b in self._shape_collision_filter_pairs:
7524+
if shape_a in source_shapes:
7525+
filtered_shapes_by_shape.setdefault(shape_a, set()).add(shape_b)
7526+
if shape_b in source_shapes:
7527+
filtered_shapes_by_shape.setdefault(shape_b, set()).add(shape_a)
75177528

75187529
for shape in shape_indices:
75197530
mesh: Mesh = self.shape_source[shape]
@@ -7522,42 +7533,55 @@ def get_shape_custom_attributes(shape: int) -> dict[str, Any] | None:
75227533
if hash_m in decompositions:
75237534
decomposition = decompositions[hash_m]
75247535
else:
7525-
if method == "coacd":
7526-
cmesh = coacd.Mesh(mesh.vertices, mesh.indices.reshape(-1, 3))
7527-
coacd_settings = {
7528-
"threshold": self.default_mesh_approximation_cfg.coacd_threshold,
7529-
"mcts_nodes": 20,
7530-
"mcts_iterations": 5,
7531-
"mcts_max_depth": 1,
7532-
"merge": False,
7533-
"max_convex_hull": mesh.maxhullvert,
7534-
}
7535-
coacd_settings.update(remeshing_kwargs)
7536-
decomposition = coacd.run_coacd(cmesh, **coacd_settings)
7537-
else:
7538-
tmesh = trimesh.Trimesh(mesh.vertices, mesh.indices.reshape(-1, 3))
7539-
vhacd_settings = {
7540-
"maxNumVerticesPerCH": mesh.maxhullvert,
7541-
}
7542-
vhacd_settings.update(remeshing_kwargs)
7543-
decomposition = trimesh.decomposition.convex_decomposition(tmesh, **vhacd_settings)
7544-
decomposition = [(d["vertices"], d["faces"]) for d in decomposition]
7536+
decomposition = []
7537+
# Decomposition backends may merge disconnected convex parts into one hull.
7538+
for component_vertices, component_faces in split_mesh_components(mesh):
7539+
if method == "coacd":
7540+
cmesh = coacd.Mesh(component_vertices, component_faces)
7541+
coacd_settings = {
7542+
"threshold": self.default_mesh_approximation_cfg.coacd_threshold,
7543+
"mcts_nodes": 20,
7544+
"mcts_iterations": 5,
7545+
"mcts_max_depth": 1,
7546+
"merge": False,
7547+
"max_convex_hull": mesh.maxhullvert,
7548+
}
7549+
coacd_settings.update(remeshing_kwargs)
7550+
decomposition.extend(coacd.run_coacd(cmesh, **coacd_settings))
7551+
else:
7552+
tmesh = trimesh.Trimesh(component_vertices, component_faces)
7553+
vhacd_settings = {
7554+
"maxNumVerticesPerCH": mesh.maxhullvert,
7555+
}
7556+
vhacd_settings.update(remeshing_kwargs)
7557+
component_decomposition = trimesh.decomposition.convex_decomposition(
7558+
tmesh, **vhacd_settings
7559+
)
7560+
decomposition.extend((d["vertices"], d["faces"]) for d in component_decomposition)
75457561
decompositions[hash_m] = decomposition
75467562
if len(decomposition) == 0:
75477563
continue
75487564
# note we need to copy the mesh to avoid modifying the original mesh
7549-
self.shape_source[shape] = self.shape_source[shape].copy(
7565+
replacement_mesh = self.shape_source[shape].copy(
75507566
vertices=decomposition[0][0], indices=decomposition[0][1]
75517567
)
7568+
# Decomposition outputs do not provide attributes remapped to the new vertices.
7569+
replacement_mesh._normals = None
7570+
replacement_mesh._uvs = None
7571+
self.shape_source[shape] = replacement_mesh
75527572
# mark as convex mesh type
75537573
self.shape_type[shape] = GeoType.CONVEX_MESH
75547574
if len(decomposition) > 1:
75557575
body = self.shape_body[shape]
75567576
xform = self.shape_transform[shape]
75577577
color = self.shape_color[shape]
75587578
custom_attributes = get_shape_custom_attributes(shape)
7579+
filtered_shapes = sorted(
7580+
filtered_shape
7581+
for filtered_shape in filtered_shapes_by_shape.get(shape, ())
7582+
if self.shape_body[filtered_shape] != body
7583+
)
75597584
cfg = ModelBuilder.ShapeConfig(
7560-
density=0.0, # do not add extra mass / inertia
75617585
ke=self.shape_material_ke[shape],
75627586
kd=self.shape_material_kd[shape],
75637587
kf=self.shape_material_kf[shape],
@@ -7569,13 +7593,16 @@ def get_shape_custom_attributes(shape: int) -> dict[str, Any] | None:
75697593
kh=self.shape_material_kh[shape],
75707594
margin=self.shape_margin[shape],
75717595
is_solid=self.shape_is_solid[shape],
7572-
collision_group=self.shape_collision_group[shape],
7573-
collision_filter_parent=self.default_shape_cfg.collision_filter_parent,
7596+
force_sdf=self.shape_force_sdf[shape],
75747597
)
75757598
cfg.flags = self.shape_flags[shape]
7599+
cfg.density = 0.0 # do not add extra mass / inertia
7600+
cfg.gap = self.shape_gap[shape]
7601+
cfg.collision_group = self.shape_collision_group[shape]
7602+
cfg.collision_filter_parent = False
75767603
for i in range(1, len(decomposition)):
75777604
# add additional convex parts as convex meshes
7578-
self.add_shape_convex_hull(
7605+
extra_shape = self.add_shape_convex_hull(
75797606
body=body,
75807607
xform=xform,
75817608
mesh=Mesh(decomposition[i][0], decomposition[i][1]),
@@ -7585,6 +7612,11 @@ def get_shape_custom_attributes(shape: int) -> dict[str, Any] | None:
75857612
label=f"{self.shape_label[shape]}_convex_{i}",
75867613
custom_attributes=custom_attributes,
75877614
)
7615+
for filtered_shape in filtered_shapes:
7616+
self.add_shape_collision_filter_pair(filtered_shape, extra_shape)
7617+
for filtered_part in convex_parts_by_shape.get(filtered_shape, ()):
7618+
self.add_shape_collision_filter_pair(filtered_part, extra_shape)
7619+
convex_parts_by_shape.setdefault(shape, []).append(extra_shape)
75887620
remeshed_shapes.add(shape)
75897621
except Exception as e:
75907622
if raise_on_failure:

0 commit comments

Comments
 (0)