Deformable Objects#
Each deformable object has a label, a world index, and ranges identifying its simulation elements. The families describe geometry: curves, surfaces, and volumes. Native construction and supported USD imports record these objects on the builder. Finalization exposes their labels, worlds, and ranges on the model.
The sections below explain what each family records. See USD Parsing and Schema Resolver System for supported USD schemas, materials, attachments, and import limitations. A deformable visual mesh can follow a recorded object for rendering without adding simulation elements.
Builder labels and worlds#
Experimental
The builder’s curve_label / curve_world, surface_label /
surface_world, and volume_label / volume_world lists may change
without the normal deprecation period.
Each pair contains one label and world index per deformable object. Native
construction records explicit labels or generates names such as curve_0,
surface_0, and volume_0. USD imports use the simulation prim’s path, or
the graph identifier for welded curves. Labels may repeat. An index in these
lists identifies a deformable object, not a body or particle.
Labels are editable. The label and world lists must remain aligned with the recorded deformable objects, so entries must not be appended, removed, or reordered manually.
The builder assigns world indices during construction and cloning.
World -1 identifies global deformable objects.
Use begin_world() / end_world(),
add_world(), or replicate()
to assign worlds when creating or cloning deformable objects. Do not edit the
world lists: changing an entry does not move its simulation elements.
Combining builders#
add_builder(), add_world(),
and replicate() preserve the records and offset their
element ranges. Label prefixes also apply to deformable labels. The builder’s
range lists remain private; use the public model arrays to inspect finalized ranges.
Model labels and ranges#
Experimental
The model’s curve_*, surface_*, and volume_* label, world,
count, and range attributes may change without the normal deprecation period.
finalize() copies the labels, worlds, and simulation
ranges to public Model attributes without reordering the
builder’s lists. Within a family, the label, world index, and element ranges
at index i describe the same deformable object. Later builder edits do not change
an existing model. Treat model labels, worlds, and ranges as read-only.
Labels are Python lists. Worlds and range endpoints are one-dimensional
int32 Warp arrays on the model device. Each list or array has one entry per
deformable object in that family. Empty families have empty lists and arrays.
World -1 still means global.
Each range has an inclusive start and an exclusive end. The notation
start/end below denotes two attributes, such as surface_particle_start
and surface_particle_end. Unlike articulation_start,
the start arrays have no extra final entry. Separate ends allow gaps between
deformable objects and empty ranges.
This follows the label/world pattern of articulation_label
and articulation_world. Model attributes describe every
recorded deformable object. The deformable selection views
select matching deformable objects and read or update their state.
Coupled solver models#
Compact coupled-solver models retain a deformable object only when all its recorded elements remain in contiguous ranges in their original order. A range may move to a different position in the compact model. A new FREE root does not count as the original joint it replaces. The ranges refer to the compact model’s arrays, not the parent model’s arrays. Particle-based deformables follow the coupled solver’s existing particle layout. Unlike builder fixed-joint collapse, a solver partition does not retain partially included curves.
Curves#
Curves currently use rods: rigid segment bodies connected by joints.
Each add_rod() call records one deformable object.
The builder stores its label and world in curve_label and curve_world.
The model exposes curve_label, curve_world, and curve_count.
Its curve_body_start/end and curve_joint_start/end arrays identify
each curve’s bodies and joints.
For example, rename a prototype cable and clone it into two worlds:
import newton
prototype = newton.ModelBuilder()
prototype.add_rod(
rod=newton.Rod([(0.0, 0.0, 1.0), (0.1, 0.0, 1.0), (0.2, 0.0, 1.0)], radius=0.02),
label="cable",
)
# Replace a template name with the application's asset name.
prototype.curve_label[0] = "gripper_cable"
scene = newton.ModelBuilder()
scene.replicate(prototype, 2, label_prefixes=["env_0", "env_1"])
print(scene.curve_label)
print(scene.curve_world)
['env_0/gripper_cable', 'env_1/gripper_cable']
[0, 1]
Finalize the scene and inspect the cables without constructing a view:
model = scene.finalize(device="cpu")
print("curve_label:", model.curve_label)
print("curve_world:", model.curve_world.numpy().tolist())
print("curve_body_start:", model.curve_body_start.numpy().tolist())
print("curve_body_end:", model.curve_body_end.numpy().tolist())
curve_label: ['env_0/gripper_cable', 'env_1/gripper_cable']
curve_world: [0, 1]
curve_body_start: [0, 2]
curve_body_end: [2, 4]
The example uses device="cpu" so it also runs without a GPU. The same
model attributes are available with device="cuda:0".
Curve index 1 identifies the second cable, env_1/gripper_cable.
Entry 1 of curve_world is 1, so this cable belongs to world 1.
Its body range starts at 2 and ends at 4. The end is excluded, so the
range contains body indices 2 and 3:
curve_index = 1
body_start = int(model.curve_body_start.numpy()[curve_index])
body_end = int(model.curve_body_end.numpy()[curve_index])
print(model.curve_label[curve_index])
print(list(range(body_start, body_end)))
env_1/gripper_cable
[2, 3]
Curve indices identify deformable objects. Body indices identify the rigid segments in the model’s body arrays. Do not use one in place of the other.
USD cable imports record one object per simulation BasisCurves prim,
including a prim containing several disconnected curves. Curves welded into
a graph share one record instead; see Rod graphs.
Native and USD-created curves use the same model attributes and
DeformableCurveView.
Curve joint ranges#
curve_joint_start and curve_joint_end
store each curve’s [start, end) joint range. The range includes the joints
created with the deformable object.
Native rods and USD cables include each generated free root or the
attachment root created in its place. Earlier rigid-body joints and independent
attachments added later are outside the range. These complete ranges differ
from the rod-joint lists returned by add_rod() and
USD’s path_cable_map, which exclude roots.
Fixed-joint collapse#
Labels do not affect collapse_fixed_joints(). Curve
records retain their labels and describe the surviving segment bodies and joints.
A curve with a remaining body is retained even if its joint range is empty.
Newton warns and omits a curve only when none of its segment bodies survive.
Use joints_to_keep to preserve the original segment layout. Particle,
triangle, and tetrahedron ranges are not affected by fixed-joint collapse.
Rod graphs#
A Rod with explicit edges can describe a graph with shared
junctions. Pass it to add_rod() with rod=....
One call records the complete graph, not separate objects for its branches.
A welded USD graph uses the same construction path. Its record includes all created segment bodies and joints, including generated roots. The source curves are not recorded as separate deformable objects.
The graph’s label is the existing graph_component identifier in
path_cable_attrs. This identifier reuses one source prim’s path but labels
the whole graph, not just that curve. Read it from the import result rather
than assuming which source path is chosen.
The return maps still describe each source curve. path_cable_map keeps its
segment body indices and an empty joint list; path_cable_attrs keeps its
material and graph identifier. Per-curve joint membership, including shared
joints, remains follow-up work. This does not limit the whole graph’s record.
result = builder.add_usd("harness.usda", return_deformable_results=True)
branch_path = "/World/Branch"
graph_label = result["path_cable_attrs"][branch_path]["graph_component"]
graph_index = builder.curve_label.index(graph_label)
graph_world = builder.curve_world[graph_index] # The complete welded graph.
branch_bodies, branch_joints = result["path_cable_map"][branch_path]
# branch_bodies contains only the branch's segments; branch_joints is [].
Surfaces#
A triangle surface contains particles, triangles, and bending edges.
add_cloth_mesh() and
add_cloth_grid() each record one surface object.
The builder stores its label and world in surface_label and surface_world.
The model exposes surface_label, surface_world, and surface_count.
Its ranges are surface_particle_start/end, surface_tri_start/end,
and surface_edge_start/end. A surface index identifies the complete cloth,
not an individual particle or triangle.
Supported USD cloth imports record each simulation Mesh prim once,
using its prim path as the label. Native and USD-created cloths use the same
model attributes and DeformableSurfaceView.
Volumes#
A tetrahedral volume contains particles and tetrahedra.
add_soft_mesh() and
add_soft_grid() each record one volume object.
The builder stores its label and world in volume_label and volume_world.
The model exposes volume_label, volume_world, and volume_count.
Its ranges are volume_particle_start/end and volume_tet_start/end.
A volume index identifies the complete soft body, not an individual particle
or tetrahedron.
Supported USD volume imports record each simulation TetMesh prim once,
using its prim path as the label. Native and USD-created volumes use the same
model attributes and DeformableVolumeView.