Primvaria

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.

OpenUSD layout

Z up, metres, kilograms; each asset's root prim is its default prim.

/ISO4017_M8x30                 Xform, RigidBodyAPI, MassAPI, quincaillerie:* attributes, variant set "finish"
  /Visual/ISO4017_M8x30_mesh   one mesh, GeomSubset per material
  /Collision/core, head, thread_0000 …   Cylinder / convexHull meshes, purpose guide
  /MateFrame_thread, /MateFrame_drive    +Z = screwing axis; hex drive across flats
  /Looks, /PhysicsMaterials

/Holder_AA
  /base                        RigidBodyAPI (ABS mass), Visual/Holder_AA_base_mesh, Visual/spring_coil, Collision/…
  /spring_plate                RigidBodyAPI, Visual, Collision/minus_plate
  /Joints/spring               PrismaticJoint base -> spring_plate, linear drive = the coil spring (N/m)

The underside of a holder is at z = 0 of the asset: it sits on a floor where it is placed. Angular drives use UsdPhysics units (per degree). The root of each part says from which distance contacts should be generated, in metres (quincaillerie:contactOffset: 0.1 mm for a threaded part, below twice the thread clearance; 0.5 mm for cells and holders). It is a request to the engine, not an engine setting: the Blender add-on takes a tenth of it as its collision margin, and the PhysX layer copies it.

PhysX layer

The files hold plain OpenUSD (UsdGeom, UsdShade, UsdPhysics) and nothing specific to an engine. What only PhysX reads is written beside each file as a layer over it: assets/<part>_physx.usda and scene_physx.usda.

On Schema Fasteners Cells and holders
every rigid body PhysxRigidBodyAPI 32 position and 4 velocity solver iterations, depenetration velocity capped at 0.05 m/s, no CCD 16 and 4 iterations, 0.1 m/s, no CCD
every collider but the ground PhysxCollisionAPI contact offset 0.1 mm, rest offset 0 contact offset 0.5 mm, rest offset 0

A layer has its file as its only sublayer and holds overs only, so opening it (Isaac Sim: File > Open) gives the file with these settings, and the file alone is still complete for every other engine. The scene's layer repeats nothing: it references the layer of each part on top of the part. python -m quincaillerie.physx <folder> checks the layers of a set; the OpenUSD validators run on them in CI. Not simulated: see Limits.

Ground and physics scene (the same in every Primvaria project)

Every file with physics carries the same two prims, so that assets from different generators can share one scene and every engine gets the same floor:

Prim What it is
/World/PhysicsScene gravity 9.81 m/s² along −Z
/World/Ground a static box (UsdGeom.Cube, CollisionAPI), its top face at z = 0, 2 cm thick, covering the content with 0.5 m to spare on each side (1 m × 1 m at least), grey and rendered, no PhysX attributes (engines derive their contact offsets)
/World/GroundMaterial its physics material: static friction 0.6, dynamic friction 0.5, restitution 0.1, a wooden or concrete floor

The code is one file, ground.py, identical in Granulaire, Maillage, Filaire and Quincaillerie; a test checks every generated file against it. The MuJoCo checks and the Blender rigid-body checks take the ground's friction from the file (Blender's cloth, in Maillage, has its own collision settings).

OpenUSD compliance

python -m quincaillerie.validate output/catalog

This runs every validator of OpenUSD's UsdValidation registry on each file, the checks usdchecker runs: composition, stage metadata (meters, Z up), geometry, material bindings, shaders, UsdPhysics rigid bodies and colliders, joints and articulations. Each file is checked once per variant (every finish), since a variant that is not selected is not composed. The tests and CI require no error and no warning, on every part of the catalogue and on varied, worn and defective instances.

MaterialX networks (ND_* shaders) are checked by these validators only with an OpenUSD build that has its MaterialX plugin; the usd-core wheels from PyPI have none, so this check is skipped there. The .mtlx document is validated against the MaterialX standard library when the materialx extra is installed.

Primvaria contract

python -m quincaillerie.conformance output/catalog

Quincaillerie is part of Primvaria and follows its mission and its OpenUSD contract, version 0, which the four generators share. Every part is an asset any tool can find without knowing Quincaillerie: a model of kind component with assetInfo (name, identifier, version) and the attributes primvaria:contract, primvaria:generator, primvaria:version, primvaria:preset and primvaria:seed; in scene.usda, /World is an assembly that references them; primvaria:preset is the catalogue entry the instance was drawn from (ISO4017_M8x30). The command above checks the stage units, the model hierarchy and this identity; the tests and CI require it on every preset.

What was drawn for each instance is in the file too, its ground truth: primvaria:variation:aspects lists the aspects that were switched on (tolerance, defect, wear, physics) and primvaria:variation:<name> holds each drawn value (the variation:* columns of instances.csv, under the same names: clearance_mm, defect, rust, spring_factor…). An aspect that is switched off is still recorded, at its nominal value, and the other aspects keep their draws.

What was measured on each instance once generated is there as well, as primvaria:measured:<name> (mass_kg, colliders). The pack's instances.csv is read back from the OpenUSD files, one row per asset with the same names (variation:<name>, measured:<name>), and manifest.json says what the pack is; both have the same shape in the four generators, and the command above fails when the table and the files disagree.

Each part also says what it is with OpenUSD's own semantic labels (UsdSemantics.LabelsAPI), which renderers such as Isaac Sim turn into segmentation masks: semantics:labels:class holds the kind of part (screw, nut, washer, cell, holder) and the catalogue entry (ISO4017_M8x30), and semantics:labels:instance the asset's name. In a scene, the instance label is the name of the prim that places the part. The classes are a column of instances.csv and are listed in manifest.json.