1use axiolid_model::NodeId;
28use ifc_model::EntityId;
29
30use crate::error::GeometryResult;
31use crate::lower::bbox::lower_bounding_box_node;
32use crate::lower::boolean::lower_boolean_result_node;
33use crate::lower::brep::{lower_face_surface_node, lower_faceted_brep_node};
34use crate::lower::collection::lower_collection_node;
35use crate::lower::csg::{
36 lower_csg_primitive_node, lower_csg_solid_node, lower_surface_curve_swept_area_solid_node,
37 lower_swept_disk_node,
38};
39use crate::lower::curve::lower_curve_node;
40use crate::lower::halfspace::lower_half_space_node;
41use crate::lower::mapped::lower_mapped_item_node;
42use crate::lower::point::{lower_point_on_curve_node, lower_point_on_surface_node};
43use crate::lower::sectioned::{
44 lower_sectioned_solid_horizontal_node, lower_sectioned_surface_node,
45};
46use crate::lower::session::LoweringSession;
47use crate::lower::station::{lower_axis2_placement_linear_node, lower_point_by_distance_node};
48use crate::lower::surface::lower_surface_node;
49use crate::lower::swept::{
50 lower_directrix_derived_reference_sweep_node, lower_extruded_area_solid_node,
51 lower_fixed_reference_sweep_node, lower_revolved_area_solid_node, lower_sectioned_spine_node,
52 lower_tapered_extrusion_node, lower_tapered_revolution_node,
53};
54use crate::lower::tessellated::{lower_polygonal_face_set_node, lower_triangulated_face_set_node};
55use crate::select::is_a;
56use crate::transform::Transform;
57
58pub const IMPLEMENTED: &[&str] = &[
63 "IFCEXTRUDEDAREASOLID",
64 "IFCREVOLVEDAREASOLID",
65 "IFCBOOLEANRESULT",
66 "IFCBOOLEANCLIPPINGRESULT",
67 "IFCMAPPEDITEM",
68 "IFCFACETEDBREP",
69 "IFCFACETEDBREPWITHVOIDS",
70 "IFCADVANCEDBREP",
71 "IFCADVANCEDBREPWITHVOIDS",
72 "IFCHALFSPACESOLID",
73 "IFCBOXEDHALFSPACE",
74 "IFCPOLYGONALBOUNDEDHALFSPACE",
75 "IFCTRIANGULATEDFACESET",
76 "IFCTRIANGULATEDIRREGULARNETWORK",
78 "IFCPOLYGONALFACESET",
79 "IFCCSGSOLID",
80 "IFCSWEPTDISKSOLID",
81 "IFCSWEPTDISKSOLIDPOLYGONAL",
82 "IFCSURFACECURVESWEPTAREASOLID",
83 "IFCBLOCK",
84 "IFCSPHERE",
85 "IFCRIGHTCIRCULARCYLINDER",
86 "IFCRIGHTCIRCULARCONE",
87 "IFCRECTANGULARPYRAMID",
88 "IFCBOUNDINGBOX",
89 "IFCEXTRUDEDAREASOLIDTAPERED",
90 "IFCREVOLVEDAREASOLIDTAPERED",
91 "IFCFIXEDREFERENCESWEPTAREASOLID",
92 "IFCDIRECTRIXDERIVEDREFERENCESWEPTAREASOLID",
94 "IFCSECTIONEDSPINE",
95 "IFCSHELLBASEDSURFACEMODEL",
96 "IFCFACEBASEDSURFACEMODEL",
97 "IFCFACESURFACE",
100 "IFCADVANCEDFACE",
101 "IFCGEOMETRICSET",
102 "IFCGEOMETRICCURVESET",
103 "IFCLINE",
106 "IFCCIRCLE",
107 "IFCELLIPSE",
108 "IFCPOLYLINE",
109 "IFCINDEXEDPOLYCURVE",
110 "IFCCOMPOSITECURVE",
111 "IFCCOMPOSITECURVEONSURFACE",
112 "IFCBOUNDARYCURVE",
113 "IFCOUTERBOUNDARYCURVE",
114 "IFCTRIMMEDCURVE",
115 "IFCOFFSETCURVE2D",
116 "IFCOFFSETCURVE3D",
117 "IFCPCURVE",
118 "IFCSURFACECURVE",
119 "IFCINTERSECTIONCURVE",
120 "IFCSEAMCURVE",
121 "IFCBSPLINECURVEWITHKNOTS",
122 "IFCRATIONALBSPLINECURVEWITHKNOTS",
123 "IFCPLANE",
124 "IFCCYLINDRICALSURFACE",
125 "IFCSPHERICALSURFACE",
126 "IFCTOROIDALSURFACE",
127 "IFCSURFACEOFLINEAREXTRUSION",
128 "IFCSURFACEOFREVOLUTION",
129 "IFCRECTANGULARTRIMMEDSURFACE",
130 "IFCCURVEBOUNDEDPLANE",
131 "IFCCURVEBOUNDEDSURFACE",
132 "IFCBSPLINESURFACEWITHKNOTS",
133 "IFCRATIONALBSPLINESURFACEWITHKNOTS",
134 "IFCPOINTONCURVE",
135 "IFCPOINTONSURFACE",
136 "IFCCURVESEGMENT",
138 "IFCGRADIENTCURVE",
139 "IFCPOINTBYDISTANCEEXPRESSION",
142 "IFCAXIS2PLACEMENTLINEAR",
143 "IFCOFFSETCURVEBYDISTANCES",
144 "IFCSECTIONEDSOLIDHORIZONTAL",
145 "IFCSECTIONEDSURFACE",
146];
147
148pub const PLANNED: &[(&str, &str)] = &[
163 ("IFCCLOTHOID", "an IfcSpiral is unbounded (-inf < u < inf) and the neutral intrinsic curve needs a \
165 finite arc length; it lowers exactly as the ParentCurve of an IfcCurveSegment"),
166 ("IFCSECONDORDERPOLYNOMIALSPIRAL", "an IfcSpiral is unbounded (-inf < u < inf) and the neutral intrinsic curve needs a \
167 finite arc length; it lowers exactly as the ParentCurve of an IfcCurveSegment"),
168 ("IFCTHIRDORDERPOLYNOMIALSPIRAL", "an IfcSpiral is unbounded (-inf < u < inf) and the neutral intrinsic curve needs a \
169 finite arc length; it lowers exactly as the ParentCurve of an IfcCurveSegment"),
170 ("IFCSEVENTHORDERPOLYNOMIALSPIRAL", "an IfcSpiral is unbounded (-inf < u < inf) and the neutral intrinsic curve needs a \
171 finite arc length; it lowers exactly as the ParentCurve of an IfcCurveSegment"),
172 ("IFCCOSINESPIRAL", "an IfcCosineSpiral or IfcSineSpiral law depends on the length L of the IfcCurveSegment \
173 using it; it lowers exactly only as the ParentCurve of an IfcCurveSegment"),
174 ("IFCSINESPIRAL", "an IfcCosineSpiral or IfcSineSpiral law depends on the length L of the IfcCurveSegment \
175 using it; it lowers exactly only as the ParentCurve of an IfcCurveSegment"),
176 ("IFCPOLYNOMIALCURVE", "an IfcPolynomialCurve is unbounded (-inf < u < inf) and the neutral vocabulary has no \
177 unbounded polynomial curve; it lowers exactly as the ParentCurve of an IfcCurveSegment"),
178 ("IFCSEGMENTEDREFERENCECURVE", "IfcSegmentedReferenceCurve states cant through segments placed at stations along its base \
179 curve and parent curves with no normative mapping to a cant law; the business cant layout \
180 lowers to a banked curve through ifc-alignment (#311)"),
181];
182
183#[derive(Debug, Clone, Copy, PartialEq, Eq)]
191pub struct Specialisation {
192 pub subtype: &'static str,
194 pub supertype: &'static str,
196 pub added_attributes: &'static [&'static str],
198 pub rationale: &'static str,
200}
201
202pub const SPECIALISATIONS: &[Specialisation] = &[Specialisation {
204 subtype: "IFCTRIANGULATEDIRREGULARNETWORK",
205 supertype: "IFCTRIANGULATEDFACESET",
206 added_attributes: &["Flags"],
207 rationale: "the face-set slots are unchanged; the lowerer checks Flags \
208 and refuses voids, holes and undocumented codes, so only \
209 breakline codes, which leave the triangles unchanged, lower",
210}];
211
212#[derive(Debug, Clone, Copy, PartialEq, Eq)]
219pub struct Variant {
220 pub family: &'static str,
222 pub variant: &'static str,
224 pub support: Support,
226 pub rationale: &'static str,
228}
229
230#[derive(Debug, Clone, Copy, PartialEq, Eq)]
232#[non_exhaustive]
233pub enum Support {
234 Admitted,
236 Refused,
238}
239
240pub const PARTIAL: &[Variant] = &[
247 Variant {
248 family: "IFCPCURVE",
249 variant: "reference curve is an IfcPolyline",
250 support: Support::Admitted,
251 rationale: "an ordered 2D point sequence needs no evaluation",
252 },
253 Variant {
254 family: "IFCPCURVE",
255 variant: "reference curve is an IfcIndexedPolyCurve with no explicit \
256 Segments, or only IfcLineIndex segments",
257 support: Support::Admitted,
258 rationale: "reads identically to a plain ordered point sequence",
259 },
260 Variant {
261 family: "IFCPCURVE",
262 variant: "reference curve is an IfcLine, IfcCircle or IfcEllipse \
263 positioned by an IfcAxis2Placement2D",
264 support: Support::Admitted,
265 rationale: "defining values are read verbatim in the surface's own \
266 (u, v) domain with no unit conversion",
267 },
268 Variant {
269 family: "IFCPCURVE",
270 variant: "reference conic positioned by an IfcAxis2Placement3D",
271 support: Support::Refused,
272 rationale: "a 3D placement's axis has no meaning in a 2D parameter \
273 domain; admitting it would require inventing a projection",
274 },
275 Variant {
276 family: "IFCPCURVE",
277 variant: "reference curve is an IfcIndexedPolyCurve with an explicit \
278 IfcArcIndex segment",
279 support: Support::Admitted,
280 rationale: "a three-point arc composes exactly from a parameter-space \
281 circumcentre into Circle2 plus a Cartesian trim, mirroring \
282 the 3D path with no approximation",
283 },
284 Variant {
285 family: "IFCPCURVE",
286 variant: "reference curve is an explicit-knot IfcBSplineCurveWithKnots \
287 or IfcRationalBSplineCurveWithKnots",
288 support: Support::Admitted,
289 rationale: "every field is dimensionless or a curve parameter; knots \
290 already pass through the 3D path unscaled, and control \
291 points are read as raw (u, v) pairs",
292 },
293 Variant {
294 family: "IFCPCURVE",
295 variant: "reference curve is a trimmed or composite curve",
296 support: Support::Admitted,
297 rationale: "trim parameters and segments stay in the surface (u, v) \
298 domain, unscaled, so no dimensional contract is needed",
299 },
300 Variant {
301 family: "IFCPCURVE",
302 variant: "reference curve is a convention-only IfcBSplineCurve",
303 support: Support::Refused,
304 rationale: "a base spline carries no authored knot vector to preserve",
305 },
306 Variant {
307 family: "IFCDIRECTRIXDERIVEDREFERENCESWEPTAREASOLID",
308 variant: "directrix defines only a tangent (no IfcCurveSegment, \
309 segment-built or surface curve reachable)",
310 support: Support::Admitted,
311 rationale: "IFC4.3 gives it exactly the behaviour of \
312 IfcFixedReferenceSweptAreaSolid in this case, so it lowers \
313 to the same FixedReferenceSweep",
314 },
315 Variant {
316 family: "IFCDIRECTRIXDERIVEDREFERENCESWEPTAREASOLID",
317 variant: "directrix defines a tangent plane",
318 support: Support::Refused,
319 rationale: "kernel: the directrix defines a tangent plane (it is built from \
320 IfcCurveSegment placements or lies on a surface), so the derived \
321 reference adds that plane's rotation to FixedReference; the neutral \
322 FixedReferenceSweep carries only a constant reference direction",
323 },
324 Variant {
325 family: "IFCTRIANGULATEDIRREGULARNETWORK",
326 variant: "every Flags value is a breakline code, 0 to 7",
327 support: Support::Admitted,
328 rationale: "a breakline marks an edge the triangulation already has, \
329 so the triangle surface is the supertype's; the flags \
330 are not carried into the mesh",
331 },
332 Variant {
333 family: "IFCTRIANGULATEDIRREGULARNETWORK",
334 variant: "a Flags value is -1 (hole) or -2 (void)",
335 support: Support::Refused,
336 rationale: "the triangle is excluded from the surface, and a hole may \
337 fall back on another surface; the neutral mesh has no \
338 face-exclusion or fall-back channel",
339 },
340 Variant {
341 family: "IFCTRIANGULATEDIRREGULARNETWORK",
342 variant: "a Flags value is outside -2 to 7",
343 support: Support::Refused,
344 rationale: "the documentation defines no meaning for it",
345 },
346 Variant {
347 family: "IFCSURFACECURVE",
348 variant: "MasterRepresentation is Curve3D, PCurveS1, or PCurveS2 with \
349 the named side present",
350 support: Support::Admitted,
351 rationale: "each side pairs a surface with its own p-curve, so the \
352 neutral MasterRepresentation names S1 and S2 exactly",
353 },
354 Variant {
355 family: "IFCCURVESEGMENT",
356 variant: "ParentCurve is an IfcLine, IfcCircle or 2D IfcPolyline, \
357 measured by IfcLengthMeasure",
358 support: Support::Admitted,
359 rationale: "a line, arc or polyline cut by arc length and placed rigidly \
360 is elementary: a polyline or an angle-trimmed circle",
361 },
362 Variant {
363 family: "IFCCURVESEGMENT",
364 variant: "ParentCurve is an IfcSpiral subtype, measured by \
365 IfcLengthMeasure",
366 support: Support::Admitted,
367 rationale: "the spiral's curvature law, rebased to the segment in closed \
368 form, on a planar intrinsic curve; nothing is integrated",
369 },
370 Variant {
371 family: "IFCCURVESEGMENT",
372 variant: "SegmentLength is zero (the closing segment of a layout)",
373 support: Support::Admitted,
374 rationale: "its placement exactly: a planar intrinsic curve of length zero",
375 },
376 Variant {
377 family: "IFCCURVESEGMENT",
378 variant: "ParentCurve is a 2D IfcPolynomialCurve with a degree-one \
379 coordinate, cut forwards from SegmentStart 0",
380 support: Support::Admitted,
381 rationale: "its Bezier over a closed-form parameter bound, placed \
382 rigidly and trimmed at TrimSelector::ArcLength: the kernel \
383 inverts the arc length, nothing is integrated here",
384 },
385 Variant {
386 family: "IFCCURVESEGMENT",
387 variant: "ParentCurve is an IfcPolynomialCurve cut from a non-zero \
388 SegmentStart, walked backwards, 3D, or with no degree-one \
389 coordinate",
390 support: Support::Refused,
391 rationale: "the placed start point inverts a non-elementary arc-length \
392 integral, a 3D polynomial has no plane for the placement, \
393 and without a degree-one coordinate the trim has no \
394 closed-form parameter bound",
395 },
396 Variant {
397 family: "IFCCURVESEGMENT",
398 variant: "SegmentStart or SegmentLength is an IfcParameterValue",
399 support: Support::Refused,
400 rationale: "SegmentStart/SegmentLength given as IfcParameterValue: IFC4.3 ADD2 defines no parametric \
401 space for IfcCurveSegment parents yet (informal proposition 1 requires IfcLengthMeasure)",
402 },
403 Variant {
404 family: "IFCCURVESEGMENT",
405 variant: "Placement is an IfcAxis2PlacementLinear",
406 support: Support::Refused,
407 rationale: "an IfcAxis2PlacementLinear placement stands at a station along a basis curve; the station \
408 itself lowers (#307), but the neutral model places a curve only by a resolved transform \
409 and has no curve placed in a station's frame (#311)",
410 },
411 Variant {
412 family: "IFCGRADIENTCURVE",
413 variant: "horizontal IfcCurveSegments over lines, arcs, spirals and \
414 2D IfcPolynomialCurves; vertical IfcCurveSegments over \
415 IfcLine, IfcCircle, an IfcSpiral subtype, or a degree-2 \
416 IfcPolynomialCurve that keeps its start tangent",
417 support: Support::Admitted,
418 rationale: "one plan parameterised by arc length (an intrinsic curve, \
419 or an arc-length chain with a polynomial piece) plus a \
420 piecewise elevation law of polynomial, circular and \
421 intrinsic pieces: Curve3::Elevated, exact",
422 },
423 Variant {
424 family: "IFCGRADIENTCURVE",
425 variant: "a vertical parabola or spiral with no following segment, \
426 closing segment or EndPoint",
427 support: Support::Refused,
428 rationale: "its plan extent inverts a non-elementary arc-length \
429 integral, and nothing states it",
430 },
431 Variant {
432 family: "IFCGRADIENTCURVE",
433 variant: "a heading kink, a closed-form position gap, or a profile \
434 that does not span the base curve",
435 support: Support::Refused,
436 rationale: "one plan curve and one elevation law cannot carry a kink or \
437 a gap, and an elevation law must cover the whole plan",
438 },
439 Variant {
440 family: "IFCGRADIENTCURVE",
441 variant: "placed by a frame that tilts, scales or mirrors the vertical",
442 support: Support::Refused,
443 rationale: "a plan plus a height is carried only by frames that keep \
444 the vertical axis",
445 },
446 Variant {
447 family: "IFCPOINTBYDISTANCEEXPRESSION",
448 variant: "DistanceAlong is an IfcLengthMeasure on the basis curve, clear of \
449 its tangent discontinuities",
450 support: Support::Admitted,
451 rationale: "a CurveStation: distance (plan distance on a gradient curve, arc \
452 length otherwise) and the lateral (left), vertical and \
453 longitudinal offsets, in the same frame IFC4.3 ADD2 8.9.3.48 \
454 states",
455 },
456 Variant {
457 family: "IFCPOINTBYDISTANCEEXPRESSION",
458 variant: "DistanceAlong is an IfcParameterValue",
459 support: Support::Refused,
460 rationale: "IFC4.3 ADD2 gives most basis curves no parameterisation a \
461 distance can be read from, and a neutral station is a distance",
462 },
463 Variant {
464 family: "IFCPOINTBYDISTANCEEXPRESSION",
465 variant: "DistanceAlong on a tangent discontinuity of the basis curve, or \
466 on a basis whose discontinuities cannot be located (a curve \
467 relation, a B-spline with a multiple knot)",
468 support: Support::Refused,
469 rationale: "IFC lets the previous segment's tangent govern there \
470 (8.9.3.48.3); the neutral station reads the next one's",
471 },
472 Variant {
473 family: "IFCAXIS2PLACEMENTLINEAR",
474 variant: "Axis and RefDirection absent, or 3D and not parallel",
475 support: Support::Admitted,
476 rationale: "an OrientedCurveStation: the ratios are components in the \
477 curve's (tangent, left, up) frame, Axis exact and RefDirection \
478 orthogonalised against it, as 8.9.3.4 states",
479 },
480 Variant {
481 family: "IFCAXIS2PLACEMENTLINEAR",
482 variant: "Location is refused as an IfcPointByDistanceExpression",
483 support: Support::Refused,
484 rationale: "the placement is its station; see IFCPOINTBYDISTANCEEXPRESSION",
485 },
486 Variant {
487 family: "IFCOFFSETCURVEBYDISTANCES",
488 variant: "OffsetValues along its own BasisCurve, spanning it or on a basis \
489 with a stated length, clear of tangent discontinuities",
490 support: Support::Admitted,
491 rationale: "OffsetByStations, linear between the offsets; a span short of the \
492 ends is carried on unchanged to them by added stations, as \
493 8.9.3.42.3 states",
494 },
495 Variant {
496 family: "IFCOFFSETCURVEBYDISTANCES",
497 variant: "offsets short of an unbounded or unstated end, a non-zero \
498 OffsetLongitudinal, a member on another basis, or a run over a \
499 tangent discontinuity",
500 support: Support::Refused,
501 rationale: "the neutral offset curve stops at its last station, IFC states \
502 no law for a longitudinal offset along a curve, and no join at \
503 a kink",
504 },
505 Variant {
506 family: "IFCSECTIONEDSOLIDHORIZONTAL",
507 variant: "area sections at positions along the Directrix, clear of its \
508 tangent discontinuities",
509 support: Support::Admitted,
510 rationale: "SectionsAtStations in the curve's frame, matched by ring and \
511 index; RefDirection is the profile normal and Axis profile Y \
512 (IFC4.x-IF#147, IFC4.x-development#1010)",
513 },
514 Variant {
515 family: "IFCSECTIONEDSOLIDHORIZONTAL",
516 variant: "positions off the Directrix, or spanning a tangent discontinuity",
517 support: Support::Refused,
518 rationale: "a station measures along its own basis, and IFC mitres at a \
519 kink (8.8.3.35.1) where the neutral sections turn with the frame",
520 },
521 Variant {
522 family: "IFCSECTIONEDSURFACE",
523 variant: "open sections tagged throughout or not at all, with one tag set",
524 support: Support::Admitted,
525 rationale: "OpenSectionsAtStations joined by tag, forwards or reversed; \
526 profile X to the left as IfcOpenCrossProfileDef states \
527 (8.15.3.15.1, #344)",
528 },
529 Variant {
530 family: "IFCSECTIONEDSURFACE",
531 variant: "mixed tagging or branching breaklines (sections with different \
532 Tags)",
533 support: Support::Refused,
534 rationale: "the neutral surface joins one tag set through every section",
535 },
536 Variant {
537 family: "IFCSURFACECURVE",
538 variant: "MasterRepresentation is PCurveS2 with only one associated \
539 p-curve",
540 support: Support::Refused,
541 rationale: "the master names a parametric side the curve does not \
542 have; the schema calls this inconsistent, so it is \
543 refused rather than resolved to the remaining p-curve",
544 },
545];
546
547pub fn lower_representation_item(
552 session: &mut LoweringSession<'_>,
553 id: EntityId,
554 frame: Transform,
555) -> GeometryResult<NodeId> {
556 let type_name = session.type_name(id)?;
557 if type_name == "IFCSECTIONEDSURFACE" {
560 return lower_sectioned_surface_node(session, id, frame);
561 }
562 let routed = SPECIALISATIONS
565 .iter()
566 .find(|row| row.subtype == type_name)
567 .map_or(type_name.as_str(), |row| row.supertype);
568 if is_a(&type_name, "IFCCURVE") {
573 return lower_curve_node(session, id, frame);
574 }
575 if is_a(&type_name, "IFCSURFACE") {
576 return lower_surface_node(session, id, frame);
577 }
578 match routed {
579 "IFCEXTRUDEDAREASOLID" => lower_extruded_area_solid_node(session, id, frame),
580 "IFCREVOLVEDAREASOLID" => lower_revolved_area_solid_node(session, id, frame),
581 "IFCBOOLEANRESULT" | "IFCBOOLEANCLIPPINGRESULT" => {
582 lower_boolean_result_node(session, id, frame)
583 }
584 "IFCHALFSPACESOLID" | "IFCBOXEDHALFSPACE" | "IFCPOLYGONALBOUNDEDHALFSPACE" => {
585 lower_half_space_node(session, id, frame)
586 }
587 "IFCMAPPEDITEM" => lower_mapped_item_node(session, id, frame),
588 "IFCCURVESEGMENT" => lower_curve_node(session, id, frame),
592 "IFCPOINTONCURVE" => lower_point_on_curve_node(session, id, frame),
593 "IFCPOINTONSURFACE" => lower_point_on_surface_node(session, id, frame),
594 "IFCFACETEDBREP"
595 | "IFCFACETEDBREPWITHVOIDS"
596 | "IFCADVANCEDBREP"
597 | "IFCADVANCEDBREPWITHVOIDS" => lower_faceted_brep_node(session, id, frame),
598 "IFCFACESURFACE" | "IFCADVANCEDFACE" => lower_face_surface_node(session, id, frame),
599 "IFCTRIANGULATEDFACESET" => lower_triangulated_face_set_node(session, id, frame),
600 "IFCPOLYGONALFACESET" => lower_polygonal_face_set_node(session, id, frame),
601 "IFCCSGSOLID" => lower_csg_solid_node(session, id, frame),
602 "IFCSWEPTDISKSOLID" | "IFCSWEPTDISKSOLIDPOLYGONAL" => {
603 lower_swept_disk_node(session, id, frame)
604 }
605 "IFCSURFACECURVESWEPTAREASOLID" => {
606 lower_surface_curve_swept_area_solid_node(session, id, frame)
607 }
608 "IFCBOUNDINGBOX" => lower_bounding_box_node(session, id, frame),
609 "IFCEXTRUDEDAREASOLIDTAPERED" => lower_tapered_extrusion_node(session, id, frame),
610 "IFCREVOLVEDAREASOLIDTAPERED" => lower_tapered_revolution_node(session, id, frame),
611 "IFCFIXEDREFERENCESWEPTAREASOLID" => lower_fixed_reference_sweep_node(session, id, frame),
612 "IFCDIRECTRIXDERIVEDREFERENCESWEPTAREASOLID" => {
613 lower_directrix_derived_reference_sweep_node(session, id, frame)
614 }
615 "IFCSECTIONEDSOLIDHORIZONTAL" => lower_sectioned_solid_horizontal_node(session, id, frame),
616 "IFCPOINTBYDISTANCEEXPRESSION" => lower_point_by_distance_node(session, id, frame),
617 "IFCAXIS2PLACEMENTLINEAR" => lower_axis2_placement_linear_node(session, id, frame),
618 "IFCSECTIONEDSPINE" => lower_sectioned_spine_node(session, id, frame),
619 "IFCSHELLBASEDSURFACEMODEL"
620 | "IFCFACEBASEDSURFACEMODEL"
621 | "IFCGEOMETRICSET"
622 | "IFCGEOMETRICCURVESET" => lower_collection_node(session, id, frame),
623 "IFCBLOCK"
624 | "IFCSPHERE"
625 | "IFCRIGHTCIRCULARCYLINDER"
626 | "IFCRIGHTCIRCULARCONE"
627 | "IFCRECTANGULARPYRAMID" => lower_csg_primitive_node(session, id, frame),
628 _ => Err(session.unsupported(id, &type_name, detail_for(&type_name))),
629 }
630}
631
632fn detail_for(type_name: &str) -> &'static str {
634 planned_detail(type_name).unwrap_or("representation item family is not lowered yet")
635}
636
637pub(crate) fn planned_detail(type_name: &str) -> Option<&'static str> {
643 PLANNED
644 .iter()
645 .find(|(name, _)| *name == type_name)
646 .map(|(_, detail)| *detail)
647}
648
649#[cfg(test)]
650mod tests {
651 use super::*;
652
653 #[test]
654 fn implemented_and_planned_families_do_not_overlap() {
655 for name in IMPLEMENTED {
656 assert!(
657 !PLANNED.iter().any(|(planned, _)| planned == name),
658 "{name} is listed as both implemented and planned"
659 );
660 }
661 }
662
663 #[test]
664 fn every_planned_family_states_a_concrete_reason() {
665 for (name, detail) in PLANNED {
666 assert!(!detail.is_empty(), "{name} has no stated reason");
667 assert_ne!(
668 *detail, "unsupported",
669 "{name} must say what specifically is missing"
670 );
671 }
672 }
673}