
Custom STEP Parser & Terminal UI · Pure Rust
Rust / Ratatui / Crossterm / Clap / Serde
A terminal IFC file viewer, because firing up Revit just to see a list of types is a waste of time. You drop a file in and get a dashboard with storeys, categories and property sets.
Revit can take minutes to start, and I just wanted to glance at how many wall types are in the model or whether the property sets actually exported. Firing up a heavy BIM tool for such a simple check is absurd.
Browse the entire contents of an IFC file in a few keystrokes: families, property sets, instances.
You open an IFC in the terminal and immediately get a dashboard: storeys, categories, types, property sets. No need for Revit or an online viewer. Model audit in 2 seconds instead of 2 minutes.
Core data structure with pre-computed HashMaps for O(1) lookups — zero tree scanning at runtime
#[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}Custom STEP/ISO-10303 state machine parser — handles nested parentheses, quoted strings, and recursive lists without regex
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}Unicode decoder for STEP format — handles 3 encoding schemes (X2 2-byte Unicode, X single-byte ISO, S shift) from Polish/German Revit exports
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(); // consume 'X' match chars.peek() { Some('2') => { // \\X2\\XXXX...\\X0\\ - 2-byte Unicode encoding chars.next(); // consume '2' chars.next(); // consume '\\' let mut hex = String::new(); while let Some(&c) = chars.peek() { if c == '\\' { break; } hex.push(c); chars.next(); } if chars.peek() == Some(&'\\') { chars.next(); // '\\' chars.next(); // 'X' chars.next(); // '0' chars.next(); // '\\' } for chunk in hex.as_bytes().chunks(4) { if chunk.len() == 4 { if let Ok(s) = std::str::from_utf8(chunk) { if let Ok(code) = u32::from_str_radix(s, 16) { if let Some(c) = char::from_u32(code) { result.push(c); } } } } } } Some('\\') => { // \\X\\XX - ISO 8859-1 single byte chars.next(); // consume '\\' let mut hex = String::new(); for _ in 0..2 { if let Some(&c) = chars.peek() { hex.push(c); chars.next(); } } if let Ok(code) = u8::from_str_radix(&hex, 16) { result.push(code as char); } } _ => { result.push('\\'); result.push('X'); } } } Some('\\') => { chars.next(); result.push('\\'); } Some('S') => { // \\S\\X - single char shift (ISO 8859-1 high bit) chars.next(); // 'S' chars.next(); // '\\' if let Some(c) = chars.next() { result.push(((c as u8) + 128) as char); } } _ => result.push('\\'), } } else if ch == '\'' { if chars.peek() == Some(&'\'') { chars.next(); } result.push('\''); } else { result.push(ch); } } result}Elm Architecture in Rust — enum-based state machine with vim-like navigation dispatched per view
#[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 bindingsmatch 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(), _ => {} }}
Dashboard View

Type Detail View

Instance Browser