Clash Detection Point Cloud API#
omni.physx.clashdetection.pointcloud is a lightweight extension that provides
point cloud extraction utilities for the clash detection pipeline.
It depends on the omni.pointcloud.streaming.potree extension to stream point cloud data
from Potree-format sources, then writes the result as UsdGeom.Points prims in the
active stage. The extracted prims are consumed by
ClashDetection when
SETTING_ENABLE_POINT_CLOUDS is enabled.
Note
This extension is included in omni.physx.clashdetection.bundle.
Enable the bundle to make this extension available.
Module#
The public surface of omni.physxclashdetectionpointcloud exports:
from omni.physxclashdetectionpointcloud import extract_point_cloud, ExtensionConfig
Functions#
- async omni.physxclashdetectionpointcloud.extract_point_cloud(
- stage: Usd.Stage,
- point_cloud_prim_path: str,
- destination_path: str,
- aabbs: List[Gf.Range3d],
- max_lod_level: int = 1000000,
- extract_layer_name: str = '',
- extract_to_session_layer: bool = True,
- progress_fn: Callable[[float], bool] | None = None,
Extracts a point cloud to
UsdGeom.Pointsprims in the given stage.The function streams point cloud geometry from a Potree source prim (referenced by
point_cloud_prim_path), applies an optional axis-aligned bounding box (AABB) filter, and writes the resulting points asUsdGeom.Pointsprims underdestination_path.- Parameters:
stage (Usd.Stage) – The USD stage containing the geometry to process.
point_cloud_prim_path (str) – USD prim path of the point cloud source.
destination_path (str) – Path where extraction results are written as
UsdGeom.Points.aabbs (List[Gf.Range3d]) – AABBs of inclusion regions of interest. Only the first entry is currently used. Pass an empty list (or a list with a single maximum-extent AABB) to include all available points; if the list is empty, a bounding box of
±1e38is used internally.max_lod_level (int) – Maximum level-of-detail level used during extraction. Higher values include finer detail at the cost of more memory and time. Defaults to 1 000 000.
extract_layer_name (str) – Display name of the sublayer to use for writing the extracted prims. When empty the current edit target is used without modification.
extract_to_session_layer (bool) – Relevant only when extract_layer_name is non-empty. If
True, the named sublayer is found or created under the session layer. IfFalse, it is found or created under the root layer.progress_fn (Optional[Callable[[float], bool]]) – Optional callback that receives the fraction of nodes fetched so far (0.0 to 1.0) each time the extraction yields a frame to Kit. Return
Falsefrom it to cancel the extraction before anything is written to the stage.
- Returns:
A
(success, error_message)tuple. error_message is an empty string on success. Returns(True, "")also when the AABB filter matches no points (this is not treated as an error). Returns(False, error_message)on streaming or write failures, and when progress_fn cancels the extraction.- Return type:
Tuple[bool, str]
Note
Node geometry is fetched in batches, and the coroutine yields a frame to Omniverse Kit between batches, so the application keeps rendering and handling input during a long fetch. The node query that precedes the fetch and the final authoring of the
UsdGeom.Pointsprims still each run as a single blocking call on the main thread.Note
The coordinate space of the extracted points depends on the position of
destination_pathrelative to the parent xform ofpoint_cloud_prim_path. Whendestination_pathis a child of that xform, points are kept in local space (the xform itself provides the world transform). Otherwise they are transformed to world space automatically.
Example#
Extracting a point cloud and running clash detection against meshes:
import asyncio
from pxr import Usd, UsdUtils, Gf
from omni.physxclashdetectionpointcloud import extract_point_cloud
from omni.physxclashdetectioncore.clash_detect import ClashDetection
from omni.physxclashdetectioncore.clash_detect_settings import SettingId
from omni.physxclashdetectioncore.clash_query import ClashQuery
from omni.physxclashdetectioncore.clash_data import ClashData
from omni.physxclashdetectioncore.clash_data_serializer_sqlite import ClashDataSerializerSqlite
from omni.physxclashdetectioncore.point_cloud_settings import PointCloudSettings
async def run_point_cloud_clash_detection(stage: Usd.Stage):
stage_id = UsdUtils.StageCache.Get().GetId(stage).ToLongInt()
# 1. Configure extraction settings
pc_settings = PointCloudSettings(
point_cloud_prim_path="/World/ScanData",
destination_path="/World/ScanData/Extracted",
save_to_session_layer=True,
skip_rendering=True,
max_lod_level=10,
)
# 2. Extract the point cloud into the stage
roi_aabb = Gf.Range3d(Gf.Vec3d(-100, -100, -100), Gf.Vec3d(100, 100, 100))
success, error = await extract_point_cloud(
stage=stage,
point_cloud_prim_path=pc_settings.point_cloud_prim_path,
destination_path=pc_settings.destination_path,
aabbs=[roi_aabb],
max_lod_level=pc_settings.max_lod_level,
extract_layer_name="ClashPointCloud",
extract_to_session_layer=pc_settings.save_to_session_layer,
)
if not success:
print(f"Extraction failed: {error}")
return
# 3. Set up ClashData and persist the point cloud settings
clash_data = ClashData(ClashDataSerializerSqlite())
clash_data.open(stage_id)
clash_data.insert_point_cloud_settings(pc_settings)
# 4. Create a point cloud clash query
query = ClashQuery(
query_name="Point Cloud and Building Clash",
object_a_path=pc_settings.point_cloud_prim_path, # point cloud source
object_b_path="/World/Building", # mesh geometry
clash_detect_settings={
SettingId.SETTING_ENABLE_POINT_CLOUDS.name: True,
SettingId.SETTING_POINT_SIZE_OVERRIDE.name: 0.0, # auto-compute size
SettingId.SETTING_LOGGING.name: True,
},
)
clash_data.insert_query(query)
# 5. Run clash detection
clash_detect = ClashDetection()
clash_detect.set_settings(query.clash_detect_settings, stage)
clash_detect.set_scope(stage, query.object_a_path, query.object_b_path)
num_steps = clash_detect.create_pipeline()
for i in range(num_steps):
clash_detect.run_pipeline_step(i)
num_overlaps = clash_detect.get_nb_overlaps()
print(f"Found {num_overlaps} point cloud clashes")
# Fetch and save results
for _ in clash_detect.fetch_and_save_overlaps(stage, clash_data, query):
pass
clash_data.save()
clash_data.close()
clash_data.destroy()
# Run with: asyncio.ensure_future(run_point_cloud_clash_detection(stage))
Dynamic Point Cloud Queries#
A point cloud query can also be run as a dynamic query, sweeping a time range instead of testing a
single time. Add SETTING_DYNAMIC and the time range to the settings dictionary of step 4 above:
query = ClashQuery(
query_name="Point Cloud and Moving Machinery",
object_a_path=pc_settings.point_cloud_prim_path, # point cloud source
object_b_path="/World/Machinery", # animated mesh geometry
clash_detect_settings={
SettingId.SETTING_ENABLE_POINT_CLOUDS.name: True,
SettingId.SETTING_POINT_SIZE_OVERRIDE.name: 0.0, # auto-compute size
SettingId.SETTING_DYNAMIC.name: True,
SettingId.SETTING_DYNAMIC_START_TIME.name: 0.0, # 0 = timeline start
SettingId.SETTING_DYNAMIC_END_TIME.name: 0.0, # 0 = timeline end
},
)
Each resulting clash then carries one record per clashing frame. The clashing points of a given frame
are read from the per-frame ClashFrameInfo objects:
overlaps = clash_data.find_all_overlaps_by_query_id(query.identifier, fetch_also_frame_info=True)
for clash_info in overlaps.values():
if not clash_info.is_point_cloud_clash:
continue
print(f"{clash_info.object_a_path}: {clash_info.num_records} clashing frame(s)")
for frame_info in clash_info.clash_frame_info_items:
# usd_points_0 holds the indices of the points of the extracted UsdGeom.Points prim
# that clash at this timecode; usd_points_0_sizes holds their widths.
print(f" t={frame_info.timecode}: {frame_info.overlap_points} point(s)")
Note
Only the transform of the point cloud is animated. extract_point_cloud() takes no time
argument, so the extracted UsdGeom.Points prim is a single snapshot that is reused for every
sampled frame. A dynamic point cloud query therefore detects clashes caused by the animation of
the mesh geometry and by the animation of the ancestor xforms of the point cloud, not by the point
positions themselves changing over time.
Note
The baked clash artifacts of a dynamic point cloud clash are authored in point-cloud-local space and animated with the per-frame transform of the cloud. The clashing-points box is therefore axis aligned in that local space, so for a rotated cloud it is an oriented box in world space rather than the world axis-aligned box a static point cloud clash produces. Points and widths are sampled on clashing frames only, so the highlight of the last clashing frame is held on any non-clashing frame inside the clash time range.
Note
Max local depth, penetration depth and motion vector estimation are not computed for point-cloud-to-mesh
pairs, nor are clashing faces and collision outlines. SETTING_TOLERANCE is ignored as
well: the point-to-mesh test has no clearance margin, so a point cloud query always reports hard
clashes only and never soft ones. The static permanent-overlap purge
(SETTING_PURGE_PERMANENT_STATIC_OVERLAPS) covers mesh-to-mesh pairs only, so a static point
cloud that permanently intersects static mesh geometry is reported once per sampled frame of the
query range. The dynamic purge (SETTING_PURGE_PERMANENT_OVERLAPS) does apply to point-cloud-to-mesh
pairs: a pair that overlaps on every sampled frame of the range is discarded just like a
mesh pair, so the Clash Query window keeps the checkbox available for point cloud queries.
Classes#
ExtensionConfig#
- class omni.physxclashdetectionpointcloud.ExtensionConfig#
A configuration class that holds extension-wide parameters read from
carb.settingsat startup.All attributes are class-level - do not instantiate this class.
- SETTINGS_DEBUG_LOGGING_ENABLED = "/persistent/clashDetection/pointClouds/debugLogging"
- Type:
str
carb.settingspath for the debug-logging toggle. When the value at this path isTrue, the extractor logs verbose messages (source path, extents, LOD level) viacarb.log_info.
- debug_logging: bool = False
Whether debug logging is currently enabled. Updated at startup and whenever
SETTINGS_DEBUG_LOGGING_ENABLEDchanges (viaSettingChangeSubscription).
- extension_path: Optional[str] = None
Absolute filesystem path to the installed extension directory. Set by
ClashDetectionPointCloudExtension.on_startup;Nonebefore the extension starts.