1use wasm_bindgen::prelude::*;
10use wasm_bindgen_futures::spawn_local;
11use js_sys::{Function, Promise};
12use ifc_lite_core::{EntityScanner, ParseEvent, StreamConfig, GeoReference, RtcOffset};
13use crate::zero_copy::{ZeroCopyMesh, MeshDataJs, MeshCollection, InstancedMeshCollection, InstancedGeometry, InstanceData};
14
15#[wasm_bindgen]
17#[derive(Debug, Clone)]
18pub struct GeoReferenceJs {
19 #[wasm_bindgen(skip)]
21 pub crs_name: Option<String>,
22 pub eastings: f64,
24 pub northings: f64,
26 pub orthogonal_height: f64,
28 pub x_axis_abscissa: f64,
30 pub x_axis_ordinate: f64,
32 pub scale: f64,
34}
35
36#[wasm_bindgen]
37impl GeoReferenceJs {
38 #[wasm_bindgen(getter, js_name = crsName)]
40 pub fn crs_name(&self) -> Option<String> {
41 self.crs_name.clone()
42 }
43
44 #[wasm_bindgen(getter)]
46 pub fn rotation(&self) -> f64 {
47 self.x_axis_ordinate.atan2(self.x_axis_abscissa)
48 }
49
50 #[wasm_bindgen(js_name = localToMap)]
52 pub fn local_to_map(&self, x: f64, y: f64, z: f64) -> Vec<f64> {
53 let cos_r = self.x_axis_abscissa;
54 let sin_r = self.x_axis_ordinate;
55 let s = self.scale;
56
57 let e = s * (cos_r * x - sin_r * y) + self.eastings;
58 let n = s * (sin_r * x + cos_r * y) + self.northings;
59 let h = z + self.orthogonal_height;
60
61 vec![e, n, h]
62 }
63
64 #[wasm_bindgen(js_name = mapToLocal)]
66 pub fn map_to_local(&self, e: f64, n: f64, h: f64) -> Vec<f64> {
67 let cos_r = self.x_axis_abscissa;
68 let sin_r = self.x_axis_ordinate;
69 let inv_scale = if self.scale.abs() < f64::EPSILON {
70 1.0
71 } else {
72 1.0 / self.scale
73 };
74
75 let dx = e - self.eastings;
76 let dy = n - self.northings;
77
78 let x = inv_scale * (cos_r * dx + sin_r * dy);
79 let y = inv_scale * (-sin_r * dx + cos_r * dy);
80 let z = h - self.orthogonal_height;
81
82 vec![x, y, z]
83 }
84
85 #[wasm_bindgen(js_name = toMatrix)]
87 pub fn to_matrix(&self) -> Vec<f64> {
88 let cos_r = self.x_axis_abscissa;
89 let sin_r = self.x_axis_ordinate;
90 let s = self.scale;
91
92 vec![
93 s * cos_r, s * sin_r, 0.0, 0.0,
94 -s * sin_r, s * cos_r, 0.0, 0.0,
95 0.0, 0.0, 1.0, 0.0,
96 self.eastings, self.northings, self.orthogonal_height, 1.0,
97 ]
98 }
99}
100
101impl From<GeoReference> for GeoReferenceJs {
102 fn from(geo: GeoReference) -> Self {
103 Self {
104 crs_name: geo.crs_name,
105 eastings: geo.eastings,
106 northings: geo.northings,
107 orthogonal_height: geo.orthogonal_height,
108 x_axis_abscissa: geo.x_axis_abscissa,
109 x_axis_ordinate: geo.x_axis_ordinate,
110 scale: geo.scale,
111 }
112 }
113}
114
115#[wasm_bindgen]
117#[derive(Debug, Clone, Default)]
118pub struct RtcOffsetJs {
119 pub x: f64,
121 pub y: f64,
123 pub z: f64,
125}
126
127#[wasm_bindgen]
128impl RtcOffsetJs {
129 #[wasm_bindgen(js_name = isSignificant)]
131 pub fn is_significant(&self) -> bool {
132 const THRESHOLD: f64 = 10000.0;
133 self.x.abs() > THRESHOLD || self.y.abs() > THRESHOLD || self.z.abs() > THRESHOLD
134 }
135
136 #[wasm_bindgen(js_name = toWorld)]
138 pub fn to_world(&self, x: f64, y: f64, z: f64) -> Vec<f64> {
139 vec![x + self.x, y + self.y, z + self.z]
140 }
141}
142
143impl From<RtcOffset> for RtcOffsetJs {
144 fn from(offset: RtcOffset) -> Self {
145 Self {
146 x: offset.x,
147 y: offset.y,
148 z: offset.z,
149 }
150 }
151}
152
153#[wasm_bindgen]
155pub struct MeshCollectionWithRtc {
156 meshes: MeshCollection,
157 rtc_offset: RtcOffsetJs,
158}
159
160#[wasm_bindgen]
161impl MeshCollectionWithRtc {
162 #[wasm_bindgen(getter)]
164 pub fn meshes(&self) -> MeshCollection {
165 self.meshes.clone()
166 }
167
168 #[wasm_bindgen(getter, js_name = rtcOffset)]
170 pub fn rtc_offset(&self) -> RtcOffsetJs {
171 self.rtc_offset.clone()
172 }
173
174 #[wasm_bindgen(getter)]
176 pub fn length(&self) -> usize {
177 self.meshes.len()
178 }
179
180 pub fn get(&self, index: usize) -> Option<MeshDataJs> {
182 self.meshes.get(index)
183 }
184}
185
186#[wasm_bindgen]
188pub struct IfcAPI {
189 initialized: bool,
190}
191
192#[wasm_bindgen]
193impl IfcAPI {
194 #[wasm_bindgen(constructor)]
196 pub fn new() -> Self {
197 #[cfg(feature = "console_error_panic_hook")]
198 console_error_panic_hook::set_once();
199
200 Self {
201 initialized: true,
202 }
203 }
204
205 #[wasm_bindgen(getter)]
207 pub fn is_ready(&self) -> bool {
208 self.initialized
209 }
210
211 #[wasm_bindgen(js_name = parseStreaming)]
222 pub fn parse_streaming(&self, content: String, callback: Function) -> Promise {
223 use futures_util::StreamExt;
224
225 let promise = Promise::new(&mut |resolve, _reject| {
226 let content = content.clone();
227 let callback = callback.clone();
228 spawn_local(async move {
229 let config = StreamConfig::default();
230 let mut stream = ifc_lite_core::parse_stream(&content, config);
231
232 while let Some(event) = stream.next().await {
233 let event_obj = parse_event_to_js(&event);
235 let _ = callback.call1(&JsValue::NULL, &event_obj);
236
237 if matches!(event, ParseEvent::Completed { .. }) {
239 resolve.call0(&JsValue::NULL).unwrap();
240 return;
241 }
242 }
243
244 resolve.call0(&JsValue::NULL).unwrap();
245 });
246 });
247
248 promise
249 }
250
251 #[wasm_bindgen]
260 pub fn parse(&self, content: String) -> Promise {
261 let promise = Promise::new(&mut |resolve, _reject| {
262 let content = content.clone();
263 spawn_local(async move {
264 let mut scanner = EntityScanner::new(&content);
266 let counts = scanner.count_by_type();
267
268 let total_entities: usize = counts.values().sum();
269
270 let result = js_sys::Object::new();
272 js_sys::Reflect::set(
273 &result,
274 &"entityCount".into(),
275 &JsValue::from_f64(total_entities as f64),
276 )
277 .unwrap();
278
279 js_sys::Reflect::set(&result, &"entityTypes".into(), &counts_to_js(&counts))
280 .unwrap();
281
282 resolve.call1(&JsValue::NULL, &result).unwrap();
283 });
284 });
285
286 promise
287 }
288
289 #[wasm_bindgen(js_name = parseZeroCopy)]
309 pub fn parse_zero_copy(&self, content: String) -> ZeroCopyMesh {
310 use ifc_lite_core::{EntityScanner, EntityDecoder, build_entity_index};
312 use ifc_lite_geometry::{GeometryRouter, Mesh, calculate_normals};
313
314 let entity_index = build_entity_index(&content);
316
317 let mut scanner = EntityScanner::new(&content);
319 let mut decoder = EntityDecoder::with_index(&content, entity_index);
320
321 let router = GeometryRouter::new();
323
324 let mut meshes: Vec<Mesh> = Vec::with_capacity(2000);
326
327 while let Some((_id, type_name, start, end)) = scanner.next_entity() {
329 if !ifc_lite_core::has_geometry_by_name(type_name) {
331 continue;
332 }
333
334 if let Ok(entity) = decoder.decode_at(start, end) {
336 if let Ok(mesh) = router.process_element(&entity, &mut decoder) {
337 if !mesh.is_empty() {
338 meshes.push(mesh);
339 }
340 }
341 }
342 }
343
344 let mut combined_mesh = Mesh::new();
346 combined_mesh.merge_all(&meshes);
347
348 if combined_mesh.normals.is_empty() && !combined_mesh.positions.is_empty() {
350 calculate_normals(&mut combined_mesh);
351 }
352
353 ZeroCopyMesh::from(combined_mesh)
354 }
355
356 #[wasm_bindgen(js_name = parseMeshes)]
371 pub fn parse_meshes(&self, content: String) -> MeshCollection {
372 use ifc_lite_core::{EntityScanner, EntityDecoder, build_entity_index};
373 use ifc_lite_geometry::{GeometryRouter, calculate_normals};
374
375 let entity_index = build_entity_index(&content);
377
378 let mut decoder = EntityDecoder::with_index(&content, entity_index.clone());
380
381 let geometry_styles = build_geometry_style_index(&content, &mut decoder);
383 let style_index = build_element_style_index(&content, &geometry_styles, &mut decoder);
384
385 let mut scanner = EntityScanner::new(&content);
387 let mut faceted_brep_ids: Vec<u32> = Vec::new();
388 while let Some((id, type_name, _, _)) = scanner.next_entity() {
389 if type_name == "IFCFACETEDBREP" {
390 faceted_brep_ids.push(id);
391 }
392 }
393
394 let router = GeometryRouter::new();
396
397 if !faceted_brep_ids.is_empty() {
400 router.preprocess_faceted_breps(&faceted_brep_ids, &mut decoder);
401 }
402
403 scanner = EntityScanner::new(&content);
405
406 let estimated_elements = content.len() / 500;
408 let mut mesh_collection = MeshCollection::with_capacity(estimated_elements);
409
410 while let Some((id, type_name, start, end)) = scanner.next_entity() {
412 if !ifc_lite_core::has_geometry_by_name(type_name) {
414 continue;
415 }
416
417 if let Ok(entity) = decoder.decode_at(start, end) {
419 if let Ok(mut mesh) = router.process_element(&entity, &mut decoder) {
420 if !mesh.is_empty() {
421 if mesh.normals.is_empty() {
423 calculate_normals(&mut mesh);
424 }
425
426 let color = style_index.get(&id)
428 .copied()
429 .unwrap_or_else(|| get_default_color_for_type(&entity.ifc_type));
430
431 let mesh_data = MeshDataJs::new(id, mesh, color);
433 mesh_collection.add(mesh_data);
434 }
435 }
436 }
437 }
438
439 mesh_collection
440 }
441
442 #[wasm_bindgen(js_name = parseMeshesInstanced)]
461 pub fn parse_meshes_instanced(&self, content: String) -> InstancedMeshCollection {
462 use ifc_lite_core::{EntityScanner, EntityDecoder, build_entity_index};
463 use ifc_lite_geometry::{GeometryRouter, calculate_normals, Mesh};
464 use rustc_hash::FxHashMap;
465 use std::hash::{Hash, Hasher};
466 use rustc_hash::FxHasher;
467
468 let entity_index = build_entity_index(&content);
470
471 let mut decoder = EntityDecoder::with_index(&content, entity_index.clone());
473
474 let geometry_styles = build_geometry_style_index(&content, &mut decoder);
476 let style_index = build_element_style_index(&content, &geometry_styles, &mut decoder);
477
478 let mut scanner = EntityScanner::new(&content);
480 let mut faceted_brep_ids: Vec<u32> = Vec::new();
481 while let Some((id, type_name, _, _)) = scanner.next_entity() {
482 if type_name == "IFCFACETEDBREP" {
483 faceted_brep_ids.push(id);
484 }
485 }
486
487 let router = GeometryRouter::new();
489
490 if !faceted_brep_ids.is_empty() {
492 router.preprocess_faceted_breps(&faceted_brep_ids, &mut decoder);
493 }
494
495 scanner = EntityScanner::new(&content);
497
498 let mut geometry_groups: FxHashMap<u64, (Mesh, Vec<(u32, [f64; 16], [f32; 4])>)> = FxHashMap::default();
502
503 while let Some((id, type_name, start, end)) = scanner.next_entity() {
505 if !ifc_lite_core::has_geometry_by_name(type_name) {
507 continue;
508 }
509
510 if let Ok(entity) = decoder.decode_at(start, end) {
512 if let Ok((mut mesh, transform)) = router.process_element_with_transform(&entity, &mut decoder) {
513 if !mesh.is_empty() {
514 if mesh.normals.is_empty() {
516 calculate_normals(&mut mesh);
517 }
518
519 let mut hasher = FxHasher::default();
521 mesh.positions.len().hash(&mut hasher);
522 mesh.indices.len().hash(&mut hasher);
523 for pos in &mesh.positions {
524 pos.to_bits().hash(&mut hasher);
525 }
526 for idx in &mesh.indices {
527 idx.hash(&mut hasher);
528 }
529 let geometry_hash = hasher.finish();
530
531 let color = style_index.get(&id)
533 .copied()
534 .unwrap_or_else(|| get_default_color_for_type(&entity.ifc_type));
535
536 let mut transform_array = [0.0; 16];
538 for col in 0..4 {
539 for row in 0..4 {
540 transform_array[col * 4 + row] = transform[(row, col)];
541 }
542 }
543
544 let entry = geometry_groups.entry(geometry_hash);
546 match entry {
547 std::collections::hash_map::Entry::Occupied(mut o) => {
548 o.get_mut().1.push((id, transform_array, color));
550 }
551 std::collections::hash_map::Entry::Vacant(v) => {
552 v.insert((mesh, vec![(id, transform_array, color)]));
554 }
555 }
556 }
557 }
558 }
559 }
560
561 let mut collection = InstancedMeshCollection::new();
563 for (geometry_id, (mesh, instances)) in geometry_groups {
564 let mut instanced_geom = InstancedGeometry::new(
565 geometry_id,
566 mesh.positions,
567 mesh.normals,
568 mesh.indices,
569 );
570
571 for (express_id, transform_array, color) in instances {
573 let mut transform_f32 = Vec::with_capacity(16);
574 for val in transform_array.iter() {
575 transform_f32.push(*val as f32);
576 }
577 instanced_geom.add_instance(InstanceData::new(express_id, transform_f32, color));
578 }
579
580 collection.add(instanced_geom);
581 }
582
583 collection
584 }
585
586 #[wasm_bindgen(js_name = parseMeshesInstancedAsync)]
606 pub fn parse_meshes_instanced_async(&self, content: String, options: JsValue) -> Promise {
607 use ifc_lite_core::{EntityScanner, EntityDecoder, build_entity_index};
608 use ifc_lite_geometry::{GeometryRouter, calculate_normals, Mesh};
609 use rustc_hash::{FxHashMap, FxHasher};
610 use std::hash::{Hash, Hasher};
611
612 let promise = Promise::new(&mut |resolve, _reject| {
613 let content = content.clone();
614 let options = options.clone();
615
616 spawn_local(async move {
617 let batch_size: usize = js_sys::Reflect::get(&options, &"batchSize".into())
619 .ok()
620 .and_then(|v| v.as_f64())
621 .map(|v| v as usize)
622 .unwrap_or(25); let on_batch = js_sys::Reflect::get(&options, &"onBatch".into())
625 .ok()
626 .and_then(|v| v.dyn_into::<Function>().ok());
627
628 let on_complete = js_sys::Reflect::get(&options, &"onComplete".into())
629 .ok()
630 .and_then(|v| v.dyn_into::<Function>().ok());
631
632 let entity_index = build_entity_index(&content);
634 let mut decoder = EntityDecoder::with_index(&content, entity_index.clone());
635
636 let geometry_styles = build_geometry_style_index(&content, &mut decoder);
638 let style_index = build_element_style_index(&content, &geometry_styles, &mut decoder);
639
640 let mut scanner = EntityScanner::new(&content);
642 let mut faceted_brep_ids: Vec<u32> = Vec::new();
643 while let Some((id, type_name, _, _)) = scanner.next_entity() {
644 if type_name == "IFCFACETEDBREP" {
645 faceted_brep_ids.push(id);
646 }
647 }
648
649 let router = GeometryRouter::new();
651
652 if !faceted_brep_ids.is_empty() {
654 router.preprocess_faceted_breps(&faceted_brep_ids, &mut decoder);
655 }
656
657 scanner = EntityScanner::new(&content);
659
660 let mut geometry_groups: FxHashMap<u64, (Mesh, Vec<(u32, [f64; 16], [f32; 4])>)> = FxHashMap::default();
663 let mut processed = 0;
664 let mut total_geometries = 0;
665 let mut total_instances = 0;
666 let mut deferred_complex: Vec<(u32, usize, usize, ifc_lite_core::IfcType)> = Vec::new();
667
668 while let Some((id, type_name, start, end)) = scanner.next_entity() {
670 if !ifc_lite_core::has_geometry_by_name(type_name) {
671 continue;
672 }
673
674 let ifc_type = ifc_lite_core::IfcType::from_str(type_name);
675
676 if matches!(type_name, "IFCWALL" | "IFCWALLSTANDARDCASE" | "IFCSLAB" |
678 "IFCBEAM" | "IFCCOLUMN" | "IFCPLATE" | "IFCROOF" |
679 "IFCCOVERING" | "IFCFOOTING" | "IFCRAILING" | "IFCSTAIR" |
680 "IFCSTAIRFLIGHT" | "IFCRAMP" | "IFCRAMPFLIGHT") {
681 if let Ok(entity) = decoder.decode_at(start, end) {
682 if let Ok((mut mesh, transform)) = router.process_element_with_transform(&entity, &mut decoder) {
683 if !mesh.is_empty() {
684 if mesh.normals.is_empty() {
685 calculate_normals(&mut mesh);
686 }
687
688 let mut hasher = FxHasher::default();
690 mesh.positions.len().hash(&mut hasher);
691 mesh.indices.len().hash(&mut hasher);
692 for pos in &mesh.positions {
693 pos.to_bits().hash(&mut hasher);
694 }
695 for idx in &mesh.indices {
696 idx.hash(&mut hasher);
697 }
698 let geometry_hash = hasher.finish();
699
700 let color = style_index.get(&id)
702 .copied()
703 .unwrap_or_else(|| get_default_color_for_type(&ifc_type));
704
705 let mut transform_array = [0.0; 16];
707 for col in 0..4 {
708 for row in 0..4 {
709 transform_array[col * 4 + row] = transform[(row, col)];
710 }
711 }
712
713 let entry = geometry_groups.entry(geometry_hash);
715 match entry {
716 std::collections::hash_map::Entry::Occupied(mut o) => {
717 o.get_mut().1.push((id, transform_array, color));
718 total_instances += 1;
719 }
720 std::collections::hash_map::Entry::Vacant(v) => {
721 v.insert((mesh, vec![(id, transform_array, color)]));
722 total_geometries += 1;
723 total_instances += 1;
724 }
725 }
726 processed += 1;
727 }
728 }
729 }
730
731 if geometry_groups.len() >= batch_size {
733 let mut batch_geometries = Vec::new();
734 let mut geometries_to_remove = Vec::new();
735
736 for (geometry_id, (mesh, instances)) in geometry_groups.iter() {
738 let mut instanced_geom = InstancedGeometry::new(
739 *geometry_id,
740 mesh.positions.clone(),
741 mesh.normals.clone(),
742 mesh.indices.clone(),
743 );
744
745 for (express_id, transform_array, color) in instances.iter() {
746 let mut transform_f32 = Vec::with_capacity(16);
747 for val in transform_array.iter() {
748 transform_f32.push(*val as f32);
749 }
750 instanced_geom.add_instance(InstanceData::new(*express_id, transform_f32, *color));
751 }
752
753 batch_geometries.push(instanced_geom);
754 geometries_to_remove.push(*geometry_id);
755 }
756
757 for geometry_id in geometries_to_remove {
759 geometry_groups.remove(&geometry_id);
760 }
761
762 if let Some(ref callback) = on_batch {
764 let js_geometries = js_sys::Array::new();
765 for geom in batch_geometries {
766 js_geometries.push(&geom.into());
767 }
768
769 let progress = js_sys::Object::new();
770 js_sys::Reflect::set(&progress, &"percent".into(), &0u32.into()).unwrap();
771 js_sys::Reflect::set(&progress, &"processed".into(), &(processed as f64).into()).unwrap();
772 js_sys::Reflect::set(&progress, &"phase".into(), &"simple".into()).unwrap();
773
774 let _ = callback.call2(&JsValue::NULL, &js_geometries, &progress);
775 }
776
777 gloo_timers::future::TimeoutFuture::new(0).await;
779 }
780 } else {
781 deferred_complex.push((id, start, end, ifc_type));
783 }
784 }
785
786 if !geometry_groups.is_empty() {
788 let mut batch_geometries = Vec::new();
789 for (geometry_id, (mesh, instances)) in geometry_groups.drain() {
790 let mut instanced_geom = InstancedGeometry::new(
791 geometry_id,
792 mesh.positions,
793 mesh.normals,
794 mesh.indices,
795 );
796
797 for (express_id, transform_array, color) in instances {
798 let mut transform_f32 = Vec::with_capacity(16);
799 for val in transform_array.iter() {
800 transform_f32.push(*val as f32);
801 }
802 instanced_geom.add_instance(InstanceData::new(express_id, transform_f32, color));
803 }
804
805 batch_geometries.push(instanced_geom);
806 }
807
808 if let Some(ref callback) = on_batch {
809 let js_geometries = js_sys::Array::new();
810 for geom in batch_geometries {
811 js_geometries.push(&geom.into());
812 }
813
814 let progress = js_sys::Object::new();
815 js_sys::Reflect::set(&progress, &"phase".into(), &"simple_complete".into()).unwrap();
816
817 let _ = callback.call2(&JsValue::NULL, &js_geometries, &progress);
818 }
819
820 gloo_timers::future::TimeoutFuture::new(0).await;
821 }
822
823 let total_elements = processed + deferred_complex.len();
825 for (id, start, end, ifc_type) in deferred_complex {
826 if let Ok(entity) = decoder.decode_at(start, end) {
827 if let Ok((mut mesh, transform)) = router.process_element_with_transform(&entity, &mut decoder) {
828 if !mesh.is_empty() {
829 if mesh.normals.is_empty() {
830 calculate_normals(&mut mesh);
831 }
832
833 let mut hasher = FxHasher::default();
835 mesh.positions.len().hash(&mut hasher);
836 mesh.indices.len().hash(&mut hasher);
837 for pos in &mesh.positions {
838 pos.to_bits().hash(&mut hasher);
839 }
840 for idx in &mesh.indices {
841 idx.hash(&mut hasher);
842 }
843 let geometry_hash = hasher.finish();
844
845 let color = style_index.get(&id)
847 .copied()
848 .unwrap_or_else(|| get_default_color_for_type(&ifc_type));
849
850 let mut transform_array = [0.0; 16];
852 for col in 0..4 {
853 for row in 0..4 {
854 transform_array[col * 4 + row] = transform[(row, col)];
855 }
856 }
857
858 let entry = geometry_groups.entry(geometry_hash);
860 match entry {
861 std::collections::hash_map::Entry::Occupied(mut o) => {
862 o.get_mut().1.push((id, transform_array, color));
863 total_instances += 1;
864 }
865 std::collections::hash_map::Entry::Vacant(v) => {
866 v.insert((mesh, vec![(id, transform_array, color)]));
867 total_geometries += 1;
868 total_instances += 1;
869 }
870 }
871 processed += 1;
872 }
873 }
874 }
875
876 if geometry_groups.len() >= batch_size {
878 let mut batch_geometries = Vec::new();
879 let mut geometries_to_remove = Vec::new();
880
881 for (geometry_id, (mesh, instances)) in geometry_groups.iter() {
882 let mut instanced_geom = InstancedGeometry::new(
883 *geometry_id,
884 mesh.positions.clone(),
885 mesh.normals.clone(),
886 mesh.indices.clone(),
887 );
888
889 for (express_id, transform_array, color) in instances.iter() {
890 let mut transform_f32 = Vec::with_capacity(16);
891 for val in transform_array.iter() {
892 transform_f32.push(*val as f32);
893 }
894 instanced_geom.add_instance(InstanceData::new(*express_id, transform_f32, *color));
895 }
896
897 batch_geometries.push(instanced_geom);
898 geometries_to_remove.push(*geometry_id);
899 }
900
901 for geometry_id in geometries_to_remove {
902 geometry_groups.remove(&geometry_id);
903 }
904
905 if let Some(ref callback) = on_batch {
906 let js_geometries = js_sys::Array::new();
907 for geom in batch_geometries {
908 js_geometries.push(&geom.into());
909 }
910
911 let progress = js_sys::Object::new();
912 let percent = (processed as f64 / total_elements as f64 * 100.0) as u32;
913 js_sys::Reflect::set(&progress, &"percent".into(), &percent.into()).unwrap();
914 js_sys::Reflect::set(&progress, &"processed".into(), &(processed as f64).into()).unwrap();
915 js_sys::Reflect::set(&progress, &"total".into(), &(total_elements as f64).into()).unwrap();
916 js_sys::Reflect::set(&progress, &"phase".into(), &"complex".into()).unwrap();
917
918 let _ = callback.call2(&JsValue::NULL, &js_geometries, &progress);
919 }
920
921 gloo_timers::future::TimeoutFuture::new(0).await;
922 }
923 }
924
925 if !geometry_groups.is_empty() {
927 let mut batch_geometries = Vec::new();
928 for (geometry_id, (mesh, instances)) in geometry_groups.drain() {
929 let mut instanced_geom = InstancedGeometry::new(
930 geometry_id,
931 mesh.positions,
932 mesh.normals,
933 mesh.indices,
934 );
935
936 for (express_id, transform_array, color) in instances {
937 let mut transform_f32 = Vec::with_capacity(16);
938 for val in transform_array.iter() {
939 transform_f32.push(*val as f32);
940 }
941 instanced_geom.add_instance(InstanceData::new(express_id, transform_f32, color));
942 }
943
944 batch_geometries.push(instanced_geom);
945 }
946
947 if let Some(ref callback) = on_batch {
948 let js_geometries = js_sys::Array::new();
949 for geom in batch_geometries {
950 js_geometries.push(&geom.into());
951 }
952
953 let progress = js_sys::Object::new();
954 js_sys::Reflect::set(&progress, &"percent".into(), &100u32.into()).unwrap();
955 js_sys::Reflect::set(&progress, &"phase".into(), &"complete".into()).unwrap();
956
957 let _ = callback.call2(&JsValue::NULL, &js_geometries, &progress);
958 }
959 }
960
961 if let Some(ref callback) = on_complete {
963 let stats = js_sys::Object::new();
964 js_sys::Reflect::set(&stats, &"totalGeometries".into(), &(total_geometries as f64).into()).unwrap();
965 js_sys::Reflect::set(&stats, &"totalInstances".into(), &(total_instances as f64).into()).unwrap();
966 let _ = callback.call1(&JsValue::NULL, &stats);
967 }
968
969 resolve.call0(&JsValue::NULL).unwrap();
970 });
971 });
972
973 promise
974 }
975
976 #[wasm_bindgen(js_name = parseMeshesAsync)]
997 pub fn parse_meshes_async(&self, content: String, options: JsValue) -> Promise {
998 use ifc_lite_core::{EntityScanner, EntityDecoder, build_entity_index};
999 use ifc_lite_geometry::{GeometryRouter, calculate_normals};
1000
1001 let promise = Promise::new(&mut |resolve, _reject| {
1002 let content = content.clone();
1003 let options = options.clone();
1004
1005 spawn_local(async move {
1006 let batch_size: usize = js_sys::Reflect::get(&options, &"batchSize".into())
1008 .ok()
1009 .and_then(|v| v.as_f64())
1010 .map(|v| v as usize)
1011 .unwrap_or(25); let on_batch = js_sys::Reflect::get(&options, &"onBatch".into())
1014 .ok()
1015 .and_then(|v| v.dyn_into::<Function>().ok());
1016
1017 let on_complete = js_sys::Reflect::get(&options, &"onComplete".into())
1018 .ok()
1019 .and_then(|v| v.dyn_into::<Function>().ok());
1020
1021 let mut decoder = EntityDecoder::new(&content);
1024
1025 let router = GeometryRouter::new();
1027
1028 let mut processed = 0;
1030 let mut total_meshes = 0;
1031 let mut total_vertices = 0;
1032 let mut total_triangles = 0;
1033 let mut batch_meshes: Vec<MeshDataJs> = Vec::with_capacity(batch_size);
1034 let mut elements_since_yield = 0;
1035
1036 let mut scanner = EntityScanner::new(&content);
1038 let mut deferred_complex: Vec<(u32, usize, usize, ifc_lite_core::IfcType)> = Vec::new();
1039 let mut faceted_brep_ids: Vec<u32> = Vec::new(); while let Some((id, type_name, start, end)) = scanner.next_entity() {
1043 if type_name == "IFCFACETEDBREP" {
1045 faceted_brep_ids.push(id);
1046 }
1047
1048 if !ifc_lite_core::has_geometry_by_name(type_name) {
1049 continue;
1050 }
1051
1052 let ifc_type = ifc_lite_core::IfcType::from_str(type_name);
1053
1054 if matches!(type_name, "IFCWALL" | "IFCWALLSTANDARDCASE" | "IFCSLAB" |
1056 "IFCBEAM" | "IFCCOLUMN" | "IFCPLATE" | "IFCROOF" |
1057 "IFCCOVERING" | "IFCFOOTING" | "IFCRAILING" | "IFCSTAIR" |
1058 "IFCSTAIRFLIGHT" | "IFCRAMP" | "IFCRAMPFLIGHT") {
1059 if let Ok(entity) = decoder.decode_at(start, end) {
1060 if let Ok(mut mesh) = router.process_element(&entity, &mut decoder) {
1061 if !mesh.is_empty() {
1062 if mesh.normals.is_empty() {
1063 calculate_normals(&mut mesh);
1064 }
1065
1066 let color = get_default_color_for_type(&ifc_type);
1067 total_vertices += mesh.positions.len() / 3;
1068 total_triangles += mesh.indices.len() / 3;
1069
1070 let mesh_data = MeshDataJs::new(id, mesh, color);
1071 batch_meshes.push(mesh_data);
1072 processed += 1;
1073 }
1074 }
1075 }
1076
1077 elements_since_yield += 1;
1078
1079 if batch_meshes.len() >= batch_size {
1081 if let Some(ref callback) = on_batch {
1082 let js_meshes = js_sys::Array::new();
1083 for mesh in batch_meshes.drain(..) {
1084 js_meshes.push(&mesh.into());
1085 }
1086
1087 let progress = js_sys::Object::new();
1088 js_sys::Reflect::set(&progress, &"percent".into(), &0u32.into()).unwrap();
1089 js_sys::Reflect::set(&progress, &"processed".into(), &(processed as f64).into()).unwrap();
1090 js_sys::Reflect::set(&progress, &"phase".into(), &"simple".into()).unwrap();
1091
1092 let _ = callback.call2(&JsValue::NULL, &js_meshes, &progress);
1093 total_meshes += js_meshes.length() as usize;
1094 }
1095
1096 gloo_timers::future::TimeoutFuture::new(0).await;
1098 elements_since_yield = 0;
1099 }
1100 } else {
1101 deferred_complex.push((id, start, end, ifc_type));
1103 }
1104 }
1105
1106 if !batch_meshes.is_empty() {
1108 if let Some(ref callback) = on_batch {
1109 let js_meshes = js_sys::Array::new();
1110 for mesh in batch_meshes.drain(..) {
1111 js_meshes.push(&mesh.into());
1112 }
1113
1114 let progress = js_sys::Object::new();
1115 js_sys::Reflect::set(&progress, &"phase".into(), &"simple_complete".into()).unwrap();
1116
1117 let _ = callback.call2(&JsValue::NULL, &js_meshes, &progress);
1118 total_meshes += js_meshes.length() as usize;
1119 }
1120
1121 gloo_timers::future::TimeoutFuture::new(0).await;
1122 }
1123
1124 let total_elements = processed + deferred_complex.len();
1125
1126 if !faceted_brep_ids.is_empty() {
1129 router.preprocess_faceted_breps(&faceted_brep_ids, &mut decoder);
1130 }
1131
1132 let geometry_styles = build_geometry_style_index(&content, &mut decoder);
1135 let style_index = build_element_style_index(&content, &geometry_styles, &mut decoder);
1136
1137 for (id, start, end, ifc_type) in deferred_complex {
1138 if let Ok(entity) = decoder.decode_at(start, end) {
1139 if let Ok(mut mesh) = router.process_element(&entity, &mut decoder) {
1140 if !mesh.is_empty() {
1141 if mesh.normals.is_empty() {
1142 calculate_normals(&mut mesh);
1143 }
1144
1145 let color = style_index.get(&id)
1146 .copied()
1147 .unwrap_or_else(|| get_default_color_for_type(&ifc_type));
1148
1149 total_vertices += mesh.positions.len() / 3;
1150 total_triangles += mesh.indices.len() / 3;
1151
1152 let mesh_data = MeshDataJs::new(id, mesh, color);
1153 batch_meshes.push(mesh_data);
1154 }
1155 }
1156 }
1157
1158 processed += 1;
1159
1160 if batch_meshes.len() >= batch_size {
1162 if let Some(ref callback) = on_batch {
1163 let js_meshes = js_sys::Array::new();
1164 for mesh in batch_meshes.drain(..) {
1165 js_meshes.push(&mesh.into());
1166 }
1167
1168 let progress = js_sys::Object::new();
1169 let percent = (processed as f64 / total_elements as f64 * 100.0) as u32;
1170 js_sys::Reflect::set(&progress, &"percent".into(), &percent.into()).unwrap();
1171 js_sys::Reflect::set(&progress, &"processed".into(), &(processed as f64).into()).unwrap();
1172 js_sys::Reflect::set(&progress, &"total".into(), &(total_elements as f64).into()).unwrap();
1173 js_sys::Reflect::set(&progress, &"phase".into(), &"complex".into()).unwrap();
1174
1175 let _ = callback.call2(&JsValue::NULL, &js_meshes, &progress);
1176 total_meshes += js_meshes.length() as usize;
1177 }
1178
1179 gloo_timers::future::TimeoutFuture::new(0).await;
1180 }
1181 }
1182
1183 if !batch_meshes.is_empty() {
1185 if let Some(ref callback) = on_batch {
1186 let js_meshes = js_sys::Array::new();
1187 for mesh in batch_meshes.drain(..) {
1188 js_meshes.push(&mesh.into());
1189 }
1190
1191 let progress = js_sys::Object::new();
1192 js_sys::Reflect::set(&progress, &"percent".into(), &100u32.into()).unwrap();
1193 js_sys::Reflect::set(&progress, &"phase".into(), &"complete".into()).unwrap();
1194
1195 let _ = callback.call2(&JsValue::NULL, &js_meshes, &progress);
1196 total_meshes += js_meshes.length() as usize;
1197 }
1198 }
1199
1200 if let Some(ref callback) = on_complete {
1202 let stats = js_sys::Object::new();
1203 js_sys::Reflect::set(&stats, &"totalMeshes".into(), &(total_meshes as f64).into()).unwrap();
1204 js_sys::Reflect::set(&stats, &"totalVertices".into(), &(total_vertices as f64).into()).unwrap();
1205 js_sys::Reflect::set(&stats, &"totalTriangles".into(), &(total_triangles as f64).into()).unwrap();
1206 let _ = callback.call1(&JsValue::NULL, &stats);
1207 }
1208
1209 resolve.call0(&JsValue::NULL).unwrap();
1210 });
1211 });
1212
1213 promise
1214 }
1215
1216 #[wasm_bindgen(js_name = getMemory)]
1218 pub fn get_memory(&self) -> JsValue {
1219 crate::zero_copy::get_memory()
1220 }
1221
1222 #[wasm_bindgen(getter)]
1224 pub fn version(&self) -> String {
1225 env!("CARGO_PKG_VERSION").to_string()
1226 }
1227
1228 #[wasm_bindgen(js_name = getGeoReference)]
1241 pub fn get_geo_reference(&self, content: String) -> Option<GeoReferenceJs> {
1242 use ifc_lite_core::{EntityScanner, EntityDecoder, build_entity_index, IfcType, GeoRefExtractor};
1243
1244 let entity_index = build_entity_index(&content);
1246 let mut decoder = EntityDecoder::with_index(&content, entity_index);
1247
1248 let mut scanner = EntityScanner::new(&content);
1250 let mut entity_types: Vec<(u32, IfcType)> = Vec::new();
1251
1252 while let Some((id, type_name, _, _)) = scanner.next_entity() {
1253 let ifc_type = IfcType::from_str(type_name);
1254 entity_types.push((id, ifc_type));
1255 }
1256
1257 match GeoRefExtractor::extract(&mut decoder, &entity_types) {
1259 Ok(Some(georef)) => Some(GeoReferenceJs::from(georef)),
1260 _ => None,
1261 }
1262 }
1263
1264 #[wasm_bindgen(js_name = parseMeshesWithRtc)]
1280 pub fn parse_meshes_with_rtc(&self, content: String) -> MeshCollectionWithRtc {
1281 use ifc_lite_core::{EntityScanner, EntityDecoder, build_entity_index, RtcOffset};
1282 use ifc_lite_geometry::{GeometryRouter, calculate_normals};
1283
1284 let entity_index = build_entity_index(&content);
1286 let mut decoder = EntityDecoder::with_index(&content, entity_index.clone());
1287
1288 let geometry_styles = build_geometry_style_index(&content, &mut decoder);
1290 let style_index = build_element_style_index(&content, &geometry_styles, &mut decoder);
1291
1292 let mut scanner = EntityScanner::new(&content);
1294 let mut faceted_brep_ids: Vec<u32> = Vec::new();
1295 while let Some((id, type_name, _, _)) = scanner.next_entity() {
1296 if type_name == "IFCFACETEDBREP" {
1297 faceted_brep_ids.push(id);
1298 }
1299 }
1300
1301 let router = GeometryRouter::new();
1302
1303 if !faceted_brep_ids.is_empty() {
1305 router.preprocess_faceted_breps(&faceted_brep_ids, &mut decoder);
1306 }
1307
1308 scanner = EntityScanner::new(&content);
1310
1311 let estimated_elements = content.len() / 500;
1312 let mut mesh_collection = MeshCollection::with_capacity(estimated_elements);
1313
1314 let mut all_positions: Vec<f32> = Vec::with_capacity(100000);
1316
1317 while let Some((id, type_name, start, end)) = scanner.next_entity() {
1319 if !ifc_lite_core::has_geometry_by_name(type_name) {
1320 continue;
1321 }
1322
1323 if let Ok(entity) = decoder.decode_at(start, end) {
1324 if let Ok(mut mesh) = router.process_element(&entity, &mut decoder) {
1325 if !mesh.is_empty() {
1326 if mesh.normals.is_empty() {
1327 calculate_normals(&mut mesh);
1328 }
1329
1330 all_positions.extend_from_slice(&mesh.positions);
1332
1333 let color = style_index.get(&id)
1334 .copied()
1335 .unwrap_or_else(|| get_default_color_for_type(&entity.ifc_type));
1336
1337 let mesh_data = MeshDataJs::new(id, mesh, color);
1338 mesh_collection.add(mesh_data);
1339 }
1340 }
1341 }
1342 }
1343
1344 let rtc_offset = RtcOffset::from_positions(&all_positions);
1346 let rtc_offset_js = RtcOffsetJs::from(rtc_offset.clone());
1347
1348 if rtc_offset.is_significant() {
1350 mesh_collection.apply_rtc_offset(rtc_offset.x, rtc_offset.y, rtc_offset.z);
1351 }
1352
1353 MeshCollectionWithRtc {
1354 meshes: mesh_collection,
1355 rtc_offset: rtc_offset_js,
1356 }
1357 }
1358
1359 #[wasm_bindgen(js_name = debugProcessEntity953)]
1361 pub fn debug_process_entity_953(&self, content: String) -> String {
1362 use ifc_lite_core::{EntityScanner, EntityDecoder};
1363 use ifc_lite_geometry::GeometryRouter;
1364
1365 let router = GeometryRouter::new();
1366 let mut scanner = EntityScanner::new(&content);
1367 let mut decoder = EntityDecoder::new(&content);
1368
1369 while let Some((id, type_name, start, end)) = scanner.next_entity() {
1371 if id == 953 {
1372 match decoder.decode_at(start, end) {
1373 Ok(entity) => {
1374 match router.process_element(&entity, &mut decoder) {
1375 Ok(mesh) => {
1376 return format!(
1377 "SUCCESS! Entity #953: {} vertices, {} triangles, empty={}",
1378 mesh.vertex_count(), mesh.triangle_count(), mesh.is_empty()
1379 );
1380 }
1381 Err(e) => {
1382 return format!("ERROR processing entity #953: {}", e);
1383 }
1384 }
1385 }
1386 Err(e) => {
1387 return format!("ERROR decoding entity #953: {}", e);
1388 }
1389 }
1390 }
1391 }
1392 "Entity #953 not found".to_string()
1393 }
1394
1395 #[wasm_bindgen(js_name = debugProcessFirstWall)]
1397 pub fn debug_process_first_wall(&self, content: String) -> String {
1398 use ifc_lite_core::{EntityScanner, EntityDecoder};
1399 use ifc_lite_geometry::GeometryRouter;
1400
1401 let router = GeometryRouter::new();
1402 let mut scanner = EntityScanner::new(&content);
1403 let mut decoder = EntityDecoder::new(&content);
1404
1405 while let Some((id, type_name, start, end)) = scanner.next_entity() {
1407 if type_name.contains("WALL") {
1408 let ifc_type = ifc_lite_core::IfcType::from_str(type_name);
1409 if router.schema().has_geometry(&ifc_type) {
1410 match decoder.decode_at(start, end) {
1412 Ok(entity) => {
1413 match router.process_element(&entity, &mut decoder) {
1414 Ok(mesh) => {
1415 return format!(
1416 "SUCCESS! Wall #{}: {} vertices, {} triangles",
1417 id, mesh.vertex_count(), mesh.triangle_count()
1418 );
1419 }
1420 Err(e) => {
1421 return format!(
1422 "ERROR processing wall #{} ({}): {}",
1423 id, type_name, e
1424 );
1425 }
1426 }
1427 }
1428 Err(e) => {
1429 return format!("ERROR decoding wall #{}: {}", id, e);
1430 }
1431 }
1432 }
1433 }
1434 }
1435
1436 "No walls found".to_string()
1437 }
1438
1439}
1440
1441impl Default for IfcAPI {
1442 fn default() -> Self {
1443 Self::new()
1444 }
1445}
1446
1447fn parse_event_to_js(event: &ParseEvent) -> JsValue {
1449 let obj = js_sys::Object::new();
1450
1451 match event {
1452 ParseEvent::Started {
1453 file_size,
1454 timestamp,
1455 } => {
1456 js_sys::Reflect::set(&obj, &"type".into(), &"started".into()).unwrap();
1457 js_sys::Reflect::set(&obj, &"fileSize".into(), &(*file_size as f64).into()).unwrap();
1458 js_sys::Reflect::set(&obj, &"timestamp".into(), &(*timestamp).into()).unwrap();
1459 }
1460 ParseEvent::EntityScanned {
1461 id,
1462 ifc_type,
1463 position,
1464 } => {
1465 js_sys::Reflect::set(&obj, &"type".into(), &"entityScanned".into()).unwrap();
1466 js_sys::Reflect::set(&obj, &"id".into(), &(*id as f64).into()).unwrap();
1467 js_sys::Reflect::set(&obj, &"ifcType".into(), &ifc_type.as_str().into()).unwrap();
1468 js_sys::Reflect::set(&obj, &"position".into(), &(*position as f64).into()).unwrap();
1469 }
1470 ParseEvent::GeometryReady {
1471 id,
1472 vertex_count,
1473 triangle_count,
1474 } => {
1475 js_sys::Reflect::set(&obj, &"type".into(), &"geometryReady".into()).unwrap();
1476 js_sys::Reflect::set(&obj, &"id".into(), &(*id as f64).into()).unwrap();
1477 js_sys::Reflect::set(&obj, &"vertexCount".into(), &(*vertex_count as f64).into())
1478 .unwrap();
1479 js_sys::Reflect::set(&obj, &"triangleCount".into(), &(*triangle_count as f64).into())
1480 .unwrap();
1481 }
1482 ParseEvent::Progress {
1483 phase,
1484 percent,
1485 entities_processed,
1486 total_entities,
1487 } => {
1488 js_sys::Reflect::set(&obj, &"type".into(), &"progress".into()).unwrap();
1489 js_sys::Reflect::set(&obj, &"phase".into(), &phase.as_str().into()).unwrap();
1490 js_sys::Reflect::set(&obj, &"percent".into(), &(*percent as f64).into()).unwrap();
1491 js_sys::Reflect::set(
1492 &obj,
1493 &"entitiesProcessed".into(),
1494 &(*entities_processed as f64).into(),
1495 )
1496 .unwrap();
1497 js_sys::Reflect::set(
1498 &obj,
1499 &"totalEntities".into(),
1500 &(*total_entities as f64).into(),
1501 )
1502 .unwrap();
1503 }
1504 ParseEvent::Completed {
1505 duration_ms,
1506 entity_count,
1507 triangle_count,
1508 } => {
1509 js_sys::Reflect::set(&obj, &"type".into(), &"completed".into()).unwrap();
1510 js_sys::Reflect::set(&obj, &"durationMs".into(), &(*duration_ms).into()).unwrap();
1511 js_sys::Reflect::set(&obj, &"entityCount".into(), &(*entity_count as f64).into())
1512 .unwrap();
1513 js_sys::Reflect::set(&obj, &"triangleCount".into(), &(*triangle_count as f64).into())
1514 .unwrap();
1515 }
1516 ParseEvent::Error { message, position } => {
1517 js_sys::Reflect::set(&obj, &"type".into(), &"error".into()).unwrap();
1518 js_sys::Reflect::set(&obj, &"message".into(), &message.as_str().into()).unwrap();
1519 if let Some(pos) = position {
1520 js_sys::Reflect::set(&obj, &"position".into(), &(*pos as f64).into()).unwrap();
1521 }
1522 }
1523 }
1524
1525 obj.into()
1526}
1527
1528fn build_geometry_style_index(
1531 content: &str,
1532 decoder: &mut ifc_lite_core::EntityDecoder,
1533) -> rustc_hash::FxHashMap<u32, [f32; 4]> {
1534 use ifc_lite_core::EntityScanner;
1535 use rustc_hash::FxHashMap;
1536
1537 let mut style_index: FxHashMap<u32, [f32; 4]> = FxHashMap::default();
1538 let mut scanner = EntityScanner::new(content);
1539
1540 while let Some((_id, type_name, start, end)) = scanner.next_entity() {
1542 if type_name != "IFCSTYLEDITEM" {
1543 continue;
1544 }
1545
1546 let styled_item = match decoder.decode_at(start, end) {
1548 Ok(entity) => entity,
1549 Err(_) => continue,
1550 };
1551
1552 let geometry_id = match styled_item.get_ref(0) {
1555 Some(id) => id,
1556 None => continue,
1557 };
1558
1559 if style_index.contains_key(&geometry_id) {
1561 continue;
1562 }
1563
1564 let styles_attr = match styled_item.get(1) {
1566 Some(attr) => attr,
1567 None => continue,
1568 };
1569
1570 if let Some(color) = extract_color_from_styles(styles_attr, decoder) {
1572 style_index.insert(geometry_id, color);
1573 }
1574 }
1575
1576 style_index
1577}
1578
1579fn build_element_style_index(
1582 content: &str,
1583 geometry_styles: &rustc_hash::FxHashMap<u32, [f32; 4]>,
1584 decoder: &mut ifc_lite_core::EntityDecoder,
1585) -> rustc_hash::FxHashMap<u32, [f32; 4]> {
1586 use ifc_lite_core::EntityScanner;
1587 use rustc_hash::FxHashMap;
1588
1589 let mut element_styles: FxHashMap<u32, [f32; 4]> = FxHashMap::default();
1590 let mut scanner = EntityScanner::new(content);
1591
1592 while let Some((element_id, type_name, start, end)) = scanner.next_entity() {
1594 if !ifc_lite_core::has_geometry_by_name(type_name) {
1596 continue;
1597 }
1598
1599 let element = match decoder.decode_at(start, end) {
1601 Ok(entity) => entity,
1602 Err(_) => continue,
1603 };
1604
1605 let repr_id = match element.get_ref(6) {
1608 Some(id) => id,
1609 None => continue,
1610 };
1611
1612 let product_shape = match decoder.decode_by_id(repr_id) {
1614 Ok(entity) => entity,
1615 Err(_) => continue,
1616 };
1617
1618 let reprs_attr = match product_shape.get(2) {
1621 Some(attr) => attr,
1622 None => continue,
1623 };
1624
1625 let reprs_list = match reprs_attr.as_list() {
1626 Some(list) => list,
1627 None => continue,
1628 };
1629
1630 for repr_item in reprs_list {
1632 let shape_repr_id = match repr_item.as_entity_ref() {
1633 Some(id) => id,
1634 None => continue,
1635 };
1636
1637 let shape_repr = match decoder.decode_by_id(shape_repr_id) {
1639 Ok(entity) => entity,
1640 Err(_) => continue,
1641 };
1642
1643 let items_attr = match shape_repr.get(3) {
1646 Some(attr) => attr,
1647 None => continue,
1648 };
1649
1650 let items_list = match items_attr.as_list() {
1651 Some(list) => list,
1652 None => continue,
1653 };
1654
1655 for geom_item in items_list {
1657 let geom_id = match geom_item.as_entity_ref() {
1658 Some(id) => id,
1659 None => continue,
1660 };
1661
1662 if let Some(&color) = geometry_styles.get(&geom_id) {
1664 element_styles.insert(element_id, color);
1665 break; }
1667 }
1668
1669 if element_styles.contains_key(&element_id) {
1671 break;
1672 }
1673 }
1674 }
1675
1676 element_styles
1677}
1678
1679fn extract_color_from_styles(
1681 styles_attr: &ifc_lite_core::AttributeValue,
1682 decoder: &mut ifc_lite_core::EntityDecoder,
1683) -> Option<[f32; 4]> {
1684 use ifc_lite_core::IfcType;
1685
1686 if let Some(list) = styles_attr.as_list() {
1688 for item in list {
1689 if let Some(style_id) = item.as_entity_ref() {
1690 if let Some(color) = extract_color_from_style_assignment(style_id, decoder) {
1691 return Some(color);
1692 }
1693 }
1694 }
1695 } else if let Some(style_id) = styles_attr.as_entity_ref() {
1696 return extract_color_from_style_assignment(style_id, decoder);
1697 }
1698
1699 None
1700}
1701
1702fn extract_color_from_style_assignment(
1704 style_id: u32,
1705 decoder: &mut ifc_lite_core::EntityDecoder,
1706) -> Option<[f32; 4]> {
1707 use ifc_lite_core::IfcType;
1708
1709 let style = decoder.decode_by_id(style_id).ok()?;
1710
1711 match style.ifc_type {
1712 IfcType::IfcPresentationStyleAssignment => {
1713 let styles_attr = style.get(0)?;
1715 if let Some(list) = styles_attr.as_list() {
1716 for item in list {
1717 if let Some(inner_id) = item.as_entity_ref() {
1718 if let Some(color) = extract_color_from_surface_style(inner_id, decoder) {
1719 return Some(color);
1720 }
1721 }
1722 }
1723 }
1724 }
1725 IfcType::IfcSurfaceStyle => {
1726 return extract_color_from_surface_style(style_id, decoder);
1727 }
1728 _ => {}
1729 }
1730
1731 None
1732}
1733
1734fn extract_color_from_surface_style(
1736 style_id: u32,
1737 decoder: &mut ifc_lite_core::EntityDecoder,
1738) -> Option<[f32; 4]> {
1739 use ifc_lite_core::IfcType;
1740
1741 let style = decoder.decode_by_id(style_id).ok()?;
1742
1743 if style.ifc_type != IfcType::IfcSurfaceStyle {
1744 return None;
1745 }
1746
1747 let styles_attr = style.get(2)?;
1750
1751 if let Some(list) = styles_attr.as_list() {
1752 for item in list {
1753 if let Some(element_id) = item.as_entity_ref() {
1754 if let Some(color) = extract_color_from_rendering(element_id, decoder) {
1755 return Some(color);
1756 }
1757 }
1758 }
1759 }
1760
1761 None
1762}
1763
1764fn extract_color_from_rendering(
1766 rendering_id: u32,
1767 decoder: &mut ifc_lite_core::EntityDecoder,
1768) -> Option<[f32; 4]> {
1769 use ifc_lite_core::IfcType;
1770
1771 let rendering = decoder.decode_by_id(rendering_id).ok()?;
1772
1773 match rendering.ifc_type {
1774 IfcType::IfcSurfaceStyleRendering | IfcType::IfcSurfaceStyleShading => {
1775 let color_ref = rendering.get_ref(0)?;
1777 return extract_color_rgb(color_ref, decoder);
1778 }
1779 _ => {}
1780 }
1781
1782 None
1783}
1784
1785fn extract_color_rgb(
1787 color_id: u32,
1788 decoder: &mut ifc_lite_core::EntityDecoder,
1789) -> Option<[f32; 4]> {
1790 use ifc_lite_core::IfcType;
1791
1792 let color = decoder.decode_by_id(color_id).ok()?;
1793
1794 if color.ifc_type != IfcType::IfcColourRgb {
1795 return None;
1796 }
1797
1798 let red = color.get_float(1).unwrap_or(0.8);
1802 let green = color.get_float(2).unwrap_or(0.8);
1803 let blue = color.get_float(3).unwrap_or(0.8);
1804
1805 Some([red as f32, green as f32, blue as f32, 1.0])
1806}
1807
1808fn get_default_color_for_type(ifc_type: &ifc_lite_core::IfcType) -> [f32; 4] {
1810 use ifc_lite_core::IfcType;
1811
1812 match ifc_type {
1813 IfcType::IfcWall | IfcType::IfcWallStandardCase => [0.85, 0.85, 0.85, 1.0],
1815
1816 IfcType::IfcSlab => [0.7, 0.7, 0.7, 1.0],
1818
1819 IfcType::IfcRoof => [0.6, 0.5, 0.4, 1.0],
1821
1822 IfcType::IfcColumn | IfcType::IfcBeam | IfcType::IfcMember => [0.6, 0.65, 0.7, 1.0],
1824
1825 IfcType::IfcWindow => [0.6, 0.8, 1.0, 0.4],
1827
1828 IfcType::IfcDoor => [0.6, 0.45, 0.3, 1.0],
1830
1831 IfcType::IfcStair => [0.75, 0.75, 0.75, 1.0],
1833
1834 IfcType::IfcRailing => [0.4, 0.4, 0.45, 1.0],
1836
1837 IfcType::IfcPlate | IfcType::IfcCovering => [0.8, 0.8, 0.8, 1.0],
1839
1840 IfcType::IfcCurtainWall => [0.5, 0.7, 0.9, 0.5],
1842
1843 IfcType::IfcFurnishingElement => [0.7, 0.55, 0.4, 1.0],
1845
1846 _ => [0.8, 0.8, 0.8, 1.0],
1848 }
1849}
1850
1851fn counts_to_js(counts: &rustc_hash::FxHashMap<String, usize>) -> JsValue {
1853 let obj = js_sys::Object::new();
1854
1855 for (type_name, count) in counts {
1856 let key = JsValue::from_str(type_name.as_str());
1857 let value = JsValue::from_f64(*count as f64);
1858 js_sys::Reflect::set(&obj, &key, &value).unwrap();
1859 }
1860
1861 obj.into()
1862}