Primvaria

Primvaria · generator

Granulaire

Procedural granular assets: sand, gravel, pebbles, seeds, fruit, pellets and regolith with measured shapes, sizes, masses and four moisture states.

pip install granulaire
Source on GitHub PyPI

What it does

  • Generates N distinct grain shapes (100 by default) from distributions borrowed from sedimentology: size on the phi scale (Wentworth), Zingg ratios (elongation I/L, flatness S/I), angularity, irregularity (fracture faces, lumps, pits) and mineralogy. Kernels, seeds and pellets use their own shape families (superellipsoids, cylinders).
  • Places each shape several times in shuffled order with random orientations: by default 100 shapes × 5 copies = 500 grains per scene (see Scene size and mixing).
  • Measures every generated grain (nominal diameter, Zingg ratios, Wadell sphericity, convexity, angularity proxy) instead of trusting the inputs.
  • Exports an OpenUSD scene: prototypes, grains by reference, UsdPreviewSurface materials, UsdPhysics rigid bodies (mass from volume and density, convex-hull collision, friction, restitution) and a collision ground.
  • Optionally gives every grain a moisture state through an OpenUSD variant set.
  • Tests every preset in MuJoCo and Blender (granulaire sim-test, granulaire blender-test): a pile of grains read back from the file falls and must settle; see Physics checks.
  • Exports packing peanuts and cherry tomatoes as rigid bodies like the other grains, plus a layer over the file that makes them deformable bodies. Deformable simulation works only with NVIDIA OmniPhysics (Omniverse, Isaac Sim 5+) and is not tested yet; see Deformable grains.

Engines

Every file is OpenUSD with standard UsdPhysics schemas. Where each engine stands, as on the Primvaria engine matrix:

Engine Status
OpenUSD validators (usdchecker) tested: every preset, every moisture state, in CI
Blender tested: pile test of every preset, through Blender's USD importer (granulaire blender-test)
MuJoCo tested: pile test of every preset, model built from the file (granulaire sim-test)
NVIDIA Isaac Sim (PhysX) not tested yet (needs an NVIDIA RTX GPU); standard UsdPhysics rigid bodies
NVIDIA OmniPhysics deformables packing peanuts and cherry tomatoes exported as a layer over the file, structure checked, not simulated yet

Installation

From PyPI:

pip install "granulaire[render]"
pip install "granulaire[sim]"     # MuJoCo pile test
pip install "granulaire[blender]" # Blender as a Python module (Python 3.13), for the Blender pile test

From a clone, for development:

python -m venv .venv && source .venv/bin/activate
pip install -e ".[render]"        # add ",dev" for tests and linting

Requires Python 3.11+ and usd-core 26.5+ (the OpenUSD bindings).

Earlier usd-core wheels generate the same files, but their OpenUSD validators do not load (25.5 ships without the validator libraries, 25.8 and 25.11 without the UsdPreviewSurface definitions), and CI tests both 26.5 and the newest release.

Usage

granulaire list
granulaire generate --preset beach_sand          # 100 shapes × 5 copies, shuffled
granulaire generate --preset beach_sand --shapes 20 --copies 10 --no-shuffle --seed 3
granulaire generate --preset beach_pebbles --moisture --moisture-weights 0,20,50,30
granulaire generate --preset rounded_gravel --no-size --no-orientation
granulaire render output/beach_sand output/crushed_gravel
granulaire render-moisture output/beach_pebbles output/crushed_gravel output/beach_sand
granulaire render-gallery output/beach_sand output/crushed_gravel --grains 5

The README gallery comes from generating every preset with --moisture, then granulaire render-gallery on all 25 output folders in category order, with --output docs/images/moisture_gallery.png.

python -m granulaire ... works too. From Python:

from granulaire import Variations, generate_scene, write_outputs

# defaults: 100 shapes × 5 copies, shuffled, seed 1; moisture turned on
sand = generate_scene("beach_sand", variations=Variations(moisture=True))
write_outputs(sand, "output")  # -> output/beach_sand/

# 20 shapes × 10 copies, copies side by side, another random draw
gravel = generate_scene("crushed_gravel", shapes=20, copies=10, shuffle=False, seed=3)
write_outputs(gravel, "output")  # -> output/crushed_gravel/

Four short scripts in examples/ show the rest, each explained by its own comments: a first pack and its ground truth, one aspect switched off, reading the file with OpenUSD alone, and the pile test in MuJoCo.

Variations (checkboxes)

Option On Off
--size diameter drawn from the grading curve all at d50
--shape elongation, flatness and irregularity drawn mean values
--roundness facets and edge rounding drawn median values
--mineralogy mineral and tint drawn dominant mineral
--orientation random orientation all aligned
--moisture (off by default) variant set + state drawn per grain no moisture

Turning one variation off does not change the other random draws: with the same seed you compare the same scene with a single difference.

Outputs (per preset, under output/<preset>/)

File Content
granulaire_<preset>.usdc OpenUSD scene (Blender: File > Import > USD)
granulaire_<preset>_omniphysics.usdc deformable presets only: the same scene with deformable bodies for NVIDIA OmniPhysics (Omniverse, Isaac Sim 5+), a layer over the file, to open instead of it
prototype_metrics.csv one row per shape: measured descriptors, mass (per moisture state), parameters
instances.csv one row per grain, read back from the file: its identity, its classes (the semantic labels renderers turn into segmentation masks), every drawn value (variation:<name>) and every measured value (measured:<name>), then its prototype and position: the ground truth
manifest.json what the pack is (contract, generator, version, preset, seed, aspects, files) and its summary: global statistics (d50, mean sphericity, masses, water) and grading, the sizes against the preset's curve (see Limits)
prototypes.npz meshes, used by the renderer
shape_sheet.png, mix_preview.png previews (granulaire render)

Limits

The short version; the full list is in Limits.

  • Values are sensible centres to randomise around, not calibrated measurements: no angle of repose is verified for any preset.
  • Physics on CPU targets a few hundred grains, with convex-hull collision.
  • Wet grains differ by friction, restitution and mass only: no capillary cohesion.
  • Deformable grains need NVIDIA OmniPhysics and are not tested yet.

Development

git clone https://github.com/drscaon/granulaire.git && cd granulaire
pip install -e ".[dev]"
pytest

Conventions and continuous integration: Development.

Documentation

Page What it holds
Usage in depth Scene size and mixing, the preset list, moisture states, deformable grains, opening a scene in Blender.
OpenUSD layout and contract The ground and physics scene, OpenUSD compliance, and what the Primvaria contract writes on every grain.
Physics: method, values and checks How shapes are made, the friction and restitution table, the pile tests in MuJoCo and Blender, and what changes with grain size.
Limits Everything Granulaire does not do or has not verified, stated plainly.
Development Setting up, commit and branch conventions, continuous integration.

License

MIT © 2026 Daniel Régis Sarmento Caon

The logo is set in IBM Plex Sans (SIL Open Font License 1.1), outlined into paths, so it displays without the font installed.


Granulaire is part of Primvaria, physics-ready variability for Physical AI, in OpenUSD: pip install primvaria installs the whole suite (source).