Collision Detection and Moving With Object

Hello! I am trying to import a custom URDF of a kinova arm, with an object attached to the end. The object will pierce a cube and then the fork and cube will move together. To lower the complexity, I have two goals I want to achieve. 1) Detect when the end-object overlaps/collides with the cube (to clarify, I want to know, in python code, when the two objects will intersect each other). 2) Have the objects then move together. Any transformation of the end-object will also move the cube the same way. The idea is that the end-object has “pierced” the cube, but since that’s too hard to implement, we’re just making them attached together. Is there an easy way to detect the above described intersection, and then to have the two objects attach each other (maybe for this latter function, a combine meshes in python?) I’m hoping to put all this code in a standalone application, and run said application using the shipped python.sh script. Thanks for the assistance!

Hi,
you should be able to detect the collision using contact reporting.
See Simulation->Physics->demos and contact report demos, view the python file to see how to get the reports.
For the second part, once the contact appears you can add a FixedJoint between the two bodies that were in a contact. You can use a helper function for that:
from omni.physx.scripts import utils
utils.createJoint(self._stage, self._joint_type, self._from_prim, self._to_prim)

regards,
Ales

Whenever I try to run an application using the python.sh script, it throws an import error.
For example, when running the following code

from pxr import Usd, UsdGeom, UsdPhysics, UsdShade, Sdf, Gf, Tf

# Set up a USD Stage to define what is the up axis and set up the units,
# which is crucial, as some default simulation parameters are derived
# from the MetersPerUnit value
stage = omni.usd.get_context().get_stage()
UsdGeom.SetStageUpAxis(stage, UsdGeom.Tokens.z)
UsdGeom.SetStageMetersPerUnit(stage, 0.01)

# Physics scene definition
scene = UsdPhysics.Scene.Define(stage, "/physicsScene")

# setup gravity
# note that gravity has to respect the selected units, if we are using cm, the gravity has to respect that
scene.CreateGravityDirectionAttr().Set(Gf.Vec3f(0.0, 0.0, -1.0))
scene.CreateGravityMagnitudeAttr().Set(981.0)

using ./python.sh in the Isaac Sim package folder, I get the following error:
ModuleNotFoundError: No module named ‘pxr’

Should I be using a different shell script? I obtained the above example from here" Omni PhysX — omni_physics 105.1 documentation.

The same thing occurs when running the contact report code.

from omni.physx.scripts.physicsUtils import *
from pxr import Usd, UsdLux, UsdGeom, UsdShade, Sdf, Gf, Tf, Vt, UsdPhysics, PhysxSchema
from omni.physx import get_physx_interface, get_physx_simulation_interface
from omni.physx.bindings._physx import SimulationEvent
from random import seed
from random import random
import omni.physxdemos as demo


class ContactReportDemo(demo.Base):
    title = "Contact Report Callback"
    category = demo.Categories.CONTACTS
    short_description = "Demo showing contact report callback listening"
    description = "Demo showing contact report listening. Press play (space) to run the simulation, the received contact information can be seen in the console."

    def create(self, stage):
        seed(1)
        # subscribe to physics contact report event, this callback issued after each simulation step
        self._contact_report_sub = None

        defaultPrimPath, scene = demo.setup_physics_scene(self, stage)

        room = demo.get_demo_room(self, stage, staticDynamicRestitution = Gf.Vec3f(0.0, 0.0, 1.0))

        # Sphere material
        materialPath = defaultPrimPath + "/sphereMaterial"
        UsdShade.Material.Define(stage, materialPath)
        material = UsdPhysics.MaterialAPI.Apply(stage.GetPrimAtPath(materialPath))
        material.CreateStaticFrictionAttr().Set(0.5)
        material.CreateDynamicFrictionAttr().Set(0.5)
        material.CreateRestitutionAttr().Set(0.9)
        material.CreateDensityAttr().Set(0.001)

        # Spheres
        spherePath = defaultPrimPath + "/sphere"

        radius = 30.0
        position = Gf.Vec3f(0.0, 0.0, 400.0)
        orientation = Gf.Quatf(1.0)
        color = demo.get_primary_color()
        density = 0.001
        linvel = Gf.Vec3f(0.0)

        add_rigid_sphere(stage, spherePath, radius, position, orientation, color, density, linvel, Gf.Vec3f(0.0))

        # Add material        
        add_physics_material_to_prim(stage, stage.GetPrimAtPath(Sdf.Path(spherePath)), Sdf.Path(materialPath))

        # apply contact report
        spherePrim = stage.GetPrimAtPath(spherePath)
        contactReportAPI = PhysxSchema.PhysxContactReportAPI.Apply(spherePrim)
        contactReportAPI.CreateThresholdAttr().Set(200000)

    def on_shutdown(self):
        self._contact_report_sub = None

    def on_stop(self):
        self._contact_report_sub = None

    def update(self, stage, dt, viewport, physxIFace):
        if not self._contact_report_sub:
            self._contact_report_sub = get_physx_simulation_interface().subscribe_contact_report_events(self._on_contact_report_event)
        
    def _on_contact_report_event(self, contact_headers, contact_data):
        for contact_header in contact_headers:
            print("Got contact header type: " + str(contact_header.type))
            print("Actor0: " + str(PhysicsSchemaTools.intToSdfPath(contact_header.actor0)))
            print("Actor1: " + str(PhysicsSchemaTools.intToSdfPath(contact_header.actor1)))
            print("Collider0: " + str(PhysicsSchemaTools.intToSdfPath(contact_header.collider0)))
            print("Collider1: " + str(PhysicsSchemaTools.intToSdfPath(contact_header.collider1)))
            print("StageId: " + str(contact_header.stage_id))
            print("Number of contacts: " + str(contact_header.num_contact_data))
            
            contact_data_offset = contact_header.contact_data_offset
            num_contact_data = contact_header.num_contact_data
            
            for index in range(contact_data_offset, contact_data_offset + num_contact_data, 1):
                print("Contact:")
                print("Contact position: " + str(contact_data[index].position))
                print("Contact normal: " + str(contact_data[index].normal))
                print("Contact impulse: " + str(contact_data[index].impulse))
                print("Contact separation: " + str(contact_data[index].separation))
                print("Contact faceIndex0: " + str(contact_data[index].face_index0))
                print("Contact faceIndex1: " + str(contact_data[index].face_index1))
                print("Contact material0: " + str(PhysicsSchemaTools.intToSdfPath(contact_data[index].material0)))
                print("Contact material1: " + str(PhysicsSchemaTools.intToSdfPath(contact_data[index].material1)))

I get this error:
from omni.physx.scripts.physicsUtils import *
ModuleNotFoundError: No module named ‘omni.physx’
There was an error running python

This occurs when running that script using ./python.sh, as I want to create a standalone application with the features I mentioned in my above post. What is the issue here?

You need to execute a headless application for that and pass the python file as exec parameter:

--exec "some_script.py arg1 arg2"

So, to summarize, I’d have to make a headless app, lets call it headless.py. My task would be to then run the command

./python.sh --exec "headless.py"

Would that be correct? Also, correct me if I’m wrong, but headless means the app will be run in the terminal, and there would be no GUI for me to interact with. If I want to see simulation playing out, would I use the WebRTC Browser Client to connect to the headless app? For context, I’m running IsaacSim on a NVIDIA GPU Workstation, and if I understand your reply correctly, I’d run IsaacSim using the headless app on that workstation, then use the Browser Client to connect to that same app from the same workstation. I was just wondering why that was a required workaround. I will follow the advice, but I am curious as to what’s going on.

You need to run some app, in case of IsaacSim I believe you need to run this app:
./isaac-sim.headless.native.sh --exec “headless.py”
The reason is, you need application definition in order to load extensions that initialize the python modules that you need.
Running this would indeed run a console application that you can talk to through remote protocol that you need.
There even is already an app:
./isaac-sim.headless.webrtc.sh that is probably doing what you need. But for that you would have readup the IsaacSim doc.

If you are not using IsaacSim app, then you need to have an application definition that enables omni.physx extensions in order to be able to access the python modules.

Ales

When I perform the first command you mentioned, the native.sh one, I keep getting this error:

2024-04-26 20:46:10 [6,066ms] [Error] [omni.kit.app.plugin] Can't find a file to execute: “/home/alif/.local/share/ov/pkg/isaac_sim-2023.1.1/my_application.py”

In the quotes, I tried putting the just the my_application.py, ./my_application.py, as well as the full file path. I have currently stashed the my_application file, with headless set to true, in the IsaacSim package directory, as shown in the full path I supplied above. I can access the simulation using the Omniverse Streaming Client, but the simulation app I wrote isn’t being run.