Mateusz Bochyński
AboutArch DesignsEngineeringContact

IFC Inspector

Custom STEP Parser & Terminal UI · Pure Rust

RustRatatuiCrosstermClapSerde
IFC Inspector
TypeDesktop App
Year2026
StatusProduction
Lines of Code2,200
Stack
RustRatatuiCrosstermClapSerde

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

1 / 3
Dashboard View — three-panel layout with levels, categories, and types
Click to zoom

Dashboard View

Type Detail View — properties and instances of selected IFC type
Click to zoom

Type Detail View

Instance Browser View — list of all instances for selected IFC type
Click to zoom

Instance Browser

Code

model/project.rsrust
#[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

parser/step.rsrust
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

parser/step.rsrust
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

ui/app.rsrust
#[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

←
PreviousRental Management System
All Projects
NextRhino Image Studio
→