use ifc_model::{Entity, EntityId, Transaction, Value};
use super::{finite, invalid};
use crate::error::AlignmentError;
use crate::slot;
#[derive(Debug, Clone, Copy)]
pub struct HorizontalSegmentDraft {
pub start_point: EntityId,
pub start_direction: f64,
pub start_radius: f64,
pub end_radius: f64,
pub segment_length: f64,
pub gravity_center_line_height: Option<f64>,
pub predefined_type: &'static str,
}
const HSEG: &str = "IFCALIGNMENTHORIZONTALSEGMENT";
pub fn horizontal_segment(
tx: &mut Transaction,
draft: &HorizontalSegmentDraft,
) -> Result<EntityId, AlignmentError> {
finite(HSEG, "StartDirection", draft.start_direction)?;
finite(HSEG, "StartRadiusOfCurvature", draft.start_radius)?;
finite(HSEG, "EndRadiusOfCurvature", draft.end_radius)?;
finite(HSEG, "SegmentLength", draft.segment_length)?;
if let Some(height) = draft.gravity_center_line_height {
finite(HSEG, "GravityCenterLineHeight", height)?;
}
if draft.segment_length < 0.0 {
return Err(invalid(
HSEG,
"SegmentLength",
"a segment cannot be shorter than nothing",
));
}
if draft.predefined_type == "LINE" && (draft.start_radius != 0.0 || draft.end_radius != 0.0) {
return Err(invalid(
HSEG,
"PredefinedType",
"LINE requires zero start and end radii",
));
}
let mut attrs = vec![Value::Null; slot::horizontal::PREDEFINED_TYPE + 1];
attrs[slot::horizontal::START_POINT] = Value::Ref(draft.start_point);
attrs[slot::horizontal::START_DIRECTION] = Value::Real(draft.start_direction);
attrs[slot::horizontal::START_RADIUS] = Value::Real(draft.start_radius);
attrs[slot::horizontal::END_RADIUS] = Value::Real(draft.end_radius);
attrs[slot::horizontal::SEGMENT_LENGTH] = Value::Real(draft.segment_length);
if let Some(height) = draft.gravity_center_line_height {
attrs[slot::horizontal::GRAVITY_CENTER_LINE_HEIGHT] = Value::Real(height);
}
attrs[slot::horizontal::PREDEFINED_TYPE] = Value::Enum(draft.predefined_type.into());
Ok(tx.create(Entity::new(HSEG, attrs)))
}
#[derive(Debug, Clone, Copy)]
pub struct VerticalSegmentDraft {
pub start_dist_along: f64,
pub horizontal_length: f64,
pub start_height: f64,
pub start_gradient: f64,
pub end_gradient: f64,
pub radius_of_curvature: Option<f64>,
pub predefined_type: &'static str,
}
const VSEG: &str = "IFCALIGNMENTVERTICALSEGMENT";
pub fn vertical_segment(
tx: &mut Transaction,
draft: &VerticalSegmentDraft,
) -> Result<EntityId, AlignmentError> {
finite(VSEG, "StartDistAlong", draft.start_dist_along)?;
finite(VSEG, "HorizontalLength", draft.horizontal_length)?;
finite(VSEG, "StartHeight", draft.start_height)?;
finite(VSEG, "StartGradient", draft.start_gradient)?;
finite(VSEG, "EndGradient", draft.end_gradient)?;
if let Some(radius) = draft.radius_of_curvature {
finite(VSEG, "RadiusOfCurvature", radius)?;
}
if draft.horizontal_length < 0.0 {
return Err(invalid(
VSEG,
"HorizontalLength",
"a segment cannot be shorter than nothing",
));
}
let needs_radius = matches!(draft.predefined_type, "CIRCULARARC" | "PARABOLICARC");
if needs_radius != draft.radius_of_curvature.is_some() {
return Err(invalid(
VSEG,
"RadiusOfCurvature",
"radius is required exactly for circular and parabolic vertical segments",
));
}
let mut attrs = vec![Value::Null; slot::vertical::PREDEFINED_TYPE + 1];
attrs[slot::vertical::START_DIST_ALONG] = Value::Real(draft.start_dist_along);
attrs[slot::vertical::HORIZONTAL_LENGTH] = Value::Real(draft.horizontal_length);
attrs[slot::vertical::START_HEIGHT] = Value::Real(draft.start_height);
attrs[slot::vertical::START_GRADIENT] = Value::Real(draft.start_gradient);
attrs[slot::vertical::END_GRADIENT] = Value::Real(draft.end_gradient);
if let Some(radius) = draft.radius_of_curvature {
attrs[slot::vertical::RADIUS_OF_CURVATURE] = Value::Real(radius);
}
attrs[slot::vertical::PREDEFINED_TYPE] = Value::Enum(draft.predefined_type.into());
Ok(tx.create(Entity::new(VSEG, attrs)))
}
#[derive(Debug, Clone, Copy)]
pub struct CantSegmentDraft {
pub start_dist_along: f64,
pub horizontal_length: f64,
pub start_cant_left: f64,
pub end_cant_left: Option<f64>,
pub start_cant_right: f64,
pub end_cant_right: Option<f64>,
pub predefined_type: &'static str,
}
const CSEG: &str = "IFCALIGNMENTCANTSEGMENT";
pub fn cant_segment(
tx: &mut Transaction,
draft: &CantSegmentDraft,
) -> Result<EntityId, AlignmentError> {
finite(CSEG, "StartDistAlong", draft.start_dist_along)?;
finite(CSEG, "HorizontalLength", draft.horizontal_length)?;
finite(CSEG, "StartCantLeft", draft.start_cant_left)?;
finite(CSEG, "StartCantRight", draft.start_cant_right)?;
if let Some(value) = draft.end_cant_left {
finite(CSEG, "EndCantLeft", value)?;
}
if let Some(value) = draft.end_cant_right {
finite(CSEG, "EndCantRight", value)?;
}
if draft.horizontal_length < 0.0 {
return Err(invalid(
CSEG,
"HorizontalLength",
"a segment cannot be shorter than nothing",
));
}
if draft.end_cant_left.is_some() != draft.end_cant_right.is_some() {
return Err(invalid(
CSEG,
"EndCantLeft",
"left and right end cant must both be supplied or both omitted",
));
}
let mut attrs = vec![Value::Null; slot::cant::PREDEFINED_TYPE + 1];
attrs[slot::cant::START_DIST_ALONG] = Value::Real(draft.start_dist_along);
attrs[slot::cant::HORIZONTAL_LENGTH] = Value::Real(draft.horizontal_length);
attrs[slot::cant::START_CANT_LEFT] = Value::Real(draft.start_cant_left);
if let Some(value) = draft.end_cant_left {
attrs[slot::cant::END_CANT_LEFT] = Value::Real(value);
}
attrs[slot::cant::START_CANT_RIGHT] = Value::Real(draft.start_cant_right);
if let Some(value) = draft.end_cant_right {
attrs[slot::cant::END_CANT_RIGHT] = Value::Real(value);
}
attrs[slot::cant::PREDEFINED_TYPE] = Value::Enum(draft.predefined_type.into());
Ok(tx.create(Entity::new(CSEG, attrs)))
}