Skip to content

Getting started: Rust ​

Two crates: openbim-ifcx reads, writes, composes and validates, and openbim-ifcx-geometry turns a composed tree into a render scene and GLB.

sh
cargo add openbim-ifcx openbim-ifcx-geometry

Every snippet on this page is a region of crates/openbim-ifcx-geometry/tests/guide.rs, which the gate compiles and runs, so it works as shown. Errors are propagated with ? into a Box<dyn Error>.

Read and write a file ​

rust
use openbim_ifcx::IfcxFile;

let bytes = std::fs::read(&path)?;
let file = IfcxFile::from_json_slice(&bytes)?;
println!("{}: {} nodes", file.header.id, file.data.len());

// Writing back is lossless: key order, unknown fields, `null`
// deletions and every number stay as read.
let text = file.to_json_string_pretty()?;
assert_eq!(IfcxFile::from_json_str(&text)?, file);

IfcxFile holds header, imports, schemas and data, the four parts of an IFCX file, and keeps fields the draft does not define. A malformed file is a ReadError with line and column.

Validate attributes ​

rust
// Check every attribute against the file's own `schemas`.
if let Err(report) = file.validate() {
    for failure in &report.failures {
        // failure.node, .attribute, .pointer (JSON pointer), .kind
        println!("{failure}");
    }
}

Each failure names the node path, the attribute, an RFC 6901 pointer into the value and a FailureKind. validate_flat and validate_attributes check flattened nodes or single values.

Compose layers ​

rust
use openbim_ifcx::{compose, flatten_owned};

// Flatten the nodes by path (later opinions win), then compose them
// into a tree: `inherits` expanded, children resolved.
let composition = compose(&flatten_owned(file.data))?;
for root in composition.roots() {
    let node = composition.get(root).expect("roots are in the tree");
    println!("{root}: {} children", node.children.len());
}

To compose several files, concatenate their data weakest first before flattening: later opinions win. flatten_owned moves attribute values instead of copying them, which is several times faster than flatten on large models. A reference cycle or a reference to a node no layer defines is a ComposeError.

Resolve imports ​

rust
use openbim_ifcx::layers::{FsResolver, LayerStackBuilder};

// Load the file with every file its `imports` name, recursively.
// `FsResolver` (feature `fs`) reads local files; anything else, such as
// an HTTP client, implements `LayerResolver`.
let stack = LayerStackBuilder::new(FsResolver::new()).build(&main)?;
stack.validate()?; // against the schemas of every layer
let federated = stack.into_federated(); // one file, ready to compose

This needs the fs feature (cargo add openbim-ifcx --features fs). The stack lists every layer after the layers it imports, so a layer overrides its imports and the main layer wins; Capabilities explains why. build_all stacks several files the way upstream's ifcx compose does.

Export GLB ​

rust
use openbim_ifcx::{compose, flatten_owned, IfcxFile};
use openbim_ifcx_geometry::{to_glb, GlbOptions, RenderScene, SceneOptions};

let file = IfcxFile::from_json_slice(&std::fs::read(&path)?)?;
let composition = compose(&flatten_owned(file.data))?;

// The flat scene a viewer draws: instances with their IFCX path and
// world matrix, shared f32 buffers, materials, bounds.
let scene = RenderScene::from_composition(&composition, &SceneOptions::default());
println!("{} instances", scene.instances.len());

// Binary glTF 2.0, Y-up, one node per instance named by its IFCX path.
let glb = to_glb(&scene, &GlbOptions::default())?;
std::fs::write(&out, glb)?;

Buffers are relative to their own centre and instance matrices to a render origin, so georeferenced models keep sub-millimetre precision in f32. From the command line, the ifcx2glb example does the same:

sh
cargo run --release -p openbim-ifcx-geometry --example ifcx2glb -- model.ifcx model.glb

Next ​

Released under the MIT licence. No buildingSMART files or texts are published here.