use axiolid_curve::{Curve2, Curve3, Elevated3};
use axiolid_model::{GeometryGraphBuilder, GeometryNode};
use ifc_model::{EntityId, Model};
use super::assemble::{finish, LoweredAlignmentCurve};
use super::elevation::{indexed_from_plan_start, profile_law_within};
use super::plan::lower_horizontal_plan;
use super::seam::HorizontalSeam;
use super::tolerance::SeamTolerance;
use crate::cant::CantLayout;
use crate::error::{AlignmentError, AlignmentResult};
use crate::horizontal::AlignmentUnits;
use crate::vertical::read_vertical_segment;
use crate::view::AlignmentView;
pub fn lower_gradient_curve(
model: &Model,
entity: EntityId,
units: AlignmentUnits,
) -> AlignmentResult<LoweredAlignmentCurve> {
let view = AlignmentView::for_model(model)?;
let alignment = model
.get(entity)
.ok_or(AlignmentError::MissingEntity { entity })?;
if !view.schema.is_a(&alignment.type_name, "IfcAlignment") {
return Err(AlignmentError::WrongType {
entity,
expected: "IfcAlignment",
actual: alignment.type_name.to_string(),
});
}
let horizontal = sole_layout(&view, entity, "IfcAlignmentHorizontal")?;
let vertical = sole_layout(&view, entity, "IfcAlignmentVertical")?;
let cant = optional_cant(model, &view, entity, units)?;
let composed = compose(model, &view, horizontal, vertical, units, cant.as_ref())?;
let mut builder = GeometryGraphBuilder::new();
let root = builder
.push(GeometryNode::Curve3(Curve3::Elevated(composed.centreline)))
.map_err(|error| AlignmentError::Graph {
detail: error.to_string(),
})?;
let mut sources = vec![horizontal, vertical];
sources.extend(composed.vertical_ids);
let mut lowered = finish(builder, root, sources)?;
lowered.seams = composed.seams;
Ok(lowered)
}
pub(super) fn sole_layout(
view: &AlignmentView,
alignment: EntityId,
expected: &'static str,
) -> AlignmentResult<EntityId> {
let children = view.nested_children(alignment, expected)?;
match children.as_slice() {
[only] => Ok(*only),
[] => Err(AlignmentError::SemanticViolation {
entity: Some(alignment),
rule: "a gradient curve needs both a horizontal and a vertical layout",
}),
_ => Err(AlignmentError::SemanticViolation {
entity: Some(alignment),
rule: "an alignment with several layouts of one kind is ambiguous to compose",
}),
}
}
pub(super) fn compose(
model: &Model,
view: &AlignmentView<'_>,
horizontal: EntityId,
vertical: EntityId,
units: AlignmentUnits,
cant: Option<&CantLayout>,
) -> AlignmentResult<Composed> {
let plan = lower_horizontal_plan(model, horizontal, units, cant)?;
let ids = view.segment_chain(vertical, "IfcAlignmentVerticalSegment")?;
let mut segments = Vec::with_capacity(ids.len());
for id in &ids {
segments.push(read_vertical_segment(model, *id, units)?);
}
let tolerance = SeamTolerance::for_model(model, units)?;
let profile = profile_law_within(&segments, tolerance)?;
let start = segments.first().map_or(0.0, |s| s.start_dist_along);
let end = segments
.last()
.map_or(0.0, |s| s.start_dist_along + s.horizontal_length);
let plan_length = match &plan.curve {
Curve2::Intrinsic(intrinsic) => Some(intrinsic.length),
Curve2::Chain(chain) => chain.length(),
_ => None,
}
.ok_or_else(|| AlignmentError::Graph {
detail: "the horizontal plan did not lower to an arc-length curve".to_owned(),
})?;
let elevation = indexed_from_plan_start(profile, start, end, plan_length, vertical, tolerance)?;
Ok(Composed {
centreline: Elevated3::new(plan.curve, elevation),
plan_length,
vertical_ids: ids,
seams: plan.seams,
})
}
pub(super) struct Composed {
pub(super) centreline: Elevated3,
pub(super) plan_length: f64,
pub(super) vertical_ids: Vec<EntityId>,
pub(super) seams: Vec<HorizontalSeam>,
}
pub fn gradient_curve3(
model: &Model,
entity: EntityId,
units: AlignmentUnits,
) -> AlignmentResult<Curve3> {
let view = AlignmentView::for_model(model)?;
let alignment = model
.get(entity)
.ok_or(AlignmentError::MissingEntity { entity })?;
if !view.schema.is_a(&alignment.type_name, "IfcAlignment") {
return Err(AlignmentError::WrongType {
entity,
expected: "IfcAlignment",
actual: alignment.type_name.to_string(),
});
}
let horizontal = sole_layout(&view, entity, "IfcAlignmentHorizontal")?;
let vertical = sole_layout(&view, entity, "IfcAlignmentVertical")?;
let cant = optional_cant(model, &view, entity, units)?;
let composed = compose(model, &view, horizontal, vertical, units, cant.as_ref())?;
Ok(Curve3::Elevated(composed.centreline))
}
fn optional_cant(
model: &Model,
view: &AlignmentView,
alignment: EntityId,
units: AlignmentUnits,
) -> AlignmentResult<Option<CantLayout>> {
let children = view.nested_children(alignment, "IfcAlignmentCant")?;
match children.as_slice() {
[] => Ok(None),
[only] => CantLayout::resolve(model, *only, units).map(Some),
_ => Err(AlignmentError::SemanticViolation {
entity: Some(alignment),
rule: "an alignment with several cant layouts is ambiguous to compose",
}),
}
}