Skip to content
Merged
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
9 changes: 9 additions & 0 deletions docs/source/how-to/cloning.rst
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,15 @@ visuals, PhysX's native replicator for rigid bodies and articulations, Newton's
world system for its parallel pipeline. The same plan drives all of them, so user
code never branches on the backend.

Newton startup requires a builder; it no longer imports the USD stage implicitly.
``InteractiveScene`` handles planning and replication internally; use it for maintained
demos, tutorials, and asset previews. The explicit cloner examples below are for tests
and code that teaches the cloner API. Native tools can instead supply a builder with
``NewtonManager.set_builder(builder)``.

Require a plan where a consumer uses it, not merely because simulation initializes.
Empty PhysX simulations and tools that supply a native Newton builder need no dummy plan.

ClonePlan
~~~~~~~~~

Expand Down
25 changes: 12 additions & 13 deletions docs/source/how-to/run_deformable_object.rst
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ The tutorial corresponds to the ``run_deformable_object.py`` script in the ``scr

.. literalinclude:: ../../../scripts/tutorials/01_assets/run_deformable_object.py
:language: python
:emphasize-lines: 71-117, 146-151, 153-162, 167-175, 177-178, 184-189
:emphasize-lines: 88-112, 135-151, 156-164, 173-178, 191-192
:linenos:


Expand All @@ -62,10 +62,10 @@ The Code Explained
Designing the scene
-------------------

Similar to the :ref:`tutorial-interact-rigid-object` tutorial, we populate the scene with a ground plane
and a light source. In addition, we add a deformable object to the scene using the :class:`assets.DeformableObject`
class. This class is responsible for spawning the prims at the input path and initializes their corresponding
deformable body physics handles.
We declare the ground plane, light, and deformable cube in a subclass of :class:`scene.InteractiveSceneCfg`.
:class:`scene.InteractiveScene` constructs the assets and handles replication internally.
``DeformableSceneCfg(num_envs=4, env_spacing=0.5)`` selects four environment origins on a centered grid
with 0.5 m spacing.

In this tutorial, we create a cubical soft object using the spawn configuration similar to the deformable cube
in the :ref:`Spawn Objects <tutorial-spawn-prims>` tutorial. The only difference is that now we wrap
Expand All @@ -81,13 +81,13 @@ when the simulation is played.
implementation.


As seen in the rigid body tutorial, we can spawn the deformable object into the scene in a similar fashion by creating
an instance of the :class:`assets.DeformableObject` class by passing the configuration object to its constructor.
The scene constructs the deformable objects from their cfg and owns their clone lifecycle.
The simulation loop accesses the resulting asset through ``scene["cube_object"]``.

.. literalinclude:: ../../../scripts/tutorials/01_assets/run_deformable_object.py
:language: python
:start-at: # Create separate groups called "env_0", "env_1", ...
:end-at: cube_object = DeformableObject(cfg=cfg)
:start-at: @configclass
:end-before: def run_simulator(

Running the simulation loop
---------------------------
Expand All @@ -104,7 +104,7 @@ are defined in the **simulation world frame** and are stored in the :attr:`asset

We use the :attr:`assets.DeformableObject.data.default_nodal_state_w` attribute to get the default nodal state of the
spawned object prims. This default state can be configured from the :attr:`assets.DeformableObjectCfg.init_state`
attribute, which we left as identity in this tutorial.
attribute, which places the cube 1 m above its environment origin in this tutorial.

.. attention::
The initial state in the configuration :attr:`assets.DeformableObjectCfg` specifies the pose
Expand Down Expand Up @@ -148,7 +148,7 @@ method.

.. literalinclude:: ../../../scripts/tutorials/01_assets/run_deformable_object.py
:language: python
:start-at: # update the kinematic target for cubes at index 0 and 3
:start-at: # update the kinematic target for cubes at the positive and negative diagonal corners
:end-at: cube_object.write_nodal_kinematic_target_to_sim_index(nodal_kinematic_target)

Similar to the rigid object and articulation, we perform the :meth:`assets.DeformableObject.write_data_to_sim` method
Expand Down Expand Up @@ -226,8 +226,7 @@ To stop the simulation, you can either close the window, or press ``Ctrl+C`` in
This tutorial showed how to spawn deformable objects and wrap them in a :class:`DeformableObject` class to initialize their
physics handles which allows setting and obtaining their state. We also saw how to apply kinematic commands to the
deformable object to move the mesh nodes in a controlled manner. The ``deformables`` example provides a more advanced
example, including surface deformables, loading USD assets, and applying deformable materials. In the next tutorial, we will see how to create
a scene using the :class:`InteractiveScene` class.
example, including surface deformables, loading USD assets, and applying deformable materials.

.. _PhysX documentation: https://nvidia-omniverse.github.io/PhysX/physx/5.4.1/docs/SoftBodies.html
.. _partial kinematic: https://nvidia-omniverse.github.io/PhysX/physx/5.4.1/docs/SoftBodies.html#kinematic-soft-bodies
133 changes: 67 additions & 66 deletions examples/cables.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,73 +38,74 @@
parser.error("--num_segments must be at least 2.")

import isaaclab.sim as sim_utils
from isaaclab.assets import AssetBaseCfg, CableObjectCfg
from isaaclab.cloner import CloneCfg
from isaaclab.physics import PhysicsCfg
from isaaclab.scene import InteractiveSceneCfg
from isaaclab.utils import configclass

if TYPE_CHECKING:
from isaaclab.assets import CableObject
from isaaclab.scene import InteractiveScene


def design_scene(num_cables: int, num_segments: int, colorize: bool) -> dict[str, CableObject]:
"""Spawn a ground plane, light, and randomly oriented cable pile.

Args:
num_cables: Number of cables to spawn.
num_segments: Number of segments per cable.
colorize: Whether to give each cable a random visual material.
"""
from isaaclab.assets import CableObject, CableObjectCfg

ground_cfg = sim_utils.GroundPlaneCfg()
ground_cfg.func("/World/defaultGroundPlane", ground_cfg)
light_cfg = sim_utils.DomeLightCfg(intensity=3000.0, color=(0.75, 0.75, 0.75))
light_cfg.func("/World/light", light_cfg)

cable_length = 0.5
segment_length = cable_length / num_segments
thickness = 0.01
radius = 0.5 * thickness
target_stretch_stiffness = 5.0e5 # [N/m] per joint
target_bend_stiffness = 20.0 # [N.m/rad] per joint
stretch_modulus = target_stretch_stiffness * segment_length / (math.pi * radius**2)
bend_modulus = target_bend_stiffness * segment_length / (0.25 * math.pi * radius**4)
xy_jitter = 0.3
z_spacing = 1.5 * thickness
z_base = 0.8
positions = [(index * segment_length, 0.0, 0.0) for index in range(num_segments + 1)]

print(f"[INFO]: Spawning {num_cables} cables...")
entities: dict[str, CableObject] = {}
for index in range(num_cables):
angle = random.uniform(0.0, 2.0 * math.pi)
position = (
random.uniform(-xy_jitter, xy_jitter) - 0.5 * cable_length * math.cos(angle),
random.uniform(-xy_jitter, xy_jitter) - 0.5 * cable_length * math.sin(angle),
z_base + index * z_spacing,
)
orientation = (0.0, 0.0, math.sin(0.5 * angle), math.cos(0.5 * angle))
visual_material = None
if colorize:
visual_material = sim_utils.PreviewSurfaceCfg(
diffuse_color=(random.random(), random.random(), random.random())
@configclass
class CablesSceneCfg(InteractiveSceneCfg):
"""A randomized cable pile with ground and lighting."""

clone_cfg = CloneCfg(clone_template="/World/Env_{}")

ground = AssetBaseCfg(prim_path="/World/defaultGroundPlane", spawn=sim_utils.GroundPlaneCfg())
light = AssetBaseCfg(
prim_path="/World/light", spawn=sim_utils.DomeLightCfg(intensity=3000.0, color=(0.75, 0.75, 0.75))
)

def __post_init__(self):
"""Configure cable geometry and sample the initial pile."""
colorize = bool(args_cli.visualizer and "kit" in args_cli.visualizer)
cable_length = 0.5
segment_length = cable_length / args_cli.num_segments
thickness = 0.01
radius = 0.5 * thickness
target_stretch_stiffness = 5.0e5 # [N/m] per joint
target_bend_stiffness = 20.0 # [N.m/rad] per joint
stretch_modulus = target_stretch_stiffness * segment_length / (math.pi * radius**2)
bend_modulus = target_bend_stiffness * segment_length / (0.25 * math.pi * radius**4)
xy_jitter = 0.3
z_spacing = 1.5 * thickness
z_base = 0.8
positions = [(index * segment_length, 0.0, 0.0) for index in range(args_cli.num_segments + 1)]

print(f"[INFO]: Spawning {args_cli.num_cables} cables...")
for index in range(args_cli.num_cables):
angle = random.uniform(0.0, 2.0 * math.pi)
position = (
random.uniform(-xy_jitter, xy_jitter) - 0.5 * cable_length * math.cos(angle),
random.uniform(-xy_jitter, xy_jitter) - 0.5 * cable_length * math.sin(angle),
z_base + index * z_spacing,
)
cfg = CableObjectCfg(
prim_path=f"/World/Env_0/Cable{index:03d}",
spawn=sim_utils.CableCfg(
positions=positions,
visual_material=visual_material,
physics_material=sim_utils.CableMaterialCfg(
thickness=thickness,
density=100.0,
stretch_stiffness=stretch_modulus,
bend_stiffness=bend_modulus,
orientation = (0.0, 0.0, math.sin(0.5 * angle), math.cos(0.5 * angle))
visual_material = None
if colorize:
visual_material = sim_utils.PreviewSurfaceCfg(
diffuse_color=(random.random(), random.random(), random.random())
)
cfg = CableObjectCfg(
prim_path=f"{{ENV_REGEX_NS}}/Cable{index:03d}",
spawn=sim_utils.CableCfg(
positions=positions,
visual_material=visual_material,
physics_material=sim_utils.CableMaterialCfg(
thickness=thickness,
density=100.0,
stretch_stiffness=stretch_modulus,
bend_stiffness=bend_modulus,
),
collision_props=[sim_utils.UsdPhysicsCollisionCfg(collision_enabled=True)],
),
collision_props=[sim_utils.UsdPhysicsCollisionCfg(collision_enabled=True)],
),
init_state=CableObjectCfg.InitialStateCfg(pos=position, rot=orientation),
)
entities[f"cable_{index:03d}"] = CableObject(cfg=cfg)

return entities
init_state=CableObjectCfg.InitialStateCfg(pos=position, rot=orientation),
)
setattr(self, f"cable_{index:03d}", cfg)


def reset_cables(entities: dict[str, CableObject]) -> None:
Expand All @@ -114,20 +115,19 @@ def reset_cables(entities: dict[str, CableObject]) -> None:
cable.write_segment_velocity_to_sim_index(segment_velocity=cable.data.default_segment_velocity_w)


def run_simulator(sim: sim_utils.SimulationContext, entities: dict[str, CableObject], max_steps: int = -1) -> None:
def run_simulator(sim: sim_utils.SimulationContext, scene: InteractiveScene, max_steps: int = -1) -> None:
"""Run the simulation and periodically restore the cable pile."""
sim_dt = sim.get_physics_dt()
reset_steps = max(1, int(2.0 / sim_dt))
count = 0

while (max_steps < 0 or count < max_steps) and sim.is_headless_or_exist_active_visualizer():
if count > 0 and count % reset_steps == 0:
reset_cables(entities)
reset_cables(scene.cable_objects)
print("[INFO]: Resetting cable state...")

sim.step(render=False)
for cable in entities.values():
cable.update(sim_dt)
scene.update(sim_dt)
if sim.is_rendering:
sim.render()
count += 1
Expand All @@ -141,11 +141,12 @@ def main() -> None:
sim_cfg = sim_utils.SimulationCfg(dt=0.01, device=args_cli.device, physics=physics_cfg)
sim = sim_utils.SimulationContext(sim_cfg)
sim.set_camera_view(eye=(2.0, 2.0, 1.0), target=(0.0, 0.0, 0.25))
colorize = bool(args_cli.visualizer and "kit" in args_cli.visualizer)
entities = design_scene(args_cli.num_cables, args_cli.num_segments, colorize)

scene_cfg = CablesSceneCfg(num_envs=1, env_spacing=0.0)
scene = scene_cfg.class_type(scene_cfg)
sim.reset()
print("[INFO]: Setup complete...")
run_simulator(sim, entities, args_cli.max_steps)
run_simulator(sim, scene, args_cli.max_steps)


if __name__ == "__main__":
Expand Down
Loading
Loading