Skip to content
Draft
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
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
# Unreleased

## Features

- Added `NewtonVbdSceneAPI`, which extends `NewtonSceneAPI` with configuration for Newton's VBD (Vertex Block Descent) solver.
- Exposes a minimal rigid/AVBD attribute set under `newton:vbd:rigid:` (`contactHistory`, joint stiffness, joint damping).
- Applying `NewtonVbdSceneAPI` implicitly applies `NewtonSceneAPI` as well.

# 0.3.1

## Fixes
Expand Down
58 changes: 58 additions & 0 deletions newton_usd_schemas/generatedSchema.usda
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,64 @@ class "NewtonKaminoSceneAPI" (
)
}

class "NewtonVbdSceneAPI" (
apiSchemas = ["NewtonSceneAPI"]
doc = "Provides Newton's VBD (Vertex Block Descent) solver configuration."
)
{
uniform bool newton:vbd:rigid:contactHistory = false (
doc = """Whether to persist body-body contact state across steps for warm-starting."""
)

uniform float newton:vbd:rigid:jointLinearStiffness = 100000.0 (
doc = """Penalty stiffness ceiling for structural linear joint constraints.

Range: [0, inf)
Units: force / distance"""
limits = {
dictionary hard = {
float minimum = 0
}
}
)

uniform float newton:vbd:rigid:jointAngularStiffness = 100000.0 (
doc = """Penalty stiffness ceiling for structural angular joint constraints.

Range: [0, inf)
Units: torque / radian"""
limits = {
dictionary hard = {
float minimum = 0
}
}
)

uniform float newton:vbd:rigid:jointLinearDamping = 0.0 (
doc = """Absolute damping coefficient for linear joint constraints.

Range: [0, inf)
Units: force * time / distance"""
limits = {
dictionary hard = {
float minimum = 0
}
}
)

uniform float newton:vbd:rigid:jointAngularDamping = 0.0 (
doc = """Absolute damping coefficient for angular joint constraints.

Range: [0, inf)
Units: torque * time / radian"""
limits = {
dictionary hard = {
float minimum = 0
}
}
)
}

class NewtonArticulationRootAPI "NewtonArticulationRootAPI" (
apiSchemas = ["PhysicsArticulationRootAPI"]
doc = """NewtonArticulationRootAPI extends the `PhysicsArticulationRootAPI` with additional attributes for Newton.
Expand Down
14 changes: 14 additions & 0 deletions newton_usd_schemas/plugInfo.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,20 @@
"PhysicsScene"
]
},
"NewtonPhysicsVbdSceneAPI": {
"schemaIdentifier": "NewtonVbdSceneAPI",
"alias": {
"UsdSchemaBase": "NewtonVbdSceneAPI"
},
"autoGenerated": false,
"bases": [
"UsdAPISchemaBase"
],
"schemaKind": "singleApplyAPI",
"apiSchemaCanOnlyApplyTo": [
"PhysicsScene"
]
},
"NewtonPhysicsArticulationRootAPI": {
"schemaIdentifier": "NewtonArticulationRootAPI",
"alias": {
Expand Down
106 changes: 106 additions & 0 deletions tests/test_scene_solver_vbd.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 The Newton Developers
# SPDX-License-Identifier: Apache-2.0

import unittest

from pxr import Plug, Usd, UsdPhysics

import newton_usd_schemas # noqa: F401

USD_HAS_LIMITS = Usd.GetVersion() >= (0, 25, 11)


class TestNewtonVbdSceneAPI(unittest.TestCase):
def setUp(self):
self.stage: Usd.Stage = Usd.Stage.CreateInMemory()
self.scene: Usd.Prim = UsdPhysics.Scene.Define(self.stage, "/Scene").GetPrim()

def test_api_registered(self):
plug_type = Plug.Registry().FindTypeByName("NewtonPhysicsVbdSceneAPI")
self.assertEqual(plug_type.typeName, "NewtonPhysicsVbdSceneAPI")
schema_type = Usd.SchemaRegistry().GetSchemaTypeName("NewtonPhysicsVbdSceneAPI")
self.assertEqual(schema_type, "NewtonVbdSceneAPI")

def test_api_application(self):
self.scene.ApplyAPI("NewtonVbdSceneAPI")
self.assertTrue(self.scene.HasAPI("NewtonSceneAPI"))
self.assertTrue(self.scene.HasAPI("NewtonVbdSceneAPI"))

def test_api_limitations(self):
prim: Usd.Prim = self.stage.DefinePrim("/NotScene", "Xform")
self.assertFalse(prim.CanApplyAPI("NewtonVbdSceneAPI"))

# -- rigid / AVBD -------------------------------------------------------

def test_rigid_contact_history(self):
self.scene.ApplyAPI("NewtonVbdSceneAPI")
attr = self.scene.GetAttribute("newton:vbd:rigid:contactHistory")
self.assertIsNotNone(attr)
self.assertEqual(attr.Get(), False)

self.assertTrue(attr.Set(True))
self.assertEqual(attr.Get(), True)

def test_rigid_joint_linear_stiffness(self):
self.scene.ApplyAPI("NewtonVbdSceneAPI")
attr = self.scene.GetAttribute("newton:vbd:rigid:jointLinearStiffness")
self.assertIsNotNone(attr)
self.assertAlmostEqual(attr.Get(), 100000.0, places=1)

self.assertTrue(attr.Set(50000.0))
self.assertAlmostEqual(attr.Get(), 50000.0, places=1)

if USD_HAS_LIMITS:
hard = attr.GetHardLimits()
self.assertTrue(hard.IsValid())
self.assertAlmostEqual(hard.GetMinimum(), 0.0)
self.assertIsNone(hard.GetMaximum())

def test_rigid_joint_angular_stiffness(self):
self.scene.ApplyAPI("NewtonVbdSceneAPI")
attr = self.scene.GetAttribute("newton:vbd:rigid:jointAngularStiffness")
self.assertIsNotNone(attr)
self.assertAlmostEqual(attr.Get(), 100000.0, places=1)

self.assertTrue(attr.Set(50000.0))
self.assertAlmostEqual(attr.Get(), 50000.0, places=1)

if USD_HAS_LIMITS:
hard = attr.GetHardLimits()
self.assertTrue(hard.IsValid())
self.assertAlmostEqual(hard.GetMinimum(), 0.0)
self.assertIsNone(hard.GetMaximum())

def test_rigid_joint_linear_damping(self):
self.scene.ApplyAPI("NewtonVbdSceneAPI")
attr = self.scene.GetAttribute("newton:vbd:rigid:jointLinearDamping")
self.assertIsNotNone(attr)
self.assertAlmostEqual(attr.Get(), 0.0, places=7)

self.assertTrue(attr.Set(1.0))
self.assertAlmostEqual(attr.Get(), 1.0, places=7)

if USD_HAS_LIMITS:
hard = attr.GetHardLimits()
self.assertTrue(hard.IsValid())
self.assertAlmostEqual(hard.GetMinimum(), 0.0)
self.assertIsNone(hard.GetMaximum())

def test_rigid_joint_angular_damping(self):
self.scene.ApplyAPI("NewtonVbdSceneAPI")
attr = self.scene.GetAttribute("newton:vbd:rigid:jointAngularDamping")
self.assertIsNotNone(attr)
self.assertAlmostEqual(attr.Get(), 0.0, places=7)

self.assertTrue(attr.Set(1.0))
self.assertAlmostEqual(attr.Get(), 1.0, places=7)

if USD_HAS_LIMITS:
hard = attr.GetHardLimits()
self.assertTrue(hard.IsValid())
self.assertAlmostEqual(hard.GetMinimum(), 0.0)
self.assertIsNone(hard.GetMaximum())


if __name__ == "__main__":
unittest.main()