use ifc_lite_processing::{
process_geometry_filtered, OpeningFilterMode, ParseResponse, ProcessingResult,
};
use std::backtrace::Backtrace;
use std::cell::RefCell;
use std::io::Write;
use std::slice;
use std::sync::{Once, OnceLock};
const PARSE_STACK_SIZE: usize = 256 * 1024 * 1024;
fn parse_pool() -> &'static rayon::ThreadPool {
static POOL: OnceLock<rayon::ThreadPool> = OnceLock::new();
POOL.get_or_init(|| {
rayon::ThreadPoolBuilder::new()
.stack_size(PARSE_STACK_SIZE)
.thread_name(|i| format!("ifc-lite-parse-{i}"))
.build()
.expect("failed to build ifc-lite parse thread pool")
})
}
thread_local! {
static CURRENT_IFC_PATH: RefCell<String> = const { RefCell::new(String::new()) };
}
static PANIC_HOOK_INIT: Once = Once::new();
fn ensure_panic_logging() {
PANIC_HOOK_INIT.call_once(|| {
let previous_hook = std::panic::take_hook();
std::panic::set_hook(Box::new(move |info| {
let path = CURRENT_IFC_PATH.with(|p| p.borrow().clone());
let backtrace = Backtrace::force_capture();
let log_path = std::env::temp_dir().join("ifc_lite_panic.log");
if let Ok(mut file) = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(&log_path)
{
let _ = writeln!(
file,
"==== ifc-lite panic ====\nfile: {}\n{info}\nbacktrace:\n{backtrace}\n",
if path.is_empty() { "<unknown>" } else { &path },
);
}
previous_hook(info);
}));
});
}
const LARGE_COORD_THRESHOLD: f64 = 1000.0;
const SITE_LOCAL_MESH_COORDINATE_SPACE: &str = "site_local";
const RAW_IFC_MESH_COORDINATE_SPACE: &str = "raw_ifc";
fn normalize_to_site_local(result: &mut ProcessingResult) {
if result.mesh_coordinate_space.as_deref() != Some(RAW_IFC_MESH_COORDINATE_SPACE) {
return;
}
let (site_tx, site_ty, site_tz) = match result.site_transform {
Some(ref st) if st.len() >= 16 => (st[12], st[13], st[14]),
_ => return,
};
if site_tx.abs() < LARGE_COORD_THRESHOLD
&& site_ty.abs() < LARGE_COORD_THRESHOLD
&& site_tz.abs() < LARGE_COORD_THRESHOLD
{
return;
}
for mesh in &mut result.meshes {
for chunk in mesh.positions.chunks_exact_mut(3) {
chunk[0] = (chunk[0] as f64 - site_tx) as f32;
chunk[1] = (chunk[1] as f64 - site_ty) as f32;
chunk[2] = (chunk[2] as f64 - site_tz) as f32;
}
}
result.mesh_coordinate_space = Some(SITE_LOCAL_MESH_COORDINATE_SPACE.to_string());
}
fn parse_impl(path_str: &str, mode: OpeningFilterMode) -> Result<Vec<u8>, i32> {
let content = std::fs::read_to_string(path_str).map_err(|_| 2)?;
CURRENT_IFC_PATH.with(|p| *p.borrow_mut() = path_str.to_string());
let result = parse_pool().install(|| {
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
process_geometry_filtered(&content, mode)
}))
});
CURRENT_IFC_PATH.with(|p| p.borrow_mut().clear());
let mut result = result.map_err(|_| 3)?;
normalize_to_site_local(&mut result);
let response = ParseResponse {
cache_key: String::new(),
meshes: result.meshes,
mesh_coordinate_space: result.mesh_coordinate_space,
site_transform: result.site_transform,
building_transform: result.building_transform,
metadata: result.metadata,
stats: result.stats,
symbolic_data: Default::default(),
};
serde_json::to_vec(&response).map_err(|_| 4)
}
unsafe fn run_parse(
path_ptr: *const u8,
path_len: usize,
mode: OpeningFilterMode,
out_ptr: *mut *mut u8,
out_len: *mut usize,
) -> i32 {
ensure_panic_logging();
if path_ptr.is_null() || out_ptr.is_null() || out_len.is_null() {
return 1;
}
let path_bytes = slice::from_raw_parts(path_ptr, path_len);
let path_str = match std::str::from_utf8(path_bytes) {
Ok(s) => s,
Err(_) => return 1,
};
let json_bytes = match parse_impl(path_str, mode) {
Ok(b) => b,
Err(code) => return code,
};
let len = json_bytes.len();
let ptr = Box::into_raw(json_bytes.into_boxed_slice()) as *mut u8;
*out_ptr = ptr;
*out_len = len;
0
}
#[no_mangle]
pub unsafe extern "C" fn ifc_lite_parse(
path_ptr: *const u8,
path_len: usize,
out_ptr: *mut *mut u8,
out_len: *mut usize,
) -> i32 {
run_parse(path_ptr, path_len, OpeningFilterMode::Default, out_ptr, out_len)
}
#[no_mangle]
pub unsafe extern "C" fn ifc_lite_parse_ex(
path_ptr: *const u8,
path_len: usize,
opening_filter_mode: i32,
out_ptr: *mut *mut u8,
out_len: *mut usize,
) -> i32 {
let mode = match opening_filter_mode {
1 => OpeningFilterMode::IgnoreAll,
2 => OpeningFilterMode::IgnoreOpaque,
_ => OpeningFilterMode::Default,
};
run_parse(path_ptr, path_len, mode, out_ptr, out_len)
}
#[no_mangle]
pub unsafe extern "C" fn ifc_lite_free(ptr: *mut u8, len: usize) {
if !ptr.is_null() && len > 0 {
let _ = Box::from_raw(slice::from_raw_parts_mut(ptr, len));
}
}
#[cfg(test)]
mod tests;