rvt 0.4.0

Apache-2.0 clean-room reader for Autodesk Revit files (.rvt, .rfa, .rte, .rft) without Revit: metadata, previews and the embedded schema of 2016-2026 files, and typed elements with measured geometry from Revit 2024 and 2025 projects, exported as IFC4, glTF and plan SVG.
Documentation
//! Q5.1 probe: dump the first-byte histogram for fields that currently
//! classify as FieldType::Unknown, and alongside it a sample field name
//! per byte so we can reason about which C++ type each discriminator
//! represents.

#![allow(
    clippy::needless_range_loop,
    clippy::type_complexity,
    clippy::collapsible_if,
    clippy::collapsible_match
)]

use rvt::{RevitFile, compression, formats, streams::FORMATS_LATEST};

fn main() -> anyhow::Result<()> {
    let path = std::env::args()
        .nth(1)
        .expect("usage: unknown_bytes <file.rfa>");
    let mut rf = RevitFile::open(&path)?;
    let raw = rf.read_stream(FORMATS_LATEST)?;
    let d = compression::inflate_at(&raw, 0)?;
    let schema = formats::parse_schema(&d)?;

    let mut hist: std::collections::BTreeMap<Vec<u8>, (u32, Vec<(String, String)>)> =
        std::collections::BTreeMap::new();
    for c in &schema.classes {
        for f in &c.fields {
            if let Some(ft) = &f.field_type {
                if let formats::FieldType::Unknown { bytes } = ft {
                    // Take first 4 bytes as a "signature"
                    let sig: Vec<u8> = bytes.iter().take(4).copied().collect();
                    let entry = hist.entry(sig).or_insert((0, Vec::new()));
                    entry.0 += 1;
                    if entry.1.len() < 3 {
                        entry.1.push((c.name.clone(), f.name.clone()));
                    }
                }
            }
        }
    }

    let mut rows: Vec<_> = hist.iter().collect();
    rows.sort_by_key(|r| std::cmp::Reverse(r.1.0));

    println!(
        "Unknown field_type first-4-byte histogram (top 30 patterns, {} distinct):",
        rows.len()
    );
    println!("  signature               count   sample fields");
    for (sig, (count, samples)) in rows.iter().take(30) {
        let sig_hex: Vec<String> = sig.iter().map(|b| format!("{b:02x}")).collect();
        let joined = sig_hex.join(" ");
        let padded = format!("{joined:<23}");
        let sample_str: Vec<String> = samples.iter().map(|(c, f)| format!("{c}.{f}")).collect();
        println!("  {padded} {count:5}   {}", sample_str.join(", "));
    }

    Ok(())
}