1use std::collections::{HashMap, HashSet};
24
25use super::earclip::{self, P3};
26use super::math::V3;
27use super::step::{self, Entity, StepFile, Value};
28use super::{DEFAULT_COLOR, Material, Mesh, Model, ParseError, Result, Surface};
29
30pub const NO_GEOMETRY: &str = "IFC: no tessellated geometry";
32
33pub fn full_geometry() -> bool {
36 super::ifc_native::available()
37}
38
39pub const MAX_BYTES: usize = 256 << 20;
42
43const MAX_MAP_DEPTH: usize = 8;
45
46const RECENTRE_BEYOND: f64 = 10_000.0;
49
50#[derive(Debug, Clone, Default, PartialEq)]
52pub struct Report {
53 pub schema: String,
55 pub shown: usize,
57 pub skipped: usize,
59 pub triangles: usize,
60 pub unsupported: Vec<(String, usize)>,
63 pub fallback: bool,
66}
67
68pub fn parse(bytes: &[u8]) -> Result<Model> {
70 let (model, _) = read(bytes)?;
71 model.ok_or_else(|| ParseError::new(NO_GEOMETRY))
72}
73
74pub fn read(bytes: &[u8]) -> Result<(Option<Model>, Report)> {
77 if bytes.len() > MAX_BYTES {
78 return Err(ParseError::new("IFC: the file is too large"));
79 }
80 let file = step::parse(bytes)?;
81 if let Some(r) = super::ifc_native::read(&file, bytes)? {
82 return Ok(r);
83 }
84 let (model, mut report) = Reader::new(&file).run();
85 report.fallback = full_geometry();
86 Ok((model, report))
87}
88
89pub fn read_tessellated(bytes: &[u8]) -> Result<(Option<Model>, Report)> {
92 if bytes.len() > MAX_BYTES {
93 return Err(ParseError::new("IFC: the file is too large"));
94 }
95 let file = step::parse(bytes)?;
96 Ok(Reader::new(&file).run())
97}
98
99pub fn summary(path: &std::path::Path) -> Result<Report> {
101 let bytes = std::fs::read(path).map_err(ParseError::new)?;
102 read(&bytes).map(|(_, r)| r)
103}
104
105pub(super) const HIDDEN: &[&str] = &[
108 "IFCOPENINGELEMENT",
109 "IFCOPENINGSTANDARDCASE",
110 "IFCVOIDINGFEATURE",
111 "IFCSPACE",
112 "IFCSPATIALZONE",
113 "IFCVIRTUALELEMENT",
114 "IFCANNOTATION",
115 "IFCGRID",
116];
117
118const TYPES: &[(&str, &str, Option<V3>)] = &[
120 ("IFCWALL", "IfcWall", Some([0.92, 0.90, 0.85])),
121 ("IFCWALLSTANDARDCASE", "IfcWallStandardCase", Some([0.92, 0.90, 0.85])),
122 ("IFCWALLELEMENTEDCASE", "IfcWallElementedCase", Some([0.92, 0.90, 0.85])),
123 ("IFCCURTAINWALL", "IfcCurtainWall", Some([0.55, 0.70, 0.80])),
124 ("IFCSLAB", "IfcSlab", Some([0.68, 0.68, 0.68])),
125 ("IFCSLABSTANDARDCASE", "IfcSlabStandardCase", Some([0.68, 0.68, 0.68])),
126 ("IFCSLABELEMENTEDCASE", "IfcSlabElementedCase", Some([0.68, 0.68, 0.68])),
127 ("IFCROOF", "IfcRoof", Some([0.64, 0.36, 0.30])),
128 ("IFCWINDOW", "IfcWindow", Some([0.55, 0.75, 0.92])),
129 ("IFCWINDOWSTANDARDCASE", "IfcWindowStandardCase", Some([0.55, 0.75, 0.92])),
130 ("IFCDOOR", "IfcDoor", Some([0.60, 0.42, 0.28])),
131 ("IFCDOORSTANDARDCASE", "IfcDoorStandardCase", Some([0.60, 0.42, 0.28])),
132 ("IFCPLATE", "IfcPlate", Some([0.62, 0.80, 0.90])),
133 ("IFCCOLUMN", "IfcColumn", Some([0.62, 0.65, 0.72])),
134 ("IFCCOLUMNSTANDARDCASE", "IfcColumnStandardCase", Some([0.62, 0.65, 0.72])),
135 ("IFCBEAM", "IfcBeam", Some([0.56, 0.60, 0.70])),
136 ("IFCBEAMSTANDARDCASE", "IfcBeamStandardCase", Some([0.56, 0.60, 0.70])),
137 ("IFCMEMBER", "IfcMember", Some([0.50, 0.55, 0.62])),
138 ("IFCMEMBERSTANDARDCASE", "IfcMemberStandardCase", Some([0.50, 0.55, 0.62])),
139 ("IFCSTAIR", "IfcStair", Some([0.76, 0.72, 0.64])),
140 ("IFCSTAIRFLIGHT", "IfcStairFlight", Some([0.76, 0.72, 0.64])),
141 ("IFCRAMP", "IfcRamp", Some([0.76, 0.72, 0.64])),
142 ("IFCRAMPFLIGHT", "IfcRampFlight", Some([0.76, 0.72, 0.64])),
143 ("IFCRAILING", "IfcRailing", Some([0.35, 0.35, 0.38])),
144 ("IFCCOVERING", "IfcCovering", Some([0.85, 0.82, 0.74])),
145 ("IFCFOOTING", "IfcFooting", Some([0.55, 0.55, 0.52])),
146 ("IFCPILE", "IfcPile", Some([0.55, 0.55, 0.52])),
147 ("IFCFURNISHINGELEMENT", "IfcFurnishingElement", Some([0.72, 0.55, 0.38])),
148 ("IFCFURNITURE", "IfcFurniture", Some([0.72, 0.55, 0.38])),
149 ("IFCSITE", "IfcSite", Some([0.46, 0.62, 0.38])),
150 ("IFCGEOGRAPHICELEMENT", "IfcGeographicElement", Some([0.46, 0.62, 0.38])),
151 ("IFCBUILDING", "IfcBuilding", None),
152 ("IFCBUILDINGSTOREY", "IfcBuildingStorey", None),
153 ("IFCBUILDINGELEMENTPROXY", "IfcBuildingElementProxy", None),
154 ("IFCFLOWSEGMENT", "IfcFlowSegment", Some([0.55, 0.66, 0.56])),
155 ("IFCFLOWFITTING", "IfcFlowFitting", Some([0.55, 0.66, 0.56])),
156 ("IFCFLOWTERMINAL", "IfcFlowTerminal", Some([0.80, 0.80, 0.84])),
157 ("IFCPIPESEGMENT", "IfcPipeSegment", Some([0.55, 0.66, 0.56])),
158 ("IFCDUCTSEGMENT", "IfcDuctSegment", Some([0.66, 0.70, 0.74])),
159 ("IFCTRIANGULATEDFACESET", "IfcTriangulatedFaceSet", None),
160 ("IFCPOLYGONALFACESET", "IfcPolygonalFaceSet", None),
161 ("IFCFACETEDBREP", "IfcFacetedBrep", None),
162 ("IFCEXTRUDEDAREASOLID", "IfcExtrudedAreaSolid", None),
163 ("IFCEXTRUDEDAREASOLIDTAPERED", "IfcExtrudedAreaSolidTapered", None),
164 ("IFCREVOLVEDAREASOLID", "IfcRevolvedAreaSolid", None),
165 ("IFCSWEPTDISKSOLID", "IfcSweptDiskSolid", None),
166 ("IFCSURFACECURVESWEPTAREASOLID", "IfcSurfaceCurveSweptAreaSolid", None),
167 ("IFCFIXEDREFERENCESWEPTAREASOLID", "IfcFixedReferenceSweptAreaSolid", None),
168 ("IFCBOOLEANRESULT", "IfcBooleanResult", None),
169 ("IFCBOOLEANCLIPPINGRESULT", "IfcBooleanClippingResult", None),
170 ("IFCADVANCEDBREP", "IfcAdvancedBrep", None),
171 ("IFCADVANCEDBREPWITHVOIDS", "IfcAdvancedBrepWithVoids", None),
172 ("IFCBSPLINESURFACEWITHKNOTS", "IfcBSplineSurfaceWithKnots", None),
173 ("IFCCSGSOLID", "IfcCsgSolid", None),
174 ("IFCBLOCK", "IfcBlock", None),
175 ("IFCSPHERE", "IfcSphere", None),
176 ("IFCHALFSPACESOLID", "IfcHalfSpaceSolid", None),
177 ("IFCPOLYGONALBOUNDEDHALFSPACE", "IfcPolygonalBoundedHalfSpace", None),
178 ("IFCBOUNDINGBOX", "IfcBoundingBox", None),
179 ("IFCPOLYLINE", "IfcPolyline", None),
180 ("IFCGEOMETRICCURVESET", "IfcGeometricCurveSet", None),
181 ("IFCGEOMETRICSET", "IfcGeometricSet", None),
182 ("IFCINDEXEDPOLYCURVE", "IfcIndexedPolyCurve", None),
183];
184
185pub fn display_name(upper: &str) -> String {
188 if let Some(t) = TYPES.iter().find(|t| t.0 == upper) {
189 return t.1.to_string();
190 }
191 match upper.strip_prefix("IFC") {
192 Some(rest) if !rest.is_empty() => {
193 let lower = rest.to_ascii_lowercase();
194 format!("Ifc{}{}", lower[..1].to_ascii_uppercase(), &lower[1..])
195 }
196 _ => upper.to_string(),
197 }
198}
199
200pub(super) fn rep_rank(identifier: &str) -> Option<u8> {
204 match identifier.to_ascii_lowercase().as_str() {
205 "body" => Some(0),
206 "facetation" => Some(1),
207 "axis" | "footprint" | "box" | "annotation" | "profile" | "clearance" | "cog" | "lighting" | "reference" | "plan" => None,
208 _ => Some(2),
209 }
210}
211
212fn default_color(upper: &str) -> V3 {
213 TYPES.iter().find(|t| t.0 == upper).and_then(|t| t.2).unwrap_or(DEFAULT_COLOR)
214}
215
216fn sub(a: P3, b: P3) -> P3 {
217 [a[0] - b[0], a[1] - b[1], a[2] - b[2]]
218}
219
220fn dot(a: P3, b: P3) -> f64 {
221 a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
222}
223
224fn cross(a: P3, b: P3) -> P3 {
225 [a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]]
226}
227
228fn normalize(a: P3) -> Option<P3> {
229 let l = dot(a, a).sqrt();
230 (l > 1e-12 && l.is_finite()).then(|| [a[0] / l, a[1] / l, a[2] / l])
231}
232
233#[derive(Debug, Clone, Copy, PartialEq)]
235struct Xf([[f64; 4]; 3]);
236
237const IDENTITY: Xf = Xf([[1.0, 0.0, 0.0, 0.0], [0.0, 1.0, 0.0, 0.0], [0.0, 0.0, 1.0, 0.0]]);
238
239impl Xf {
240 fn from_axes(o: P3, x: P3, y: P3, z: P3) -> Xf {
242 Xf([[x[0], y[0], z[0], o[0]], [x[1], y[1], z[1], o[1]], [x[2], y[2], z[2], o[2]]])
243 }
244
245 fn frame(o: P3, z: Option<P3>, x: Option<P3>) -> Xf {
248 let z = z.and_then(normalize).unwrap_or([0.0, 0.0, 1.0]);
249 let x0 = x.and_then(normalize).unwrap_or([1.0, 0.0, 0.0]);
250 let x = normalize(sub(x0, scale(z, dot(x0, z))))
251 .or_else(|| normalize(sub([1.0, 0.0, 0.0], scale(z, z[0]))))
252 .or_else(|| normalize(sub([0.0, 1.0, 0.0], scale(z, z[1]))))
253 .unwrap_or([1.0, 0.0, 0.0]);
254 let y = cross(z, x);
255 Xf::from_axes(o, x, y, z)
256 }
257
258 fn then(&self, inner: &Xf) -> Xf {
260 let (a, b) = (&self.0, &inner.0);
261 let mut m = [[0.0; 4]; 3];
262 for r in 0..3 {
263 for c in 0..4 {
264 m[r][c] = (0..3).map(|k| a[r][k] * b[k][c]).sum::<f64>() + if c == 3 { a[r][3] } else { 0.0 };
265 }
266 }
267 Xf(m)
268 }
269
270 fn point(&self, p: P3) -> P3 {
271 let m = &self.0;
272 [0, 1, 2].map(|r| m[r][0] * p[0] + m[r][1] * p[1] + m[r][2] * p[2] + m[r][3])
273 }
274
275 fn vector(&self, v: P3) -> P3 {
276 let m = &self.0;
277 [0, 1, 2].map(|r| m[r][0] * v[0] + m[r][1] * v[1] + m[r][2] * v[2])
278 }
279}
280
281fn scale(a: P3, s: f64) -> P3 {
282 [a[0] * s, a[1] * s, a[2] * s]
283}
284
285fn coords(v: &Value) -> Option<P3> {
287 let l = v.as_list()?;
288 let n = |k: usize| l.get(k).and_then(Value::as_f64);
289 let p = [n(0)?, n(1)?, n(2).unwrap_or(0.0)];
290 p.iter().all(|c| c.is_finite()).then_some(p)
291}
292
293#[derive(Default)]
296struct Piece {
297 material: Option<Material>,
298 positions: Vec<P3>,
299 normals: Option<Vec<P3>>,
300 indices: Vec<u32>,
301}
302
303impl Piece {
304 fn new(material: Option<Material>) -> Piece {
305 Piece { material, ..Default::default() }
306 }
307}
308
309type GroupKey = (String, String, [u32; 3]);
311
312struct Reader<'a> {
313 f: &'a StepFile,
314 placements: HashMap<u64, Xf>,
316 item_styles: HashMap<u64, Material>,
318 product_styles: HashMap<u64, Material>,
320 points: HashMap<u64, Option<P3>>,
322}
323
324impl<'a> Reader<'a> {
325 fn new(f: &'a StepFile) -> Reader<'a> {
326 let mut r = Reader { f, placements: HashMap::new(), item_styles: HashMap::new(), product_styles: HashMap::new(), points: HashMap::new() };
327 r.collect_styles();
328 r
329 }
330
331 fn entities_named(&self, name: &'a str) -> impl Iterator<Item = (u64, &'a Entity)> + 'a {
332 self.f.entities.iter().filter(move |(_, e)| e.name == name).map(|(&id, e)| (id, e))
333 }
334
335 fn point(&mut self, v: &Value) -> Option<P3> {
336 let id = v.as_ref_id()?;
337 if let Some(p) = self.points.get(&id) {
338 return *p;
339 }
340 let p = self.f.deref_named(v, "IFCCARTESIANPOINT").and_then(|e| coords(e.arg(0)));
341 self.points.insert(id, p);
342 p
343 }
344
345 fn direction(&self, v: &Value) -> Option<P3> {
346 self.f.deref_named(v, "IFCDIRECTION").and_then(|e| coords(e.arg(0)))
347 }
348
349 fn axis_placement(&mut self, v: &Value) -> Xf {
351 let Some(e) = self.f.deref(v) else { return IDENTITY };
352 match e.name.as_str() {
353 "IFCAXIS2PLACEMENT3D" => {
354 let o = self.point(e.arg(0)).unwrap_or([0.0; 3]);
355 Xf::frame(o, self.direction(e.arg(1)), self.direction(e.arg(2)))
356 }
357 "IFCAXIS2PLACEMENT2D" => {
358 let o = self.point(e.arg(0)).unwrap_or([0.0; 3]);
359 Xf::frame(o, None, self.direction(e.arg(1)))
360 }
361 "IFCAXIS1PLACEMENT" => {
362 let o = self.point(e.arg(0)).unwrap_or([0.0; 3]);
363 Xf::frame(o, self.direction(e.arg(1)), None)
364 }
365 _ => IDENTITY,
366 }
367 }
368
369 fn placement(&mut self, v: &Value) -> Xf {
372 let mut chain = Vec::new();
373 let mut seen = HashSet::new();
374 let mut base = IDENTITY;
375 let mut cur = v.as_ref_id();
376 while let Some(id) = cur {
377 if let Some(x) = self.placements.get(&id) {
378 base = *x;
379 break;
380 }
381 let Some(e) = self.f.get(id).filter(|e| e.name == "IFCLOCALPLACEMENT") else { break };
382 if !seen.insert(id) {
383 break;
384 }
385 chain.push(id);
386 cur = e.arg(0).as_ref_id();
387 }
388 let mut x = base;
389 for id in chain.into_iter().rev() {
390 let e = self.f.get(id).expect("found above");
391 x = x.then(&self.axis_placement(e.arg(1)));
392 self.placements.insert(id, x);
393 }
394 x
395 }
396
397 fn operator(&mut self, v: &Value) -> Xf {
400 let Some(e) = self.f.deref(v) else { return IDENTITY };
401 if !e.name.starts_with("IFCCARTESIANTRANSFORMATIONOPERATOR") {
402 return IDENTITY;
403 }
404 let three_d = e.name.contains("3D");
405 let o = self.point(e.arg(2)).unwrap_or([0.0; 3]);
406 let s = e.arg(3).as_f64().unwrap_or(1.0);
407 let z = if three_d { self.direction(e.arg(4)) } else { None };
408 let mut x = Xf::frame(o, z, self.direction(e.arg(0)));
409 let (s2, s3) = match e.name.as_str() {
410 "IFCCARTESIANTRANSFORMATIONOPERATOR3DNONUNIFORM" => (e.arg(5).as_f64().unwrap_or(s), e.arg(6).as_f64().unwrap_or(s)),
411 "IFCCARTESIANTRANSFORMATIONOPERATOR2DNONUNIFORM" => (e.arg(4).as_f64().unwrap_or(s), s),
412 _ => (s, s),
413 };
414 for row in &mut x.0 {
415 row[0] *= s;
416 row[1] *= s2;
417 row[2] *= s3;
418 }
419 x
420 }
421
422 fn style_material(&self, v: &Value, depth: usize) -> Option<Material> {
424 if depth > 4 {
425 return None;
426 }
427 let refs: Vec<&Value> = match v {
428 Value::List(l) => l.iter().collect(),
429 other => vec![other],
430 };
431 for r in refs {
432 let Some(e) = self.f.deref(r) else { continue };
433 match e.name.as_str() {
434 "IFCPRESENTATIONSTYLEASSIGNMENT" => {
435 if let Some(m) = self.style_material(e.arg(0), depth + 1) {
436 return Some(m);
437 }
438 }
439 "IFCSURFACESTYLE" => {
440 let name = e.arg(0).as_str().unwrap_or("").to_string();
441 for s in e.arg(2).as_list().unwrap_or_default() {
442 let Some(se) = self.f.deref(s) else { continue };
443 if se.name != "IFCSURFACESTYLERENDERING" && se.name != "IFCSURFACESTYLESHADING" {
444 continue;
445 }
446 let Some(c) = self.f.deref_named(se.arg(0), "IFCCOLOURRGB") else { continue };
447 let ch = |k: usize| c.arg(k).as_f64().unwrap_or(0.8).clamp(0.0, 1.0) as f32;
448 let transparency = se.arg(1).as_f64().filter(|t| t.is_finite()).unwrap_or(0.0).clamp(0.0, 1.0) as f32;
449 return Some(Material { name, color: [ch(1), ch(2), ch(3)], opacity: 1.0 - transparency });
450 }
451 }
452 _ => {}
453 }
454 }
455 None
456 }
457
458 fn collect_styles(&mut self) {
460 for (_, e) in self.entities_named("IFCSTYLEDITEM") {
461 if let (Some(item), Some(m)) = (e.arg(0).as_ref_id(), self.style_material(e.arg(1), 0)) {
462 self.item_styles.entry(item).or_insert(m);
463 }
464 }
465 let mut material_styles: HashMap<u64, Material> = HashMap::new();
467 for (_, e) in self.entities_named("IFCMATERIALDEFINITIONREPRESENTATION") {
468 let Some(mat_id) = e.arg(3).as_ref_id() else { continue };
469 let mat_name = self.f.get(mat_id).and_then(|m| m.arg(0).as_str()).unwrap_or("").to_string();
470 'reps: for rep in e.arg(2).as_list().unwrap_or_default() {
471 let Some(re) = self.f.deref(rep) else { continue };
472 for item in re.arg(3).as_list().unwrap_or_default() {
473 let Some(si) = self.f.deref_named(item, "IFCSTYLEDITEM") else { continue };
474 if let Some(mut m) = self.style_material(si.arg(1), 0) {
475 if m.name.is_empty() {
476 m.name = mat_name.clone();
477 }
478 material_styles.insert(mat_id, m);
479 break 'reps;
480 }
481 }
482 }
483 }
484 if material_styles.is_empty() {
485 return;
486 }
487 for (_, e) in self.entities_named("IFCRELASSOCIATESMATERIAL") {
488 let Some(m) = self.material_of(e.arg(5), 0).and_then(|id| material_styles.get(&id)) else { continue };
489 for o in e.arg(4).as_list().unwrap_or_default() {
490 if let Some(id) = o.as_ref_id() {
491 self.product_styles.entry(id).or_insert_with(|| m.clone());
492 }
493 }
494 }
495 for (_, e) in self.entities_named("IFCRELDEFINESBYTYPE") {
497 let Some(m) = e.arg(5).as_ref_id().and_then(|t| self.product_styles.get(&t)).cloned() else { continue };
498 for o in e.arg(4).as_list().unwrap_or_default() {
499 if let Some(id) = o.as_ref_id() {
500 self.product_styles.entry(id).or_insert_with(|| m.clone());
501 }
502 }
503 }
504 }
505
506 fn material_of(&self, v: &Value, depth: usize) -> Option<u64> {
509 if depth > 4 {
510 return None;
511 }
512 let id = v.as_ref_id()?;
513 let e = self.f.get(id)?;
514 let first = |k: usize| e.arg(k).as_list().and_then(|l| l.first());
515 match e.name.as_str() {
516 "IFCMATERIAL" => Some(id),
517 "IFCMATERIALLAYERSETUSAGE" | "IFCMATERIALPROFILESETUSAGE" => self.material_of(e.arg(0), depth + 1),
518 "IFCMATERIALLAYERSET" | "IFCMATERIALLIST" => self.material_of(first(0)?, depth + 1),
519 "IFCMATERIALLAYER" => self.material_of(e.arg(0), depth + 1),
520 "IFCMATERIALCONSTITUENTSET" | "IFCMATERIALPROFILESET" => self.material_of(first(2)?, depth + 1),
521 "IFCMATERIALCONSTITUENT" | "IFCMATERIALPROFILE" => self.material_of(e.arg(2), depth + 1),
522 _ => None,
523 }
524 }
525
526 fn length_scale(&self) -> f64 {
528 let project_units = self.entities_named("IFCPROJECT").next().and_then(|(_, p)| p.arg(8).as_ref_id());
529 let assignments: Vec<&Entity> = project_units
530 .and_then(|id| self.f.get(id))
531 .into_iter()
532 .chain(self.entities_named("IFCUNITASSIGNMENT").map(|(_, e)| e))
533 .collect();
534 for a in assignments {
535 for u in a.arg(0).as_list().unwrap_or_default() {
536 if let Some(s) = self.unit_scale(u, 0) {
537 return s;
538 }
539 }
540 }
541 1.0
542 }
543
544 fn unit_scale(&self, v: &Value, depth: usize) -> Option<f64> {
546 let e = self.f.deref(v)?;
547 if e.arg(1).as_enum() != Some("LENGTHUNIT") || depth > 4 {
548 return None;
549 }
550 match e.name.as_str() {
551 "IFCSIUNIT" => {
552 if e.arg(3).as_enum() != Some("METRE") {
553 return None;
554 }
555 Some(match e.arg(2).as_enum() {
556 Some("KILO") => 1e3,
557 Some("HECTO") => 1e2,
558 Some("DECA") => 1e1,
559 Some("DECI") => 1e-1,
560 Some("CENTI") => 1e-2,
561 Some("MILLI") => 1e-3,
562 Some("MICRO") => 1e-6,
563 _ => 1.0,
564 })
565 }
566 "IFCCONVERSIONBASEDUNIT" | "IFCCONVERSIONBASEDUNITWITHOFFSET" => {
567 let m = self.f.deref_named(e.arg(3), "IFCMEASUREWITHUNIT")?;
568 let value = m.arg(0).as_f64()?;
569 let base = self.unit_scale(m.arg(1), depth + 1).unwrap_or(1.0);
570 (value > 0.0 && value.is_finite()).then_some(value * base)
571 }
572 _ => None,
573 }
574 }
575
576 fn point_list(&self, v: &Value) -> Option<Vec<P3>> {
578 let e = self.f.deref(v)?;
579 if !e.name.starts_with("IFCCARTESIANPOINTLIST") {
580 return None;
581 }
582 e.arg(0).as_list()?.iter().map(coords).collect()
583 }
584
585 fn item(&mut self, v: &Value, xf: &Xf, depth: usize, inherited: Option<&Material>, out: &mut Vec<Piece>, skipped: &mut Vec<String>) {
588 let Some(id) = v.as_ref_id() else { return };
589 let Some(e) = self.f.get(id) else { return };
590 let style = self.item_styles.get(&id).cloned().or_else(|| inherited.cloned());
591 match e.name.as_str() {
592 "IFCTRIANGULATEDFACESET" | "IFCTRIANGULATEDIRREGULARNETWORK" => {
593 if let Some(p) = self.triangulated(e, xf, style) {
594 out.push(p);
595 }
596 }
597 "IFCPOLYGONALFACESET" => {
598 if let Some(p) = self.polygonal(e, xf, style) {
599 out.push(p);
600 }
601 }
602 "IFCFACETEDBREP" | "IFCFACETEDBREPWITHVOIDS" => {
603 let mut p = Piece::new(style);
604 let mut shells = vec![e.arg(0).clone()];
605 shells.extend(e.arg(1).as_list().unwrap_or_default().iter().cloned());
606 for s in &shells {
607 self.shell(s, xf, &mut p);
608 }
609 out.push(p);
610 }
611 "IFCCLOSEDSHELL" | "IFCOPENSHELL" | "IFCCONNECTEDFACESET" => {
612 let mut p = Piece::new(style);
613 self.shell(v, xf, &mut p);
614 out.push(p);
615 }
616 "IFCSHELLBASEDSURFACEMODEL" | "IFCFACEBASEDSURFACEMODEL" => {
617 let mut p = Piece::new(style);
618 for s in e.arg(0).as_list().unwrap_or_default() {
619 self.shell(s, xf, &mut p);
620 }
621 out.push(p);
622 }
623 "IFCMAPPEDITEM" => {
624 if depth >= MAX_MAP_DEPTH {
625 return;
626 }
627 let Some(map) = self.f.deref_named(e.arg(0), "IFCREPRESENTATIONMAP") else { return };
628 let origin = self.axis_placement(map.arg(0));
629 let target = self.operator(e.arg(1));
630 let inner = xf.then(&target).then(&origin);
631 let Some(rep) = self.f.deref(map.arg(1)) else { return };
632 for it in rep.arg(3).as_list().unwrap_or_default() {
633 self.item(it, &inner, depth + 1, style.as_ref(), out, skipped);
634 }
635 }
636 other => skipped.push(display_name(other)),
637 }
638 }
639
640 fn triangulated(&mut self, e: &Entity, xf: &Xf, style: Option<Material>) -> Option<Piece> {
644 let pts = self.point_list(e.arg(0))?;
645 fn nested(v: &Value) -> Option<&[Value]> {
646 v.as_list().filter(|l| !l.is_empty() && l.iter().all(|x| x.as_list().is_some()))
647 }
648 let mut index = None;
649 let mut normals = None;
650 let mut pn = None;
651 for a in e.args.iter().skip(1) {
652 if let Some(l) = nested(a) {
653 let ints = l.iter().all(|t| t.as_list().is_some_and(|t| t.iter().all(|x| x.as_int().is_some())));
654 if ints && index.is_none() {
655 index = Some(l);
656 } else if !ints && normals.is_none() {
657 normals = Some(l);
658 }
659 } else if let Some(l) = a.as_list().filter(|l| !l.is_empty() && l.iter().all(|x| x.as_int().is_some())) {
660 pn = Some(l);
661 }
662 }
663 let index = index?;
664 let lookup = |k: i64| -> Option<u32> {
665 let k = match pn {
666 Some(pn) => pn.get(usize::try_from(k - 1).ok()?)?.as_int()?,
667 None => k,
668 };
669 let k = usize::try_from(k - 1).ok()?;
670 (k < pts.len()).then_some(k as u32)
671 };
672 let mut p = Piece::new(style);
673 p.positions = pts.iter().map(|&q| xf.point(q)).collect();
674 for t in index {
675 let t = t.as_list().unwrap_or_default();
676 if t.len() < 3 {
677 continue;
678 }
679 let ids: Option<Vec<u32>> = t.iter().take(3).map(|x| x.as_int().and_then(lookup)).collect();
680 if let Some(ids) = ids {
681 p.indices.extend(ids);
682 }
683 }
684 if pn.is_none()
685 && let Some(n) = normals
686 && n.len() == pts.len()
687 {
688 let ns: Option<Vec<P3>> = n.iter().map(|v| coords(v).map(|c| xf.vector(c))).collect();
689 p.normals = ns;
690 }
691 Some(p)
692 }
693
694 fn polygonal(&mut self, e: &Entity, xf: &Xf, style: Option<Material>) -> Option<Piece> {
697 let pts = self.point_list(e.arg(0))?;
698 let pn: Option<Vec<i64>> = e.arg(3).as_list().map(|l| l.iter().filter_map(Value::as_int).collect());
699 let resolve = |k: &Value| -> Option<usize> {
700 let k = k.as_int()?;
701 let k = match &pn {
702 Some(pn) if !pn.is_empty() => *pn.get(usize::try_from(k - 1).ok()?)?,
703 _ => k,
704 };
705 let k = usize::try_from(k - 1).ok()?;
706 (k < pts.len()).then_some(k)
707 };
708 let loop_of = |v: &Value| -> Option<Vec<usize>> { v.as_list()?.iter().map(resolve).collect() };
709 let mut p = Piece::new(style);
710 p.positions = pts.iter().map(|&q| xf.point(q)).collect();
711 for f in e.arg(2).as_list().unwrap_or_default() {
712 let Some(fe) = self.f.deref(f) else { continue };
713 if !fe.name.starts_with("IFCINDEXEDPOLYGONALFACE") {
714 continue;
715 }
716 let Some(outer) = loop_of(fe.arg(0)) else { continue };
717 let holes: Vec<Vec<usize>> = if fe.name == "IFCINDEXEDPOLYGONALFACEWITHVOIDS" {
718 fe.arg(1).as_list().unwrap_or_default().iter().filter_map(loop_of).collect()
719 } else {
720 Vec::new()
721 };
722 let all: Vec<usize> = outer.iter().chain(holes.iter().flatten()).copied().collect();
723 let o3: Vec<P3> = outer.iter().map(|&k| pts[k]).collect();
724 let h3: Vec<Vec<P3>> = holes.iter().map(|h| h.iter().map(|&k| pts[k]).collect()).collect();
725 let hr: Vec<&[P3]> = h3.iter().map(Vec::as_slice).collect();
726 for t in earclip::triangulate_3d(&o3, &hr) {
727 p.indices.extend(t.map(|k| all[k] as u32));
728 }
729 }
730 Some(p)
731 }
732
733 fn shell(&mut self, v: &Value, xf: &Xf, p: &mut Piece) {
735 let Some(e) = self.f.deref(v) else { return };
736 if !matches!(e.name.as_str(), "IFCCLOSEDSHELL" | "IFCOPENSHELL" | "IFCCONNECTEDFACESET") {
737 return;
738 }
739 for face in e.arg(0).as_list().unwrap_or_default() {
740 self.face(face, xf, p);
741 }
742 }
743
744 fn face(&mut self, v: &Value, xf: &Xf, p: &mut Piece) {
746 let Some(e) = self.f.deref(v) else { return };
747 if e.name != "IFCFACE" && e.name != "IFCFACESURFACE" {
748 return;
749 }
750 let mut outer: Option<Vec<P3>> = None;
751 let mut loops: Vec<Vec<P3>> = Vec::new();
752 for b in e.arg(0).as_list().unwrap_or_default() {
753 let Some(be) = self.f.deref(b) else { continue };
754 let Some(pl) = self.f.deref_named(be.arg(0), "IFCPOLYLOOP") else { continue };
755 let refs = pl.arg(0).as_list().unwrap_or_default();
756 let mut pts: Vec<P3> = refs.iter().filter_map(|r| self.point(r)).collect();
757 if pts.len() > 3 && pts.first() == pts.last() {
759 pts.pop();
760 }
761 if pts.len() < 3 {
762 continue;
763 }
764 if be.arg(1).as_enum() == Some("F") {
765 pts.reverse();
766 }
767 if be.name == "IFCFACEOUTERBOUND" && outer.is_none() {
768 outer = Some(pts);
769 } else {
770 loops.push(pts);
771 }
772 }
773 let outer = match outer {
774 Some(o) => o,
775 None => {
776 let area = |l: &Vec<P3>| {
778 let n = earclip::newell(l);
779 dot(n, n)
780 };
781 let Some(k) = (0..loops.len()).max_by(|&a, &b| area(&loops[a]).total_cmp(&area(&loops[b]))) else { return };
782 loops.swap_remove(k)
783 }
784 };
785 let hr: Vec<&[P3]> = loops.iter().map(Vec::as_slice).collect();
786 let tris = earclip::triangulate_3d(&outer, &hr);
787 if tris.is_empty() {
788 return;
789 }
790 let base = p.positions.len() as u32;
791 p.positions.extend(outer.iter().chain(loops.iter().flatten()).map(|&q| xf.point(q)));
792 for t in tris {
793 p.indices.extend(t.map(|k| base + k as u32));
794 }
795 }
796
797 fn product(&mut self, e: &Entity, pds: &Entity, style: Option<&Material>, skipped: &mut Vec<String>) -> Vec<Piece> {
800 let xf = self.placement(e.arg(5));
801 let mut ranked: Vec<(u8, &Entity)> = Vec::new();
802 for r in pds.arg(2).as_list().unwrap_or_default() {
803 let Some(re) = self.f.deref(r) else { continue };
804 let Some(rank) = rep_rank(re.arg(1).as_str().unwrap_or("")) else { continue };
805 ranked.push((rank, re));
806 }
807 ranked.sort_by_key(|r| r.0);
808 let mut k = 0;
809 while k < ranked.len() {
810 let rank = ranked[k].0;
811 let mut out = Vec::new();
812 while k < ranked.len() && ranked[k].0 == rank {
813 for it in ranked[k].1.arg(3).as_list().unwrap_or_default() {
814 self.item(it, &xf, 0, style, &mut out, skipped);
815 }
816 k += 1;
817 }
818 out.retain(|p| !p.indices.is_empty());
819 if !out.is_empty() {
820 return out;
821 }
822 }
823 Vec::new()
824 }
825
826 fn run(mut self) -> (Option<Model>, Report) {
827 let f = self.f;
828 let mut report = Report { schema: f.schemas.first().cloned().unwrap_or_default(), ..Default::default() };
829 let unit = self.length_scale();
830 let mut ids: Vec<u64> = f.entities.keys().copied().collect();
831 ids.sort_unstable();
832 let mut groups: Vec<(GroupKey, Mesh, bool)> = Vec::new();
835 let mut where_: HashMap<GroupKey, usize> = HashMap::new();
836 let mut unsupported: HashMap<String, usize> = HashMap::new();
837 let mut world: Vec<Vec<[f64; 3]>> = Vec::new();
838 for id in ids {
839 let e = &f.entities[&id];
840 if !e.name.starts_with("IFC") || HIDDEN.contains(&e.name.as_str()) {
841 continue;
842 }
843 let Some(pds) = f.deref_named(e.arg(6), "IFCPRODUCTDEFINITIONSHAPE") else { continue };
844 let style = self.product_styles.get(&id).cloned();
845 let mut skipped = Vec::new();
846 let pieces = self.product(e, pds, style.as_ref(), &mut skipped);
847 if pieces.is_empty() {
848 report.skipped += 1;
849 for s in skipped {
850 *unsupported.entry(s).or_default() += 1;
851 }
852 continue;
853 }
854 report.shown += 1;
855 let type_name = display_name(&e.name);
856 for piece in pieces {
857 let mat = piece.material.unwrap_or_else(|| Material { name: type_name.clone(), color: default_color(&e.name), opacity: 1.0 });
858 let key = (type_name.clone(), mat.name.clone(), mat.color.map(f32::to_bits));
859 let g = *where_.entry(key.clone()).or_insert_with(|| {
860 let surface = Surface { blend: mat.opacity.is_finite() && (mat.opacity - 1.0).abs() > 1e-6, ..Surface::default() };
861 groups.push((key, Mesh { name: type_name.clone(), material: Some(mat.clone()), surface, normals: Some(Vec::new()), ..Default::default() }, true));
862 world.push(Vec::new());
863 groups.len() - 1
864 });
865 let (_, mesh, with_normals) = &mut groups[g];
866 let base = world[g].len() as u32;
867 report.triangles += piece.indices.len() / 3;
868 mesh.indices.extend(piece.indices.iter().map(|i| base + i));
869 world[g].extend(piece.positions.iter().map(|q| [q[0] * unit, q[2] * unit, -q[1] * unit]));
871 match (piece.normals, mesh.normals.as_mut()) {
872 (Some(ns), Some(out)) if *with_normals => out.extend(ns.iter().map(|n| {
873 let n = normalize(*n).unwrap_or([0.0, 0.0, 1.0]);
874 [n[0] as f32, n[2] as f32, -n[1] as f32]
875 })),
876 _ => {
877 *with_normals = false;
878 mesh.normals = None;
879 }
880 }
881 }
882 }
883 let mut unsupported: Vec<(String, usize)> = unsupported.into_iter().collect();
884 unsupported.sort_by(|a, b| b.1.cmp(&a.1).then_with(|| a.0.cmp(&b.0)));
885 report.unsupported = unsupported;
886 if report.triangles == 0 {
887 return (None, report);
888 }
889 let (mut lo, mut hi) = ([f64::MAX; 3], [f64::MIN; 3]);
891 for q in world.iter().flatten() {
892 for k in 0..3 {
893 lo[k] = lo[k].min(q[k]);
894 hi[k] = hi[k].max(q[k]);
895 }
896 }
897 let centre = [0, 1, 2].map(|k| (lo[k] + hi[k]) / 2.0);
898 let shift = if centre.iter().any(|c| c.abs() > RECENTRE_BEYOND) { centre } else { [0.0; 3] };
899 let mut meshes = Vec::with_capacity(groups.len());
900 for ((_, mut mesh, _), pts) in groups.into_iter().zip(world) {
901 mesh.positions = pts.iter().map(|q| [(q[0] - shift[0]) as f32, (q[1] - shift[1]) as f32, (q[2] - shift[2]) as f32]).collect();
902 meshes.push(mesh);
903 }
904 match (Model { meshes }).finish() {
905 Ok(m) => {
906 report.triangles = m.triangle_count();
907 (Some(m), report)
908 }
909 Err(_) => {
910 report.triangles = 0;
911 (None, report)
912 }
913 }
914 }
915}
916
917#[cfg(test)]
918#[path = "ifc_tests.rs"]
919mod tests;