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::{Mesh, Stage};

const ASSET: &str = "examples/variants.usda";
const EDIT_LAYER: &str = "examples/paint_edits_paint.usda";
const PRIM_PATH: &str = "/Cube";

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());

    let prim = stage.prim_at_path(PRIM_PATH).unwrap_or_else(|| {
        eprintln!("prim {} not found", PRIM_PATH);
        process::exit(1);
    });
    let mesh = prim.as_mesh().unwrap_or_else(|| {
        eprintln!("{} is not a mesh", PRIM_PATH);
        process::exit(1);
    });

    let point_count = mesh.points().len() / 3;
    println!("painting on {} ({} verts)", mesh.prim_path(), point_count);

    // primvars:wear — per-vertex float
    let wear: Vec<f32> = (0..point_count)
        .map(|i| i as f32 / (point_count.max(1) as f32 - 1.0).max(1.0))
        .collect();
    assert!(mesh.create_primvar_float("wear", &wear, "vertex"));
    println!("authored primvars:wear = {:?}", wear);

    // primvars:dustColor — per-vertex color3f
    let dust: Vec<f32> = (0..point_count)
        .flat_map(|i| {
            let t = i as f32 / (point_count.max(1) as f32 - 1.0).max(1.0);
            [0.5 + 0.5 * t, 0.4, 0.3 - 0.3 * t]
        })
        .collect();
    assert!(mesh.create_primvar_color3f("dustColor", &dust, "vertex"));
    println!(
        "authored primvars:dustColor ({} components / {} colors)",
        dust.len(),
        dust.len() / 3
    );

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

    // Round-trip: re-open and verify.
    let reopened = Stage::open(EDIT_LAYER).expect("reopen failed");
    let reprim = reopened.prim_at_path(PRIM_PATH).expect("prim missing on reopen");
    let remesh = reprim.as_mesh().expect("not a mesh on reopen");

    println!("\nprimvars on {} after reopen:", PRIM_PATH);
    for name in remesh.primvar_names() {
        if let Some(pv) = remesh.primvar(&name) {
            print_primvar(&name, &pv, &remesh);
        }
    }

    // Also surface Mesh::normals_interpolation for the Tier 1 polish.
    println!(
        "\nnormals interpolation on /World/Cube of test.usda: {}",
        Stage::open("examples/test.usda")
            .expect("test.usda")
            .prim_at_path("/World/Cube")
            .and_then(|p| p.as_mesh())
            .map(|m| m.normals_interpolation())
            .unwrap_or_else(|| "<none>".into())
    );

    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);
    }
}

fn print_primvar(name: &str, pv: &rust_usd::Primvar, _mesh: &Mesh) {
    let type_name = pv.type_name();
    let interp = pv.interpolation();
    let header = format!("  {:<14} type={:<12} interp={}", name, type_name, interp);
    match type_name.as_str() {
        "float[]" => {
            let v = pv.as_float_array();
            println!("{} values={:?}", header, v);
        }
        "color3f[]" | "float3[]" | "vector3f[]" => {
            let v = pv.as_vec3f_array();
            let chunks: Vec<String> = v
                .chunks_exact(3)
                .map(|c| format!("({:.3},{:.3},{:.3})", c[0], c[1], c[2]))
                .collect();
            println!("{} values=[{}]", header, chunks.join(", "));
        }
        "float2[]" | "texCoord2f[]" => {
            let v = pv.as_vec2f_array();
            let chunks: Vec<String> = v
                .chunks_exact(2)
                .map(|c| format!("({:.3},{:.3})", c[0], c[1]))
                .collect();
            println!("{} values=[{}]", header, chunks.join(", "));
        }
        "int[]" => {
            let v = pv.as_int_array();
            println!("{} values={:?}", header, v);
        }
        _ => {
            println!("{} (unhandled type)", header);
        }
    }
}