Skip to content

Commit 739ab80

Browse files
authored
Import Newton curve damping from USD (#4056)
Signed-off-by: JC Chang <jumyungc@nvidia.com>
1 parent 91f49c3 commit 739ab80

8 files changed

Lines changed: 362 additions & 100 deletions

File tree

changelog/3980.added.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Import per-mode curve damping from `NewtonCurvesDeformableMaterialAPI` and discretize each value using the rod joint's dual rest length.

docs/concepts/usd_parsing.rst

Lines changed: 37 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -156,16 +156,31 @@ The first release deliberately supports a narrow, predictable set of inputs:
156156

157157
* Valid, enabled, **dynamic** cable, cloth, and volume simulation prims that use the AOUSD
158158
deformable APIs. A bound simulation material supplies thickness, stiffness, and density.
159-
For cables, each authored ``physics:curves*Stiffness`` is a structural stiffness; an
160-
unauthored mode is derived instead from ``physics:youngsModulus``, ``physics:poissonsRatio``,
161-
and ``physics:curvesThickness``, each of which falls back to the proposal's assumed 1 MPa,
162-
0.3, and 1 mm when it is itself unauthored.
163-
Newton divides each resolved structural stiffness by that joint's own dual rest length
164-
``0.5 * (L_parent + L_child)``, so unevenly sampled curves keep per-joint accuracy.
165-
The earlier unprefixed cable material attributes remain accepted with their former modulus
166-
interpretation during a deprecation window and emit a ``DeprecationWarning``.
159+
For cables, ``PhysicsCurvesDeformableMaterialAPI`` provides structural stiffness for stretch,
160+
shear, bend, and twist through ``physics:curves*Stiffness``. A material may additionally apply
161+
``NewtonCurvesDeformableMaterialAPI`` to provide matching structural damping through
162+
``newton:curves*Damping``. These are material-level quantities, not direct per-joint target
163+
values. For each generated rod joint, Newton uses the joint-local dual rest length
164+
``L_joint = 0.5 * (L_parent + L_child)`` to compute
165+
``joint_target_ke = structural_stiffness / L_joint`` and
166+
``joint_target_kd = structural_damping / L_joint``.
167+
For stretch and shear, stiffness and damping have units of force and force times time,
168+
respectively; for bend and twist, they have units of force times distance squared and force
169+
times distance squared times time. Division by ``L_joint`` produces the corresponding linear
170+
or angular per-joint units.
171+
An unauthored or ``-inf`` stiffness is derived from ``physics:youngsModulus``,
172+
``physics:poissonsRatio``, and ``physics:curvesThickness`` (with assumed values of 1 MPa, 0.3,
173+
and 1 mm diameter when those are unauthored). An unauthored or ``-inf`` damping instead leaves
174+
that mode at its rod-joint default; authored damping modes are not copied to unauthored modes.
175+
Newton damping requires the base ``PhysicsCurvesDeformableMaterialAPI``; without it, the
176+
damping is ignored.
177+
The deprecated unprefixed ``physics:stretchStiffness``, ``physics:shearStiffness``,
178+
``physics:bendStiffness``, and ``physics:twistStiffness`` attributes remain accepted with their
179+
former modulus interpretation and emit a ``DeprecationWarning``. The
180+
``newton:curves*Damping`` attributes always use structural units and remain independent per mode,
181+
even on a material using those deprecated stiffness attributes.
167182
* The points and topology **as currently authored**. Newton builds the deformable at that pose;
168-
a cable's ``restShapePoints`` may affect stiffness normalization but never establishes an
183+
a cable's ``restShapePoints`` may affect material-gain discretization but never establishes an
169184
initial strain state.
170185
* Point attachments only where the authored constraint can be represented without moving any
171186
geometry: hard cable-to-xform attachments, and hard, coincident cable-to-cable junctions.
@@ -200,10 +215,10 @@ Known gaps of the experimental importer, tracked as follow-ups:
200215
* **Rest state** -- authored rest geometry is not imported as the deformable's simulated rest
201216
configuration. Cloth and volume rest attributes are ignored with a warning. For a cable,
202217
including one in a welded graph, valid ``restShapePoints`` supplies only the segment lengths
203-
used to discretize material stiffness into joint stiffness; invalid values warn and fall back
204-
to the current ``points`` lengths. The rod itself is still built relaxed at the current
205-
``points`` pose, and mass distribution also uses the current geometry. A body saved in a
206-
deformed pose therefore resumes relaxed at that pose instead of springing back.
218+
used to discretize structural stiffness and damping into per-joint stiffness and damping gains.
219+
Invalid values warn and fall back to the current ``points`` lengths. The rod itself is still
220+
built relaxed at the current ``points`` pose, and mass distribution also uses the current geometry.
221+
A body saved in a deformed pose therefore resumes relaxed at that pose instead of springing back.
207222
* **Springy attachments** -- attachments with a finite stiffness are not simulated. They are
208223
preserved in ``path_attachment_attrs`` with their authored stiffness and damping (silently
209224
hardening them would change the authored physics); only hard attachments (unauthored or
@@ -306,22 +321,23 @@ close a loop, so they stay outside the articulation.
306321
model = builder.finalize() # cables are already wrapped and finalize-ready
307322
308323
The :meth:`~newton.ModelBuilder.add_usd` return dict carries ``path_cable_attrs``,
309-
``path_cloth_attrs`` and ``path_soft_attrs``, mapping each prim path to its attributes exactly
310-
as authored, independent of any solver. The cable and cloth entries expose the parsed
311-
authored ``material`` values and the ``resolved_density``. The volume entry exposes the
324+
``path_cloth_attrs`` and ``path_soft_attrs``, mapping each prim path to validated import metadata,
325+
independent of any solver.
326+
The cable and cloth entries expose validated authored ``material`` values and the
327+
``resolved_density``. The volume entry exposes the
312328
``resolved_density`` (a volume material's ``youngsModulus`` / ``poissonsRatio`` are applied to
313-
the built soft body and not repeated there). A cable entry preserves authored
314-
``curvesThickness``, ``youngsModulus``, ``poissonsRatio``, and the per-mode structural
315-
stiffnesses, plus any earlier unprefixed attributes the material still authors during the
329+
the built soft body and not repeated there). A cable entry preserves validated authored
330+
``curvesThickness``, ``youngsModulus``, ``poissonsRatio``, and the per-mode structural stiffness
331+
and damping values, plus any earlier unprefixed attributes the material still authors during the
316332
deprecation window; a cloth entry keeps moduli its isotropic membrane cannot express. This
317333
lets another solver rebuild the deformable without re-parsing the stage. A cable entry carries a
318334
``graph_component`` identifier only when the curve was welded into a rod graph; curves of one
319335
graph share it, and independent or fallback cables have no such key.
320336

321337
.. note::
322338

323-
Solver tuning that is not part of the AOUSD schema (e.g. damping) is not imported; supply it
324-
on the builder or model after import.
339+
Solver tuning that is not part of the supported AOUSD schemas or a documented Newton schema
340+
extension is not imported; supply it on the builder or model after import.
325341

326342
Material Color Spaces
327343
---------------------

newton/_src/sim/builder.py

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3817,7 +3817,7 @@ def add_usd(
38173817
* - ``"path_soft_map"``
38183818
- Mapping from prim path (str) of a soft body (a volume deformable, or a legacy bare TetMesh) to its ``[start, end)`` index ranges, keyed ``"particle"`` / ``"tet"``. Present only with ``return_deformable_results=True``.
38193819
* - ``"path_cable_attrs"``
3820-
- Mapping from prim path (str) of a curve deformable (cable) to its as-authored, solver-neutral attributes (``material`` moduli, ``resolved_density``, ``closed``); includes moduli the imported rod cannot express (e.g. shear / twist). ``graph_component`` is present only for curves successfully welded into the same rod graph; curves in one graph share the component identifier. Present only with ``return_deformable_results=True``.
3820+
- Mapping from prim path (str) of a curve deformable (cable) to its validated, solver-neutral cable import metadata (``material``, ``resolved_density``, ``closed``). ``material`` contains supported per-mode structural values before per-joint discretization: stretch/shear stiffness [N] and damping [N·s]; bend/twist stiffness [N·m²] and damping [N·m²·s]. ``graph_component`` is present only for curves successfully welded into the same rod graph; curves in one graph share the identifier. Present only with ``return_deformable_results=True``.
38213821
* - ``"path_cloth_attrs"``
38223822
- Mapping from prim path (str) of a surface deformable (cloth) to its as-authored, solver-neutral attributes (``material`` moduli, ``resolved_density``). Present only with ``return_deformable_results=True``.
38233823
* - ``"path_soft_attrs"``
@@ -5648,32 +5648,58 @@ def add_joint_cable(
56485648
**kwargs,
56495649
)
56505650

5651-
def _set_joint_rod_stiffnesses(
5651+
def _set_joint_rod_material_gains(
56525652
self,
56535653
joint: int,
56545654
*,
5655-
stretch_stiffness: float | None,
5656-
shear_stiffness: float | None,
5657-
bend_stiffness: float | None,
5658-
twist_stiffness: float | None,
5655+
stretch_stiffness: float | None = None,
5656+
stretch_damping: float | None = None,
5657+
shear_stiffness: float | None = None,
5658+
shear_damping: float | None = None,
5659+
bend_stiffness: float | None = None,
5660+
bend_damping: float | None = None,
5661+
twist_stiffness: float | None = None,
5662+
twist_damping: float | None = None,
56595663
) -> None:
5660-
"""Overwrite each non-None stiffness and its inferred target mode, in :meth:`add_joint_rod` axis order."""
5664+
"""Overwrite non-None material gains and target modes in :meth:`add_joint_rod` slot order.
5665+
5666+
Args:
5667+
joint: Rod joint index.
5668+
stretch_stiffness: Per-joint stretch stiffness [N/m], or ``None`` to preserve it.
5669+
stretch_damping: Per-joint stretch damping [N·s/m], or ``None`` to preserve it.
5670+
shear_stiffness: Per-joint shear stiffness [N/m], or ``None`` to preserve it.
5671+
shear_damping: Per-joint shear damping [N·s/m], or ``None`` to preserve it.
5672+
bend_stiffness: Per-joint bend stiffness [N·m/rad], or ``None`` to preserve it.
5673+
bend_damping: Per-joint bend damping [N·m·s/rad], or ``None`` to preserve it.
5674+
twist_stiffness: Per-joint twist stiffness [N·m/rad], or ``None`` to preserve it.
5675+
twist_damping: Per-joint twist damping [N·m·s/rad], or ``None`` to preserve it.
5676+
"""
56615677
joint_type = self.joint_type[joint]
56625678
joint_dof_dim = self.joint_dof_dim[joint]
56635679
if joint_type != JointType.ROD or joint_dof_dim != (2, 2):
56645680
raise ValueError(
5665-
"_set_joint_rod_stiffnesses() expected the four-slot ROD layout "
5681+
"_set_joint_rod_material_gains() expected the four-slot ROD layout "
56665682
f"(2 linear, 2 angular); got joint type {JointType(joint_type).name} with dimensions "
56675683
f"{joint_dof_dim}. Update the ROD material-slot mapping when changing its slot layout."
56685684
)
56695685
dof_start = self.joint_qd_start[joint]
5670-
for offset, stiffness in enumerate((stretch_stiffness, shear_stiffness, bend_stiffness, twist_stiffness)):
5671-
if stiffness is not None:
5686+
stiffnesses = (stretch_stiffness, shear_stiffness, bend_stiffness, twist_stiffness)
5687+
dampings = (stretch_damping, shear_damping, bend_damping, twist_damping)
5688+
for offset, (stiffness, damping) in enumerate(zip(stiffnesses, dampings, strict=True)):
5689+
if stiffness is not None or damping is not None:
56725690
dof = dof_start + offset
5673-
damping = self.joint_target_kd[dof]
5674-
self.joint_target_ke[dof] = stiffness
5691+
if stiffness is not None:
5692+
self.joint_target_ke[dof] = stiffness
5693+
if damping is not None:
5694+
self.joint_target_kd[dof] = damping
5695+
resolved_stiffness = self.joint_target_ke[dof]
5696+
resolved_damping = self.joint_target_kd[dof]
56755697
self.joint_target_mode[dof] = int(
5676-
JointTargetMode.from_gains(stiffness, damping, has_drive=stiffness != 0.0 or damping != 0.0)
5698+
JointTargetMode.from_gains(
5699+
resolved_stiffness,
5700+
resolved_damping,
5701+
has_drive=resolved_stiffness != 0.0 or resolved_damping != 0.0,
5702+
)
56775703
)
56785704

56795705
def add_constraint_mimic(

newton/_src/usd/utils.py

Lines changed: 68 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2128,7 +2128,13 @@ def _read_physics_attr(prim: Usd.Prim, name: str, compat_namespaces: Sequence[st
21282128

21292129

21302130
def _read_deformable_material(
2131-
prim: Usd.Prim, read_attr: Callable[[Usd.Prim, str], Any], api_schema: str, attr_names: Sequence[str]
2131+
prim: Usd.Prim,
2132+
read_attr: Callable[[Usd.Prim, str], Any],
2133+
api_schema: str,
2134+
attr_names: Sequence[str],
2135+
*,
2136+
attr_namespace: str = "physics",
2137+
material_prim: Usd.Prim | None = None,
21322138
) -> dict[str, float] | None:
21332139
"""Read a per-family deformable material's authored, in-range parameters bound to a prim.
21342140
@@ -2140,13 +2146,25 @@ def _read_deformable_material(
21402146
21412147
Returns a dict of the authored, in-range values among ``attr_names``, or ``None`` if the bound
21422148
material does not declare ``api_schema``; an applied API with no valid authored values returns
2143-
an empty dict. Stiffness and Young's modulus accept zero; thickness must be positive; density
2144-
must be positive to be returned, while zero is its ignored sentinel; and Poisson's ratio must
2145-
lie in ``(-1, 0.5]``. The ``-inf`` simulator-default sentinel used by stiffness, Young's modulus,
2146-
and thickness is silently dropped. Other out-of-range or non-finite values are dropped with a
2147-
warning.
2148-
"""
2149-
material_prim = _find_physics_material_prim(prim)
2149+
an empty dict. ``attr_namespace`` identifies the schema namespace in diagnostics; ``read_attr``
2150+
remains responsible for the actual namespace resolution. ``material_prim`` may supply an
2151+
already-resolved binding when one caller reads multiple APIs from the same material. Stiffness,
2152+
damping, and Young's modulus accept zero; thickness must be positive; density must be positive
2153+
to be returned, while zero is its ignored sentinel; and Poisson's ratio must lie in
2154+
``(-1, 0.5]``. The ``-inf`` simulator-default sentinel used by stiffness, damping, Young's
2155+
modulus, and thickness is silently dropped. Other out-of-range or non-finite values are dropped
2156+
with a warning.
2157+
2158+
Args:
2159+
prim: Prim whose bound physics material is resolved.
2160+
read_attr: Callable that reads an attribute from the bound material.
2161+
api_schema: Applied material API required on the bound material.
2162+
attr_names: Attribute names to read and validate.
2163+
attr_namespace: Namespace used when reporting invalid attributes.
2164+
material_prim: Previously resolved bound material, if available.
2165+
"""
2166+
if material_prim is None:
2167+
material_prim = _find_physics_material_prim(prim)
21502168
if material_prim is None or not has_applied_api_schema(material_prim, api_schema):
21512169
return None
21522170
out: dict[str, float] = {}
@@ -2161,12 +2179,12 @@ def _read_deformable_material(
21612179
if not math.isfinite(val):
21622180
expected = "a finite value or the -inf sentinel" if has_negative_infinity_sentinel else "a finite value"
21632181
warnings.warn(
2164-
f"{material_prim.GetPath()}: invalid physics:{name} {val:g} (expected {expected}); "
2182+
f"{material_prim.GetPath()}: invalid {attr_namespace}:{name} {val:g} (expected {expected}); "
21652183
f"treating it as unauthored.",
21662184
stacklevel=2,
21672185
)
21682186
continue
2169-
# Stiffness and Young's modulus accept [0, inf), so an authored zero is preserved.
2187+
# Stiffness, damping, and Young's modulus accept [0, inf), so an authored zero is preserved.
21702188
# Thickness and density must be strictly positive.
21712189
if name in ("thickness", "curvesThickness", "density"):
21722190
if val > 0.0:
@@ -2179,7 +2197,7 @@ def _read_deformable_material(
21792197
# unauthored sentinel (-inf); say it is dropped so users can tell it apart
21802198
# from an unauthored value.
21812199
warnings.warn(
2182-
f"{material_prim.GetPath()}: invalid physics:{name} {val:g} (expected > 0); "
2200+
f"{material_prim.GetPath()}: invalid {attr_namespace}:{name} {val:g} (expected > 0); "
21832201
f"treating it as unauthored.",
21842202
stacklevel=2,
21852203
)
@@ -2188,32 +2206,50 @@ def _read_deformable_material(
21882206
out[name] = val
21892207
else:
21902208
warnings.warn(
2191-
f"{material_prim.GetPath()}: invalid physics:{name} {val:g} "
2209+
f"{material_prim.GetPath()}: invalid {attr_namespace}:{name} {val:g} "
21922210
f"(expected -1 < value <= 0.5); treating it as unauthored.",
21932211
stacklevel=2,
21942212
)
21952213
elif val >= 0.0:
21962214
out[name] = val
21972215
else:
21982216
warnings.warn(
2199-
f"{material_prim.GetPath()}: invalid physics:{name} {val:g} "
2217+
f"{material_prim.GetPath()}: invalid {attr_namespace}:{name} {val:g} "
22002218
f"(expected >= 0); treating it as unauthored.",
22012219
stacklevel=2,
22022220
)
22032221
return out
22042222

22052223

2224+
_NEWTON_CURVE_DAMPING_ATTRS = (
2225+
"curvesStretchDamping",
2226+
"curvesShearDamping",
2227+
"curvesBendDamping",
2228+
"curvesTwistDamping",
2229+
)
2230+
2231+
22062232
def _get_curve_deformable_material(
22072233
prim: Usd.Prim, read_attr: Callable[[Usd.Prim, str], Any]
22082234
) -> dict[str, float] | None:
22092235
"""Read curve-deformable (cable) ``PhysicsCurvesDeformableMaterialAPI`` parameters bound to a prim.
22102236
22112237
Returns a dict of authored, in-range values from the current AOUSD curve material proposal,
2212-
plus the earlier unprefixed material attributes during their deprecation window; or ``None``
2213-
if the bound material does not declare ``PhysicsCurvesDeformableMaterialAPI``. See
2214-
:func:`_read_deformable_material` for value-validation rules.
2238+
the earlier unprefixed material attributes during their deprecation window, and the four
2239+
per-mode ``newton:curves*Damping`` values when ``NewtonCurvesDeformableMaterialAPI`` is also
2240+
applied. Returns ``None`` if the bound material does not declare
2241+
``PhysicsCurvesDeformableMaterialAPI``. See :func:`_read_deformable_material` for
2242+
value-validation rules.
2243+
2244+
Args:
2245+
prim: Curve prim whose bound physics material is read.
2246+
read_attr: Callable that reads an AOUSD attribute from the bound material.
22152247
"""
2216-
return _read_deformable_material(
2248+
material_prim = _find_physics_material_prim(prim)
2249+
if material_prim is None:
2250+
return None
2251+
2252+
material = _read_deformable_material(
22172253
prim,
22182254
read_attr,
22192255
"PhysicsCurvesDeformableMaterialAPI",
@@ -2233,7 +2269,22 @@ def _get_curve_deformable_material(
22332269
"bendStiffness",
22342270
"twistStiffness",
22352271
),
2272+
material_prim=material_prim,
2273+
)
2274+
if material is None:
2275+
return None
2276+
2277+
newton_damping = _read_deformable_material(
2278+
prim,
2279+
lambda mat_prim, name: get_attribute(mat_prim, f"newton:{name}"),
2280+
"NewtonCurvesDeformableMaterialAPI",
2281+
_NEWTON_CURVE_DAMPING_ATTRS,
2282+
attr_namespace="newton",
2283+
material_prim=material_prim,
22362284
)
2285+
if newton_damping is not None:
2286+
material.update(newton_damping)
2287+
return material
22372288

22382289

22392290
def _get_surface_deformable_material(

0 commit comments

Comments
 (0)