#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum SchemaVersion {
#[default]
AtlaS001,
Tm3323,
Tm3324,
}
impl SchemaVersion {
pub fn version_string(&self) -> &'static str {
match self {
SchemaVersion::AtlaS001 => "1.0",
SchemaVersion::Tm3323 => "1.1",
SchemaVersion::Tm3324 => "1.2",
}
}
pub fn root_element(&self) -> &'static str {
match self {
SchemaVersion::AtlaS001 => "LuminaireOpticalData",
SchemaVersion::Tm3323 | SchemaVersion::Tm3324 => "IESTM33-22",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum SymmetryType {
#[default]
None,
Bi0,
Bi90,
Quad,
Full,
Arbitrary,
}
impl SymmetryType {
pub fn from_tm33_str(s: &str) -> Self {
let normalized = s.trim().replace(" _ ", "_").replace(" ", "").to_uppercase();
match normalized.as_str() {
"SYMM_NONE" | "NONE" => SymmetryType::None,
"SYMM_BI_0" | "SYMM_BI0" | "BI0" | "BI_0" => SymmetryType::Bi0,
"SYMM_BI_90" | "SYMM_BI90" | "BI90" | "BI_90" => SymmetryType::Bi90,
"SYMM_QUAD" | "QUAD" => SymmetryType::Quad,
"SYMM_FULL" | "FULL" => SymmetryType::Full,
"SYMM_ARBITRARY" | "ARBITRARY" => SymmetryType::Arbitrary,
_ => SymmetryType::None,
}
}
pub fn to_tm33_str(&self) -> &'static str {
match self {
SymmetryType::None => "Symm _ None",
SymmetryType::Bi0 => "Symm _ Bi _ 0",
SymmetryType::Bi90 => "Symm _ Bi _90",
SymmetryType::Quad => "Symm _ Quad",
SymmetryType::Full => "Symm _ Full",
SymmetryType::Arbitrary => "Symm _ Arbitrary",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum GoniometerTypeEnum {
CieA,
CieB,
#[default]
CieC,
IesA,
IesB,
IesC,
Custom,
}
impl GoniometerTypeEnum {
pub fn parse(s: &str) -> Self {
let normalized = s.trim().replace(" _ ", "_").replace(" ", "").to_uppercase();
match normalized.as_str() {
"CIE_A" | "CIEA" | "TYPEA" | "A" => GoniometerTypeEnum::CieA,
"CIE_B" | "CIEB" | "TYPEB" | "B" => GoniometerTypeEnum::CieB,
"CIE_C" | "CIEC" | "TYPEC" | "C" => GoniometerTypeEnum::CieC,
"IES_A" | "IESA" => GoniometerTypeEnum::IesA,
"IES_B" | "IESB" => GoniometerTypeEnum::IesB,
"IES_C" | "IESC" => GoniometerTypeEnum::IesC,
"CUSTOM" => GoniometerTypeEnum::Custom,
_ => GoniometerTypeEnum::CieC,
}
}
pub fn to_tm33_str(&self) -> &'static str {
match self {
GoniometerTypeEnum::CieA => "CIE _ A",
GoniometerTypeEnum::CieB => "CIE _ B",
GoniometerTypeEnum::CieC => "CIE _ C",
GoniometerTypeEnum::IesA => "IES _ A",
GoniometerTypeEnum::IesB => "IES _ B",
GoniometerTypeEnum::IesC => "IES _ C",
GoniometerTypeEnum::Custom => "CUSTOM",
}
}
pub fn to_atla_str(&self) -> &'static str {
match self {
GoniometerTypeEnum::CieA | GoniometerTypeEnum::IesA => "TypeA",
GoniometerTypeEnum::CieB | GoniometerTypeEnum::IesB => "TypeB",
GoniometerTypeEnum::CieC | GoniometerTypeEnum::IesC => "TypeC",
GoniometerTypeEnum::Custom => "CUSTOM",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum RegulatoryValue {
Measured,
Nominal,
Rated,
}
impl RegulatoryValue {
pub fn parse(s: &str) -> Option<Self> {
match s.trim().to_lowercase().as_str() {
"measured" => Some(RegulatoryValue::Measured),
"nominal" => Some(RegulatoryValue::Nominal),
"rated" => Some(RegulatoryValue::Rated),
_ => None,
}
}
pub fn as_str(&self) -> &'static str {
match self {
RegulatoryValue::Measured => "Measured",
RegulatoryValue::Nominal => "Nominal",
RegulatoryValue::Rated => "Rated",
}
}
}
#[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct LuminaireOpticalData {
pub schema_version: SchemaVersion,
pub version: String,
pub header: Header,
pub luminaire: Option<Luminaire>,
pub equipment: Option<Equipment>,
pub emitters: Vec<Emitter>,
pub custom_data: Option<CustomData>,
pub custom_data_items: Vec<CustomDataItem>,
}
#[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Header {
pub manufacturer: Option<String>,
pub catalog_number: Option<String>,
pub description: Option<String>,
pub gtin: Option<String>,
pub gtin_int: Option<i64>,
pub uuid: Option<String>,
pub reference: Option<String>,
pub references: Vec<String>,
pub more_info_uri: Option<String>,
pub laboratory: Option<String>,
pub report_number: Option<String>,
pub report_date: Option<String>,
pub test_date: Option<String>,
pub issue_date: Option<String>,
pub document_creator: Option<String>,
pub document_creation_date: Option<String>,
pub unique_identifier: Option<String>,
pub luminaire_type: Option<String>,
pub comments: Option<String>,
pub comments_list: Vec<String>,
}
#[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Luminaire {
pub dimensions: Option<Dimensions>,
pub luminous_openings: Vec<LuminousOpening>,
pub mounting: Option<String>,
pub num_emitters: Option<u32>,
}
#[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Dimensions {
pub length: f64,
pub width: f64,
pub height: f64,
}
#[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct LuminousOpening {
pub shape: LuminousOpeningShape,
pub dimensions: OpeningDimensions,
pub position: Option<Position3D>,
}
#[derive(Debug, Clone, Default, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum LuminousOpeningShape {
#[default]
Rectangular,
Circular,
Elliptical,
Point,
}
#[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct OpeningDimensions {
pub length: f64,
pub width: Option<f64>,
}
#[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Position3D {
pub x: f64,
pub y: f64,
pub z: f64,
}
#[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Equipment {
pub goniometer: Option<GoniometerInfo>,
pub integrating_sphere: Option<IntegratingSphereInfo>,
pub spectroradiometer: Option<SpectroradiometerInfo>,
pub accreditation: Option<Accreditation>,
}
#[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct GoniometerInfo {
pub manufacturer: Option<String>,
pub model: Option<String>,
pub goniometer_type: Option<String>,
pub distance: Option<f64>,
}
#[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct IntegratingSphereInfo {
pub manufacturer: Option<String>,
pub model: Option<String>,
pub diameter: Option<f64>,
}
#[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct SpectroradiometerInfo {
pub manufacturer: Option<String>,
pub model: Option<String>,
pub wavelength_min: Option<f64>,
pub wavelength_max: Option<f64>,
pub resolution: Option<f64>,
}
#[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Accreditation {
pub body: Option<String>,
pub number: Option<String>,
pub scope: Option<String>,
}
#[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Emitter {
pub id: Option<String>,
pub description: Option<String>,
pub catalog_number: Option<String>,
pub quantity: u32,
pub rated_lumens: Option<f64>,
pub measured_lumens: Option<f64>,
pub input_watts: Option<f64>,
pub power_factor: Option<f64>,
pub ballast_factor: Option<f64>,
pub cct: Option<f64>,
pub color_rendering: Option<ColorRendering>,
pub duv: Option<f64>,
pub sp_ratio: Option<f64>,
pub data_generation: Option<DataGeneration>,
pub intensity_distribution: Option<IntensityDistribution>,
pub spectral_distribution: Option<SpectralDistribution>,
pub angular_spectral: Option<AngularSpectralData>,
pub angular_color: Option<AngularColorData>,
pub tilt_angles: Option<TiltAngles>,
pub regulatory: Option<Regulatory>,
}
#[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct ColorRendering {
pub ra: Option<f64>,
pub r9: Option<f64>,
pub rf: Option<f64>,
pub rg: Option<f64>,
}
#[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct DataGeneration {
pub source: DataSource,
pub scaled: bool,
pub interpolated: bool,
pub software: Option<String>,
pub uncertainty: Option<f64>,
}
#[derive(Debug, Clone, Default, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum DataSource {
#[default]
Measured,
Simulated,
Derived,
}
#[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct IntensityDistribution {
pub photometry_type: PhotometryType,
pub metric: IntensityMetric,
pub units: IntensityUnits,
pub horizontal_angles: Vec<f64>,
pub vertical_angles: Vec<f64>,
pub intensities: Vec<Vec<f64>>,
pub symmetry: Option<SymmetryType>,
pub multiplier: Option<f32>,
pub absolute_photometry: Option<bool>,
pub number_measured: Option<i32>,
}
#[derive(Debug, Clone, Default, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum PhotometryType {
TypeA,
TypeB,
#[default]
TypeC,
}
#[derive(Debug, Clone, Default, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum IntensityMetric {
#[default]
Luminous,
Radiant,
Photon,
Spectral,
}
#[derive(Debug, Clone, Default, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum IntensityUnits {
Candela,
#[default]
CandelaPerKilolumen,
WattsPerSteradian,
MicromolesPerSteradianPerSecond,
WattsPerSteradianPerNanometer,
}
#[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct SpectralDistribution {
pub wavelengths: Vec<f64>,
pub values: Vec<f64>,
pub units: SpectralUnits,
pub start_wavelength: Option<f64>,
pub wavelength_interval: Option<f64>,
}
#[derive(Debug, Clone, Default, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum SpectralUnits {
#[default]
WattsPerNanometer,
Relative,
}
#[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct CustomData {
pub namespace: Option<String>,
pub data: String,
}
#[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct CustomDataItem {
pub name: String,
pub unique_identifier: String,
pub raw_content: String,
}
#[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct AngularSpectralData {
pub absolute: Option<bool>,
pub symmetry: Option<SymmetryType>,
pub multiplier: Option<f32>,
pub number_measured: i32,
pub number_horz: i32,
pub number_vert: i32,
pub number_wavelength: i32,
pub data_points: Vec<AngularSpectralPoint>,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct AngularSpectralPoint {
pub h: f64,
pub v: f64,
pub w: f64,
pub value: f32,
}
#[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct AngularColorData {
pub symmetry: Option<SymmetryType>,
pub multiplier: Option<f32>,
pub number_measured: i32,
pub number_horz: i32,
pub number_vert: i32,
pub data_points: Vec<AngularColorPoint>,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct AngularColorPoint {
pub h: f64,
pub v: f64,
pub x: f64,
pub y: f64,
}
#[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct TiltAngles {
pub number_angles: i32,
pub angles: Vec<f64>,
}
#[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Regulatory {
pub input_wattage: Option<RegulatoryValue>,
pub power_factor: Option<RegulatoryValue>,
pub ballast_factor: Option<RegulatoryValue>,
pub color_temperature: Option<RegulatoryValue>,
pub cie_cri: Option<RegulatoryValue>,
pub ies_tm30: Option<RegulatoryValue>,
pub duv: Option<RegulatoryValue>,
pub sp_ratio: Option<RegulatoryValue>,
pub luminous_intensity: Option<RegulatoryValue>,
pub luminous_flux: Option<RegulatoryValue>,
pub radiant_intensity: Option<RegulatoryValue>,
pub radiant_flux: Option<RegulatoryValue>,
pub photon_intensity: Option<RegulatoryValue>,
pub photon_flux: Option<RegulatoryValue>,
pub spectral_power: Option<RegulatoryValue>,
pub spectral_intensity: Option<RegulatoryValue>,
pub angular_color: Option<RegulatoryValue>,
pub illuminance: Option<RegulatoryValue>,
pub irradiance: Option<RegulatoryValue>,
pub photon_flux_density: Option<RegulatoryValue>,
pub spectral_irradiance: Option<RegulatoryValue>,
}
#[derive(Debug, Clone, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Tm30ColorRendering {
pub rf: i32,
pub rg: i32,
pub rfh: [Option<i32>; 16],
pub rcsh: [Option<i32>; 16],
}
impl LuminaireOpticalData {
pub fn new() -> Self {
Self {
version: "1.0".to_string(),
..Default::default()
}
}
pub fn total_luminous_flux(&self) -> f64 {
self.emitters
.iter()
.filter_map(|e| e.measured_lumens.or(e.rated_lumens))
.sum()
}
pub fn total_input_watts(&self) -> f64 {
self.emitters.iter().filter_map(|e| e.input_watts).sum()
}
pub fn efficacy(&self) -> Option<f64> {
let flux = self.total_luminous_flux();
let watts = self.total_input_watts();
if watts > 0.0 {
Some(flux / watts)
} else {
None
}
}
}
impl IntensityDistribution {
pub fn sample(&self, horizontal: f64, vertical: f64) -> Option<f64> {
let h_idx = self
.horizontal_angles
.iter()
.position(|&a| (a - horizontal).abs() < 0.001)?;
let v_idx = self
.vertical_angles
.iter()
.position(|&a| (a - vertical).abs() < 0.001)?;
self.intensities.get(h_idx)?.get(v_idx).copied()
}
pub fn max_intensity(&self) -> f64 {
self.intensities
.iter()
.flat_map(|row| row.iter())
.fold(0.0_f64, |max, &val| max.max(val))
}
}