1use crate::gldf::GldfProduct;
7use crate::FileBufGldf;
8
9#[derive(Debug, Clone)]
11pub struct L3dLdtMapping {
12 pub l3d_file_id: String,
14 pub l3d_file_name: Option<String>,
16 pub ldt_file_id: Option<String>,
18 pub ldt_file_name: Option<String>,
20 pub variant_id: String,
22 pub emitter_id: Option<String>,
24}
25
26#[derive(Debug, Clone, Default)]
28pub struct EmitterRenderData {
29 pub leo_name: String,
31 pub emitter_id: String,
33 pub luminous_flux: Option<i32>,
35 pub color_temperature: Option<i32>,
37 pub ldt_file_id: Option<String>,
39 pub ldt_file_name: Option<String>,
41 pub emergency_behavior: Option<String>,
43}
44
45#[derive(Debug, Clone, PartialEq)]
47pub enum MountingType {
48 Ceiling,
50 Wall,
52 Ground,
54 WorkingPlane,
56}
57
58#[derive(Debug, Clone, Default)]
60pub struct MountingInfo {
61 pub mounting_type: Option<MountingType>,
63 pub recessed_depth_mm: Option<i32>,
65 pub pendant_length_mm: Option<f64>,
67 pub mounting_height_mm: Option<i32>,
69 pub pole_height_mm: Option<i32>,
71 pub is_surface_mounted: bool,
73 pub is_free_standing: bool,
75}
76
77impl MountingInfo {
78 pub fn get_position_offset(&self, room_height: f32) -> (f32, bool) {
81 match self.mounting_type {
82 Some(MountingType::Ceiling) => {
83 if let Some(pendant_mm) = self.pendant_length_mm {
84 (room_height - (pendant_mm as f32 / 1000.0), true)
86 } else if self.recessed_depth_mm.is_some() {
87 (room_height, true)
89 } else {
90 (room_height - 0.05, true)
92 }
93 }
94 Some(MountingType::Wall) => {
95 let height_m = self.mounting_height_mm.unwrap_or(2000) as f32 / 1000.0;
97 (height_m, false)
98 }
99 Some(MountingType::Ground) => {
100 if let Some(pole_mm) = self.pole_height_mm {
101 (pole_mm as f32 / 1000.0, false)
103 } else if self.is_free_standing {
104 (0.0, false)
106 } else {
107 (0.0, false)
109 }
110 }
111 Some(MountingType::WorkingPlane) => {
112 (0.75, false)
114 }
115 None => {
116 (room_height - 0.1, true)
118 }
119 }
120 }
121}
122
123#[derive(Debug, Clone, Default)]
125pub struct VariantEmitterData {
126 pub variant_id: String,
128 pub l3d_file_id: Option<String>,
130 pub l3d_file_name: Option<String>,
132 pub emitters: Vec<EmitterRenderData>,
134 pub mounting: MountingInfo,
136}
137
138pub fn get_l3d_ldt_mappings(gldf: &GldfProduct) -> Vec<L3dLdtMapping> {
154 let mut mappings = Vec::new();
155
156 let Some(variants) = &gldf.product_definitions.variants else {
158 return mappings;
159 };
160
161 let general_defs = &gldf.general_definitions;
163
164 let geometries = general_defs.geometries.as_ref();
166 let emitters = general_defs.emitters.as_ref();
167 let photometries = general_defs.photometries.as_ref();
168 let files = &general_defs.files.file;
169
170 let get_file_name = |file_id: &str| -> Option<String> {
172 files
173 .iter()
174 .find(|f| f.id == file_id)
175 .map(|f| f.file_name.clone())
176 };
177
178 let get_l3d_file_id = |geometry_id: &str| -> Option<String> {
180 geometries.and_then(|g| {
181 g.model_geometry
182 .iter()
183 .find(|mg| mg.id == geometry_id)
184 .and_then(|mg| mg.geometry_file_reference.first())
185 .map(|gfr| gfr.file_id.clone())
186 })
187 };
188
189 let get_photometry_id = |emitter_id: &str| -> Option<String> {
191 emitters.and_then(|e| {
192 e.emitter
193 .iter()
194 .find(|em| em.id == emitter_id)
195 .and_then(|em| {
196 em.fixed_light_emitter
198 .first()
199 .map(|fle| fle.photometry_reference.photometry_id.clone())
200 .or_else(|| {
201 em.changeable_light_emitter
203 .first()
204 .map(|cle| cle.photometry_reference.photometry_id.clone())
205 })
206 })
207 })
208 };
209
210 let get_ldt_file_id = |photometry_id: &str| -> Option<String> {
212 photometries.and_then(|p| {
213 p.photometry
214 .iter()
215 .find(|ph| ph.id == photometry_id)
216 .and_then(|ph| ph.photometry_file_reference.as_ref())
217 .map(|pfr| pfr.file_id.clone())
218 })
219 };
220
221 for variant in &variants.variant {
223 if let Some(geometry) = &variant.geometry {
225 if let Some(model_geo_ref) = &geometry.model_geometry_reference {
227 let geometry_id = &model_geo_ref.geometry_id;
228
229 let l3d_file_id = get_l3d_file_id(geometry_id);
231 let l3d_file_name = l3d_file_id.as_ref().and_then(|id| get_file_name(id));
232
233 let mut ldt_file_id = None;
235 let mut ldt_file_name = None;
236 let mut emitter_id = None;
237
238 for emitter_ref in &model_geo_ref.emitter_reference {
239 emitter_id = Some(emitter_ref.emitter_id.clone());
240
241 if let Some(photometry_id) = get_photometry_id(&emitter_ref.emitter_id) {
242 if let Some(file_id) = get_ldt_file_id(&photometry_id) {
243 ldt_file_name = get_file_name(&file_id);
244 ldt_file_id = Some(file_id);
245 break; }
247 }
248 }
249
250 if let Some(l3d_id) = l3d_file_id {
251 mappings.push(L3dLdtMapping {
252 l3d_file_id: l3d_id,
253 l3d_file_name,
254 ldt_file_id,
255 ldt_file_name,
256 variant_id: variant.id.clone(),
257 emitter_id,
258 });
259 }
260 }
261
262 if let Some(simple_geo_ref) = &geometry.simple_geometry_reference {
264 let emitter_id = &simple_geo_ref.emitter_id;
265
266 if let Some(photometry_id) = get_photometry_id(emitter_id) {
268 if let Some(ldt_file_id) = get_ldt_file_id(&photometry_id) {
269 let ldt_file_name = get_file_name(&ldt_file_id);
270
271 mappings.push(L3dLdtMapping {
273 l3d_file_id: simple_geo_ref.geometry_id.clone(),
274 l3d_file_name: None, ldt_file_id: Some(ldt_file_id),
276 ldt_file_name,
277 variant_id: variant.id.clone(),
278 emitter_id: Some(emitter_id.clone()),
279 });
280 }
281 }
282 }
283 }
284 }
285
286 mappings
287}
288
289pub fn get_l3d_files_with_ldt(gldf: &FileBufGldf) -> Vec<L3dWithLdt> {
291 let mappings = get_l3d_ldt_mappings(&gldf.gldf);
292 let mut results = Vec::new();
293
294 for mapping in mappings {
295 let l3d_content = gldf
297 .files
298 .iter()
299 .find(|f| {
300 f.file_id.as_ref() == Some(&mapping.l3d_file_id)
301 || f.name.as_ref() == mapping.l3d_file_name.as_ref()
302 })
303 .and_then(|f| f.content.clone());
304
305 let ldt_content = mapping.ldt_file_id.as_ref().and_then(|ldt_id| {
307 gldf.files
308 .iter()
309 .find(|f| {
310 f.file_id.as_ref() == Some(ldt_id)
311 || f.name.as_ref() == mapping.ldt_file_name.as_ref()
312 })
313 .and_then(|f| f.content.clone())
314 });
315
316 if l3d_content.is_some() {
317 results.push(L3dWithLdt {
318 l3d_file_name: mapping
319 .l3d_file_name
320 .unwrap_or_else(|| mapping.l3d_file_id.clone()),
321 l3d_content,
322 ldt_file_name: mapping.ldt_file_name,
323 ldt_content,
324 variant_id: mapping.variant_id,
325 });
326 }
327 }
328
329 results
330}
331
332#[derive(Debug, Clone)]
334pub struct L3dWithLdt {
335 pub l3d_file_name: String,
337 pub l3d_content: Option<Vec<u8>>,
339 pub ldt_file_name: Option<String>,
341 pub ldt_content: Option<Vec<u8>>,
343 pub variant_id: String,
345}
346
347impl L3dWithLdt {
348 pub fn ldt_as_string(&self) -> Option<String> {
350 self.ldt_content
351 .as_ref()
352 .and_then(|content| std::str::from_utf8(content).ok().map(|s| s.to_string()))
353 }
354
355 pub fn has_both(&self) -> bool {
357 self.l3d_content.is_some() && self.ldt_content.is_some()
358 }
359
360 pub fn has_l3d(&self) -> bool {
362 self.l3d_content.is_some()
363 }
364
365 pub fn has_ldt(&self) -> bool {
367 self.ldt_content.is_some()
368 }
369}
370
371pub fn get_first_l3d_with_ldt(gldf: &FileBufGldf) -> Option<L3dWithLdt> {
373 get_l3d_files_with_ldt(gldf).into_iter().next()
374}
375
376pub fn get_variant_emitter_data(gldf: &GldfProduct, variant_id: &str) -> VariantEmitterData {
395 let mut result = VariantEmitterData {
396 variant_id: variant_id.to_string(),
397 ..Default::default()
398 };
399
400 let Some(variants) = &gldf.product_definitions.variants else {
402 return result;
403 };
404
405 let Some(variant) = variants.variant.iter().find(|v| v.id == variant_id) else {
407 return result;
408 };
409
410 let general_defs = &gldf.general_definitions;
412 let geometries = general_defs.geometries.as_ref();
413 let emitters = general_defs.emitters.as_ref();
414 let light_sources = general_defs.light_sources.as_ref();
415 let photometries = general_defs.photometries.as_ref();
416 let files = &general_defs.files.file;
417
418 let get_file_name = |file_id: &str| -> Option<String> {
420 files
421 .iter()
422 .find(|f| f.id == file_id)
423 .map(|f| f.file_name.clone())
424 };
425
426 let get_l3d_file_id = |geometry_id: &str| -> Option<String> {
428 geometries.and_then(|g| {
429 g.model_geometry
430 .iter()
431 .find(|mg| mg.id == geometry_id)
432 .and_then(|mg| mg.geometry_file_reference.first())
433 .map(|gfr| gfr.file_id.clone())
434 })
435 };
436
437 let get_photometry_info = |emitter_id: &str| -> (Option<String>, Option<String>) {
439 let photometry_id = emitters.and_then(|e| {
440 e.emitter
441 .iter()
442 .find(|em| em.id == emitter_id)
443 .and_then(|em| {
444 em.fixed_light_emitter
445 .first()
446 .map(|fle| fle.photometry_reference.photometry_id.clone())
447 .or_else(|| {
448 em.changeable_light_emitter
449 .first()
450 .map(|cle| cle.photometry_reference.photometry_id.clone())
451 })
452 })
453 });
454
455 if let Some(ref photo_id) = photometry_id {
456 let ldt_file_id = photometries.and_then(|p| {
457 p.photometry
458 .iter()
459 .find(|ph| ph.id == *photo_id)
460 .and_then(|ph| ph.photometry_file_reference.as_ref())
461 .map(|pfr| pfr.file_id.clone())
462 });
463 let ldt_file_name = ldt_file_id.as_ref().and_then(|id| get_file_name(id));
464 (ldt_file_id, ldt_file_name)
465 } else {
466 (None, None)
467 }
468 };
469
470 let get_emitter_details = |emitter_id: &str| -> (Option<i32>, Option<i32>, Option<String>) {
472 let emitter = emitters.and_then(|e| e.emitter.iter().find(|em| em.id == emitter_id));
473
474 let Some(emitter) = emitter else {
475 return (None, None, None);
476 };
477
478 if let Some(fle) = emitter.fixed_light_emitter.first() {
480 let flux = fle.rated_luminous_flux;
481 let emergency = fle.emergency_behaviour.clone();
482
483 let color_temp = fle
485 .light_source_reference
486 .fixed_light_source_id
487 .as_ref()
488 .and_then(|ls_id| {
489 light_sources.and_then(|lss| {
490 lss.fixed_light_source
491 .iter()
492 .find(|ls| ls.id == *ls_id)
493 .and_then(|ls| ls.color_information.as_ref())
494 .and_then(|ci| ci.correlated_color_temperature)
495 })
496 });
497
498 return (flux, color_temp, emergency);
499 }
500
501 if let Some(cle) = emitter.changeable_light_emitter.first() {
503 let emergency = cle.emergency_behaviour.clone();
504 return (None, None, emergency);
506 }
507
508 (None, None, None)
509 };
510
511 if let Some(geometry) = &variant.geometry {
513 if let Some(model_geo_ref) = &geometry.model_geometry_reference {
514 let geometry_id = &model_geo_ref.geometry_id;
515
516 result.l3d_file_id = get_l3d_file_id(geometry_id);
518 result.l3d_file_name = result.l3d_file_id.as_ref().and_then(|id| get_file_name(id));
519
520 for emitter_ref in &model_geo_ref.emitter_reference {
522 let (ldt_file_id, ldt_file_name) = get_photometry_info(&emitter_ref.emitter_id);
523 let (flux, color_temp, emergency) = get_emitter_details(&emitter_ref.emitter_id);
524
525 result.emitters.push(EmitterRenderData {
526 leo_name: emitter_ref.emitter_object_external_name.clone(),
527 emitter_id: emitter_ref.emitter_id.clone(),
528 luminous_flux: flux,
529 color_temperature: color_temp,
530 ldt_file_id,
531 ldt_file_name,
532 emergency_behavior: emergency,
533 });
534 }
535 }
536 }
537
538 result.mounting = extract_mounting_info(variant);
540
541 result
542}
543
544fn extract_mounting_info(variant: &crate::gldf::product_definitions::Variant) -> MountingInfo {
551 let mut info = MountingInfo::default();
552
553 let Some(mountings) = &variant.mountings else {
554 return info;
555 };
556
557 if let Some(wall) = &mountings.wall {
561 if info.mounting_type.is_none() {
562 info.mounting_type = Some(MountingType::Wall);
563 }
564 info.mounting_height_mm = Some(wall.mounting_height);
565
566 if let Some(recessed) = &wall.recessed {
567 info.recessed_depth_mm = Some(recessed.recessed_depth);
568 }
569
570 if wall.surface_mounted.is_some() {
571 info.is_surface_mounted = true;
572 }
573 }
574
575 if let Some(ceiling) = &mountings.ceiling {
577 if info.mounting_type.is_none() {
578 info.mounting_type = Some(MountingType::Ceiling);
579 }
580
581 if let Some(recessed) = &ceiling.recessed {
582 info.recessed_depth_mm = Some(recessed.recessed_depth);
583 }
584
585 if let Some(pendant) = &ceiling.pendant {
586 info.pendant_length_mm = Some(pendant.pendant_length);
587 }
588
589 if ceiling.surface_mounted.is_some() {
590 info.is_surface_mounted = true;
591 }
592 }
593
594 if let Some(ground) = &mountings.ground {
596 if info.mounting_type.is_none() {
597 info.mounting_type = Some(MountingType::Ground);
598 }
599
600 if let Some(pole_top) = &ground.pole_top {
601 info.pole_height_mm = pole_top.get_pole_height();
602 }
603
604 if let Some(pole_integrated) = &ground.pole_integrated {
605 if info.pole_height_mm.is_none() {
606 info.pole_height_mm = pole_integrated.get_pole_height();
607 }
608 }
609
610 if ground.free_standing.is_some() {
611 info.is_free_standing = true;
612 }
613
614 if ground.surface_mounted.is_some() {
615 info.is_surface_mounted = true;
616 }
617
618 if let Some(recessed) = &ground.recessed {
619 if info.recessed_depth_mm.is_none() {
620 info.recessed_depth_mm = Some(recessed.recessed_depth);
621 }
622 }
623 }
624
625 if let Some(working_plane) = &mountings.working_plane {
627 if info.mounting_type.is_none() {
628 info.mounting_type = Some(MountingType::WorkingPlane);
629 }
630
631 if working_plane.free_standing.is_some() {
632 info.is_free_standing = true;
633 }
634 }
635
636 info
637}
638
639#[cfg(test)]
640mod tests {
641 use super::*;
642
643 #[test]
644 fn test_mapping_creation() {
645 let mapping = L3dLdtMapping {
647 l3d_file_id: "geo_001".to_string(),
648 l3d_file_name: Some("luminaire.l3d".to_string()),
649 ldt_file_id: Some("photo_001".to_string()),
650 ldt_file_name: Some("luminaire.ldt".to_string()),
651 variant_id: "variant_1".to_string(),
652 emitter_id: Some("emitter_1".to_string()),
653 };
654
655 assert_eq!(mapping.l3d_file_id, "geo_001");
656 assert_eq!(mapping.ldt_file_id, Some("photo_001".to_string()));
657 }
658}