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.
cargo add openbim-ifcx openbim-ifcx-geometryEvery 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
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
// 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
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
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 composeThis 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
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:
cargo run --release -p openbim-ifcx-geometry --example ifcx2glb -- model.ifcx model.glbNext
- The
openbim-ifcxandopenbim-ifcx-geometryreference pages list the public API, with links into the rustdoc. - Capabilities records what is implemented and the evidence for it.