use ifc_model::{EntityId, Model, Transaction, Value};
use ifc_schema::Schema;
use crate::authoring::{build_named, invalid_authoring, validate_ratio, validate_ref};
use crate::StyleResult;
#[derive(Debug, Clone, Copy)]
pub struct LightSourceDraft<'a> {
pub name: Option<&'a str>,
pub light_colour: EntityId,
pub ambient_intensity: Option<f64>,
pub intensity: Option<f64>,
}
#[derive(Debug, Clone, Copy)]
pub struct Attenuation {
pub constant: f64,
pub distance: f64,
pub quadric: f64,
}
fn check_light(entity: &'static str, draft: &LightSourceDraft<'_>) -> StyleResult<()> {
if let Some(value) = draft.ambient_intensity {
validate_ratio(entity, "AmbientIntensity", value)?;
}
if let Some(value) = draft.intensity {
validate_ratio(entity, "Intensity", value)?;
}
Ok(())
}
fn light_values<'a>(draft: &LightSourceDraft<'a>) -> Vec<(&'static str, Value)> {
let mut values = vec![("LightColour", Value::Ref(draft.light_colour))];
if let Some(name) = draft.name {
values.push(("Name", Value::Text(name.into())));
}
if let Some(value) = draft.ambient_intensity {
values.push(("AmbientIntensity", Value::Real(value)));
}
if let Some(value) = draft.intensity {
values.push(("Intensity", Value::Real(value)));
}
values
}
fn check_attenuation(entity: &'static str, attenuation: &Attenuation) -> StyleResult<()> {
let all = [
("ConstantAttenuation", attenuation.constant),
("DistanceAttenuation", attenuation.distance),
("QuadricAttenuation", attenuation.quadric),
];
for (attribute, value) in all {
if !value.is_finite() {
return Err(invalid_authoring(
entity,
attribute,
format!("expected a finite coefficient, got {value}"),
));
}
}
if all.iter().all(|(_, value)| *value == 0.0) {
return Err(invalid_authoring(
entity,
"ConstantAttenuation",
"all three coefficients zero leaves no attenuation function",
));
}
Ok(())
}
pub fn create_light_source_ambient(
tx: &mut Transaction,
model: &Model,
schema: &Schema,
draft: LightSourceDraft<'_>,
) -> StyleResult<EntityId> {
check_light("IfcLightSourceAmbient", &draft)?;
validate_ref(tx, model, schema, draft.light_colour, "IfcColourRgb")?;
Ok(tx.create(build_named(
schema,
"IfcLightSourceAmbient",
light_values(&draft),
)?))
}
pub fn create_light_source_directional(
tx: &mut Transaction,
model: &Model,
schema: &Schema,
draft: LightSourceDraft<'_>,
orientation: EntityId,
) -> StyleResult<EntityId> {
check_light("IfcLightSourceDirectional", &draft)?;
validate_ref(tx, model, schema, draft.light_colour, "IfcColourRgb")?;
validate_ref(tx, model, schema, orientation, "IfcDirection")?;
let mut values = light_values(&draft);
values.push(("Orientation", Value::Ref(orientation)));
Ok(tx.create(build_named(schema, "IfcLightSourceDirectional", values)?))
}
pub fn create_light_source_positional(
tx: &mut Transaction,
model: &Model,
schema: &Schema,
draft: LightSourceDraft<'_>,
point: PointLight,
) -> StyleResult<EntityId> {
let PointLight {
position,
radius,
attenuation,
} = point;
check_light("IfcLightSourcePositional", &draft)?;
if !radius.is_finite() || radius <= 0.0 {
return Err(invalid_authoring(
"IfcLightSourcePositional",
"Radius",
format!("expected a positive length, got {radius}"),
));
}
check_attenuation("IfcLightSourcePositional", &attenuation)?;
validate_ref(tx, model, schema, draft.light_colour, "IfcColourRgb")?;
validate_ref(tx, model, schema, position, "IfcCartesianPoint")?;
let mut values = light_values(&draft);
values.extend([
("Position", Value::Ref(position)),
("Radius", Value::Real(radius)),
("ConstantAttenuation", Value::Real(attenuation.constant)),
("DistanceAttenuation", Value::Real(attenuation.distance)),
("QuadricAttenuation", Value::Real(attenuation.quadric)),
]);
Ok(tx.create(build_named(schema, "IfcLightSourcePositional", values)?))
}
#[derive(Debug, Clone, Copy)]
pub struct PointLight {
pub position: EntityId,
pub radius: f64,
pub attenuation: Attenuation,
}
impl Default for Attenuation {
fn default() -> Self {
Self {
constant: 1.0,
distance: 0.0,
quadric: 0.0,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct SpotCone {
pub orientation: EntityId,
pub spread_angle: f64,
pub beam_width_angle: f64,
pub concentration_exponent: Option<f64>,
}
pub fn create_light_source_spot(
tx: &mut Transaction,
model: &Model,
schema: &Schema,
draft: LightSourceDraft<'_>,
point: PointLight,
cone: SpotCone,
) -> StyleResult<EntityId> {
let PointLight {
position,
radius,
attenuation,
} = point;
check_light("IfcLightSourceSpot", &draft)?;
if !radius.is_finite() || radius <= 0.0 {
return Err(invalid_authoring(
"IfcLightSourceSpot",
"Radius",
format!("expected a positive length, got {radius}"),
));
}
check_attenuation("IfcLightSourceSpot", &attenuation)?;
for (attribute, value) in [
("SpreadAngle", cone.spread_angle),
("BeamWidthAngle", cone.beam_width_angle),
] {
if !value.is_finite() || value <= 0.0 {
return Err(invalid_authoring(
"IfcLightSourceSpot",
attribute,
format!("expected a positive plane angle, got {value}"),
));
}
}
validate_ref(tx, model, schema, draft.light_colour, "IfcColourRgb")?;
validate_ref(tx, model, schema, position, "IfcCartesianPoint")?;
validate_ref(tx, model, schema, cone.orientation, "IfcDirection")?;
let mut values = light_values(&draft);
values.extend([
("Position", Value::Ref(position)),
("Radius", Value::Real(radius)),
("ConstantAttenuation", Value::Real(attenuation.constant)),
("DistanceAttenuation", Value::Real(attenuation.distance)),
("QuadricAttenuation", Value::Real(attenuation.quadric)),
("Orientation", Value::Ref(cone.orientation)),
("SpreadAngle", Value::Real(cone.spread_angle)),
("BeamWidthAngle", Value::Real(cone.beam_width_angle)),
]);
if let Some(value) = cone.concentration_exponent {
values.push(("ConcentrationExponent", Value::Real(value)));
}
Ok(tx.create(build_named(schema, "IfcLightSourceSpot", values)?))
}
pub fn create_surface_style_lighting(
tx: &mut Transaction,
model: &Model,
schema: &Schema,
diffuse_transmission: EntityId,
diffuse_reflection: EntityId,
transmission: EntityId,
reflectance: EntityId,
) -> StyleResult<EntityId> {
for colour in [
diffuse_transmission,
diffuse_reflection,
transmission,
reflectance,
] {
validate_ref(tx, model, schema, colour, "IfcColourRgb")?;
}
let values = vec![
(
"DiffuseTransmissionColour",
Value::Ref(diffuse_transmission),
),
("DiffuseReflectionColour", Value::Ref(diffuse_reflection)),
("TransmissionColour", Value::Ref(transmission)),
("ReflectanceColour", Value::Ref(reflectance)),
];
Ok(tx.create(build_named(schema, "IfcSurfaceStyleLighting", values)?))
}
pub fn create_surface_style_refraction(
tx: &mut Transaction,
schema: &Schema,
refraction_index: Option<f64>,
dispersion_factor: Option<f64>,
) -> StyleResult<EntityId> {
if let Some(value) = refraction_index {
if !value.is_finite() || value < 1.0 {
return Err(invalid_authoring(
"IfcSurfaceStyleRefraction",
"RefractionIndex",
format!("expected at least 1.0, the vacuum index, got {value}"),
));
}
}
if let Some(value) = dispersion_factor {
if !value.is_finite() {
return Err(invalid_authoring(
"IfcSurfaceStyleRefraction",
"DispersionFactor",
format!("expected a finite factor, got {value}"),
));
}
}
let mut values = Vec::new();
if let Some(value) = refraction_index {
values.push(("RefractionIndex", Value::Real(value)));
}
if let Some(value) = dispersion_factor {
values.push(("DispersionFactor", Value::Real(value)));
}
Ok(tx.create(build_named(schema, "IfcSurfaceStyleRefraction", values)?))
}
#[derive(Debug, Clone, Copy)]
pub struct GoniometricLight {
pub position: EntityId,
pub colour_appearance: Option<EntityId>,
pub colour_temperature: f64,
pub luminous_flux: f64,
pub emission_source: &'static str,
pub distribution_data_source: EntityId,
}
pub fn create_light_source_goniometric(
tx: &mut Transaction,
model: &Model,
schema: &Schema,
draft: LightSourceDraft<'_>,
light: GoniometricLight,
) -> StyleResult<EntityId> {
const ENTITY: &str = "IfcLightSourceGoniometric";
check_light(ENTITY, &draft)?;
validate_ref(tx, model, schema, draft.light_colour, "IfcColourRgb")?;
if let Some(colour) = light.colour_appearance {
validate_ref(tx, model, schema, colour, "IfcColourRgb")?;
}
for (value, attribute) in [
(light.colour_temperature, "ColourTemperature"),
(light.luminous_flux, "LuminousFlux"),
] {
if !value.is_finite() || value <= 0.0 {
return Err(invalid_authoring(
ENTITY,
attribute,
format!("expected a positive measure, got {value}"),
));
}
}
let declared = schema
.attributes(ENTITY)
.iter()
.find(|a| a.name.eq_ignore_ascii_case("LightEmissionSource"))
.and_then(|a| schema.type_def(&a.type_name))
.is_some_and(|def| match &def.kind {
ifc_schema::TypeKind::Enumeration(values) => values
.iter()
.any(|v| v.eq_ignore_ascii_case(light.emission_source)),
_ => false,
});
if !declared {
return Err(invalid_authoring(
ENTITY,
"LightEmissionSource",
light.emission_source,
));
}
let mut values = light_values(&draft);
values.push(("Position", Value::Ref(light.position)));
if let Some(colour) = light.colour_appearance {
values.push(("ColourAppearance", Value::Ref(colour)));
}
values.push(("ColourTemperature", Value::Real(light.colour_temperature)));
values.push(("LuminousFlux", Value::Real(light.luminous_flux)));
values.push((
"LightEmissionSource",
Value::Enum(light.emission_source.into()),
));
values.push((
"LightDistributionDataSource",
Value::Ref(light.distribution_data_source),
));
Ok(tx.create(build_named(schema, ENTITY, values)?))
}