use crate::gldf::GldfProduct;
use crate::FileBufGldf;
#[derive(Debug, Clone)]
pub struct L3dLdtMapping {
pub l3d_file_id: String,
pub l3d_file_name: Option<String>,
pub ldt_file_id: Option<String>,
pub ldt_file_name: Option<String>,
pub variant_id: String,
pub emitter_id: Option<String>,
}
#[derive(Debug, Clone, Default)]
pub struct EmitterRenderData {
pub leo_name: String,
pub emitter_id: String,
pub luminous_flux: Option<i32>,
pub color_temperature: Option<i32>,
pub ldt_file_id: Option<String>,
pub ldt_file_name: Option<String>,
pub emergency_behavior: Option<String>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum MountingType {
Ceiling,
Wall,
Ground,
WorkingPlane,
}
#[derive(Debug, Clone, Default)]
pub struct MountingInfo {
pub mounting_type: Option<MountingType>,
pub recessed_depth_mm: Option<i32>,
pub pendant_length_mm: Option<f64>,
pub mounting_height_mm: Option<i32>,
pub pole_height_mm: Option<i32>,
pub is_surface_mounted: bool,
pub is_free_standing: bool,
}
impl MountingInfo {
pub fn get_position_offset(&self, room_height: f32) -> (f32, bool) {
match self.mounting_type {
Some(MountingType::Ceiling) => {
if let Some(pendant_mm) = self.pendant_length_mm {
(room_height - (pendant_mm as f32 / 1000.0), true)
} else if self.recessed_depth_mm.is_some() {
(room_height, true)
} else {
(room_height - 0.05, true)
}
}
Some(MountingType::Wall) => {
let height_m = self.mounting_height_mm.unwrap_or(2000) as f32 / 1000.0;
(height_m, false)
}
Some(MountingType::Ground) => {
if let Some(pole_mm) = self.pole_height_mm {
(pole_mm as f32 / 1000.0, false)
} else if self.is_free_standing {
(0.0, false)
} else {
(0.0, false)
}
}
Some(MountingType::WorkingPlane) => {
(0.75, false)
}
None => {
(room_height - 0.1, true)
}
}
}
}
#[derive(Debug, Clone, Default)]
pub struct VariantEmitterData {
pub variant_id: String,
pub l3d_file_id: Option<String>,
pub l3d_file_name: Option<String>,
pub emitters: Vec<EmitterRenderData>,
pub mounting: MountingInfo,
}
pub fn get_l3d_ldt_mappings(gldf: &GldfProduct) -> Vec<L3dLdtMapping> {
let mut mappings = Vec::new();
let Some(variants) = &gldf.product_definitions.variants else {
return mappings;
};
let general_defs = &gldf.general_definitions;
let geometries = general_defs.geometries.as_ref();
let emitters = general_defs.emitters.as_ref();
let photometries = general_defs.photometries.as_ref();
let files = &general_defs.files.file;
let get_file_name = |file_id: &str| -> Option<String> {
files
.iter()
.find(|f| f.id == file_id)
.map(|f| f.file_name.clone())
};
let get_l3d_file_id = |geometry_id: &str| -> Option<String> {
geometries.and_then(|g| {
g.model_geometry
.iter()
.find(|mg| mg.id == geometry_id)
.and_then(|mg| mg.geometry_file_reference.first())
.map(|gfr| gfr.file_id.clone())
})
};
let get_photometry_id = |emitter_id: &str| -> Option<String> {
emitters.and_then(|e| {
e.emitter
.iter()
.find(|em| em.id == emitter_id)
.and_then(|em| {
em.fixed_light_emitter
.first()
.map(|fle| fle.photometry_reference.photometry_id.clone())
.or_else(|| {
em.changeable_light_emitter
.first()
.map(|cle| cle.photometry_reference.photometry_id.clone())
})
})
})
};
let get_ldt_file_id = |photometry_id: &str| -> Option<String> {
photometries.and_then(|p| {
p.photometry
.iter()
.find(|ph| ph.id == photometry_id)
.and_then(|ph| ph.photometry_file_reference.as_ref())
.map(|pfr| pfr.file_id.clone())
})
};
for variant in &variants.variant {
if let Some(geometry) = &variant.geometry {
if let Some(model_geo_ref) = &geometry.model_geometry_reference {
let geometry_id = &model_geo_ref.geometry_id;
let l3d_file_id = get_l3d_file_id(geometry_id);
let l3d_file_name = l3d_file_id.as_ref().and_then(|id| get_file_name(id));
let mut ldt_file_id = None;
let mut ldt_file_name = None;
let mut emitter_id = None;
for emitter_ref in &model_geo_ref.emitter_reference {
emitter_id = Some(emitter_ref.emitter_id.clone());
if let Some(photometry_id) = get_photometry_id(&emitter_ref.emitter_id) {
if let Some(file_id) = get_ldt_file_id(&photometry_id) {
ldt_file_name = get_file_name(&file_id);
ldt_file_id = Some(file_id);
break; }
}
}
if let Some(l3d_id) = l3d_file_id {
mappings.push(L3dLdtMapping {
l3d_file_id: l3d_id,
l3d_file_name,
ldt_file_id,
ldt_file_name,
variant_id: variant.id.clone(),
emitter_id,
});
}
}
if let Some(simple_geo_ref) = &geometry.simple_geometry_reference {
let emitter_id = &simple_geo_ref.emitter_id;
if let Some(photometry_id) = get_photometry_id(emitter_id) {
if let Some(ldt_file_id) = get_ldt_file_id(&photometry_id) {
let ldt_file_name = get_file_name(&ldt_file_id);
mappings.push(L3dLdtMapping {
l3d_file_id: simple_geo_ref.geometry_id.clone(),
l3d_file_name: None, ldt_file_id: Some(ldt_file_id),
ldt_file_name,
variant_id: variant.id.clone(),
emitter_id: Some(emitter_id.clone()),
});
}
}
}
}
}
mappings
}
pub fn get_l3d_files_with_ldt(gldf: &FileBufGldf) -> Vec<L3dWithLdt> {
let mappings = get_l3d_ldt_mappings(&gldf.gldf);
let mut results = Vec::new();
for mapping in mappings {
let l3d_content = gldf
.files
.iter()
.find(|f| {
f.file_id.as_ref() == Some(&mapping.l3d_file_id)
|| f.name.as_ref() == mapping.l3d_file_name.as_ref()
})
.and_then(|f| f.content.clone());
let ldt_content = mapping.ldt_file_id.as_ref().and_then(|ldt_id| {
gldf.files
.iter()
.find(|f| {
f.file_id.as_ref() == Some(ldt_id)
|| f.name.as_ref() == mapping.ldt_file_name.as_ref()
})
.and_then(|f| f.content.clone())
});
if l3d_content.is_some() {
results.push(L3dWithLdt {
l3d_file_name: mapping
.l3d_file_name
.unwrap_or_else(|| mapping.l3d_file_id.clone()),
l3d_content,
ldt_file_name: mapping.ldt_file_name,
ldt_content,
variant_id: mapping.variant_id,
});
}
}
results
}
#[derive(Debug, Clone)]
pub struct L3dWithLdt {
pub l3d_file_name: String,
pub l3d_content: Option<Vec<u8>>,
pub ldt_file_name: Option<String>,
pub ldt_content: Option<Vec<u8>>,
pub variant_id: String,
}
impl L3dWithLdt {
pub fn ldt_as_string(&self) -> Option<String> {
self.ldt_content
.as_ref()
.and_then(|content| std::str::from_utf8(content).ok().map(|s| s.to_string()))
}
pub fn has_both(&self) -> bool {
self.l3d_content.is_some() && self.ldt_content.is_some()
}
pub fn has_l3d(&self) -> bool {
self.l3d_content.is_some()
}
pub fn has_ldt(&self) -> bool {
self.ldt_content.is_some()
}
}
pub fn get_first_l3d_with_ldt(gldf: &FileBufGldf) -> Option<L3dWithLdt> {
get_l3d_files_with_ldt(gldf).into_iter().next()
}
pub fn get_variant_emitter_data(gldf: &GldfProduct, variant_id: &str) -> VariantEmitterData {
let mut result = VariantEmitterData {
variant_id: variant_id.to_string(),
..Default::default()
};
let Some(variants) = &gldf.product_definitions.variants else {
return result;
};
let Some(variant) = variants.variant.iter().find(|v| v.id == variant_id) else {
return result;
};
let general_defs = &gldf.general_definitions;
let geometries = general_defs.geometries.as_ref();
let emitters = general_defs.emitters.as_ref();
let light_sources = general_defs.light_sources.as_ref();
let photometries = general_defs.photometries.as_ref();
let files = &general_defs.files.file;
let get_file_name = |file_id: &str| -> Option<String> {
files
.iter()
.find(|f| f.id == file_id)
.map(|f| f.file_name.clone())
};
let get_l3d_file_id = |geometry_id: &str| -> Option<String> {
geometries.and_then(|g| {
g.model_geometry
.iter()
.find(|mg| mg.id == geometry_id)
.and_then(|mg| mg.geometry_file_reference.first())
.map(|gfr| gfr.file_id.clone())
})
};
let get_photometry_info = |emitter_id: &str| -> (Option<String>, Option<String>) {
let photometry_id = emitters.and_then(|e| {
e.emitter
.iter()
.find(|em| em.id == emitter_id)
.and_then(|em| {
em.fixed_light_emitter
.first()
.map(|fle| fle.photometry_reference.photometry_id.clone())
.or_else(|| {
em.changeable_light_emitter
.first()
.map(|cle| cle.photometry_reference.photometry_id.clone())
})
})
});
if let Some(ref photo_id) = photometry_id {
let ldt_file_id = photometries.and_then(|p| {
p.photometry
.iter()
.find(|ph| ph.id == *photo_id)
.and_then(|ph| ph.photometry_file_reference.as_ref())
.map(|pfr| pfr.file_id.clone())
});
let ldt_file_name = ldt_file_id.as_ref().and_then(|id| get_file_name(id));
(ldt_file_id, ldt_file_name)
} else {
(None, None)
}
};
let get_emitter_details = |emitter_id: &str| -> (Option<i32>, Option<i32>, Option<String>) {
let emitter = emitters.and_then(|e| e.emitter.iter().find(|em| em.id == emitter_id));
let Some(emitter) = emitter else {
return (None, None, None);
};
if let Some(fle) = emitter.fixed_light_emitter.first() {
let flux = fle.rated_luminous_flux;
let emergency = fle.emergency_behaviour.clone();
let color_temp = fle
.light_source_reference
.fixed_light_source_id
.as_ref()
.and_then(|ls_id| {
light_sources.and_then(|lss| {
lss.fixed_light_source
.iter()
.find(|ls| ls.id == *ls_id)
.and_then(|ls| ls.color_information.as_ref())
.and_then(|ci| ci.correlated_color_temperature)
})
});
return (flux, color_temp, emergency);
}
if let Some(cle) = emitter.changeable_light_emitter.first() {
let emergency = cle.emergency_behaviour.clone();
return (None, None, emergency);
}
(None, None, None)
};
if let Some(geometry) = &variant.geometry {
if let Some(model_geo_ref) = &geometry.model_geometry_reference {
let geometry_id = &model_geo_ref.geometry_id;
result.l3d_file_id = get_l3d_file_id(geometry_id);
result.l3d_file_name = result.l3d_file_id.as_ref().and_then(|id| get_file_name(id));
for emitter_ref in &model_geo_ref.emitter_reference {
let (ldt_file_id, ldt_file_name) = get_photometry_info(&emitter_ref.emitter_id);
let (flux, color_temp, emergency) = get_emitter_details(&emitter_ref.emitter_id);
result.emitters.push(EmitterRenderData {
leo_name: emitter_ref.emitter_object_external_name.clone(),
emitter_id: emitter_ref.emitter_id.clone(),
luminous_flux: flux,
color_temperature: color_temp,
ldt_file_id,
ldt_file_name,
emergency_behavior: emergency,
});
}
}
}
result.mounting = extract_mounting_info(variant);
result
}
fn extract_mounting_info(variant: &crate::gldf::product_definitions::Variant) -> MountingInfo {
let mut info = MountingInfo::default();
let Some(mountings) = &variant.mountings else {
return info;
};
if let Some(wall) = &mountings.wall {
if info.mounting_type.is_none() {
info.mounting_type = Some(MountingType::Wall);
}
info.mounting_height_mm = Some(wall.mounting_height);
if let Some(recessed) = &wall.recessed {
info.recessed_depth_mm = Some(recessed.recessed_depth);
}
if wall.surface_mounted.is_some() {
info.is_surface_mounted = true;
}
}
if let Some(ceiling) = &mountings.ceiling {
if info.mounting_type.is_none() {
info.mounting_type = Some(MountingType::Ceiling);
}
if let Some(recessed) = &ceiling.recessed {
info.recessed_depth_mm = Some(recessed.recessed_depth);
}
if let Some(pendant) = &ceiling.pendant {
info.pendant_length_mm = Some(pendant.pendant_length);
}
if ceiling.surface_mounted.is_some() {
info.is_surface_mounted = true;
}
}
if let Some(ground) = &mountings.ground {
if info.mounting_type.is_none() {
info.mounting_type = Some(MountingType::Ground);
}
if let Some(pole_top) = &ground.pole_top {
info.pole_height_mm = pole_top.get_pole_height();
}
if let Some(pole_integrated) = &ground.pole_integrated {
if info.pole_height_mm.is_none() {
info.pole_height_mm = pole_integrated.get_pole_height();
}
}
if ground.free_standing.is_some() {
info.is_free_standing = true;
}
if ground.surface_mounted.is_some() {
info.is_surface_mounted = true;
}
if let Some(recessed) = &ground.recessed {
if info.recessed_depth_mm.is_none() {
info.recessed_depth_mm = Some(recessed.recessed_depth);
}
}
}
if let Some(working_plane) = &mountings.working_plane {
if info.mounting_type.is_none() {
info.mounting_type = Some(MountingType::WorkingPlane);
}
if working_plane.free_standing.is_some() {
info.is_free_standing = true;
}
}
info
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_mapping_creation() {
let mapping = L3dLdtMapping {
l3d_file_id: "geo_001".to_string(),
l3d_file_name: Some("luminaire.l3d".to_string()),
ldt_file_id: Some("photo_001".to_string()),
ldt_file_name: Some("luminaire.ldt".to_string()),
variant_id: "variant_1".to_string(),
emitter_id: Some("emitter_1".to_string()),
};
assert_eq!(mapping.l3d_file_id, "geo_001");
assert_eq!(mapping.ldt_file_id, Some("photo_001".to_string()));
}
}