Metadata-Version: 2.4
Name: ovclash
Version: 0.2.2
Summary: Standalone Python library for OpenUSD-based clash detection
Author: Omniverse Physics Team
License-Expression: LicenseRef-NVIDIA-Omniverse
Project-URL: Homepage, https://github.com/NVIDIA-Omniverse/ovclash
Project-URL: Repository, https://github.com/NVIDIA-Omniverse/ovclash
Project-URL: Documentation, https://nvidia-omniverse.github.io/ovclash/
Project-URL: Issues, https://github.com/NVIDIA-Omniverse/ovclash/issues
Project-URL: Changelog, https://github.com/NVIDIA-Omniverse/ovclash/blob/main/ovclash/CHANGELOG.md
Keywords: clash-detection,usd,openusd,nvidia,omniverse
Classifier: Programming Language :: Python :: 3
Classifier: Operating System :: POSIX :: Linux
Classifier: Operating System :: Microsoft :: Windows
Classifier: Topic :: Scientific/Engineering
Classifier: Development Status :: 3 - Alpha
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE.txt
License-File: ovclash-LICENSES.zip
Requires-Dist: ovstage==0.3.0.1791986420
Provides-Extra: viewer-ovstage
Requires-Dist: numpy<3,>=1.24; extra == "viewer-ovstage"
Requires-Dist: warp-lang<2,>=1.16; extra == "viewer-ovstage"
Provides-Extra: viewer-openusd
Requires-Dist: usd-core==26.5; extra == "viewer-openusd"
Requires-Dist: numpy<3,>=1.24; extra == "viewer-openusd"
Requires-Dist: warp-lang<2,>=1.16; extra == "viewer-openusd"
Provides-Extra: viewer
Requires-Dist: numpy<3,>=1.24; extra == "viewer"
Requires-Dist: warp-lang<2,>=1.16; extra == "viewer"
Dynamic: license-file

# ovclash Python Package

The `ovclash` [wheel](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/concepts.html#glossary) runs clash detection on [USD](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/concepts.html#glossary)
stages from Python, outside [Omniverse Kit](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/concepts.html#glossary). It installs three packages.
All three bind the same native library through `ctypes`.

| Package | Use it for |
| --- | --- |
| [`ovclash`](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/README.html#ovclash) | The [canonical API](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/concepts.html#glossary). It stays close to the [C ABI](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/concepts.html#glossary): one runtime, one stage, one [query](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/concepts.html#glossary) at a time. |
| [`ovclash_suite`](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/README.html#ovclash_suite) | The [Suite API](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/concepts.html#glossary): named queries, stored results, and HTML and JSON export. |
| [`omni.physxclashdetectioncore`](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/README.html#omniphysxclashdetectioncore) | The Suite API under the module names of the Kit extension, for code that you migrate from Kit. |

Pick one package and stay in it. Same-named types in `ovclash` and `ovclash_suite` are not
interchangeable; [Choosing an API](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/choosing_an_api.html#same-names-different-types) lists the
differences.

This page is the Python guide. It ships as `docs/consumer/README.md` in the
[SDK archive](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/concepts.html#glossary) and is the wheel's description on the package index; the wheel
ships no pages. [overview.md](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/overview.html) introduces the whole package and lists every page. The
C/C++ guide is the [C quickstart](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/c_quickstart.html).

## Installation

Requirements:

- Python 3.10 or newer.
- Windows x86_64, Linux x86_64 or Linux aarch64.
- On Windows, the
  [Microsoft Visual C++ Redistributable (x64)](https://aka.ms/vs/17/release/vc_redist.x64.exe).
  ovclash does not bundle the MSVC runtime, and its native library does not load without it.
- On Linux, glibc 2.35 or newer and a `libstdc++` that provides `GLIBCXX_3.4.30`; see
  [Linux glibc baseline](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/compatibility.html#linux-glibc-baseline).

[compatibility.md](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/compatibility.html) gives the platform tags and the version policy.

Install the wheel with pip:

```bash
python -m pip install ovclash
```

The wheel depends on one exact version of `ovstage`, the runtime that ovclash reads USD through.
pip installs it together with ovclash. That version is published on public PyPI.

Check the installation:

```bash
python -m ovclash --version
```

The command prints the version of the loaded native library, as `ovclash <version>`.
Without the pinned `ovstage` package, `import ovclash` raises `OSError`.

### Optional extras

The core API needs no extra. Install one with `python -m pip install "ovclash[viewer]"`. When pip
cannot resolve an extra's packages, compare your environment with the version ranges that
[Python versions](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/compatibility.html#python-versions) lists.

| Extra | Adds | Use it for |
| --- | --- | --- |
| `viewer`, `viewer-ovstage` | NumPy and `warp-lang` ([Warp](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/concepts.html#glossary)) | The zero-copy `numpy()` and `warp()` adapters of [overlap-report](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/concepts.html#glossary) buffers. |
| `viewer-openusd` | NumPy, `warp-lang` and `usd-core` | Code that imports Python [OpenUSD](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/concepts.html#glossary) (`pxr`) itself. `usd-core` comes from public PyPI. |

Do not install `usd-core` to fix a failing query. ovstage supplies the USD runtime, and a second
`pxr` in the process can shadow it. [USD coexistence](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/README.html#usd-coexistence) explains how the two can
share a process.

## Quick start

The wheel ships a small stage, `samples/data/time_sampled.usda`, in the installed `ovclash`
package directory. Its [prims](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/concepts.html#glossary) `/Root/Xform_Primitives/Cube` and
`/Root/Xform_Primitives/Plane` overlap. `python -m pip show ovclash` prints the parent of that
directory as `Location`, written `<site-packages>` on this page.

### First query from the command line

```bash
python -m ovclash --stage <site-packages>/ovclash/samples/data/time_sampled.usda \
    --scope-a /Root/Xform_Primitives/Cube --scope-b /Root/Xform_Primitives/Plane
```

stdout receives the JSON summary:

```json
{
  "duplicate_count": 0,
  "duplicates": [],
  "overlap_count": 1,
  "overlaps": [
    {
      "name0": "/Root/Xform_Primitives/Cube",
      "name1": "/Root/Xform_Primitives/Plane",
      "nb_elements": 10,
      "time": 0.0,
      "type": 0
    }
  ],
  "pipeline_steps": [
    "Releasing previous data...",
    "Finding meshes...",
    "Computing initial mesh data...",
    "Finding shared meshes...",
    "Cooking meshes...",
    "Computing global mesh data...",
    "Building BVH...",
    "Running broad-phase...",
    "Purging duplicates...",
    "Running narrow-phase...",
    "Processing results..."
  ],
  "stage": "<site-packages>/ovclash/samples/data/time_sampled.usda"
}
```

[Command line](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/README.html#command-line) lists every option.

### First query in Python

```python
from pathlib import Path

import ovclash
from ovclash import ClashRuntime, RunSettings

# The wheel ships this stage in samples/data/ of the package directory.
stage = Path(ovclash.__file__).parent / "samples" / "data" / "time_sampled.usda"

runtime = ClashRuntime()
try:
    runtime.open_stage(str(stage))
    runtime.set_run_settings(RunSettings(tolerance=0.0))
    runtime.set_scope(["/Root/Xform_Primitives/Cube"], ["/Root/Xform_Primitives/Plane"])
    runtime.run_pipeline()
    for index in range(runtime.get_overlap_count()):
        details = runtime.get_overlap_details(index)
        print(details.name0, details.name1, details.nb_elements)
finally:
    runtime.destroy()
```

Output:

```text
/Root/Xform_Primitives/Cube /Root/Xform_Primitives/Plane 10
```

The [ovclash_suite example](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/README.html#ovclash_suite) runs the same query and saves its results. The
`samples/` directory of the package holds complete Python samples;
[samples.md](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/samples.html) describes them and their launchers.

## Command line

`python -m ovclash` opens one stage, runs one query through the canonical `ovclash` package and
prints a JSON summary. The wheel installs no separate console script.

```bash
python -m ovclash [--stage STAGE | STAGE [PATHS ...]] [--scope-a PATHS] [--scope-b PATHS] [options]
```

| Option | Type | Required | Default | Meaning |
| --- | --- | --- | --- | --- |
| `--stage PATH` | String: a file path | One of `--stage` and `STAGE` | None | The USD stage file. |
| `STAGE [PATHS ...]` | Strings: a file path, then prim paths | One of `--stage` and `STAGE` | None | Positional form: the first argument is the stage, and the rest join [search set](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/concepts.html#glossary) A. Use this form or `--stage`, not both: with `--stage`, the first positional argument is ignored. |
| `--scope-a PATHS` | String, repeatable | No | The whole stage | Prim paths of search set A. Repeat the option, or separate paths with commas, semicolons or whitespace. |
| `--scope-b PATHS` | String, repeatable | No | None | Prim paths of search set B, with the same rules. |
| `--tolerance D` | Float | No | `0.0` | Clearance distance in [stage units](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/concepts.html#glossary). Mesh pairs closer than this clash. `0.0` reports touching or interpenetrating geometry only. |
| `--dynamic` | Flag | No | Off | Query the time range of `--start-time` and `--end-time`. Without it, the query is a [static query](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/concepts.html#glossary). |
| `--start-time S` | Float, in seconds | No | `0.0` | Start of the time range, in seconds, not USD [time codes](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/concepts.html#glossary). |
| `--end-time S` | Float, in seconds | No | `0.0` | End of the time range, in seconds. With `--dynamic`, it must not be below `--start-time`. An equal value runs a static query at that time. |
| `--time-codes-per-second N` | Float | No | `24.0` | Samples per second over the time range. This is not the stage's time-code rate. It must not be negative, and `0` collapses the range to one sample. |
| `--point-clouds` | Flag | No | Off | Also run [point-vs-mesh](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/concepts.html#glossary) queries. |
| `--duplicates` | Flag | No | Off | Find [duplicate](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/concepts.html#glossary) meshes only, and skip overlap detection. |
| `--single-threaded` | Flag | No | Off | Run mesh preparation and collision on one thread. Overlap membership does not change. |
| `--log` | Flag | No | Off | Write the [backend](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/concepts.html#glossary)'s progress and diagnostic lines to stderr. The JSON summary does not change. |
| `--version` | Flag | No | Off | Print `ovclash` and the version of the loaded native library to stdout, then exit. |
| `-h`, `--help` | Flag | No | Off | Print the usage and the options to stdout, then exit. |

Every other `RunSettings` field keeps its default. The command also accepts `--multi-threaded`
from older command lines. It has no effect, and `--help` does not list it. `--help` and
`--version` load the native library, but they do not open a stage.

Scope rules:

- Without scope paths, the command queries the whole stage.
- With set A only, the query tests the meshes of set A against each other.
- With both sets, it tests set A against set B.
- A scope path also covers the prim's descendants.

Query rules:

- A [dynamic query](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/concepts.html#glossary) (`--dynamic` with `--end-time` above `--start-time`)
  drops [overlaps](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/concepts.html#glossary) between two meshes whose transforms are not animated. Run
  a static query to find those overlaps.
- `--duplicates` finds nothing in a dynamic query. Equal start and end times select a static
  query, which runs the duplicate search. On the bundled `time_sampled.usda`, `--duplicates`
  reports one pair, `/Root/Xform_Primitives/Cube_Dup1` and `/Root/Xform_Primitives/Cube_Dup2`,
  with 12 triangles.
- To query a range given in USD time codes, divide each time code by the stage's
  time-codes-per-second rate. `ClashRuntime.get_stage_timeline_data()` reports that rate.

Output rules:

- stdout holds only the JSON summary: one object, indented by two spaces, with sorted keys. You
  can redirect it to a file or pipe it straight into a JSON parser.
- The version header, `ovclash <version> - Standalone query CLI`, and every diagnostic go to
  stderr. This includes the lines that native plugins write while the stage loads.
- An overlap record holds the overlap's first sampled frame only, so a dynamic query reports one
  frame per overlap.

Every field of the summary is always present:

| Field | Type | Required | Default or values | Description |
| --- | --- | --- | --- | --- |
| `stage` | string | Yes | The path as given | The stage path. |
| `pipeline_steps` | array of strings | Yes | 11 names for a static query | The names of the [pipeline](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/concepts.html#glossary) steps that ran, in order. |
| `overlap_count` | integer | Yes | 0 or more | The number of entries in `overlaps`. |
| `overlaps` | array of objects | Yes | Can be empty | One object per overlap, with five fields: `name0`, `name1`, `type`, `time` and `nb_elements`. |
| `overlaps[].name0` | string or `null` | Yes | A prim path | The first prim: the point cloud of a point-vs-mesh pair, else a mesh. |
| `overlaps[].name1` | string or `null` | Yes | A prim path | The second prim, a mesh. |
| `overlaps[].type` | integer | Yes | `0` mesh-vs-mesh, `1` point-vs-mesh | The pair type. |
| `overlaps[].time` | number | Yes | Seconds | The time of the record, in seconds, not USD time codes. |
| `overlaps[].nb_elements` | integer | Yes | 0 or more | The number of overlapping triangles or points. |
| `duplicate_count` | integer | Yes | 0 or more | The number of entries in `duplicates`. |
| `duplicates` | array of objects | Yes | Can be empty | One object per duplicate mesh pair, with four fields: `name0`, `name1`, `nb_tris` and `time`. |
| `duplicates[].name0`, `duplicates[].name1` | string or `null` | Yes | Prim paths | The two meshes of the pair. |
| `duplicates[].nb_tris` | integer | Yes | 0 or more | The number of triangles in the duplicated mesh. |
| `duplicates[].time` | number | Yes | Seconds | The time of the pair. |

Exit status:

| Status | Meaning |
| --- | --- |
| `0` | The summary was printed, or `--help` or `--version` ran. |
| `2` | A usage error, such as a missing stage path or a non-numeric time. The command prints the usage and the error on stderr. |
| `1` | The query raised. `ValueError` reports a non-finite time, rate or tolerance, a negative `--time-codes-per-second` or, with `--dynamic`, an `--end-time` below `--start-time`. `RuntimeError` reports a failed stage open or query. The command prints a traceback on stderr and writes nothing to stdout. |

## ovclash

`ovclash` is a thin binding of the canonical C ABI. One `ClashRuntime` owns the stage, the
settings, the scope and the results. The [first query](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/README.html#first-query-in-python) shows the
lifecycle:

1. `ClashRuntime()` creates a native [context](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/concepts.html#glossary).
2. `open_stage(path)` opens a local USD file. Asset URIs are rejected.
3. `set_run_settings(RunSettings(...))` applies the settings. Unknown field names raise
   `TypeError`, and out-of-range values raise `ValueError`.
4. `set_scope(paths_a, paths_b)` records the search sets. Call it after `open_stage()`, because a
   successful open drops the recorded scope.
5. `run_pipeline()` runs every pipeline step on the calling thread.
6. Read the results.
7. `destroy()` releases the native context.

A failed native call raises `RuntimeError` with the context's last error text. Serialize the
calls on one runtime. The docstrings give the full contract of each method:
`help(ovclash.ClashRuntime)` prints them, and
[References in the package](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/overview.html#references-in-the-package) lists them with the other
references.
[developer_guide.md](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/developer_guide.html) gives the runtime rules that both languages share, such
as [setting a scope](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/developer_guide.html#setting-a-scope).

### Run settings

`RunSettings` holds the settings that standalone queries commonly tune: the tolerance, the time
range (`set_times`, `start_time`, `end_time`, `time_codes_per_second`), point clouds, duplicate
search, [full-scene traversal](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/concepts.html#glossary), invisible prims, the depth mode, threading
(`single_threaded`, `new_task_manager`, `nb_tasks`) and logging. [Names in each API](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/settings.html#names-in-each-api)
lists its fields, and [settings.md](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/settings.html) gives each field's
default, unit and effect, beside the matching C and Suite settings.

- `set_run_settings(settings, **changes)` applies `settings`, or `RunSettings()` when you omit
  it, with the keyword changes on top. It does not merge with the settings that the runtime
  already holds, so every field that you do not pass returns to its default. To change one field
  and keep the rest, pass `runtime.get_run_settings()` as the base.
- `nb_tasks` must be 1 to 255 when `new_task_manager` is `False` and `single_threaded` is not
  set. Otherwise any unsigned 32-bit value is accepted, and the new task manager ignores it.
- A dynamic query (`set_times=True` with `end_time` above `start_time`) drops overlaps between
  two meshes whose transforms are not animated. `RunSettings` has no field for this engine default.
  Run a static query to find those overlaps.
- `use_usdrt` has no effect.

### Reading results

| Method | Returns |
| --- | --- |
| `get_overlap_count()` | The number of overlapping prim pairs. |
| `get_overlap_record(index)` | An `OverlapRecord`: `name0`, `name1`, `nb_records`, `nb_elements`, `type` and `time`. |
| `get_overlap_details(index)` | An `OverlapDetails`: the record fields plus `clash_hash`, `mesh_hash0`, `mesh_hash1`, `min_distance` and `max_local_depth`. |
| `get_duplicate_count()`, `get_duplicate(index)` | Duplicate mesh pairs, as `DuplicateMesh` values: `name0`, `name1`, `clash_hash`, `mesh_hash0`, `mesh_hash1`, `nb_tris` and `time`. |
| `get_query_stats()` | A `QueryStats`: `nb_meshes`, `nb_shared_meshes`, `nb_cooked_meshes` and `nb_point_clouds`. |
| `get_overlap_report(index)` | An `OverlapReport` with face, outline and point buffers. See [Overlap reports](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/README.html#overlap-reports). |

- All of these except `get_overlap_report()` return copied values. They stay valid after later
  runs.
- The overlap order can differ between runs. Identify an overlap by `name0` and `name1`, or by
  `clash_hash`, never by its index; see
  [Overlap index order is not contractual](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/developer_guide.html#overlap-index-order-is-not-contractual).

`OverlapRecord`, `OverlapDetails` and `DuplicateMesh` are dataclasses with shared field names.
Each field is set on every value of the types that the Description column names:

| Field | Type | Required | Default or values | Description |
| --- | --- | --- | --- | --- |
| `name0` | `str` or `None` | Yes | A prim path; `None` when empty | The first prim: the point cloud of a point-vs-mesh pair, else a mesh. All three types. |
| `name1` | `str` or `None` | Yes | A prim path; `None` when empty | The second prim, a mesh. All three types. |
| `nb_records` | `int` | Yes | Always 1 for a static query | The number of frames in which the pair overlaps. `OverlapRecord` and `OverlapDetails`. |
| `nb_elements` | `int` | Yes | 0 or more | The number of overlapping triangles or points in this frame. `OverlapRecord` and `OverlapDetails`. |
| `type` | `OverlapType` | Yes | `MESH_VS_MESH` (0), `POINT_VS_MESH` (1) or `UNDEFINED` | The pair type. Test it as `== OverlapType.POINT_VS_MESH`, never as `!= 0`, which reads `UNDEFINED` as a point-cloud pair. `OverlapRecord` and `OverlapDetails`. |
| `time` | `float` | Yes | Seconds | The time of the frame, or of the duplicate pair, in seconds, not time codes. All three types. |
| `clash_hash` | `tuple[int, int]` | Yes | The two uint64 words of a 128-bit hash | The [clash hash](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/concepts.html#glossary) of the two prim paths. `OverlapDetails` and `DuplicateMesh`. |
| `mesh_hash0`, `mesh_hash1` | `tuple[int, int]` | Yes | The two uint64 words of a 128-bit hash | The [mesh hash](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/concepts.html#glossary) of the first and of the second prim. `OverlapDetails` and `DuplicateMesh`. |
| `min_distance` | `float` | Yes | Stage units; `0.0` when the meshes touch or interpenetrate | The minimum distance between the triangle pairs. `OverlapDetails` only. |
| `max_local_depth` | `float` | Yes | Stage units; `-1.0` when depth was not computed; always `0.0` for a point-vs-mesh overlap | The [maximum local depth](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/concepts.html#glossary) between the triangle pairs. `OverlapDetails` only. |
| `nb_tris` | `int` | Yes | 0 or more | The number of triangles in the duplicated mesh. `DuplicateMesh` only. |

`QueryStats` holds four counters of the last run, all zero before the pipeline has run:

| Field | Type | Required | Default or values | Description |
| --- | --- | --- | --- | --- |
| `nb_meshes` | `int` | Yes | 0 or more | The number of selected meshes. |
| `nb_shared_meshes` | `int` | Yes | 0 or more | The number of selected meshes that are shared instances. |
| `nb_cooked_meshes` | `int` | Yes | 0 or more | The number of unique meshes cooked. A value below `nb_meshes` minus `nb_shared_meshes` means that the run skipped meshes it could not cook. |
| `nb_point_clouds` | `int` | Yes | 0 or more | The number of selected point clouds. |

### Overlap reports

An `OverlapReport` holds the geometry behind one overlap record. `get_overlap_report(index)` fills
every buffer of the record's first frame. Its keyword arguments narrow it: `record_index` selects
another frame, and `flags`, a combination of the `OverlapReportFlag` members `FACES0`, `FACES1`,
`OUTLINE` and `POINTS0`, selects the buffers to fill. A report has these attributes:

| Attribute | Type | Required | Default or values | Description |
| --- | --- | --- | --- | --- |
| `faces0`, `faces1` | `BufferView` of `uint32` | Yes | Empty unless `flags` holds `FACES0` or `FACES1` | The USD face indices of the colliding faces of the first and of the second mesh. |
| `outline` | `BufferView` of `float32`, shape `(segments, 6)` | Yes | Empty unless `flags` holds `OUTLINE` | The intersection outline, one segment per row: `x0, y0, z0, x1, y1, z1`. |
| `points0` | `BufferView` of `uint32` | Yes | Empty unless `flags` holds `POINTS0` | The USD point indices of the colliding points of the point cloud. |
| `point_size0` | `float` | Yes | Stage units; `0.0` when the cloud authors per-point widths that differ | The point width (diameter), without the prim's transform scaling. A uniform per-point width array reports its single value. |
| `matrix0`, `matrix1` | tuple of 16 `float` | Yes | A row-major 4x4 matrix | The world transforms of the first and of the second prim for the record. |

The buffers borrow native memory. Continue the first query, before `destroy()`:

```python
with runtime.get_overlap_report(0) as report:
    faces0 = report.faces0.numpy().copy()  # uint32 face indices of the first mesh
    outline = report.outline.numpy().copy()  # float32, shape (segments, 6)
print(len(faces0), outline.shape)
```

Output:

```text
4 (10, 6)
```

- `numpy()`, `warp()` and [DLPack](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/concepts.html#glossary) (`np.from_dlpack(view)`,
  `torch.from_dlpack(view)`) hand the buffer over without copying. `numpy()` and DLPack need
  NumPy, and `warp()` needs `warp-lang`; the `viewer` extra installs both. `tolist()` copies a
  buffer without either.
- Close a report when you are done, or use it as a context manager.
- The runtime closes open reports itself before it changes its stage, settings, scope or
  pipeline, and on `reset()` and `destroy()`.
- Arrays already exported through `numpy()`, `warp()` or DLPack are not tracked. Copy what you
  keep before those calls.

[threading_and_ownership.md](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/threading_and_ownership.html) gives the lifetime of every borrowed
buffer.

### Asynchronous runs

`run_async()` starts the pipeline on a native worker and returns an `AsyncPipeline`. This code
replaces `runtime.run_pipeline()` and the result loop of the
[first query](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/README.html#first-query-in-python):

```python
import time

pipeline = runtime.run_async()
running, step = pipeline.poll()
while running:  # drive the loop by the running flag, not by step.finished
    print(f"{step.progress:.0%} {step.name}")
    time.sleep(0.05)
    running, step = pipeline.poll()
pipeline.finish()  # raises RuntimeError when the run failed or was cancelled
print(runtime.get_overlap_count())
```

Until you call `finish()` or `cancel()`, most runtime methods raise `RuntimeError`. A cancelled
run can report zero overlaps, so check `is_cancelled()` before you read zero as clash-free.
[async_pipelines.md](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/async_pipelines.html) describes the whole lifecycle.

The `ovclash-basics` playbook covers the query kinds and common mistakes.
[References in the package](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/overview.html#references-in-the-package) gives its path in the package
directory and lists every playbook.

## ovclash_suite

`ovclash_suite` is the Suite API. It keeps the legacy clash object model where practical:

- `ClashDetection` drives stage loading, settings, scopes, pipeline execution, overlap
  processing and reports.
- `ClashQuery`, `ClashInfo`, `ClashFrameInfo` and `PointCloudSettings` keep the legacy model
  names and serialization shape.
- `ClashData` is the facade over a [store](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/concepts.html#glossary). By default it creates and owns a `ClashDataSerializerSqlite`.
  That serializer opens `.ovclashdb` databases and
  [legacy `.clashDetection` containers](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/legacy_container.html) by path.
  No store is loaded from a USD stage.
- `ClashDataSerializerDirectory` stores the same records as readable JSON files, one file per
  record.
- `export_to_html(title, subtitle, column_defs, rows, additional_info=None)` and
  `export_to_json(column_defs, rows, additional_info=None)` are module-level functions, not
  methods. They return the document as UTF-8 bytes. Each `ExportColumnDef` in `column_defs`
  describes one column; [Python API](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/export.html#python-api) lists its fields.

[persistence.md](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/persistence.html) helps you choose a store and describes both,
[sqlite_serializer.md](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/sqlite_serializer.html) and
[directory_serializer.md](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/directory_serializer.html) describe each store's format, and
[export.md](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/export.html) describes the reports.

This example runs a query, saves its results, reads them back and exports them as JSON.
[The object model](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/persistence.html#the-object-model) lists the fields of `ClashQuery` and
`ClashInfo`:

```python
from contextlib import closing
from pathlib import Path

import ovclash
from ovclash_suite import ClashData, ClashDetection, ClashQuery, ExportColumnDef, SettingId, export_to_json

stage = Path(ovclash.__file__).parent / "samples" / "data" / "time_sampled.usda"
query = ClashQuery(
    query_name="Cube vs Plane",
    object_a_path="/Root/Xform_Primitives/Cube",
    object_b_path="/Root/Xform_Primitives/Plane",
    clash_detect_settings={SettingId.SETTING_TOLERANCE.name: 0.0},
)
clash = ClashDetection()
data = ClashData()  # owns a SQLite serializer
try:
    data.open("results.ovclashdb")
    data.insert_query(query)  # assigns query.identifier
    clash.open_stage(str(stage))
    clash.set_settings(query.clash_detect_settings)
    clash.set_scope_paths([query.object_a_path], [query.object_b_path])
    clash.run_pipeline()
    with closing(clash.fetch_and_save_overlaps(None, data, query)) as fetch:
        for progress in fetch:
            pass
    clashes = data.find_all_overlaps_by_query_id(query.identifier, False)
    rows = [[info.object_a_path, info.object_b_path, info.state.name] for info in clashes.values()]
    columns = [ExportColumnDef(0, "Object A"), ExportColumnDef(1, "Object B"), ExportColumnDef(2, "State")]
    print(export_to_json(columns, rows).decode("utf-8"))
finally:
    data.destroy()
    clash.destroy()
```

Output:

```json
{
    "columns": [
        "Object A",
        "Object B",
        "State"
    ],
    "rows": [
        [
            "/Root/Xform_Primitives/Cube",
            "/Root/Xform_Primitives/Plane",
            "NEW"
        ]
    ]
}
```

The `ovclash-suite-python` playbook, which
[References in the package](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/overview.html#references-in-the-package) lists, covers the settings
model, persistence, export and common mistakes. [suite_workflows.md](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/suite_workflows.html) walks
through the five queries of the `clash_suite_api` sample.

### Query settings keys

Key a `ClashQuery`'s settings by the `SettingId` member name, as in the example. The query
stores its settings as JSON. A `SettingId` or integer key becomes a decimal string there, and
later lookups ignore it. `ClashDetection.set_settings()` also accepts `SettingId` members and
their integer values as keys. It ignores an unknown key without an error.
[Names in each API](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/settings.html#names-in-each-api) lists every `SettingId` key, and
[settings.md](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/settings.html) gives its default.

### Ownership and errors

- Native failures raise Python exceptions that carry the native last-error text.
- `AsyncPipeline` and `AsyncFetchAndSave` are context managers.
- `ClashDetection`, `ClashData` and the built-in serializers are not context managers. Release
  each with `destroy()`.
- `ClashData.destroy()` only closes a serializer that you passed in. That serializer stays yours
  to destroy.

Some legacy `ClashData` and `ClashQuery` methods report a failure through their return value
instead of raising. [Failures that do not raise](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/persistence.html#failures-that-do-not-raise) lists
them and the failures that do raise.

### Fetching Suite runtime results

`ClashDetection.fetch_and_save_overlaps(stage, data, query)`, the
[fetch-and-save](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/concepts.html#glossary) entry point, saves the results of the completed run for
`query` into an open store. The store is a `ClashData` or a serializer. `stage` is kept
for compatibility and can be `None`; the results come from the runtime.

The method is a lazy generator that yields progress:

- The argument checks, the native start and the snapshot of the runtime's threading settings
  happen on the first iteration, not when you call the method.
- Each step handles at most 1,024 frame reports and stops once it reaches about 8 MiB of report
  payload. One indivisible report can exceed that bound, and a long overlap spans several steps.
- The final `1.0` yield follows a successful commit.
- An optional `progress_callback` receives `(written, total)` counts. `written` equal to `total`
  does not mean that the results are committed.
- The query's `SETTING_DUP_MESHES` decides whether duplicates are saved. The runtime's
  duplicate-only detection setting does not.

Iterate to the end, or close the iterator when you stop early, for example with
`contextlib.closing`. Until then, the iterator reserves the runtime and the built-in store, and
conflicting changes and lifecycle calls fail. Close an abandoned iterator before you close or
destroy either owner. Serialize the calls that share a runtime or a store.

With `async_mode=True`, initialization, writes and cleanup run on a native worker:

- Custom serializer callbacks run on that worker.
- Progress, modification and timing callbacks stay on the iterating thread.
- While the fetch is live, reads of the runtime and of the built-in store fail too.
- Stopping the iteration without closing leaves the worker running. Closing requests
  cancellation and waits for the worker.
- Cancellation can race completion. It does not undo a completed commit.

For explicit polling, `ClashDetection.run_async_fetch_and_save(stage, data, query)` returns an
`AsyncFetchAndSave` context manager. It has `is_running()`, `get_step_data()`, `get_counts()`
(which returns `(written, total, initialized)`), `get_last_error()`, `finish()`, `cancel()` and
`close()`.

- Finish asynchronous detection before you start a fetch.
- Close the fetch before you read the store.
- Close it with `close()` or a `with` block, not through garbage collection, so that cleanup is
  timely and its errors are visible.

Fetch-and-save is not all-or-nothing for every failure.
[Saving detection results](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/persistence.html#saving-detection-results) gives the outcome of each
failure, and how to publish results only after complete success.

### Clash states

By default, a fetch updates the stored clash states as
[Clash states](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/persistence.html#clash-states) describes: re-detected clashes become `ACTIVE`, an
unchanged `APPROVED` clash keeps its approval, and clashes that are no longer detected become
`RESOLVED`. The same flag controls the `ACTIVE` update in `process_overlap()` and
`process_duplicate()`. To keep the stored states, clear the flag before you start the fetch:

```python
from omni.physxclashdetectioncore.config import ExtensionConfig

ExtensionConfig.update_clash_states_after_detection = False
```

### Custom serializers

A custom serializer derives from `AbstractClashDataSerializer` and must override
`fetch_transaction(operation)`. That method handles the operations `FETCH_BEGIN`,
`FETCH_COMMIT` and `FETCH_ROLLBACK`, and the savepoints `FETCH_BEGIN_OVERLAP`,
`FETCH_END_OVERLAP` and `FETCH_ABORT_OVERLAP`, with the semantics that
[Custom stores](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/persistence.html#custom-stores) gives. Without that override, fetching raises
`NotImplementedError` before any work starts, because rollback cannot be emulated through the
CRUD methods.

For long `APPROVED` histories, a SQL-backed store should also override
`fetch_iter_saved_frames(identifier, first_frame_offset=0)` with a forward cursor:

- The cursor yields owned frames in persisted order and has a `close()` method.
- The fetch may close the cursor and reopen it at a nonzero offset around writes.
- The default implementation issues one ranged read per frame.

The docstrings of `AbstractClashDataSerializer` and its `fetch_*` hooks give the full contract;
[References in the package](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/overview.html#references-in-the-package) says how to read them.
[Numeric fidelity](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/persistence.html#numeric-fidelity) describes which numeric values each store
keeps exactly.

## omni.physxclashdetectioncore

`omni.physxclashdetectioncore` exposes the `ovclash_suite` API under the Kit extension's module
names, for code that you migrate from Kit. New code imports `ovclash_suite`.

- A few upstream names are absent.
- The USD-dependent helpers in its `usd_utils` module raise `NotImplementedError`.
- `help(omni.physxclashdetectioncore)` lists every gap, and
  [What does not](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/kit_compatibility.html#what-does-not) summarizes them.

[kit_compatibility.md](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/kit_compatibility.html) describes the porting shim.

## Comparing ovstage matrix decompositions

By default, the ovstage backend decomposes mesh matrices with OpenUSD-compatible math. To
compare with the original ovclash math without rebuilding, select it on a `ClashRuntime`. Here
`runtime` is the runtime of the [first query](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/README.html#first-query-in-python), and these lines replace its
`runtime.run_pipeline()` call:

```python
runtime.set_ovstage_matrix_decomposition("ovclash")
runtime.run_pipeline()
runtime.set_ovstage_matrix_decomposition("openusd")  # restore the default
runtime.run_pipeline()
```

- `runtime.get_ovstage_matrix_decomposition()` returns the selection.
- The mode belongs to one context. It survives `reset()` and stage replacement.
- Changing the mode clears existing results, closes open reports and discards the pipeline. The
  next `run_pipeline()` or `run_async()` recreates it.
- Selecting the current mode again changes nothing.
- Finish any outstanding async handle first, even when its worker has stopped or the mode is
  unchanged.
- Any other name raises `ValueError`.

The ovclash math can change zero-tolerance clash membership and report payloads. Raw reported
world matrices and point-cloud scaling do not change. In both modes, these values fail the
affected query instead of being normalized: non-finite or out-of-range matrix values, and
invalid resulting poses or scales, including non-unit rotation quaternions.

C callers include `<ovclash/ovclash_ovstage.h>`, release borrowed reports, and call
`ovclash_ovstage_set_matrix_decomposition(context, OVCLASH_OVSTAGE_MATRIX_DECOMPOSITION_OVCLASH)`.
`OVCLASH_OVSTAGE_MATRIX_DECOMPOSITION_OPENUSD` restores the default. Create a new pipeline for the
next run. These controls exist only with ovstage, and they do not switch the scene backend. The
Suite API, in Python and in C, has no equivalent control.
[ovstage_integration.md](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/ovstage_integration.html) covers the other ovstage-only entry points.

## Native library lookup

Importing `ovclash`, `ovclash_suite` or `omni.physxclashdetectioncore` loads the native library
once per process. The packages look for it in this order:

1. `OVCLASH_SUITE_LIB`, else `OVCLASH_LIB`. The value must be the absolute path of the library;
   a relative path is rejected.
2. The library bundled in the installed `ovclash` package. Only the wheel bundles one. The SDK
   archive's Python packages need `OVCLASH_LIB`.
3. The bare library name, through the platform's library search. This step runs only when
   `OVCLASH_ALLOW_BARE_LIBRARY_LOAD` is enabled, and it is for development only.

The import raises `OSError` when it cannot load the library. It raises `RuntimeError` when the
library's version differs from the version of the Python package.

### The ovstage runtime

- The wheel uses the `ovstage` package that pip installed as its dependency. It ignores
  `OVCLASH_OVSTAGE_PACKAGE_DIR`.
- The SDK archive does not bundle ovstage. When `OVCLASH_SUITE_LIB` or `OVCLASH_LIB` names its
  library, the packages use the `ovstage` that the SDK archive's `requirements.txt` pins, as
  installed for the running interpreter.
- With the SDK archive, `OVCLASH_OVSTAGE_PACKAGE_DIR` selects another installation instead. Set
  it to the absolute path of that installation's `ovstage` package directory.
- With the SDK archive, the selected `ovstage` must be exactly the pinned version, or the import
  raises `OSError`.

### Process environment

Loading prepares the process for the runtime:

- It prepends the runtime's OpenUSD plugin directories to `OV_PXR_PLUGINPATH_2511`. It also
  prepends them to `PXR_PLUGINPATH_NAME` when the runtime's OpenUSD is not namespaced.
- On Windows, it adds the runtime's directories to `PATH` and to the DLL search path, followed by
  the directories that `OVCLASH_DLL_DIRS` lists, separated by `;`.
- With the wheel, it sets `OVSTAGE_LIBRARY_PATH_HINT` when the variable is unset. With the SDK
  archive, it replaces an inherited value with the directory of the selected ovstage.
- On Linux, it loads the ovstage libraries into the global symbol namespace.

These changes affect the whole process. Child processes inherit the environment variables.

### USD coexistence

ovclash has no OpenUSD linkage of its own: it reads stages through the
ovstage API, and the pinned ovstage runtime carries a namespaced OpenUSD. That OpenUSD reads its
plugin registry from `OV_PXR_PLUGINPATH_2511`, so the packages publish the registry only there.
An unnamespaced OpenUSD, such as the `usd-core` wheel, reads `PXR_PLUGINPATH_NAME` instead and
does not load that registry.

- Keep the ovstage plugin directory out of `PXR_PLUGINPATH_NAME` when an unnamespaced OpenUSD
  shares the process. That OpenUSD would read the variable and then fail to declare its own schema
  types.
- Install `usd-core` only for code that imports `pxr` itself, through the `viewer-openusd`
  extra. A query needs no Python `pxr`.

When another component in your process already manages the OpenUSD plugin paths, set
`OVCLASH_DISABLE_BUNDLED_PLUGIN_PATHS=1`:

- The packages then add nothing to `PXR_PLUGINPATH_NAME` or `OV_PXR_PLUGINPATH_2511`.
- On Windows, they also leave the plugin directories of the ovclash payload out of the DLL search
  path.
- With the wheel, they still add the `ovstage` package's runtime and plugin directories to the
  DLL search path.
- The native library does not read this variable.

### Environment variables

The Python packages read these variables when they load the native library:

| Variable | Values | Required | Default | Effect |
| --- | --- | --- | --- | --- |
| `OVCLASH_SUITE_LIB`, `OVCLASH_LIB` | The absolute path of the native library | With the SDK archive's Python packages | Unset: the wheel loads the library it bundles | Selects the native library. `OVCLASH_SUITE_LIB` is checked first, and a relative path raises `OSError`. See [Native library lookup](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/README.html#native-library-lookup). |
| `OVCLASH_ALLOW_BARE_LIBRARY_LOAD` | Flag | No | Off | Development only. Allows the bare-name library lookup when neither override is set and no bundled library exists. |
| `OVCLASH_OVSTAGE_PACKAGE_DIR` | The absolute path of an `ovstage` package directory | No | Unset: the `ovstage` of the running interpreter | SDK archive only; the wheel ignores it. Selects another installation of the pinned ovstage. See [The ovstage runtime](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/README.html#the-ovstage-runtime). |
| `OVCLASH_DLL_DIRS` | Directories separated by `;` | No | Unset | Windows only. Adds the directories to `PATH` and to the DLL search path, after the runtime's directories. |
| `OVCLASH_DISABLE_BUNDLED_PLUGIN_PATHS` | Flag | No | Off | Leaves the plugin path variables, and on Windows the plugin directories of the ovclash payload, unchanged. See [USD coexistence](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/README.html#usd-coexistence). |

A flag is enabled by any value other than empty, `0`, `false`, `off` or `no`, ignoring case.
[Environment variables](https://nvidia-omniverse.github.io/ovclash/0.2.2/consumer/developer_guide.html#environment-variables) lists every variable that the
Python packages, the native library and the sample launchers read, with its exact rules.
