Primvaria

Primvaria · generator

Quincaillerie

Procedural hardware assets: threaded screws, nuts and washers, battery cells and their holders, where tolerances, defects and wear change how parts really behave.

pip install quincaillerie
Source on GitHub PyPI

Every instance is different

Ten instances of an M10 screw, nut and washer, eight AA cells and three 9V blocks, each drawn with its own variation

quincaillerie generate screw M10 --with nut,washer --count 10 and the same for AA and 9 V cells: each part is a distinct file. Rust, grime, oil and scratches; scuffed and faded labels; a dished washer, oval and crushed nuts, crushed and bent screws (not all visible from here); and, invisible in a picture, a different thread fit, alloy density, friction and spring for each.

Aspect Fasteners Cells Holders
tolerance thread clearance per side within the fit class: 6g for screws, 6H for nuts (at M8: 0.015–0.075 mm and 0.015–0.080 mm, see Thread fit), across flats, head and nut height, length diameter and length under nominal, within the IEC bands side play of the cradle (0–0.6 mm)
defect (--defect-rate, 15 %) crushed thread (crests pushed sideways over 40–140°: jams a nut), bent shank (0.8–3°), burr at the tip; oval or crushed nut; dished washer dented can, corroded terminal (crust)
wear new, used, oily, rusty, dirty: rust, oil, grime and scratches baked into the instance's own textures; friction follows (rust ×1.8 at most, oil ×0.6) scuffed and faded label, grime grey or black ABS
physics density of the alloy (brass and stainless are solid alloys, the rest plated steel), friction mass ±3 %, friction spring stiffness ±8 %, tired springs (preload down to 70 %), clip torque ±10 %, friction

Each aspect has its own random stream: --no-vary wear keeps the same tolerances and defects with clean parts; the same seed gives the same parts. Everything drawn is in instances.csv and in each file as primvaria:variation:* attributes; the clean finishes stay available in the finish variant set of a worn part. --nominal gives the reference part.

The same nut driven onto a sound M12 screw and onto one with crushed threads

The defects are physical. The same nut, the same light torque (a hand on a nut driver), simulated in MuJoCo from the files: it runs down the sound screw (left) and stops at the crushed thread of the other (right). quincaillerie sim-test --defects checks this for every crushed screw of a batch.

Only crushed threads jam a nut: one screw in three with a defect, so the jam rate is a third of --defect-rate (5 % at the default 15 %). Measured on 40 screws: Defect rate and jam rate.

The physics, in motion

Dropped in Blender Screwed in MuJoCo Inserted in MuJoCo
Assemblies and parts dropped on the floor in Blender A nut driven down an M10 screw An AA cell and a CR2032 pushed into their holders
Two screw + washer + nut assemblies, an AA cell in its holder and loose parts, imported with File > Import > USD (add-on enabled) and dropped: Blender's rigid bodies, nothing set by hand. The nuts stay on their threads, the cell stays in its holder. A nut driven by a torque on an M10 screw: it advances 1.5 mm per turn on the modelled thread, the washer waits below. An AA cell and a CR2032 carried into their holders and released: the spring plate slides back and the coil compresses, the clip lifts and holds the coin cell.

All three are rendered in Blender (Cycles) from the generated OpenUSD files: scripts/physics_gifs.py.

What it does

  • Fasteners, metric coarse thread, M3 to M12: hex head screws (ISO 4017, fully threaded, any length), hex nuts (ISO 4032), plain washers (ISO 7089). The visual mesh carries the ISO 68-1 thread profile; the collision is a core cylinder plus one convex piece per 10° of helix (≈ 800 per screw, 200 per nut) with 0.05 mm of radial clearance, so a nut really screws on. Six finishes as a variant set: clear and yellow zinc, black oxide, stainless A2, plain steel, brass.
  • Cells and holders: AAA, AA, C, D, CR123A, 18650, 21700 in cradles with a coil spring; the 9 V block in a holder whose terminal slots are keyed; CR2032 and LR44 under a hinged leaf-spring clip. The spring stiffness and wire come from the contact force and preload (k = G d⁴ / 8 D³ n); the clip torsion from the force at its lever arm.
  • Variation: --count N distinct instances of each part, with drawn tolerances, defects, wear and physics (see above), and their ground truth.
  • Related elements on request (--with): a screw with its nut and washer, a cell with its holder, and a scene with every instance resting on a floor, plus the assemblies (made of instances without defects).
  • OpenUSD: one file per part, one rigid body per moving part with one visual mesh (so every DCC imports one object per body), mass, centre of mass and principal inertia, convex collision shapes, physics materials, joints with drives, mate frames (thread axis, drive, terminals, cell seat). The files hold nothing specific to an engine: what only PhysX reads (Isaac Sim) is a layer over each file, <file>_physx.usda. A shared material library in UsdPreviewSurface and MaterialX.
  • Checks (quincaillerie sim-test, quincaillerie blender-test): the same scenes, read back from the files, in MuJoCo and in Blender.
  • A Blender add-on (quincaillerie blender-addon): rigid bodies and constraints set up on every USD import.
  • --no-physics for visual-only assets.

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 part of the catalogue and varied instances, in CI
Blender tested: drop and assemblies through the add-on (quincaillerie blender-test)
MuJoCo tested: drop, screwing, jammed threads, insertion (quincaillerie sim-test)
NVIDIA Isaac Sim (PhysX) not tested yet (needs an NVIDIA RTX GPU); UsdPhysics, PhysX settings in a layer over each file

Installation

From PyPI:

pip install "quincaillerie[sim]"

From a clone, for development:

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

Requires Python 3.11+. Dependencies: NumPy, SciPy, Pillow and the OpenUSD bindings (usd-core 26.5+); MuJoCo for the checks. The Blender checks need Blender as a Python module (pip install bpy, Python 3.13) or a Blender executable.

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

quincaillerie list                                          # sizes, formats, finishes
quincaillerie generate screw M8 --length 30 --with nut,washer --count 100
quincaillerie generate cell 18650 --with holder --count 20 --defect-rate 0.3
quincaillerie generate screw M8 --count 50 --no-vary wear   # clean parts, varied fits and defects
quincaillerie generate nut M10 --nominal                    # the reference part
quincaillerie generate holder 9V --with cell --no-physics   # visuals only
quincaillerie generate all --nominal                        # the whole catalogue (41 assets)
quincaillerie sim-test output/ISO4017_M8x30 --defects       # MuJoCo checks (+ crushed threads jam)
quincaillerie blender-test output/catalog                   # the same in Blender
quincaillerie blender-addon                                 # quincaillerie_physics.zip

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

from quincaillerie import generate, resolve

parts = resolve("screw", "M6", length=25, with_=["nut", "washer"])
generate(parts, "output", count=100, defect_rate=0.2)  # -> output/ISO4017_M6x25/
generate(parts, "output", vary=("tolerance", "physics"))  # clean, undamaged, varied fits

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 files with OpenUSD alone, and a nut screwed on in MuJoCo.

Outputs (under output/<set>/)

File Content
assets/*.usdc, assets/*.usda one file per instance, ISO4017_M8x30_007.usdc (Blender: File > Import > USD)
materials/ quincaillerie_materials.usda (UsdPreviewSurface + MaterialX), .mtlx, textures; instances/: the baked textures of worn instances
scene.usda every instance on the shared ground (see below), plus the assemblies
assets/*_physx.usda, scene_physx.usda the same files with the PhysX settings (Isaac Sim): a layer over each file, to open instead of it, see PhysX layer
instances.csv one row per instance, read back from the files: 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 kind, size or format: the ground truth
manifest.json what the pack is (contract, generator, version, preset, seed, aspects, files) and its summary: counts by kind and by defect, the options used
sim_test/ test scenes and reports (sim-test, blender-test)

Limits

The short version; the full list is in Limits and roadmap.

  • Dimensions are nominal, from public tables; variation ranges are plausible orders of magnitude, not measured distributions.
  • A thread is about 1000 convex pieces: exact enough to screw, heavy for large scenes. --collider simple is light, but then nothing screws or jams.
  • Holders are generic designs, with no electrical contact model.
  • Checked in MuJoCo and Blender; not tested in Isaac Sim yet.

Development

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

Conventions and continuous integration: Development.

Documentation

Page What it holds
Usage in depth Every option of quincaillerie generate and the catalogue of sizes and cell formats.
OpenUSD layout and contract How a part is laid out in its file, the ground and physics scene, OpenUSD compliance, and what the Primvaria contract writes on every instance.
Physics: fits, colliders and checks The thread fit classes, thread and simple colliders, the Blender add-on, and the MuJoCo and Blender checks with their results.
Defect rate and jam rate Which defects stop a nut, measured on 40 screws in MuJoCo, and the resulting curve.
Limits and roadmap Everything Quincaillerie does not do or has not verified, stated plainly, and what comes next.
Development Branch and release conventions, how the README images are made, continuous integration.

License

MIT © 2026 Daniel Régis Sarmento Caon

The logo is drawn from DejaVu Sans Bold outlines (Bitstream Vera license), converted to paths, so it displays without the font installed.


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