use crate::decoder::EntityDecoder;
use crate::error::Result;
#[inline]
pub fn get_si_prefix_multiplier(prefix: &str) -> f64 {
match prefix {
"ATTO" => 1e-18,
"FEMTO" => 1e-15,
"PICO" => 1e-12,
"NANO" => 1e-9,
"MICRO" => 1e-6,
"MILLI" => 1e-3, "CENTI" => 1e-2, "DECI" => 1e-1, "DECA" => 1e1, "HECTO" => 1e2, "KILO" => 1e3, "MEGA" => 1e6,
"GIGA" => 1e9,
"TERA" => 1e12,
"PETA" => 1e15,
"EXA" => 1e18,
_ => 1.0, }
}
#[inline]
pub fn get_conversion_based_unit_factor(name: &str) -> Option<f64> {
match name.to_uppercase().as_str() {
"FOOT" | "FEET" | "'FOOT'" => Some(0.3048),
"INCH" | "'INCH'" => Some(0.0254),
"YARD" | "'YARD'" => Some(0.9144),
"MILE" | "'MILE'" => Some(1609.344),
_ => None,
}
}
pub fn extract_length_unit_scale(decoder: &mut EntityDecoder, project_id: u32) -> Result<f64> {
let project = decoder.decode_by_id(project_id)?;
if project.ifc_type.as_str() != "IFCPROJECT" {
return Ok(1.0); }
let units_attr = match project.get(8) {
Some(attr) => attr,
None => return Ok(1.0), };
let units_ref = match units_attr.as_entity_ref() {
Some(ref_id) => ref_id,
None => return Ok(1.0), };
let unit_assignment = decoder.decode_by_id(units_ref)?;
if unit_assignment.ifc_type.as_str() != "IFCUNITASSIGNMENT" {
return Ok(1.0); }
let units_list_attr = match unit_assignment.get(0) {
Some(attr) => attr,
None => return Ok(1.0), };
let units_list = match units_list_attr.as_list() {
Some(list) => list,
None => return Ok(1.0), };
for unit_attr in units_list {
let unit_ref = match unit_attr.as_entity_ref() {
Some(ref_id) => ref_id,
None => continue,
};
let unit_entity = match decoder.decode_by_id(unit_ref) {
Ok(entity) => entity,
Err(_) => continue, };
let unit_type_str = unit_entity.ifc_type.as_str();
if unit_type_str == "IFCSIUNIT" {
let unit_type_attr = match unit_entity.get(1) {
Some(attr) => attr,
None => continue,
};
let unit_type = match unit_type_attr.as_enum() {
Some(type_str) => type_str,
None => continue,
};
if unit_type != "LENGTHUNIT" {
continue; }
let prefix_attr = match unit_entity.get(2) {
Some(attr) => attr,
None => return Ok(1.0), };
if prefix_attr.is_null() {
return Ok(1.0); }
let prefix = match prefix_attr.as_enum() {
Some(prefix_str) => prefix_str,
None => return Ok(1.0), };
return Ok(get_si_prefix_multiplier(prefix));
}
if unit_type_str == "IFCCONVERSIONBASEDUNIT" {
let unit_type_attr = match unit_entity.get(1) {
Some(attr) => attr,
None => continue,
};
let unit_type = match unit_type_attr.as_enum() {
Some(type_str) => type_str,
None => continue,
};
if unit_type != "LENGTHUNIT" {
continue; }
if let Some(name_attr) = unit_entity.get(2) {
if let Some(name) = name_attr.as_string() {
if let Some(factor) = get_conversion_based_unit_factor(name) {
return Ok(factor);
}
}
}
let conversion_factor_ref = match unit_entity.get_ref(3) {
Some(ref_id) => ref_id,
None => continue,
};
let measure_with_unit = match decoder.decode_by_id(conversion_factor_ref) {
Ok(entity) => entity,
Err(_) => continue,
};
let value_attr = match measure_with_unit.get(0) {
Some(attr) => attr,
None => continue,
};
let conversion_value = if let Some(val) = value_attr.as_float() {
val
} else if let Some(val) = value_attr.as_int() {
val as f64
} else {
1.0
};
if conversion_value > 0.0 {
let mut unit_component_scale = 1.0;
if let Some(unit_component_ref) = measure_with_unit.get_ref(1) {
if let Ok(unit_component) = decoder.decode_by_id(unit_component_ref) {
if unit_component.ifc_type.as_str() == "IFCSIUNIT" {
if let Some(prefix_attr) = unit_component.get(2) {
if !prefix_attr.is_null() {
if let Some(prefix) = prefix_attr.as_enum() {
unit_component_scale = get_si_prefix_multiplier(prefix);
}
}
}
}
}
}
return Ok(conversion_value * unit_component_scale);
}
}
}
Ok(1.0)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_si_prefix_multipliers() {
assert_eq!(get_si_prefix_multiplier("MILLI"), 0.001);
assert_eq!(get_si_prefix_multiplier("CENTI"), 0.01);
assert_eq!(get_si_prefix_multiplier("DECI"), 0.1);
assert_eq!(get_si_prefix_multiplier("KILO"), 1000.0);
assert_eq!(get_si_prefix_multiplier(""), 1.0);
assert_eq!(get_si_prefix_multiplier("UNKNOWN"), 1.0);
}
#[test]
fn test_extract_unit_from_real_file() {
let ifc_content = r#"ISO-10303-21;
HEADER;
FILE_DESCRIPTION(('Test'),'2;1');
FILE_NAME('test.ifc','2024-01-01',(''),(''),'','','');
FILE_SCHEMA(('IFC4'));
ENDSEC;
DATA;
#1=IFCPROJECT('guid',$,'Test',$,$,$,$,(#2),#3);
#2=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.E-5,#4,$);
#3=IFCUNITASSIGNMENT((#5));
#4=IFCAXIS2PLACEMENT3D(#6,$,$);
#5=IFCSIUNIT(*,.LENGTHUNIT.,.MILLI.,.METRE.);
#6=IFCCARTESIANPOINT((0.,0.,0.));
ENDSEC;
END-ISO-10303-21;
"#;
let mut decoder = EntityDecoder::new(ifc_content);
let scale = extract_length_unit_scale(&mut decoder, 1).unwrap();
assert!(
(scale - 0.001).abs() < 0.0001,
"Expected 0.001 for MILLI, got {}",
scale
);
}
#[test]
fn test_extract_unit_meters() {
let ifc_content = r#"ISO-10303-21;
HEADER;
FILE_DESCRIPTION(('Test'),'2;1');
FILE_NAME('test.ifc','2024-01-01',(''),(''),'','','');
FILE_SCHEMA(('IFC4'));
ENDSEC;
DATA;
#1=IFCPROJECT('guid',$,'Test',$,$,$,$,(#2),#3);
#2=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.E-5,#4,$);
#3=IFCUNITASSIGNMENT((#5));
#4=IFCAXIS2PLACEMENT3D(#6,$,$);
#5=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.);
#6=IFCCARTESIANPOINT((0.,0.,0.));
ENDSEC;
END-ISO-10303-21;
"#;
let mut decoder = EntityDecoder::new(ifc_content);
let scale = extract_length_unit_scale(&mut decoder, 1).unwrap();
assert!(
(scale - 1.0).abs() < 0.0001,
"Expected 1.0 for meters, got {}",
scale
);
}
#[test]
fn test_conversion_based_unit_factors() {
assert_eq!(get_conversion_based_unit_factor("FOOT"), Some(0.3048));
assert_eq!(get_conversion_based_unit_factor("foot"), Some(0.3048));
assert_eq!(get_conversion_based_unit_factor("FEET"), Some(0.3048));
assert_eq!(get_conversion_based_unit_factor("'FOOT'"), Some(0.3048));
assert_eq!(get_conversion_based_unit_factor("INCH"), Some(0.0254));
assert_eq!(get_conversion_based_unit_factor("YARD"), Some(0.9144));
assert_eq!(get_conversion_based_unit_factor("MILE"), Some(1609.344));
assert_eq!(get_conversion_based_unit_factor("UNKNOWN_UNIT"), None);
}
#[test]
fn test_extract_unit_imperial_feet() {
let ifc_content = r#"ISO-10303-21;
HEADER;
FILE_DESCRIPTION(('Test'),'2;1');
FILE_NAME('test.ifc','2024-01-01',(''),(''),'','','');
FILE_SCHEMA(('IFC4'));
ENDSEC;
DATA;
#1=IFCPROJECT('guid',$,'Test',$,$,$,$,(#2),#3);
#2=IFCGEOMETRICREPRESENTATIONCONTEXT($,'Model',3,1.E-5,#4,$);
#3=IFCUNITASSIGNMENT((#5));
#4=IFCAXIS2PLACEMENT3D(#6,$,$);
#5=IFCCONVERSIONBASEDUNIT(#7,.LENGTHUNIT.,'FOOT',#8);
#6=IFCCARTESIANPOINT((0.,0.,0.));
#7=IFCDIMENSIONALEXPONENTS(1,0,0,0,0,0,0);
#8=IFCMEASUREWITHUNIT(IFCLENGTHMEASURE(0.3048),#9);
#9=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.);
ENDSEC;
END-ISO-10303-21;
"#;
let mut decoder = EntityDecoder::new(ifc_content);
let scale = extract_length_unit_scale(&mut decoder, 1).unwrap();
assert!(
(scale - 0.3048).abs() < 0.0001,
"Expected 0.3048 for FOOT, got {}",
scale
);
}
}