1use axiolid_model::NodeId;
12use ifc_model::EntityId;
13
14use crate::error::GeometryResult;
15use crate::lower::bbox::lower_bounding_box_node;
16use crate::lower::boolean::lower_boolean_result_node;
17use crate::lower::brep::{lower_face_surface_node, lower_faceted_brep_node};
18use crate::lower::collection::lower_collection_node;
19use crate::lower::csg::{
20 lower_csg_primitive_node, lower_csg_solid_node, lower_surface_curve_swept_area_solid_node,
21 lower_swept_disk_node,
22};
23use crate::lower::curve::lower_curve_node;
24use crate::lower::halfspace::lower_half_space_node;
25use crate::lower::mapped::lower_mapped_item_node;
26use crate::lower::point::{lower_point_on_curve_node, lower_point_on_surface_node};
27use crate::lower::session::LoweringSession;
28use crate::lower::surface::lower_surface_node;
29use crate::lower::swept::{
30 lower_extruded_area_solid_node, lower_fixed_reference_sweep_node,
31 lower_revolved_area_solid_node, lower_sectioned_spine_node, lower_tapered_extrusion_node,
32 lower_tapered_revolution_node,
33};
34use crate::lower::tessellated::{lower_polygonal_face_set_node, lower_triangulated_face_set_node};
35use crate::select::is_a;
36use crate::transform::Transform;
37
38pub const IMPLEMENTED: &[&str] = &[
43 "IFCEXTRUDEDAREASOLID",
44 "IFCREVOLVEDAREASOLID",
45 "IFCBOOLEANRESULT",
46 "IFCBOOLEANCLIPPINGRESULT",
47 "IFCMAPPEDITEM",
48 "IFCFACETEDBREP",
49 "IFCFACETEDBREPWITHVOIDS",
50 "IFCADVANCEDBREP",
51 "IFCADVANCEDBREPWITHVOIDS",
52 "IFCHALFSPACESOLID",
53 "IFCBOXEDHALFSPACE",
54 "IFCPOLYGONALBOUNDEDHALFSPACE",
55 "IFCTRIANGULATEDFACESET",
56 "IFCPOLYGONALFACESET",
57 "IFCCSGSOLID",
58 "IFCSWEPTDISKSOLID",
59 "IFCSWEPTDISKSOLIDPOLYGONAL",
60 "IFCSURFACECURVESWEPTAREASOLID",
61 "IFCBLOCK",
62 "IFCSPHERE",
63 "IFCRIGHTCIRCULARCYLINDER",
64 "IFCRIGHTCIRCULARCONE",
65 "IFCRECTANGULARPYRAMID",
66 "IFCBOUNDINGBOX",
67 "IFCEXTRUDEDAREASOLIDTAPERED",
68 "IFCREVOLVEDAREASOLIDTAPERED",
69 "IFCFIXEDREFERENCESWEPTAREASOLID",
70 "IFCSECTIONEDSPINE",
71 "IFCSHELLBASEDSURFACEMODEL",
72 "IFCFACEBASEDSURFACEMODEL",
73 "IFCFACESURFACE",
76 "IFCADVANCEDFACE",
77 "IFCGEOMETRICSET",
78 "IFCGEOMETRICCURVESET",
79 "IFCLINE",
82 "IFCCIRCLE",
83 "IFCELLIPSE",
84 "IFCPOLYLINE",
85 "IFCINDEXEDPOLYCURVE",
86 "IFCCOMPOSITECURVE",
87 "IFCCOMPOSITECURVEONSURFACE",
88 "IFCBOUNDARYCURVE",
89 "IFCOUTERBOUNDARYCURVE",
90 "IFCTRIMMEDCURVE",
91 "IFCOFFSETCURVE2D",
92 "IFCOFFSETCURVE3D",
93 "IFCPCURVE",
94 "IFCSURFACECURVE",
95 "IFCINTERSECTIONCURVE",
96 "IFCSEAMCURVE",
97 "IFCBSPLINECURVEWITHKNOTS",
98 "IFCRATIONALBSPLINECURVEWITHKNOTS",
99 "IFCPLANE",
100 "IFCCYLINDRICALSURFACE",
101 "IFCSPHERICALSURFACE",
102 "IFCTOROIDALSURFACE",
103 "IFCSURFACEOFLINEAREXTRUSION",
104 "IFCSURFACEOFREVOLUTION",
105 "IFCRECTANGULARTRIMMEDSURFACE",
106 "IFCCURVEBOUNDEDPLANE",
107 "IFCCURVEBOUNDEDSURFACE",
108 "IFCBSPLINESURFACEWITHKNOTS",
109 "IFCRATIONALBSPLINESURFACEWITHKNOTS",
110 "IFCPOINTONCURVE",
111 "IFCPOINTONSURFACE",
112];
113
114pub const PLANNED: &[(&str, &str)] = &[];
123
124#[derive(Debug, Clone, Copy, PartialEq, Eq)]
131pub struct Variant {
132 pub family: &'static str,
134 pub variant: &'static str,
136 pub support: Support,
138 pub rationale: &'static str,
140}
141
142#[derive(Debug, Clone, Copy, PartialEq, Eq)]
144#[non_exhaustive]
145pub enum Support {
146 Admitted,
148 Refused,
150}
151
152pub const PARTIAL: &[Variant] = &[
159 Variant {
160 family: "IFCPCURVE",
161 variant: "reference curve is an IfcPolyline",
162 support: Support::Admitted,
163 rationale: "an ordered 2D point sequence needs no evaluation",
164 },
165 Variant {
166 family: "IFCPCURVE",
167 variant: "reference curve is an IfcIndexedPolyCurve with no explicit \
168 Segments, or only IfcLineIndex segments",
169 support: Support::Admitted,
170 rationale: "reads identically to a plain ordered point sequence",
171 },
172 Variant {
173 family: "IFCPCURVE",
174 variant: "reference curve is an IfcLine, IfcCircle or IfcEllipse \
175 positioned by an IfcAxis2Placement2D",
176 support: Support::Admitted,
177 rationale: "defining values are read verbatim in the surface's own \
178 (u, v) domain with no unit conversion",
179 },
180 Variant {
181 family: "IFCPCURVE",
182 variant: "reference conic positioned by an IfcAxis2Placement3D",
183 support: Support::Refused,
184 rationale: "a 3D placement's axis has no meaning in a 2D parameter \
185 domain; admitting it would require inventing a projection",
186 },
187 Variant {
188 family: "IFCPCURVE",
189 variant: "reference curve is an IfcIndexedPolyCurve with an explicit \
190 IfcArcIndex segment",
191 support: Support::Admitted,
192 rationale: "a three-point arc composes exactly from a parameter-space \
193 circumcentre into Circle2 plus a Cartesian trim, mirroring \
194 the 3D path with no approximation",
195 },
196 Variant {
197 family: "IFCPCURVE",
198 variant: "reference curve is an explicit-knot IfcBSplineCurveWithKnots \
199 or IfcRationalBSplineCurveWithKnots",
200 support: Support::Admitted,
201 rationale: "every field is dimensionless or a curve parameter; knots \
202 already pass through the 3D path unscaled, and control \
203 points are read as raw (u, v) pairs",
204 },
205 Variant {
206 family: "IFCPCURVE",
207 variant: "reference curve is a trimmed or composite curve",
208 support: Support::Admitted,
209 rationale: "trim parameters and segments stay in the surface (u, v) \
210 domain, unscaled, so no dimensional contract is needed",
211 },
212 Variant {
213 family: "IFCPCURVE",
214 variant: "reference curve is a convention-only IfcBSplineCurve",
215 support: Support::Refused,
216 rationale: "a base spline carries no authored knot vector to preserve",
217 },
218 Variant {
219 family: "IFCSURFACECURVE",
220 variant: "MasterRepresentation is Curve3D, PCurveS1, or PCurveS2 with \
221 the named side present",
222 support: Support::Admitted,
223 rationale: "each side pairs a surface with its own p-curve, so the \
224 neutral MasterRepresentation names S1 and S2 exactly",
225 },
226 Variant {
227 family: "IFCSURFACECURVE",
228 variant: "MasterRepresentation is PCurveS2 with only one associated \
229 p-curve",
230 support: Support::Refused,
231 rationale: "the master names a parametric side the curve does not \
232 have; the schema calls this inconsistent, so it is \
233 refused rather than resolved to the remaining p-curve",
234 },
235];
236
237pub fn lower_representation_item(
242 session: &mut LoweringSession<'_>,
243 id: EntityId,
244 frame: Transform,
245) -> GeometryResult<NodeId> {
246 let type_name = session.type_name(id)?;
247 if is_a(&type_name, "IFCCURVE") {
252 return lower_curve_node(session, id, frame);
253 }
254 if is_a(&type_name, "IFCSURFACE") {
255 return lower_surface_node(session, id, frame);
256 }
257 match type_name.as_str() {
258 "IFCEXTRUDEDAREASOLID" => lower_extruded_area_solid_node(session, id, frame),
259 "IFCREVOLVEDAREASOLID" => lower_revolved_area_solid_node(session, id, frame),
260 "IFCBOOLEANRESULT" | "IFCBOOLEANCLIPPINGRESULT" => {
261 lower_boolean_result_node(session, id, frame)
262 }
263 "IFCHALFSPACESOLID" | "IFCBOXEDHALFSPACE" | "IFCPOLYGONALBOUNDEDHALFSPACE" => {
264 lower_half_space_node(session, id, frame)
265 }
266 "IFCMAPPEDITEM" => lower_mapped_item_node(session, id, frame),
267 "IFCPOINTONCURVE" => lower_point_on_curve_node(session, id, frame),
268 "IFCPOINTONSURFACE" => lower_point_on_surface_node(session, id, frame),
269 "IFCFACETEDBREP"
270 | "IFCFACETEDBREPWITHVOIDS"
271 | "IFCADVANCEDBREP"
272 | "IFCADVANCEDBREPWITHVOIDS" => lower_faceted_brep_node(session, id, frame),
273 "IFCFACESURFACE" | "IFCADVANCEDFACE" => lower_face_surface_node(session, id, frame),
274 "IFCTRIANGULATEDFACESET" => lower_triangulated_face_set_node(session, id, frame),
275 "IFCPOLYGONALFACESET" => lower_polygonal_face_set_node(session, id, frame),
276 "IFCCSGSOLID" => lower_csg_solid_node(session, id, frame),
277 "IFCSWEPTDISKSOLID" | "IFCSWEPTDISKSOLIDPOLYGONAL" => {
278 lower_swept_disk_node(session, id, frame)
279 }
280 "IFCSURFACECURVESWEPTAREASOLID" => {
281 lower_surface_curve_swept_area_solid_node(session, id, frame)
282 }
283 "IFCBOUNDINGBOX" => lower_bounding_box_node(session, id, frame),
284 "IFCEXTRUDEDAREASOLIDTAPERED" => lower_tapered_extrusion_node(session, id, frame),
285 "IFCREVOLVEDAREASOLIDTAPERED" => lower_tapered_revolution_node(session, id, frame),
286 "IFCFIXEDREFERENCESWEPTAREASOLID" => lower_fixed_reference_sweep_node(session, id, frame),
287 "IFCSECTIONEDSPINE" => lower_sectioned_spine_node(session, id, frame),
288 "IFCSHELLBASEDSURFACEMODEL"
289 | "IFCFACEBASEDSURFACEMODEL"
290 | "IFCGEOMETRICSET"
291 | "IFCGEOMETRICCURVESET" => lower_collection_node(session, id, frame),
292 "IFCBLOCK"
293 | "IFCSPHERE"
294 | "IFCRIGHTCIRCULARCYLINDER"
295 | "IFCRIGHTCIRCULARCONE"
296 | "IFCRECTANGULARPYRAMID" => lower_csg_primitive_node(session, id, frame),
297 other => Err(session.unsupported(id, other, detail_for(other))),
298 }
299}
300
301fn detail_for(type_name: &str) -> &'static str {
303 PLANNED
304 .iter()
305 .find(|(name, _)| *name == type_name)
306 .map(|(_, detail)| *detail)
307 .unwrap_or("representation item family is not lowered yet")
308}
309
310#[cfg(test)]
311mod tests {
312 use super::*;
313
314 #[test]
315 fn implemented_and_planned_families_do_not_overlap() {
316 for name in IMPLEMENTED {
317 assert!(
318 !PLANNED.iter().any(|(planned, _)| planned == name),
319 "{name} is listed as both implemented and planned"
320 );
321 }
322 }
323
324 #[test]
325 fn every_planned_family_states_a_concrete_reason() {
326 for (name, detail) in PLANNED {
327 assert!(!detail.is_empty(), "{name} has no stated reason");
328 assert_ne!(
329 *detail, "unsupported",
330 "{name} must say what specifically is missing"
331 );
332 }
333 }
334}