Sorry this took way longer than what I expected. I have created an example code that simulated a winch for you to try. I am basically creating a chain of capsules to simulate the rope and you can play with how granular you want the chains to be. I am attaching the end of the rope to a rotating cylinder to simulate the winch mechanism. The example provided with Physics Examples creates the rope with a PointInstancer, but I find it easier to make your own script to instantiate various shapes.
Here is the code I made:
#!/usr/bin/env python3
# Copyright (c) 2022-2024, NVIDIA CORPORATION. All rights reserved.
#
# NVIDIA CORPORATION and its licensors retain all intellectual property
# and proprietary rights in and to this software, related documentation
# and any modifications thereto. Any use, reproduction, disclosure or
# distribution of this software and related documentation without an express
# license agreement from NVIDIA CORPORATION is strictly prohibited.
"""
This script demonstrates how to create a cable/rope mechanism using individual prims.
The rope is made of rigid body capsules connected with spherical joints.
"""
from isaacsim import SimulationApp
# Launch Isaac Sim
simulation_app = SimulationApp({"headless": False})
import time
from pxr import Usd, UsdGeom, UsdPhysics, Gf, Sdf, PhysxSchema, UsdShade, UsdLux
from omni.physx.scripts import physicsUtils
from omni.timeline import get_timeline_interface
from isaacsim.core.api import World
from isaacsim.core.utils.prims import get_prim_at_path
# Set Light stage to Grey Studio
stage = simulation_app.context.get_stage()
light_stage = stage.GetPrimAtPath("/Environment")
if not light_stage:
light_stage = stage.DefinePrim("/Environment", "Xform")
# Create Grey Studio lighting environment
dome_light = UsdLux.DomeLight.Define(stage, "/Environment/GreyStudio")
dome_light.CreateIntensityAttr().Set(1000.0)
class RopeCreator:
def __init__(self, stage, default_prim_path, physics_material_path, pivot_point=None,
link_half_length=0.1, rope_length=2.0, num_ropes=1, rope_spacing=0.1,
rope_color=None, rope_damping=2.0, rope_stiffness=0.0, contact_offset=0.01):
self.stage = stage
self.default_prim_path = default_prim_path
self.physics_material_path = physics_material_path
self.pivot_point = pivot_point if pivot_point is not None else Gf.Vec3f(0.0, 0.0, 1.0)
# Configure rope parameters
self.link_half_length = link_half_length
self.link_radius = 0.5 * self.link_half_length
self.rope_length = rope_length
self.num_ropes = num_ropes
self.rope_spacing = rope_spacing
self.rope_color = rope_color if rope_color is not None else Gf.Vec3f(0.2, 0.6, 0.8) # Blue-ish color
self.rope_damping = rope_damping
self.rope_stiffness = rope_stiffness
self.contact_offset = contact_offset
def create_capsule(self, path, position):
capsule_geom = UsdGeom.Capsule.Define(self.stage, path)
capsule_geom.CreateHeightAttr(self.link_half_length)
capsule_geom.CreateRadiusAttr(self.link_radius)
capsule_geom.CreateAxisAttr("X")
capsule_geom.CreateDisplayColorAttr().Set([self.rope_color])
# Set position
capsule_geom.AddTranslateOp().Set(position)
UsdPhysics.CollisionAPI.Apply(capsule_geom.GetPrim())
UsdPhysics.RigidBodyAPI.Apply(capsule_geom.GetPrim())
mass_api = UsdPhysics.MassAPI.Apply(capsule_geom.GetPrim())
mass_api.CreateDensityAttr().Set(0.00005)
physx_collision_api = PhysxSchema.PhysxCollisionAPI.Apply(capsule_geom.GetPrim())
physx_collision_api.CreateRestOffsetAttr().Set(0.0)
physx_collision_api.CreateContactOffsetAttr().Set(self.contact_offset)
physicsUtils.add_physics_material_to_prim(self.stage, capsule_geom.GetPrim(), self.physics_material_path)
return capsule_geom
def create_joint(self, joint_path, body0_path, body1_path, local_pos0, local_pos1):
# Create a spherical joint
joint = UsdPhysics.SphericalJoint.Define(self.stage, joint_path)
# Get the joint prim
joint_prim = joint.GetPrim()
# Set the bodies to connect
joint.GetBody0Rel().SetTargets([body0_path])
joint.GetBody1Rel().SetTargets([body1_path])
# Set local positions
joint.CreateLocalPos0Attr().Set(local_pos0)
joint.CreateLocalPos1Attr().Set(local_pos1)
# Set local rotations (identity quaternions)
joint.CreateLocalRot0Attr().Set(Gf.Quatf(1.0))
joint.CreateLocalRot1Attr().Set(Gf.Quatf(1.0))
# Add joint drives for rope dynamics
drive_api = UsdPhysics.DriveAPI.Apply(joint_prim, "angular")
drive_api.CreateTypeAttr("force")
drive_api.CreateDampingAttr(self.rope_damping)
drive_api.CreateStiffnessAttr(self.rope_stiffness)
return joint
def create_ropes(self):
# Calculate spacing and dimensions
link_length = 1.0 * self.link_half_length
num_links = int(self.rope_length / link_length)
y_start = -(self.num_ropes // 2) * self.rope_spacing
for rope_ind in range(self.num_ropes):
scope_path = self.default_prim_path.AppendChild(f"Rope{rope_ind}")
UsdGeom.Scope.Define(self.stage, scope_path)
y = y_start + rope_ind * self.rope_spacing
z = self.pivot_point[2] # Use pivot point's z coordinate
capsule_prims = []
# Create all capsule links
for link_ind in range(num_links):
x = self.pivot_point[0] + link_ind * link_length # Start from pivot point's x coordinate
position = Gf.Vec3f(x, self.pivot_point[1] + y, z) # Add y offset for multiple ropes
capsule_path = scope_path.AppendChild(f"capsule_{link_ind}")
capsule_geom = self.create_capsule(capsule_path, position)
capsule_prims.append(capsule_path)
# Create joints between consecutive capsules
joint_x = self.link_half_length - 0.25 * self.link_radius
for link_ind in range(num_links - 1):
joint_path = scope_path.AppendChild(f"joint_{link_ind}")
body0_path = capsule_prims[link_ind]
body1_path = capsule_prims[link_ind + 1]
local_pos0 = Gf.Vec3f(joint_x, 0, 0)
local_pos1 = Gf.Vec3f(-joint_x, 0, 0)
self.create_joint(joint_path, body0_path, body1_path, local_pos0, local_pos1)
def create_cylinder(stage, parent_path, name, radius=0.5, height=1.0, position=None, rotation_y=90.0):
"""
Create a cylinder with physics properties and a revolute joint.
Args:
stage: The USD stage
parent_path: Path to the parent prim
name: Name for the cylinder prim
radius: Radius of the cylinder in meters
height: Height of the cylinder in meters
position: Position of the cylinder (Gf.Vec3f), defaults to (0, 0, 2)
rotation_y: Rotation around Y axis in degrees, defaults to 90
Returns:
tuple: (cylinder_path, joint_path) - Paths to the created cylinder and joint
"""
if position is None:
position = Gf.Vec3f(0.0, 0.0, 1.0)
# Create cylinder
cylinder_path = parent_path.AppendChild(name)
cylinder_geom = UsdGeom.Cylinder.Define(stage, cylinder_path)
cylinder_geom.CreateRadiusAttr().Set(radius)
cylinder_geom.CreateHeightAttr().Set(height)
cylinder_geom.CreateAxisAttr().Set(UsdGeom.Tokens.z)
# Set position and rotation
cylinder_geom.AddTranslateOp().Set(position)
cylinder_geom.AddRotateYOp().Set(rotation_y)
# Add physics properties
UsdPhysics.CollisionAPI.Apply(cylinder_geom.GetPrim())
UsdPhysics.RigidBodyAPI.Apply(cylinder_geom.GetPrim())
# Create revolute joint
joint_path = parent_path.AppendChild(f"{name}_joint")
joint = UsdPhysics.RevoluteJoint.Define(stage, joint_path)
# Set joint bodies
joint.CreateBody0Rel().SetTargets([parent_path]) # World
joint.CreateBody1Rel().SetTargets([cylinder_path]) # Cylinder
# Set joint axis
joint.CreateAxisAttr().Set("Y")
# Set local positions
joint.CreateLocalPos0Attr().Set(position)
joint.CreateLocalPos1Attr().Set(Gf.Vec3f(0.0, 0.0, 0.0))
# Set local rotations
joint.CreateLocalRot0Attr().Set(Gf.Quatf(1.0, 0.0, 0.0, 0.0))
joint.CreateLocalRot1Attr().Set(Gf.Quatf(0.707, 0.707, 0.0, 0.0))
# Add joint drive
drive = UsdPhysics.DriveAPI.Apply(joint.GetPrim(), "angular")
drive.CreateTypeAttr().Set("force")
drive.CreateTargetVelocityAttr().Set(1.0) # Rotate at 1 rad/s
drive.CreateDampingAttr().Set(1.0)
drive.CreateStiffnessAttr().Set(10.0)
return cylinder_path, joint_path, drive
# Create a world
my_world = World(stage_units_in_meters=1.0)
# Add default ground plane
my_world.scene.add_default_ground_plane()
# Get the stage
stage = simulation_app.context.get_stage()
# Enable Physics
scene = UsdPhysics.Scene.Define(stage, "/physicsScene")
scene.CreateGravityDirectionAttr().Set(Gf.Vec3f(0.0, 0.0, -1.0))
scene.CreateGravityMagnitudeAttr().Set(9.81)
# Create default prim and physics material
default_prim_path = Sdf.Path("/World")
UsdGeom.Xform.Define(stage, default_prim_path)
stage.SetDefaultPrim(stage.GetPrimAtPath(default_prim_path))
# Create the cylinder using the helper function
cylinder_radius = 0.3
cylinder_height = 2.0
cylinder_path, joint_path, drive = create_cylinder(stage, default_prim_path, "cylinder", radius=cylinder_radius, height=cylinder_height)
# Create the rope creator and generate ropes
physics_material_path = default_prim_path.AppendChild("PhysicsMaterial")
UsdShade.Material.Define(stage, physics_material_path)
material = UsdPhysics.MaterialAPI.Apply(stage.GetPrimAtPath(physics_material_path))
material.CreateStaticFrictionAttr().Set(0.5)
material.CreateDynamicFrictionAttr().Set(0.5)
material.CreateRestitutionAttr().Set(0)
rope_creator = RopeCreator(stage,
default_prim_path,
physics_material_path,
pivot_point=Gf.Vec3f(0.5, 0.0, 1.0),
rope_length=4.0,
num_ropes=1,
rope_spacing=0.1)
rope_creator.create_ropes()
# Create a spherical joint between the cylinder perimeter and the first capsule of the rope
# Get the first capsule path
first_capsule_path = "/World/Rope0/capsule_0"
# Create the spherical joint
attachment_joint_path = default_prim_path.AppendChild("cylinder_rope_attachment")
attachment_joint = UsdPhysics.SphericalJoint.Define(stage, attachment_joint_path)
# Connect the cylinder and first capsule
attachment_joint.GetBody0Rel().SetTargets([cylinder_path])
attachment_joint.GetBody1Rel().SetTargets([first_capsule_path])
# Set local positions - attach at cylinder perimeter (radius 0.5) and capsule center
attachment_joint.CreateLocalPos0Attr().Set(Gf.Vec3f(cylinder_radius, 0.0, 0.0)) # At cylinder perimeter
attachment_joint.CreateLocalPos1Attr().Set(Gf.Vec3f(-0.1, 0.0, 0.0)) # At capsule edge
# Set local rotations (identity quaternions)
attachment_joint.CreateLocalRot0Attr().Set(Gf.Quatf(1.0, 0.0, 0.0, 0.0))
attachment_joint.CreateLocalRot1Attr().Set(Gf.Quatf(1.0, 0.0, 0.0, 0.0))
# Initial setup for the simulation
timeline = get_timeline_interface()
# Start the simulation
timeline.play()
# Wait for the simulation to start
while not timeline.is_playing():
simulation_app.update()
time.sleep(0.01)
# Keep the simulation running
print("Simulation started. Press Ctrl+C to exit")
position = 0.0
try:
while timeline.is_playing():
# Continuously increment the joint drive position
position += 1. # Increment by 0.01 radians per frame
drive.CreateTargetPositionAttr().Set(position)
simulation_app.update()
time.sleep(0.01)
except KeyboardInterrupt:
print("Simulation stopped by user")
finally:
# Stop the simulation
timeline.stop()
simulation_app.close()
Hope that helps!