Metadata-Version: 2.4
Name: simready-validate
Version: 2026.7.1
Summary: SimReady Validation Library
Author: NVIDIA Corporation
License-Expression: Apache-2.0
Project-URL: Homepage, https://www.nvidia.com
Keywords: nvidia,simready,validate
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE.txt
Requires-Dist: usd-validation-nvidia==1.20.0
Requires-Dist: usd-profiles-nvidia>=1.16.0
Requires-Dist: usd-core>=22.11
Dynamic: license-file

# simready.validate

Standalone Python library for validating USD assets against SimReady profiles. Reports
which features passed or failed, with per-requirement detail on failures.

Part of the [SimReady Python Library Suite](https://developer.nvidia.com/simready) — no
Omniverse or Kit installation required.

---

## Installation

```bash
pip install simready-validate
```

## Quick start

```python
import simready.validate as sv
from pathlib import Path

# Load profiles from your spec data (omit when running inside Kit)
specs = Path("/path/to/specs")
sv.initialize(
    rules_and_requirements_paths=[specs / "capabilities"],
    features_paths=[specs / "features"],
    profiles_paths=[specs / "profiles/profiles.toml"],
)

result = sv.validate_asset(sv.AssetValidationConfig(
    asset_path="/assets/props/crate_01/crate_01.usd",
    profile_id="Prop-Robotics-Neutral",
    profile_version="2.0.0",
))

if result is None:
    print("Could not validate — see the validation diagnostics.")
else:
    passed = all(f["passed"] for f in result.features_summary.values())
    print(f"{result.profile_id} v{result.profile_version}: {'PASSED' if passed else 'FAILED'}")
    for feat_id, data in result.features_summary.items():
        if not data["passed"]:
            print(f"  {feat_id}: failing {data.get('failing requirements')}")

sv.destroy()
```

---

## Key concepts

### Profiles

A **profile** is a named set of features (e.g. `Prop-Robotics-Neutral`, `Prop-Robotics-Physx`).
Each feature groups a set of validation requirements. Passing a profile means every requirement
of every feature in that profile passed.

Profile data (rules, features, profile TOML) is loaded via `initialize()`. When running inside
a Kit environment, profiles are already registered and `initialize()` can be omitted.

### Profile inference

If `profile_id` is omitted from `AssetValidationConfig`, the library first reads
`SimReady_Metadata.validation.profile` from the asset's `.simready/validation.json`
receipt, then falls back to the same metadata embedded in USD `customLayerData`.
This allows previously-stamped assets to be re-validated without specifying the profile.

### Stamping results into USD

Setting `write_metadata=True` writes the validation outcome to the wrapped
`.simready/validation.json` receipt while preserving unknown receipt keys. For writable
`.usd`, `.usda`, and `.usdc` assets, the same metadata is embedded in
`customLayerData["SimReady_Metadata"]["validation"]`. USDZ archives are read-only for this
operation: their bytes are preserved and only the receipt is written. Writes use atomic
same-directory replacements. Stamping applies to both passing and failing results.

### Runtime variant validation

Physics runtime features (PhysX, Newton, MuJoCo) declare a `runtime` field in their feature
JSON manifest naming the USD physics variant set to enable. When validating, the library groups
the profile's requirements by that tag, then validates the neutral base with all physics
variants disabled and each runtime group with only its variant enabled — so runtime-specific
schemas (which compose only when the variant is on) are visible to their rules. Requirements
shared with the neutral base are checked once. Variant selection is applied on the stage's
session layer, so the asset's authored composition is never modified. Adding a new physics
engine is a data-only change (a new `runtime` value); no code change is required. See
[`docs/api.md`](docs/api.md#runtime-variant-validation) for details.

---

## Python API

### Initialization

```python
sv.initialize(
    rules_and_requirements_paths: list[Path],
    features_paths:               list[Path],
    profiles_paths:               list[Path],
) -> None

sv.destroy() -> None
```

#### From a project config file

```python
# Reads [validate] section from project_config.toml and calls initialize()
sv.initialize_from_config("/workspace/project_config.toml")
```

`project_config.toml` format:

```toml
[project_root]
setting = ".."   # optional offset from config file's directory

[validate]
requirements_paths = ["nv_core/tiers/simready_foundation_tier_core/simready/foundation/tier_core/capabilities"]
features_paths     = ["nv_core/tiers/simready_foundation_tier_core/simready/foundation/tier_core/features"]
profiles_paths     = ["nv_core/tiers/simready_foundation_tier_core/simready/foundation/tier_core/profiles"]

[docs]
url = "https://nvidia.github.io/simready-foundation/latest/"
```

Omit `[validate]` entirely to load installed tier wheels instead (see
[Initialization](#initialization)). When the paths *are* given, they may point either at a
tier's package tree, as above, or at a plain spec folder with no owning Python package.

### Validate a single asset

```python
sv.validate_asset(config: AssetValidationConfig) -> AssetValidationResult | None
```

Accepts `.usd`, `.usda`, `.usdc`, and `.usdz`. Returns `None` (with a distinct log
diagnostic) if the file is missing, unsupported, cannot be opened, or has no usable profile.

### Validate multiple assets

```python
sv.validate_asset_list(
    configs:          list[AssetValidationConfig],
    report_file_path: str | None = None,          # merged JSON report
) -> list[AssetValidationResult | None]
```

### Configuration and result types

```python
@dataclass
class AssetValidationConfig:
    asset_path:      str       # path to .usd / .usda / .usdc / .usdz file
    profile_id:      str | None = None   # None → receipt first, then USD metadata
    profile_version: str | None = None   # None → any registered version
    write_metadata:  bool = False         # stamp results into USD (pass or fail)

@dataclass
class AssetValidationResult:
    asset_path:       str
    profile_id:       str
    profile_version:  str
    features_summary: dict[str, dict]   # feature_id → {version, passed, failing requirements}
    issues:           list              # raw failing issues from the engine
```

### Batch validation with JSON report

```python
configs = [
    sv.AssetValidationConfig(asset_path=str(p), profile_id="Prop-Robotics-Neutral")
    for p in Path("/assets").rglob("*.usd")
]

results = sv.validate_asset_list(configs, report_file_path="/reports/validation.json")

n_passed = sum(
    1 for r in results
    if r and all(f["passed"] for f in r.features_summary.values())
)
print(f"{n_passed}/{len(configs)} passed")
```

The JSON report is **merged** — new results for the same asset path override existing entries;
other entries are preserved. The parent directory must exist before calling.

---

## CLI

The `simready-validate` command is included with the package.

```bash
# Validate a single asset
simready-validate \
  --rules-path /specs/capabilities \
  --features-path /specs/features \
  --profiles-path /specs/profiles \
  --profile Prop-Robotics-Neutral --version 2.0.0 \
  /assets/crate_01.usd

# Validate a list of assets and save a JSON report
simready-validate \
  --rules-path /specs/capabilities \
  --features-path /specs/features \
  --profiles-path /specs/profiles \
  --profile Prop-Robotics-Neutral \
  --output /reports/results.json \
  --asset_list assets.txt

# Stamp results into the USD (pass or fail)
simready-validate ... --stamp-asset-validation /assets/crate_01.usd

# Load paths from a project config file
simready-validate \
  --project-config /workspace/project_config.toml \
  --profile Prop-Robotics-Neutral \
  /assets/crate_01.usd
```

| Flag | Description |
|------|-------------|
| `ASSET_PATH` | Single `.usd` / `.usda` / `.usdc` / `.usdz` file to validate |
| `--asset_list FILE` | Text file, one asset path per line |
| `--profile ID` | Profile to validate against (omit for receipt-first inference) |
| `--version VER` | Profile version (requires `--profile`) |
| `--stamp-asset-validation` | Write the receipt and, except for USDZ, USD `customLayerData` |
| `--output JSON` | Append results to a JSON report file |
| `--project-config TOML` | Load spec paths from `project_config.toml` `[validate]` section |
| `--rules-path DIR` / `--features-path DIR` | Rules/features directories (repeatable; must be directories) |
| `--profiles-path DIR_OR_FILE` | Profiles directory or a single `.toml` file (repeatable) |
| `-v` / `--verbose` | Per-issue detail on failure + DEBUG logging |

Exit code: `0` = all passed, `1` = any failed or not found.

---

## Metadata ownership

`simready.validate` owns only the `validation` sub-key of `SimReady_Metadata` in USD
`customLayerData` and in the wrapped receipt. It preserves `runtime_testing`, `asset_id`,
and all other unknown receipt or metadata keys.

```
customLayerData["SimReady_Metadata"]
├── asset_id        <- simready.create
├── validation      <- simready.validate
└── runtime_testing.tested_features   <- simready.test
```

---

## License

Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
SPDX-License-Identifier: Apache-2.0
