About
Inspecting IFC files typically means launching Revit or Solibri and waiting minutes for a model to load — just to answer "how many wall types are in this file?" IFC Inspector opens any IFC file in under 2 seconds with a three-panel terminal dashboard. Built with a custom STEP/ISO-10303 parser (zero external IFC libraries), pre-computed O(1) relationship lookups, and #![forbid(unsafe_code)]. Supports IFC2X3 and IFC4 schemas, Unicode type names, and exports to CSV/JSON.
Gallery

Dashboard View

Type Detail View

Instance Browser
Code
#[derive(Debug, Serialize)]
pub struct IfcProject {
pub name: String,
pub schema: String,
pub file_path: String,
pub categories: Vec<Category>,
pub storeys: Vec<Storey>,
pub elements: HashMap<u64, Element>,
pub element_to_storey: HashMap<u64, u64>, // element -> storey O(1)
pub element_properties: HashMap<u64, HashMap<String, String>>,
pub instance_global_ids: HashMap<u64, String>, // instance -> GlobalId
}Core data structure with pre-computed HashMaps for O(1) lookups — zero tree scanning at runtime
fn parse_values(s: &str) -> Vec<StepValue> {
let mut values = Vec::new();
let mut current = String::new();
let mut in_string = false;
let mut paren_depth = 0;
for ch in s.chars() {
match ch {
'\'' if paren_depth == 0 => {
in_string = !in_string;
current.push(ch);
}
'(' if !in_string => {
paren_depth += 1;
current.push(ch);
}
')' if !in_string => {
paren_depth -= 1;
current.push(ch);
}
',' if !in_string && paren_depth == 0 => {
values.push(Self::parse_single_value(current.trim()));
current.clear();
}
_ => current.push(ch),
}
}
values
}Custom STEP/ISO-10303 state machine parser — handles nested parentheses, quoted strings, and recursive lists without regex
fn decode_step_string(s: &str) -> String {
let mut result = String::with_capacity(s.len());
let mut chars = s.chars().peekable();
while let Some(ch) = chars.next() {
if ch == '\\' {
match chars.peek() {
Some('X') => {
chars.next();
match chars.peek() {
Some('2') => {
// \\X2\\00D3\\X0\\ — 2-byte Unicode (BMP)
for chunk in hex.as_bytes().chunks(4) {
if let Ok(code) = u32::from_str_radix(s, 16) {
if let Some(c) = char::from_u32(code) {
result.push(c);
}
}
}
}
_ => { /* \\X\\XX — ISO 8859-1 single byte */ }
}
}
_ => result.push('\\'),
}
} else { result.push(ch); }
}
result
}Unicode decoder for STEP format — handles 3 encoding schemes (X2 2-byte Unicode, X single-byte ISO, S shift) from Polish/German Revit exports
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum View {
Dashboard,
TypeDetail,
InstanceBrowser,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum FocusPanel {
Levels,
Categories,
Types,
}
// Event dispatch — each view handles its own key bindings
match self.view {
View::Dashboard => self.handle_dashboard_keys(key.code),
View::TypeDetail => self.handle_detail_keys(key.code),
View::InstanceBrowser => self.handle_instance_keys(key.code),
}
fn handle_dashboard_keys(&mut self, code: KeyCode) {
match code {
KeyCode::Char('q') | KeyCode::Esc => self.should_quit = true,
KeyCode::Up | KeyCode::Char('k') => self.navigate_up(),
KeyCode::Down | KeyCode::Char('j') => self.navigate_down(),
KeyCode::Left | KeyCode::Char('h') => self.navigate_left(),
KeyCode::Right | KeyCode::Char('l') => self.navigate_right(),
KeyCode::Enter => self.enter_type_detail(),
_ => {}
}
}Elm Architecture in Rust — enum-based state machine with vim-like navigation dispatched per view
