use axiolid_model::NodeId;
use ifc_model::EntityId;
use crate::error::GeometryResult;
use crate::lower::bbox::lower_bounding_box_node;
use crate::lower::boolean::lower_boolean_result_node;
use crate::lower::brep::{lower_face_surface_node, lower_faceted_brep_node};
use crate::lower::collection::lower_collection_node;
use crate::lower::csg::{
lower_csg_primitive_node, lower_csg_solid_node, lower_surface_curve_swept_area_solid_node,
lower_swept_disk_node,
};
use crate::lower::curve::lower_curve_node;
use crate::lower::halfspace::lower_half_space_node;
use crate::lower::mapped::lower_mapped_item_node;
use crate::lower::point::{lower_point_on_curve_node, lower_point_on_surface_node};
use crate::lower::sectioned::{
lower_sectioned_solid_horizontal_node, lower_sectioned_surface_node,
};
use crate::lower::session::LoweringSession;
use crate::lower::surface::lower_surface_node;
use crate::lower::swept::{
lower_directrix_derived_reference_sweep_node, lower_extruded_area_solid_node,
lower_fixed_reference_sweep_node, lower_revolved_area_solid_node, lower_sectioned_spine_node,
lower_tapered_extrusion_node, lower_tapered_revolution_node,
};
use crate::lower::tessellated::{lower_polygonal_face_set_node, lower_triangulated_face_set_node};
use crate::select::is_a;
use crate::transform::Transform;
pub const IMPLEMENTED: &[&str] = &[
"IFCEXTRUDEDAREASOLID",
"IFCREVOLVEDAREASOLID",
"IFCBOOLEANRESULT",
"IFCBOOLEANCLIPPINGRESULT",
"IFCMAPPEDITEM",
"IFCFACETEDBREP",
"IFCFACETEDBREPWITHVOIDS",
"IFCADVANCEDBREP",
"IFCADVANCEDBREPWITHVOIDS",
"IFCHALFSPACESOLID",
"IFCBOXEDHALFSPACE",
"IFCPOLYGONALBOUNDEDHALFSPACE",
"IFCTRIANGULATEDFACESET",
"IFCTRIANGULATEDIRREGULARNETWORK",
"IFCPOLYGONALFACESET",
"IFCCSGSOLID",
"IFCSWEPTDISKSOLID",
"IFCSWEPTDISKSOLIDPOLYGONAL",
"IFCSURFACECURVESWEPTAREASOLID",
"IFCBLOCK",
"IFCSPHERE",
"IFCRIGHTCIRCULARCYLINDER",
"IFCRIGHTCIRCULARCONE",
"IFCRECTANGULARPYRAMID",
"IFCBOUNDINGBOX",
"IFCEXTRUDEDAREASOLIDTAPERED",
"IFCREVOLVEDAREASOLIDTAPERED",
"IFCFIXEDREFERENCESWEPTAREASOLID",
"IFCDIRECTRIXDERIVEDREFERENCESWEPTAREASOLID",
"IFCSECTIONEDSPINE",
"IFCSHELLBASEDSURFACEMODEL",
"IFCFACEBASEDSURFACEMODEL",
"IFCFACESURFACE",
"IFCADVANCEDFACE",
"IFCGEOMETRICSET",
"IFCGEOMETRICCURVESET",
"IFCLINE",
"IFCCIRCLE",
"IFCELLIPSE",
"IFCPOLYLINE",
"IFCINDEXEDPOLYCURVE",
"IFCCOMPOSITECURVE",
"IFCCOMPOSITECURVEONSURFACE",
"IFCBOUNDARYCURVE",
"IFCOUTERBOUNDARYCURVE",
"IFCTRIMMEDCURVE",
"IFCOFFSETCURVE2D",
"IFCOFFSETCURVE3D",
"IFCPCURVE",
"IFCSURFACECURVE",
"IFCINTERSECTIONCURVE",
"IFCSEAMCURVE",
"IFCBSPLINECURVEWITHKNOTS",
"IFCRATIONALBSPLINECURVEWITHKNOTS",
"IFCPLANE",
"IFCCYLINDRICALSURFACE",
"IFCSPHERICALSURFACE",
"IFCTOROIDALSURFACE",
"IFCSURFACEOFLINEAREXTRUSION",
"IFCSURFACEOFREVOLUTION",
"IFCRECTANGULARTRIMMEDSURFACE",
"IFCCURVEBOUNDEDPLANE",
"IFCCURVEBOUNDEDSURFACE",
"IFCBSPLINESURFACEWITHKNOTS",
"IFCRATIONALBSPLINESURFACEWITHKNOTS",
"IFCPOINTONCURVE",
"IFCPOINTONSURFACE",
"IFCCURVESEGMENT",
"IFCGRADIENTCURVE",
];
pub const PLANNED: &[(&str, &str)] = &[
("IFCCLOTHOID", "an IfcSpiral is unbounded (-inf < u < inf) and the neutral intrinsic curve needs a \
finite arc length; it lowers exactly as the ParentCurve of an IfcCurveSegment"),
("IFCSECONDORDERPOLYNOMIALSPIRAL", "an IfcSpiral is unbounded (-inf < u < inf) and the neutral intrinsic curve needs a \
finite arc length; it lowers exactly as the ParentCurve of an IfcCurveSegment"),
("IFCTHIRDORDERPOLYNOMIALSPIRAL", "an IfcSpiral is unbounded (-inf < u < inf) and the neutral intrinsic curve needs a \
finite arc length; it lowers exactly as the ParentCurve of an IfcCurveSegment"),
("IFCSEVENTHORDERPOLYNOMIALSPIRAL", "an IfcSpiral is unbounded (-inf < u < inf) and the neutral intrinsic curve needs a \
finite arc length; it lowers exactly as the ParentCurve of an IfcCurveSegment"),
("IFCCOSINESPIRAL", "an IfcCosineSpiral or IfcSineSpiral law depends on the length L of the IfcCurveSegment \
using it; it lowers exactly only as the ParentCurve of an IfcCurveSegment"),
("IFCSINESPIRAL", "an IfcCosineSpiral or IfcSineSpiral law depends on the length L of the IfcCurveSegment \
using it; it lowers exactly only as the ParentCurve of an IfcCurveSegment"),
("IFCPOLYNOMIALCURVE", "an IfcPolynomialCurve is unbounded (-inf < u < inf) and the neutral vocabulary has no \
unbounded polynomial curve; bounded by an IfcCurveSegment it needs an arc-length trim (#90)"),
("IFCSEGMENTEDREFERENCECURVE", "IfcSegmentedReferenceCurve adds cant (a roll of the section about the centreline); the \
pinned neutral curve vocabulary has no roll law to carry it exactly (#93)"),
(
"IFCSECTIONEDSOLIDHORIZONTAL",
"kernel: sections stand at IfcAxis2PlacementLinear stations (a measure \
along the directrix plus offsets) and are swept horizontally with \
tag-matched linear interpolation; the neutral SectionedSpine takes only \
resolved section frames, and resolving a station is curve evaluation",
),
(
"IFCSECTIONEDSURFACE",
"kernel: no neutral sectioned-surface relation exists; its open sections \
stand at IfcAxis2PlacementLinear stations along the directrix and are \
joined by tag, and the neutral SectionedSpine is a solid over area \
profiles",
),
(
"IFCOFFSETCURVEBYDISTANCES",
"offsets are stated at stations along the basis curve as \
IfcPointByDistanceExpression values; the neutral model has no \
distance-along-curve point or station-offset curve to hold them",
),
(
"IFCPOINTBYDISTANCEEXPRESSION",
"a point at a distance along a basis curve, offset in that curve's \
frame; the neutral model has no distance-along-curve point relation",
),
(
"IFCAXIS2PLACEMENTLINEAR",
"a frame located by an IfcPointByDistanceExpression; the neutral model \
has no distance-along-curve point relation to anchor it",
),
];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Specialisation {
pub subtype: &'static str,
pub supertype: &'static str,
pub added_attributes: &'static [&'static str],
pub rationale: &'static str,
}
pub const SPECIALISATIONS: &[Specialisation] = &[Specialisation {
subtype: "IFCTRIANGULATEDIRREGULARNETWORK",
supertype: "IFCTRIANGULATEDFACESET",
added_attributes: &["Flags"],
rationale: "the face-set slots are unchanged; the lowerer checks Flags \
and refuses voids, holes and undocumented codes, so only \
breakline codes, which leave the triangles unchanged, lower",
}];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Variant {
pub family: &'static str,
pub variant: &'static str,
pub support: Support,
pub rationale: &'static str,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum Support {
Admitted,
Refused,
}
pub const PARTIAL: &[Variant] = &[
Variant {
family: "IFCPCURVE",
variant: "reference curve is an IfcPolyline",
support: Support::Admitted,
rationale: "an ordered 2D point sequence needs no evaluation",
},
Variant {
family: "IFCPCURVE",
variant: "reference curve is an IfcIndexedPolyCurve with no explicit \
Segments, or only IfcLineIndex segments",
support: Support::Admitted,
rationale: "reads identically to a plain ordered point sequence",
},
Variant {
family: "IFCPCURVE",
variant: "reference curve is an IfcLine, IfcCircle or IfcEllipse \
positioned by an IfcAxis2Placement2D",
support: Support::Admitted,
rationale: "defining values are read verbatim in the surface's own \
(u, v) domain with no unit conversion",
},
Variant {
family: "IFCPCURVE",
variant: "reference conic positioned by an IfcAxis2Placement3D",
support: Support::Refused,
rationale: "a 3D placement's axis has no meaning in a 2D parameter \
domain; admitting it would require inventing a projection",
},
Variant {
family: "IFCPCURVE",
variant: "reference curve is an IfcIndexedPolyCurve with an explicit \
IfcArcIndex segment",
support: Support::Admitted,
rationale: "a three-point arc composes exactly from a parameter-space \
circumcentre into Circle2 plus a Cartesian trim, mirroring \
the 3D path with no approximation",
},
Variant {
family: "IFCPCURVE",
variant: "reference curve is an explicit-knot IfcBSplineCurveWithKnots \
or IfcRationalBSplineCurveWithKnots",
support: Support::Admitted,
rationale: "every field is dimensionless or a curve parameter; knots \
already pass through the 3D path unscaled, and control \
points are read as raw (u, v) pairs",
},
Variant {
family: "IFCPCURVE",
variant: "reference curve is a trimmed or composite curve",
support: Support::Admitted,
rationale: "trim parameters and segments stay in the surface (u, v) \
domain, unscaled, so no dimensional contract is needed",
},
Variant {
family: "IFCPCURVE",
variant: "reference curve is a convention-only IfcBSplineCurve",
support: Support::Refused,
rationale: "a base spline carries no authored knot vector to preserve",
},
Variant {
family: "IFCDIRECTRIXDERIVEDREFERENCESWEPTAREASOLID",
variant: "directrix defines only a tangent (no IfcCurveSegment, \
segment-built or surface curve reachable)",
support: Support::Admitted,
rationale: "IFC4.3 gives it exactly the behaviour of \
IfcFixedReferenceSweptAreaSolid in this case, so it lowers \
to the same FixedReferenceSweep",
},
Variant {
family: "IFCDIRECTRIXDERIVEDREFERENCESWEPTAREASOLID",
variant: "directrix defines a tangent plane",
support: Support::Refused,
rationale: "kernel: the directrix defines a tangent plane (it is built from \
IfcCurveSegment placements or lies on a surface), so the derived \
reference adds that plane's rotation to FixedReference; the neutral \
FixedReferenceSweep carries only a constant reference direction",
},
Variant {
family: "IFCTRIANGULATEDIRREGULARNETWORK",
variant: "every Flags value is a breakline code, 0 to 7",
support: Support::Admitted,
rationale: "a breakline marks an edge the triangulation already has, \
so the triangle surface is the supertype's; the flags \
are not carried into the mesh",
},
Variant {
family: "IFCTRIANGULATEDIRREGULARNETWORK",
variant: "a Flags value is -1 (hole) or -2 (void)",
support: Support::Refused,
rationale: "the triangle is excluded from the surface, and a hole may \
fall back on another surface; the neutral mesh has no \
face-exclusion or fall-back channel",
},
Variant {
family: "IFCTRIANGULATEDIRREGULARNETWORK",
variant: "a Flags value is outside -2 to 7",
support: Support::Refused,
rationale: "the documentation defines no meaning for it",
},
Variant {
family: "IFCSURFACECURVE",
variant: "MasterRepresentation is Curve3D, PCurveS1, or PCurveS2 with \
the named side present",
support: Support::Admitted,
rationale: "each side pairs a surface with its own p-curve, so the \
neutral MasterRepresentation names S1 and S2 exactly",
},
Variant {
family: "IFCCURVESEGMENT",
variant: "ParentCurve is an IfcLine, IfcCircle or 2D IfcPolyline, \
measured by IfcLengthMeasure",
support: Support::Admitted,
rationale: "a line, arc or polyline cut by arc length and placed rigidly \
is elementary: a polyline or an angle-trimmed circle",
},
Variant {
family: "IFCCURVESEGMENT",
variant: "ParentCurve is an IfcSpiral subtype, measured by \
IfcLengthMeasure",
support: Support::Admitted,
rationale: "the spiral's curvature law, rebased to the segment in closed \
form, on a planar intrinsic curve; nothing is integrated",
},
Variant {
family: "IFCCURVESEGMENT",
variant: "SegmentLength is zero (the closing segment of a layout)",
support: Support::Admitted,
rationale: "its placement exactly: a planar intrinsic curve of length zero",
},
Variant {
family: "IFCCURVESEGMENT",
variant: "ParentCurve is an IfcPolynomialCurve",
support: Support::Refused,
rationale: "an IfcPolynomialCurve trimmed by arc length: the end parameter inverts a non-elementary \
arc-length integral and the neutral vocabulary has no arc-length trim (#90)",
},
Variant {
family: "IFCCURVESEGMENT",
variant: "SegmentStart or SegmentLength is an IfcParameterValue",
support: Support::Refused,
rationale: "SegmentStart/SegmentLength given as IfcParameterValue: IFC4.3 ADD2 defines no parametric \
space for IfcCurveSegment parents yet (informal proposition 1 requires IfcLengthMeasure)",
},
Variant {
family: "IFCCURVESEGMENT",
variant: "Placement is an IfcAxis2PlacementLinear",
support: Support::Refused,
rationale: "an IfcAxis2PlacementLinear placement belongs to an IfcSegmentedReferenceCurve (cant); \
the neutral vocabulary has no roll law to carry it (#93)",
},
Variant {
family: "IFCGRADIENTCURVE",
variant: "horizontal IfcCurveSegments over lines, arcs and spirals; \
vertical IfcCurveSegments over IfcLine or a degree-2 \
IfcPolynomialCurve that keeps its start tangent",
support: Support::Admitted,
rationale: "one intrinsic plan with a piecewise curvature law plus a \
piecewise polynomial elevation law: Curve3::Elevated, exact",
},
Variant {
family: "IFCGRADIENTCURVE",
variant: "a vertical IfcCircle or IfcClothoid segment",
support: Support::Refused,
rationale: "neither is polynomial in plan distance and the pinned \
ElevationLaw has only polynomial pieces (#258)",
},
Variant {
family: "IFCGRADIENTCURVE",
variant: "a vertical parabola with no following segment, closing \
segment or EndPoint",
support: Support::Refused,
rationale: "its end abscissa inverts a non-elementary arc-length \
integral (#90)",
},
Variant {
family: "IFCGRADIENTCURVE",
variant: "a heading kink, a closed-form position gap, or a profile \
that does not span the base curve",
support: Support::Refused,
rationale: "one plan curve and one elevation law cannot carry a kink or \
a gap, and an elevation law must cover the whole plan",
},
Variant {
family: "IFCGRADIENTCURVE",
variant: "placed by a frame that tilts, scales or mirrors the vertical",
support: Support::Refused,
rationale: "a plan plus a height is carried only by frames that keep \
the vertical axis",
},
Variant {
family: "IFCSURFACECURVE",
variant: "MasterRepresentation is PCurveS2 with only one associated \
p-curve",
support: Support::Refused,
rationale: "the master names a parametric side the curve does not \
have; the schema calls this inconsistent, so it is \
refused rather than resolved to the remaining p-curve",
},
];
pub fn lower_representation_item(
session: &mut LoweringSession<'_>,
id: EntityId,
frame: Transform,
) -> GeometryResult<NodeId> {
let type_name = session.type_name(id)?;
if type_name == "IFCSECTIONEDSURFACE" {
return lower_sectioned_surface_node(session, id);
}
let routed = SPECIALISATIONS
.iter()
.find(|row| row.subtype == type_name)
.map_or(type_name.as_str(), |row| row.supertype);
if is_a(&type_name, "IFCCURVE") {
return lower_curve_node(session, id, frame);
}
if is_a(&type_name, "IFCSURFACE") {
return lower_surface_node(session, id, frame);
}
match routed {
"IFCEXTRUDEDAREASOLID" => lower_extruded_area_solid_node(session, id, frame),
"IFCREVOLVEDAREASOLID" => lower_revolved_area_solid_node(session, id, frame),
"IFCBOOLEANRESULT" | "IFCBOOLEANCLIPPINGRESULT" => {
lower_boolean_result_node(session, id, frame)
}
"IFCHALFSPACESOLID" | "IFCBOXEDHALFSPACE" | "IFCPOLYGONALBOUNDEDHALFSPACE" => {
lower_half_space_node(session, id, frame)
}
"IFCMAPPEDITEM" => lower_mapped_item_node(session, id, frame),
"IFCCURVESEGMENT" => lower_curve_node(session, id, frame),
"IFCPOINTONCURVE" => lower_point_on_curve_node(session, id, frame),
"IFCPOINTONSURFACE" => lower_point_on_surface_node(session, id, frame),
"IFCFACETEDBREP"
| "IFCFACETEDBREPWITHVOIDS"
| "IFCADVANCEDBREP"
| "IFCADVANCEDBREPWITHVOIDS" => lower_faceted_brep_node(session, id, frame),
"IFCFACESURFACE" | "IFCADVANCEDFACE" => lower_face_surface_node(session, id, frame),
"IFCTRIANGULATEDFACESET" => lower_triangulated_face_set_node(session, id, frame),
"IFCPOLYGONALFACESET" => lower_polygonal_face_set_node(session, id, frame),
"IFCCSGSOLID" => lower_csg_solid_node(session, id, frame),
"IFCSWEPTDISKSOLID" | "IFCSWEPTDISKSOLIDPOLYGONAL" => {
lower_swept_disk_node(session, id, frame)
}
"IFCSURFACECURVESWEPTAREASOLID" => {
lower_surface_curve_swept_area_solid_node(session, id, frame)
}
"IFCBOUNDINGBOX" => lower_bounding_box_node(session, id, frame),
"IFCEXTRUDEDAREASOLIDTAPERED" => lower_tapered_extrusion_node(session, id, frame),
"IFCREVOLVEDAREASOLIDTAPERED" => lower_tapered_revolution_node(session, id, frame),
"IFCFIXEDREFERENCESWEPTAREASOLID" => lower_fixed_reference_sweep_node(session, id, frame),
"IFCDIRECTRIXDERIVEDREFERENCESWEPTAREASOLID" => {
lower_directrix_derived_reference_sweep_node(session, id, frame)
}
"IFCSECTIONEDSOLIDHORIZONTAL" => lower_sectioned_solid_horizontal_node(session, id),
"IFCSECTIONEDSPINE" => lower_sectioned_spine_node(session, id, frame),
"IFCSHELLBASEDSURFACEMODEL"
| "IFCFACEBASEDSURFACEMODEL"
| "IFCGEOMETRICSET"
| "IFCGEOMETRICCURVESET" => lower_collection_node(session, id, frame),
"IFCBLOCK"
| "IFCSPHERE"
| "IFCRIGHTCIRCULARCYLINDER"
| "IFCRIGHTCIRCULARCONE"
| "IFCRECTANGULARPYRAMID" => lower_csg_primitive_node(session, id, frame),
_ => Err(session.unsupported(id, &type_name, detail_for(&type_name))),
}
}
fn detail_for(type_name: &str) -> &'static str {
planned_detail(type_name).unwrap_or("representation item family is not lowered yet")
}
pub(crate) fn planned_detail(type_name: &str) -> Option<&'static str> {
PLANNED
.iter()
.find(|(name, _)| *name == type_name)
.map(|(_, detail)| *detail)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn implemented_and_planned_families_do_not_overlap() {
for name in IMPLEMENTED {
assert!(
!PLANNED.iter().any(|(planned, _)| planned == name),
"{name} is listed as both implemented and planned"
);
}
}
#[test]
fn every_planned_family_states_a_concrete_reason() {
for (name, detail) in PLANNED {
assert!(!detail.is_empty(), "{name} has no stated reason");
assert_ne!(
*detail, "unsupported",
"{name} must say what specifically is missing"
);
}
}
}