Functions to return objects that in contacts/collisions

Hi there,

I would like to use this simulator to test collision detection algorithms. I am wondering if there are functions I can use to return any objects that are in contacts/collisions? Thansk!

Hi,

There is a PhysX demo about Contact report (at least in Isaac Sim 2020.2.2 as far I know): Demo showing contact report listening

You can execute it from menu Physics > Demo > PhysX under Snippets section.

Also, you can find the code at /isaac-sim/_build/target-deps/kit_sdk_release/_build/linux-x86_64/release/extsPhysics/omni.physx/omni/physx/scripts/scenes/ContactReportDemo.py

The demo script prints the next information for the contact:

Contact found:
Actor0: /World/sphere
Actor1: /World/groundPlane/collisionPlane
Collider0: /World/sphere
Collider1: /World/groundPlane/collisionPlane
Num contacts: 1
Normal: 1.3435885648505064e-07 0.0 1.0000001192092896
Position: 0.1749073565006256 0.0 -0.8332841396331787
Impulse: 0.049639005213975906 0.0 369450.96875

Contact lost:
Actor0: /World/sphere
Actor1: /World/groundPlane/collisionPlane
Collider0: /World/sphere
Collider1: /World/groundPlane/collisionPlane
Num contacts: 0
1 Like

Thanks a lot for replying. The demo works but there is a problem. I find that if I change the sphere size to be smaller (radius = 5 for example). Contact found will not work even tho the contact lost works. If I increase the density, the contact found will work again, so it is something related to the mass of the part it seems like. Do you have any idea why is that?

Another question is is there any specific extension I have to enable If I want to run a python script to use this function? I try to use this function in a python script rather than in that demo but the program freezes in kit.update() as soon as there is a contact.

Hi,

The problem seems to be the contact report threshold parameter (physxContactReport:threshold) of the contact report API (ContactReportAPI). Actualy, this parameter has a value of 200000.000. I changed this value to 1.0 for the sphere (with radius = 5), for example, and the contact reports works again. So,in order to have your system working, you need to play with this parameter :)

1 Like

Thanks again Toni. Things work great in the demo. However, when I use python script to reproduce this demo, the simulator just freezes when there is contact between ball and the floor. Do you have any insights on this? Here is the script I run.

import os
from omni.isaac.synthetic_utils import OmniKitHelper
from omni.physx.scripts.scenes.ContactReportDemo import ContactReportDemo
from omni.physx.scripts.scenes.Base import Demo
from omni.physx import _physx
import omni
TRANSLATION_RANGE = 300.0
SCALE = 50.0

CUSTOM_CONFIG = {
    "width": 2048,
    "height": 1024,
    "renderer": "RayTracedLighting",
    "samples_per_pixel_per_frame": 64,
    "max_bounces": 10,
    "max_specular_transmission_bounces": 6,
    "max_volume_bounces": 4,
    "subdiv_refinement_level": 2,
    "headless": False,
    "experience": f'{os.environ["EXP_PATH"]}/isaac-sim-python.json',
}

def main():
    kit = OmniKitHelper(CUSTOM_CONFIG)

    # Get the current stage
    kit.create_prim(
        "/World/Light1",
        "SphereLight",
        translation=(0, 200, 0),
        attributes={"radius": 100, "intensity": 100000.0, "color": (1, 1, 1)},
    )
    collision_report = ContactReportDemo()
    stage = kit.get_stage()
    collision_report.create(stage)
    frame = 0
    print("simulating physics...")
    while frame < 120 or kit.is_loading():
        kit.update(1 / 120.0)
        frame = frame + 1

    kit.play()

    while 1:
        kit.update(1.0 / 60.0, 1.0 / 60.0, 4)
    # Return to user specified render mode
    print("done")
    # everything is captured, stop simulating
    kit.stop()


if __name__ == "__main__":
    main()

Hi,

How are you running that script? Could you detail about launch command, environment configuration, current working directory, etc…?

I am running that command using python 3.6 in terminal and I set up the environment following this https://docs.omniverse.nvidia.com/app_isaacsim/app_isaacsim/python_samples.html. The working dir is python_samples. Please let me know if you need other information.

Hi,

Sorry for my late reply, last week was very busy.

You can also reproduce the demo as a custom extension and enable it Window > Extension Manager

For example:

[extensions-folder]
code.test/
    |-- config
    |   `-- extension.toml
    `-- scripts
        `-- test
            |-- __init__.py

extension.toml

display_name = "Test"

[dependencies]
"omni.kit.editor" = {}
"omni.physx" = {}

[[python.modules]]
name = "scripts.test"

init.py

import omni.kit.editor
import omni.ext
import omni.appwindow
import omni.kit.ui
import gc
import asyncio

# ------------
from omni.physx.scripts.scenes.ContactReportDemo import ContactReportDemo
# ------------

EXTENSION_NAME = "Test"

class Extension(omni.ext.IExt):
    def on_startup(self):
        self._editor = omni.kit.editor.get_editor_interface()
        self._usd_context = omni.usd.get_context()
        self._stage = self._usd_context.get_stage()
        self._window = omni.kit.ui.Window(
            EXTENSION_NAME,
            300,
            200,
            menu_path=EXTENSION_NAME,
            open=False,
            dock=omni.kit.ui.DockPreference.LEFT_BOTTOM,
        )
        self._load_btn = self._window.layout.add_child(omni.kit.ui.Button("Load"))
        self._load_btn.set_clicked_fn(self._on_environment_setup)

        # ------------
        self.collision_report = ContactReportDemo()
        # ------------

    async def _create(self, task):
        done, pending = await asyncio.wait({task})
        if task in done:

            # ------------
            self._stage = self._usd_context.get_stage()
            self.collision_report.create(self._stage)
            # ------------
    
            self._editor_event_subscription = self._editor.subscribe_to_update_events(self._on_editor_step)

    def _on_environment_setup(self, widget):
        task = asyncio.ensure_future(omni.kit.asyncapi.new_stage())
        asyncio.ensure_future(self._create(task))

    def _on_editor_step(self, step):
        pass

    def on_shutdown(self):
        self._editor.stop()
        self._window = None
        gc.collect()

Thanks for your reply again. Yes I can reproduce the demo as a custom extension without any problem. The problem is just we want to run the program using OmniKitHelper as a python script so that the script is independent of the files under _build. However, when I tried that, the simulation just freezes when there is contact. It seems like the contact report is not supported when running simulation using OmniKitHelper?

Hi,

I’m glad to hear about that. I hope this post helps you to solve your main question :)

Btw, I prefer to work with extensions due to the extensions discovery service: “extension manager constantly monitor folders for changes and automatically syncs all extensions found in specified folders”. Then, the code is reloaded after any changes without the need to restart the simulator…

Hello @alphaleiw,
I was able to reproduce the issue here. We will work on a fix, the recommended action for now is to either run it as an extension, or execute your script through the script editor:

  1. launch Isaac sim with ./_build/linux-x86_64/release/isaac-sim.sh.
  2. Open Window->Script Editor
  3. On the Script editor window, either open a python script, or type in your script to execute.
  4. Hit Ctrl-Enter to execute.

Here’s an working example to get the physX contact report example running using the script editor:

from pxr import Usd, UsdGeom
import omni.usd
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
from omni.physx.bindings._physx import SimulationEvent
from random import seed
from random import random
from omni.physx.scripts import demo


class ContactReportDemo(demo.Base):
    title = "Contact report"
    category = demo.Categories.SNIPPETS
    short_description = "Demo showing contact report listening"
    description = "Demo showing contact report listening. Press play (space) to run the simulation."

    def create(self, stage):
        seed(1)
        self._stage = stage
        events = get_physx_interface().get_simulation_event_stream()
        self._simulation_event_sub = events.create_subscription_to_pop(self._on_simulation_event)
        self.collider0 = ''

        # set up axis to z
        UsdGeom.SetStageUpAxis(stage, UsdGeom.Tokens.z)
        UsdGeom.SetStageMetersPerUnit(stage, 0.01)

        defaultPrimPath = str(stage.GetDefaultPrim().GetPath())

        # light
        sphereLight = UsdLux.SphereLight.Define(stage, defaultPrimPath + "/SphereLight")
        sphereLight.CreateRadiusAttr(150)
        sphereLight.CreateIntensityAttr(30000)
        sphereLight.AddTranslateOp().Set(Gf.Vec3f(650.0, 0.0, 1150.0))

        # Physics scene
        scene = UsdPhysics.Scene.Define(stage, defaultPrimPath + "/physicsScene")
        scene.CreateGravityDirectionAttr().Set(Gf.Vec3f(0.0, 0.0, -1.0))
        scene.CreateGravityMagnitudeAttr().Set(981.0)


        # Floor Material
        path = defaultPrimPath + "/floorMaterial"
        UsdShade.Material.Define(stage, path)
        material = UsdPhysics.MaterialAPI.Apply(stage.GetPrimAtPath(path))
        material.CreateStaticFrictionAttr().Set(0.0)
        material.CreateDynamicFrictionAttr().Set(0.0)
        material.CreateRestitutionAttr().Set(1.0)

        # Plane
        add_quad_plane(stage, "/groundPlane", "Z", 750.0, Gf.Vec3f(0.0), Gf.Vec3f(0.5))

        # Add material
        collisionPlanePath = defaultPrimPath + "/groundPlane"
        materialPath = defaultPrimPath + "/floorMaterial"        
        add_physics_material_to_prim(stage, stage.GetPrimAtPath(Sdf.Path(collisionPlanePath)), Sdf.Path(materialPath))

        # 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 = "/sphere"

        radius = 30.0
        position = Gf.Vec3f(0.0, 0.0, 800.0)
        orientation = Gf.Quatf(1.0)
        color = Gf.Vec3f(71.0 / 255.0, 165.0 / 255.0, 1.0)
        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(defaultPrimPath + spherePath)), Sdf.Path(materialPath))

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

    def _print_event(self, event, contactDict):        
        print("Actor0: " + str(contactDict["actor0"]))
        print("Actor1: " + str(contactDict["actor1"]))
        print("Collider0: " + str(contactDict["collider0"]))
        print("Collider1: " + str(contactDict["collider1"]))
        print("Num contacts: " + str(event.payload['numContactData']))

    def _on_simulation_event(self, event):
        if event.type == int(SimulationEvent.CONTACT_DATA):
            print("Normal: " + str(event.payload['normal']))
            print("Position: " + str(event.payload['position']))
            print("Impulse: " + str(event.payload['impulse']))

            otherPrim = self._stage.GetPrimAtPath(self.collider0)
            usdGeom = UsdGeom.Mesh.Get(self._stage, otherPrim.GetPath())
            color = Vt.Vec3fArray([Gf.Vec3f(random(), random(), random())])
            usdGeom.GetDisplayColorAttr().Set(color)
        if event.type == int(SimulationEvent.CONTACT_FOUND):
            print("Contact found:")
            contactDict = resolveContactEventPaths(event)
            self._print_event(event, contactDict)
            self.collider0 = contactDict["collider0"]
        if event.type == int(SimulationEvent.CONTACT_PERSISTS):
            print("Contact persist:")            
            contactDict = resolveContactEventPaths(event)
            self._print_event(event, contactDict)
            self.collider0 = contactDict["collider0"]
        if event.type == int(SimulationEvent.CONTACT_LOST):
            print("Contact lost:")
            contactDict = resolveContactEventPaths(event)
            self._print_event(event, contactDict)
            self.collider0 = ''

def create(a, b):
    stage = omni.usd.get_context().get_stage()
    demo = ContactReportDemo()
    demo.create(stage)
    
omni.usd.get_context().new_stage_with_callback(create)