rust-usd 0.0.4

Rust bindings to OpenUSD (pxr C++): stage open, prim/mesh attrs, variants, sublayer authoring, UsdShade read+write, ArResolver hook.
use std::fs;
use std::process;

use rust_usd::Stage;

const ASSET: &str = "examples/variants.usda";
const EDIT_LAYER: &str = "examples/paint_edits_shade.usda";
const MESH_PATH: &str = "/Cube";
const MATERIAL_PATH: &str = "/Looks/PaintedMat";

fn main() {
    // Deterministic reruns.
    let _ = fs::remove_file(EDIT_LAYER);
    let asset_bytes_before = fs::read(ASSET).expect("variants.usda missing");

    let stage = Stage::open_for_painting(ASSET, EDIT_LAYER).unwrap_or_else(|e| {
        eprintln!("open_for_painting failed: {}", e.what());
        process::exit(1);
    });
    println!("edit target → {}", stage.edit_layer_path());

    // Author a Scope to host materials, then build the material graph:
    //
    //   /Looks/PaintedMat (Material)
    //     outputs:surface → Surface.outputs:surface
    //     Surface (UsdPreviewSurface)
    //       inputs:roughness = 0.4
    //       inputs:diffuseColor.connect → DiffuseTex.outputs:rgb
    //     DiffuseTex (UsdUVTexture)
    //       inputs:file = @./tex/diffuse.<UDIM>.png@
    //       outputs:rgb (float3)
    stage
        .define_prim("/Looks", "Scope")
        .expect("define /Looks");

    let mat = stage
        .create_material(MATERIAL_PATH)
        .expect("create material");
    println!("material → {}", mat.path());

    let surface = mat
        .create_shader("Surface", "UsdPreviewSurface")
        .expect("create surface shader");
    surface.set_input_float("roughness", 0.4);

    let diffuse_tex = mat
        .create_shader("DiffuseTex", "UsdUVTexture")
        .expect("create diffuse texture");
    diffuse_tex.set_input_asset("file", "./tex/diffuse.<UDIM>.png");
    diffuse_tex.declare_output("rgb", "float3");

    surface.connect_input("diffuseColor", &diffuse_tex, "rgb");
    mat.connect_surface(&surface);

    let prim = stage.prim_at_path(MESH_PATH).expect("/Cube missing");
    let mesh = prim.as_mesh().expect("/Cube not a mesh");
    if !mesh.bind_material(&mat) {
        eprintln!("bind_material failed");
        process::exit(1);
    }
    println!("bound {}{}", MESH_PATH, mat.path());

    stage.save_edit_layer();
    println!("saved {}", EDIT_LAYER);

    // Round-trip: reopen the edit layer and confirm dump_textures-style read
    // surfaces our newly-authored texture.
    let reopened = Stage::open(EDIT_LAYER).expect("reopen failed");
    let reprim = reopened.prim_at_path(MESH_PATH).expect("prim gone");
    let remesh = reprim.as_mesh().expect("not a mesh");
    let texs = remesh.bound_texture_paths();
    println!("\nbound textures on {} after reopen:", MESH_PATH);
    if texs.is_empty() {
        eprintln!("  (none — material binding round-trip failed)");
        process::exit(2);
    }
    for t in &texs {
        println!("  {}", t);
    }

    if reopened.material_at_path(MATERIAL_PATH).is_some() {
        println!("material survived round-trip at {}", MATERIAL_PATH);
    } else {
        eprintln!("material missing after round-trip");
        process::exit(2);
    }

    let asset_bytes_after = fs::read(ASSET).expect("variants.usda gone");
    if asset_bytes_before == asset_bytes_after {
        println!("\nverified {} is byte-identical to its original", ASSET);
    } else {
        eprintln!("ASSET WAS MUTATED — this should never happen");
        process::exit(2);
    }
}