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_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 "IFCGEOMETRICSET",
74 "IFCGEOMETRICCURVESET",
75 "IFCLINE",
78 "IFCCIRCLE",
79 "IFCELLIPSE",
80 "IFCPOLYLINE",
81 "IFCINDEXEDPOLYCURVE",
82 "IFCCOMPOSITECURVE",
83 "IFCCOMPOSITECURVEONSURFACE",
84 "IFCBOUNDARYCURVE",
85 "IFCOUTERBOUNDARYCURVE",
86 "IFCTRIMMEDCURVE",
87 "IFCOFFSETCURVE2D",
88 "IFCOFFSETCURVE3D",
89 "IFCPCURVE",
90 "IFCSURFACECURVE",
91 "IFCINTERSECTIONCURVE",
92 "IFCSEAMCURVE",
93 "IFCBSPLINECURVEWITHKNOTS",
94 "IFCRATIONALBSPLINECURVEWITHKNOTS",
95 "IFCPLANE",
96 "IFCCYLINDRICALSURFACE",
97 "IFCSPHERICALSURFACE",
98 "IFCTOROIDALSURFACE",
99 "IFCSURFACEOFLINEAREXTRUSION",
100 "IFCSURFACEOFREVOLUTION",
101 "IFCRECTANGULARTRIMMEDSURFACE",
102 "IFCCURVEBOUNDEDPLANE",
103 "IFCCURVEBOUNDEDSURFACE",
104 "IFCBSPLINESURFACEWITHKNOTS",
105 "IFCRATIONALBSPLINESURFACEWITHKNOTS",
106 "IFCPOINTONCURVE",
107 "IFCPOINTONSURFACE",
108];
109
110pub const PLANNED: &[(&str, &str)] = &[];
119
120#[derive(Debug, Clone, Copy, PartialEq, Eq)]
127pub struct Variant {
128 pub family: &'static str,
130 pub variant: &'static str,
132 pub support: Support,
134 pub rationale: &'static str,
136}
137
138#[derive(Debug, Clone, Copy, PartialEq, Eq)]
140pub enum Support {
141 Admitted,
143 Refused,
145}
146
147pub const PARTIAL: &[Variant] = &[
154 Variant {
155 family: "IFCPCURVE",
156 variant: "reference curve is an IfcPolyline",
157 support: Support::Admitted,
158 rationale: "an ordered 2D point sequence needs no evaluation",
159 },
160 Variant {
161 family: "IFCPCURVE",
162 variant: "reference curve is an IfcIndexedPolyCurve with no explicit \
163 Segments, or only IfcLineIndex segments",
164 support: Support::Admitted,
165 rationale: "reads identically to a plain ordered point sequence",
166 },
167 Variant {
168 family: "IFCPCURVE",
169 variant: "reference curve is an IfcLine, IfcCircle or IfcEllipse \
170 positioned by an IfcAxis2Placement2D",
171 support: Support::Admitted,
172 rationale: "defining values are read verbatim in the surface's own \
173 (u, v) domain with no unit conversion",
174 },
175 Variant {
176 family: "IFCPCURVE",
177 variant: "reference conic positioned by an IfcAxis2Placement3D",
178 support: Support::Refused,
179 rationale: "a 3D placement's axis has no meaning in a 2D parameter \
180 domain; admitting it would require inventing a projection",
181 },
182 Variant {
183 family: "IFCPCURVE",
184 variant: "reference curve is an IfcIndexedPolyCurve with an explicit \
185 IfcArcIndex segment",
186 support: Support::Admitted,
187 rationale: "a three-point arc composes exactly from a parameter-space \
188 circumcentre into Circle2 plus a Cartesian trim, mirroring \
189 the 3D path with no approximation",
190 },
191 Variant {
192 family: "IFCPCURVE",
193 variant: "reference curve is an explicit-knot IfcBSplineCurveWithKnots \
194 or IfcRationalBSplineCurveWithKnots",
195 support: Support::Admitted,
196 rationale: "every field is dimensionless or a curve parameter; knots \
197 already pass through the 3D path unscaled, and control \
198 points are read as raw (u, v) pairs",
199 },
200 Variant {
201 family: "IFCPCURVE",
202 variant: "reference curve is a trimmed or composite curve",
203 support: Support::Admitted,
204 rationale: "trim parameters and segments stay in the surface (u, v) \
205 domain, unscaled, so no dimensional contract is needed",
206 },
207 Variant {
208 family: "IFCPCURVE",
209 variant: "reference curve is a convention-only IfcBSplineCurve",
210 support: Support::Refused,
211 rationale: "a base spline carries no authored knot vector to preserve",
212 },
213 Variant {
214 family: "IFCSURFACECURVE",
215 variant: "MasterRepresentation is Curve3D, PCurveS1, or PCurveS2 with \
216 the named side present",
217 support: Support::Admitted,
218 rationale: "each side pairs a surface with its own p-curve, so the \
219 neutral MasterRepresentation names S1 and S2 exactly",
220 },
221 Variant {
222 family: "IFCSURFACECURVE",
223 variant: "MasterRepresentation is PCurveS2 with only one associated \
224 p-curve",
225 support: Support::Refused,
226 rationale: "the master names a parametric side the curve does not \
227 have; the schema calls this inconsistent, so it is \
228 refused rather than resolved to the remaining p-curve",
229 },
230];
231
232pub fn lower_representation_item(
237 session: &mut LoweringSession<'_>,
238 id: EntityId,
239 frame: Transform,
240) -> GeometryResult<NodeId> {
241 let type_name = session.type_name(id)?;
242 if is_a(&type_name, "IFCCURVE") {
247 return lower_curve_node(session, id, frame);
248 }
249 if is_a(&type_name, "IFCSURFACE") {
250 return lower_surface_node(session, id, frame);
251 }
252 match type_name.as_str() {
253 "IFCEXTRUDEDAREASOLID" => lower_extruded_area_solid_node(session, id, frame),
254 "IFCREVOLVEDAREASOLID" => lower_revolved_area_solid_node(session, id, frame),
255 "IFCBOOLEANRESULT" | "IFCBOOLEANCLIPPINGRESULT" => {
256 lower_boolean_result_node(session, id, frame)
257 }
258 "IFCHALFSPACESOLID" | "IFCBOXEDHALFSPACE" | "IFCPOLYGONALBOUNDEDHALFSPACE" => {
259 lower_half_space_node(session, id, frame)
260 }
261 "IFCMAPPEDITEM" => lower_mapped_item_node(session, id, frame),
262 "IFCPOINTONCURVE" => lower_point_on_curve_node(session, id, frame),
263 "IFCPOINTONSURFACE" => lower_point_on_surface_node(session, id, frame),
264 "IFCFACETEDBREP"
265 | "IFCFACETEDBREPWITHVOIDS"
266 | "IFCADVANCEDBREP"
267 | "IFCADVANCEDBREPWITHVOIDS" => lower_faceted_brep_node(session, id, frame),
268 "IFCTRIANGULATEDFACESET" => lower_triangulated_face_set_node(session, id, frame),
269 "IFCPOLYGONALFACESET" => lower_polygonal_face_set_node(session, id, frame),
270 "IFCCSGSOLID" => lower_csg_solid_node(session, id, frame),
271 "IFCSWEPTDISKSOLID" | "IFCSWEPTDISKSOLIDPOLYGONAL" => {
272 lower_swept_disk_node(session, id, frame)
273 }
274 "IFCSURFACECURVESWEPTAREASOLID" => {
275 lower_surface_curve_swept_area_solid_node(session, id, frame)
276 }
277 "IFCBOUNDINGBOX" => lower_bounding_box_node(session, id, frame),
278 "IFCEXTRUDEDAREASOLIDTAPERED" => lower_tapered_extrusion_node(session, id, frame),
279 "IFCREVOLVEDAREASOLIDTAPERED" => lower_tapered_revolution_node(session, id, frame),
280 "IFCFIXEDREFERENCESWEPTAREASOLID" => lower_fixed_reference_sweep_node(session, id, frame),
281 "IFCSECTIONEDSPINE" => lower_sectioned_spine_node(session, id, frame),
282 "IFCSHELLBASEDSURFACEMODEL"
283 | "IFCFACEBASEDSURFACEMODEL"
284 | "IFCGEOMETRICSET"
285 | "IFCGEOMETRICCURVESET" => lower_collection_node(session, id, frame),
286 "IFCBLOCK"
287 | "IFCSPHERE"
288 | "IFCRIGHTCIRCULARCYLINDER"
289 | "IFCRIGHTCIRCULARCONE"
290 | "IFCRECTANGULARPYRAMID" => lower_csg_primitive_node(session, id, frame),
291 other => Err(session.unsupported(id, other, detail_for(other))),
292 }
293}
294
295fn detail_for(type_name: &str) -> &'static str {
297 PLANNED
298 .iter()
299 .find(|(name, _)| *name == type_name)
300 .map(|(_, detail)| *detail)
301 .unwrap_or("representation item family is not lowered yet")
302}
303
304#[cfg(test)]
305mod tests {
306 use super::*;
307
308 #[test]
309 fn implemented_and_planned_families_do_not_overlap() {
310 for name in IMPLEMENTED {
311 assert!(
312 !PLANNED.iter().any(|(planned, _)| planned == name),
313 "{name} is listed as both implemented and planned"
314 );
315 }
316 }
317
318 #[test]
319 fn every_planned_family_states_a_concrete_reason() {
320 for (name, detail) in PLANNED {
321 assert!(!detail.is_empty(), "{name} has no stated reason");
322 assert_ne!(
323 *detail, "unsupported",
324 "{name} must say what specifically is missing"
325 );
326 }
327 }
328}