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::surface::lower_surface_node;
48use crate::lower::swept::{
49 lower_directrix_derived_reference_sweep_node, lower_extruded_area_solid_node,
50 lower_fixed_reference_sweep_node, lower_revolved_area_solid_node, lower_sectioned_spine_node,
51 lower_tapered_extrusion_node, lower_tapered_revolution_node,
52};
53use crate::lower::tessellated::{lower_polygonal_face_set_node, lower_triangulated_face_set_node};
54use crate::select::is_a;
55use crate::transform::Transform;
56
57pub const IMPLEMENTED: &[&str] = &[
62 "IFCEXTRUDEDAREASOLID",
63 "IFCREVOLVEDAREASOLID",
64 "IFCBOOLEANRESULT",
65 "IFCBOOLEANCLIPPINGRESULT",
66 "IFCMAPPEDITEM",
67 "IFCFACETEDBREP",
68 "IFCFACETEDBREPWITHVOIDS",
69 "IFCADVANCEDBREP",
70 "IFCADVANCEDBREPWITHVOIDS",
71 "IFCHALFSPACESOLID",
72 "IFCBOXEDHALFSPACE",
73 "IFCPOLYGONALBOUNDEDHALFSPACE",
74 "IFCTRIANGULATEDFACESET",
75 "IFCTRIANGULATEDIRREGULARNETWORK",
77 "IFCPOLYGONALFACESET",
78 "IFCCSGSOLID",
79 "IFCSWEPTDISKSOLID",
80 "IFCSWEPTDISKSOLIDPOLYGONAL",
81 "IFCSURFACECURVESWEPTAREASOLID",
82 "IFCBLOCK",
83 "IFCSPHERE",
84 "IFCRIGHTCIRCULARCYLINDER",
85 "IFCRIGHTCIRCULARCONE",
86 "IFCRECTANGULARPYRAMID",
87 "IFCBOUNDINGBOX",
88 "IFCEXTRUDEDAREASOLIDTAPERED",
89 "IFCREVOLVEDAREASOLIDTAPERED",
90 "IFCFIXEDREFERENCESWEPTAREASOLID",
91 "IFCDIRECTRIXDERIVEDREFERENCESWEPTAREASOLID",
93 "IFCSECTIONEDSPINE",
94 "IFCSHELLBASEDSURFACEMODEL",
95 "IFCFACEBASEDSURFACEMODEL",
96 "IFCFACESURFACE",
99 "IFCADVANCEDFACE",
100 "IFCGEOMETRICSET",
101 "IFCGEOMETRICCURVESET",
102 "IFCLINE",
105 "IFCCIRCLE",
106 "IFCELLIPSE",
107 "IFCPOLYLINE",
108 "IFCINDEXEDPOLYCURVE",
109 "IFCCOMPOSITECURVE",
110 "IFCCOMPOSITECURVEONSURFACE",
111 "IFCBOUNDARYCURVE",
112 "IFCOUTERBOUNDARYCURVE",
113 "IFCTRIMMEDCURVE",
114 "IFCOFFSETCURVE2D",
115 "IFCOFFSETCURVE3D",
116 "IFCPCURVE",
117 "IFCSURFACECURVE",
118 "IFCINTERSECTIONCURVE",
119 "IFCSEAMCURVE",
120 "IFCBSPLINECURVEWITHKNOTS",
121 "IFCRATIONALBSPLINECURVEWITHKNOTS",
122 "IFCPLANE",
123 "IFCCYLINDRICALSURFACE",
124 "IFCSPHERICALSURFACE",
125 "IFCTOROIDALSURFACE",
126 "IFCSURFACEOFLINEAREXTRUSION",
127 "IFCSURFACEOFREVOLUTION",
128 "IFCRECTANGULARTRIMMEDSURFACE",
129 "IFCCURVEBOUNDEDPLANE",
130 "IFCCURVEBOUNDEDSURFACE",
131 "IFCBSPLINESURFACEWITHKNOTS",
132 "IFCRATIONALBSPLINESURFACEWITHKNOTS",
133 "IFCPOINTONCURVE",
134 "IFCPOINTONSURFACE",
135 "IFCCURVESEGMENT",
137 "IFCGRADIENTCURVE",
138];
139
140pub const PLANNED: &[(&str, &str)] = &[
155 ("IFCCLOTHOID", "an IfcSpiral is unbounded (-inf < u < inf) and the neutral intrinsic curve needs a \
157 finite arc length; it lowers exactly as the ParentCurve of an IfcCurveSegment"),
158 ("IFCSECONDORDERPOLYNOMIALSPIRAL", "an IfcSpiral is unbounded (-inf < u < inf) and the neutral intrinsic curve needs a \
159 finite arc length; it lowers exactly as the ParentCurve of an IfcCurveSegment"),
160 ("IFCTHIRDORDERPOLYNOMIALSPIRAL", "an IfcSpiral is unbounded (-inf < u < inf) and the neutral intrinsic curve needs a \
161 finite arc length; it lowers exactly as the ParentCurve of an IfcCurveSegment"),
162 ("IFCSEVENTHORDERPOLYNOMIALSPIRAL", "an IfcSpiral is unbounded (-inf < u < inf) and the neutral intrinsic curve needs a \
163 finite arc length; it lowers exactly as the ParentCurve of an IfcCurveSegment"),
164 ("IFCCOSINESPIRAL", "an IfcCosineSpiral or IfcSineSpiral law depends on the length L of the IfcCurveSegment \
165 using it; it lowers exactly only as the ParentCurve of an IfcCurveSegment"),
166 ("IFCSINESPIRAL", "an IfcCosineSpiral or IfcSineSpiral law depends on the length L of the IfcCurveSegment \
167 using it; it lowers exactly only as the ParentCurve of an IfcCurveSegment"),
168 ("IFCPOLYNOMIALCURVE", "an IfcPolynomialCurve is unbounded (-inf < u < inf) and the neutral vocabulary has no \
169 unbounded polynomial curve; bounded by an IfcCurveSegment it needs an arc-length trim (#90)"),
170 ("IFCSEGMENTEDREFERENCECURVE", "IfcSegmentedReferenceCurve adds cant (a roll of the section about the centreline); the \
171 pinned neutral curve vocabulary has no roll law to carry it exactly (#93)"),
172 (
174 "IFCSECTIONEDSOLIDHORIZONTAL",
175 "kernel: sections stand at IfcAxis2PlacementLinear stations (a measure \
176 along the directrix plus offsets) and are swept horizontally with \
177 tag-matched linear interpolation; the neutral SectionedSpine takes only \
178 resolved section frames, and resolving a station is curve evaluation",
179 ),
180 (
181 "IFCSECTIONEDSURFACE",
182 "kernel: no neutral sectioned-surface relation exists; its open sections \
183 stand at IfcAxis2PlacementLinear stations along the directrix and are \
184 joined by tag, and the neutral SectionedSpine is a solid over area \
185 profiles",
186 ),
187 (
189 "IFCOFFSETCURVEBYDISTANCES",
190 "offsets are stated at stations along the basis curve as \
191 IfcPointByDistanceExpression values; the neutral model has no \
192 distance-along-curve point or station-offset curve to hold them",
193 ),
194 (
195 "IFCPOINTBYDISTANCEEXPRESSION",
196 "a point at a distance along a basis curve, offset in that curve's \
197 frame; the neutral model has no distance-along-curve point relation",
198 ),
199 (
200 "IFCAXIS2PLACEMENTLINEAR",
201 "a frame located by an IfcPointByDistanceExpression; the neutral model \
202 has no distance-along-curve point relation to anchor it",
203 ),
204];
205
206#[derive(Debug, Clone, Copy, PartialEq, Eq)]
214pub struct Specialisation {
215 pub subtype: &'static str,
217 pub supertype: &'static str,
219 pub added_attributes: &'static [&'static str],
221 pub rationale: &'static str,
223}
224
225pub const SPECIALISATIONS: &[Specialisation] = &[Specialisation {
227 subtype: "IFCTRIANGULATEDIRREGULARNETWORK",
228 supertype: "IFCTRIANGULATEDFACESET",
229 added_attributes: &["Flags"],
230 rationale: "the face-set slots are unchanged; the lowerer checks Flags \
231 and refuses voids, holes and undocumented codes, so only \
232 breakline codes, which leave the triangles unchanged, lower",
233}];
234
235#[derive(Debug, Clone, Copy, PartialEq, Eq)]
242pub struct Variant {
243 pub family: &'static str,
245 pub variant: &'static str,
247 pub support: Support,
249 pub rationale: &'static str,
251}
252
253#[derive(Debug, Clone, Copy, PartialEq, Eq)]
255#[non_exhaustive]
256pub enum Support {
257 Admitted,
259 Refused,
261}
262
263pub const PARTIAL: &[Variant] = &[
270 Variant {
271 family: "IFCPCURVE",
272 variant: "reference curve is an IfcPolyline",
273 support: Support::Admitted,
274 rationale: "an ordered 2D point sequence needs no evaluation",
275 },
276 Variant {
277 family: "IFCPCURVE",
278 variant: "reference curve is an IfcIndexedPolyCurve with no explicit \
279 Segments, or only IfcLineIndex segments",
280 support: Support::Admitted,
281 rationale: "reads identically to a plain ordered point sequence",
282 },
283 Variant {
284 family: "IFCPCURVE",
285 variant: "reference curve is an IfcLine, IfcCircle or IfcEllipse \
286 positioned by an IfcAxis2Placement2D",
287 support: Support::Admitted,
288 rationale: "defining values are read verbatim in the surface's own \
289 (u, v) domain with no unit conversion",
290 },
291 Variant {
292 family: "IFCPCURVE",
293 variant: "reference conic positioned by an IfcAxis2Placement3D",
294 support: Support::Refused,
295 rationale: "a 3D placement's axis has no meaning in a 2D parameter \
296 domain; admitting it would require inventing a projection",
297 },
298 Variant {
299 family: "IFCPCURVE",
300 variant: "reference curve is an IfcIndexedPolyCurve with an explicit \
301 IfcArcIndex segment",
302 support: Support::Admitted,
303 rationale: "a three-point arc composes exactly from a parameter-space \
304 circumcentre into Circle2 plus a Cartesian trim, mirroring \
305 the 3D path with no approximation",
306 },
307 Variant {
308 family: "IFCPCURVE",
309 variant: "reference curve is an explicit-knot IfcBSplineCurveWithKnots \
310 or IfcRationalBSplineCurveWithKnots",
311 support: Support::Admitted,
312 rationale: "every field is dimensionless or a curve parameter; knots \
313 already pass through the 3D path unscaled, and control \
314 points are read as raw (u, v) pairs",
315 },
316 Variant {
317 family: "IFCPCURVE",
318 variant: "reference curve is a trimmed or composite curve",
319 support: Support::Admitted,
320 rationale: "trim parameters and segments stay in the surface (u, v) \
321 domain, unscaled, so no dimensional contract is needed",
322 },
323 Variant {
324 family: "IFCPCURVE",
325 variant: "reference curve is a convention-only IfcBSplineCurve",
326 support: Support::Refused,
327 rationale: "a base spline carries no authored knot vector to preserve",
328 },
329 Variant {
330 family: "IFCDIRECTRIXDERIVEDREFERENCESWEPTAREASOLID",
331 variant: "directrix defines only a tangent (no IfcCurveSegment, \
332 segment-built or surface curve reachable)",
333 support: Support::Admitted,
334 rationale: "IFC4.3 gives it exactly the behaviour of \
335 IfcFixedReferenceSweptAreaSolid in this case, so it lowers \
336 to the same FixedReferenceSweep",
337 },
338 Variant {
339 family: "IFCDIRECTRIXDERIVEDREFERENCESWEPTAREASOLID",
340 variant: "directrix defines a tangent plane",
341 support: Support::Refused,
342 rationale: "kernel: the directrix defines a tangent plane (it is built from \
343 IfcCurveSegment placements or lies on a surface), so the derived \
344 reference adds that plane's rotation to FixedReference; the neutral \
345 FixedReferenceSweep carries only a constant reference direction",
346 },
347 Variant {
348 family: "IFCTRIANGULATEDIRREGULARNETWORK",
349 variant: "every Flags value is a breakline code, 0 to 7",
350 support: Support::Admitted,
351 rationale: "a breakline marks an edge the triangulation already has, \
352 so the triangle surface is the supertype's; the flags \
353 are not carried into the mesh",
354 },
355 Variant {
356 family: "IFCTRIANGULATEDIRREGULARNETWORK",
357 variant: "a Flags value is -1 (hole) or -2 (void)",
358 support: Support::Refused,
359 rationale: "the triangle is excluded from the surface, and a hole may \
360 fall back on another surface; the neutral mesh has no \
361 face-exclusion or fall-back channel",
362 },
363 Variant {
364 family: "IFCTRIANGULATEDIRREGULARNETWORK",
365 variant: "a Flags value is outside -2 to 7",
366 support: Support::Refused,
367 rationale: "the documentation defines no meaning for it",
368 },
369 Variant {
370 family: "IFCSURFACECURVE",
371 variant: "MasterRepresentation is Curve3D, PCurveS1, or PCurveS2 with \
372 the named side present",
373 support: Support::Admitted,
374 rationale: "each side pairs a surface with its own p-curve, so the \
375 neutral MasterRepresentation names S1 and S2 exactly",
376 },
377 Variant {
378 family: "IFCCURVESEGMENT",
379 variant: "ParentCurve is an IfcLine, IfcCircle or 2D IfcPolyline, \
380 measured by IfcLengthMeasure",
381 support: Support::Admitted,
382 rationale: "a line, arc or polyline cut by arc length and placed rigidly \
383 is elementary: a polyline or an angle-trimmed circle",
384 },
385 Variant {
386 family: "IFCCURVESEGMENT",
387 variant: "ParentCurve is an IfcSpiral subtype, measured by \
388 IfcLengthMeasure",
389 support: Support::Admitted,
390 rationale: "the spiral's curvature law, rebased to the segment in closed \
391 form, on a planar intrinsic curve; nothing is integrated",
392 },
393 Variant {
394 family: "IFCCURVESEGMENT",
395 variant: "SegmentLength is zero (the closing segment of a layout)",
396 support: Support::Admitted,
397 rationale: "its placement exactly: a planar intrinsic curve of length zero",
398 },
399 Variant {
400 family: "IFCCURVESEGMENT",
401 variant: "ParentCurve is an IfcPolynomialCurve",
402 support: Support::Refused,
403 rationale: "an IfcPolynomialCurve trimmed by arc length: the end parameter inverts a non-elementary \
404 arc-length integral and the neutral vocabulary has no arc-length trim (#90)",
405 },
406 Variant {
407 family: "IFCCURVESEGMENT",
408 variant: "SegmentStart or SegmentLength is an IfcParameterValue",
409 support: Support::Refused,
410 rationale: "SegmentStart/SegmentLength given as IfcParameterValue: IFC4.3 ADD2 defines no parametric \
411 space for IfcCurveSegment parents yet (informal proposition 1 requires IfcLengthMeasure)",
412 },
413 Variant {
414 family: "IFCCURVESEGMENT",
415 variant: "Placement is an IfcAxis2PlacementLinear",
416 support: Support::Refused,
417 rationale: "an IfcAxis2PlacementLinear placement belongs to an IfcSegmentedReferenceCurve (cant); \
418 the neutral vocabulary has no roll law to carry it (#93)",
419 },
420 Variant {
421 family: "IFCGRADIENTCURVE",
422 variant: "horizontal IfcCurveSegments over lines, arcs and spirals; \
423 vertical IfcCurveSegments over IfcLine or a degree-2 \
424 IfcPolynomialCurve that keeps its start tangent",
425 support: Support::Admitted,
426 rationale: "one intrinsic plan with a piecewise curvature law plus a \
427 piecewise polynomial elevation law: Curve3::Elevated, exact",
428 },
429 Variant {
430 family: "IFCGRADIENTCURVE",
431 variant: "a vertical IfcCircle or IfcClothoid segment",
432 support: Support::Refused,
433 rationale: "neither is polynomial in plan distance and the pinned \
434 ElevationLaw has only polynomial pieces (#258)",
435 },
436 Variant {
437 family: "IFCGRADIENTCURVE",
438 variant: "a vertical parabola with no following segment, closing \
439 segment or EndPoint",
440 support: Support::Refused,
441 rationale: "its end abscissa inverts a non-elementary arc-length \
442 integral (#90)",
443 },
444 Variant {
445 family: "IFCGRADIENTCURVE",
446 variant: "a heading kink, a closed-form position gap, or a profile \
447 that does not span the base curve",
448 support: Support::Refused,
449 rationale: "one plan curve and one elevation law cannot carry a kink or \
450 a gap, and an elevation law must cover the whole plan",
451 },
452 Variant {
453 family: "IFCGRADIENTCURVE",
454 variant: "placed by a frame that tilts, scales or mirrors the vertical",
455 support: Support::Refused,
456 rationale: "a plan plus a height is carried only by frames that keep \
457 the vertical axis",
458 },
459 Variant {
460 family: "IFCSURFACECURVE",
461 variant: "MasterRepresentation is PCurveS2 with only one associated \
462 p-curve",
463 support: Support::Refused,
464 rationale: "the master names a parametric side the curve does not \
465 have; the schema calls this inconsistent, so it is \
466 refused rather than resolved to the remaining p-curve",
467 },
468];
469
470pub fn lower_representation_item(
475 session: &mut LoweringSession<'_>,
476 id: EntityId,
477 frame: Transform,
478) -> GeometryResult<NodeId> {
479 let type_name = session.type_name(id)?;
480 if type_name == "IFCSECTIONEDSURFACE" {
483 return lower_sectioned_surface_node(session, id);
484 }
485 let routed = SPECIALISATIONS
488 .iter()
489 .find(|row| row.subtype == type_name)
490 .map_or(type_name.as_str(), |row| row.supertype);
491 if is_a(&type_name, "IFCCURVE") {
496 return lower_curve_node(session, id, frame);
497 }
498 if is_a(&type_name, "IFCSURFACE") {
499 return lower_surface_node(session, id, frame);
500 }
501 match routed {
502 "IFCEXTRUDEDAREASOLID" => lower_extruded_area_solid_node(session, id, frame),
503 "IFCREVOLVEDAREASOLID" => lower_revolved_area_solid_node(session, id, frame),
504 "IFCBOOLEANRESULT" | "IFCBOOLEANCLIPPINGRESULT" => {
505 lower_boolean_result_node(session, id, frame)
506 }
507 "IFCHALFSPACESOLID" | "IFCBOXEDHALFSPACE" | "IFCPOLYGONALBOUNDEDHALFSPACE" => {
508 lower_half_space_node(session, id, frame)
509 }
510 "IFCMAPPEDITEM" => lower_mapped_item_node(session, id, frame),
511 "IFCCURVESEGMENT"
514 | "IFCGRADIENTCURVE"
515 | "IFCSEGMENTEDREFERENCECURVE"
516 | "IFCPOLYNOMIALCURVE"
517 | "IFCCLOTHOID"
518 | "IFCCOSINESPIRAL"
519 | "IFCSINESPIRAL"
520 | "IFCSECONDORDERPOLYNOMIALSPIRAL"
521 | "IFCTHIRDORDERPOLYNOMIALSPIRAL"
522 | "IFCSEVENTHORDERPOLYNOMIALSPIRAL" => lower_curve_node(session, id, frame),
523 "IFCPOINTONCURVE" => lower_point_on_curve_node(session, id, frame),
524 "IFCPOINTONSURFACE" => lower_point_on_surface_node(session, id, frame),
525 "IFCFACETEDBREP"
526 | "IFCFACETEDBREPWITHVOIDS"
527 | "IFCADVANCEDBREP"
528 | "IFCADVANCEDBREPWITHVOIDS" => lower_faceted_brep_node(session, id, frame),
529 "IFCFACESURFACE" | "IFCADVANCEDFACE" => lower_face_surface_node(session, id, frame),
530 "IFCTRIANGULATEDFACESET" => lower_triangulated_face_set_node(session, id, frame),
531 "IFCPOLYGONALFACESET" => lower_polygonal_face_set_node(session, id, frame),
532 "IFCCSGSOLID" => lower_csg_solid_node(session, id, frame),
533 "IFCSWEPTDISKSOLID" | "IFCSWEPTDISKSOLIDPOLYGONAL" => {
534 lower_swept_disk_node(session, id, frame)
535 }
536 "IFCSURFACECURVESWEPTAREASOLID" => {
537 lower_surface_curve_swept_area_solid_node(session, id, frame)
538 }
539 "IFCBOUNDINGBOX" => lower_bounding_box_node(session, id, frame),
540 "IFCEXTRUDEDAREASOLIDTAPERED" => lower_tapered_extrusion_node(session, id, frame),
541 "IFCREVOLVEDAREASOLIDTAPERED" => lower_tapered_revolution_node(session, id, frame),
542 "IFCFIXEDREFERENCESWEPTAREASOLID" => lower_fixed_reference_sweep_node(session, id, frame),
543 "IFCDIRECTRIXDERIVEDREFERENCESWEPTAREASOLID" => {
544 lower_directrix_derived_reference_sweep_node(session, id, frame)
545 }
546 "IFCSECTIONEDSOLIDHORIZONTAL" => lower_sectioned_solid_horizontal_node(session, id),
547 "IFCSECTIONEDSPINE" => lower_sectioned_spine_node(session, id, frame),
548 "IFCSHELLBASEDSURFACEMODEL"
549 | "IFCFACEBASEDSURFACEMODEL"
550 | "IFCGEOMETRICSET"
551 | "IFCGEOMETRICCURVESET" => lower_collection_node(session, id, frame),
552 "IFCBLOCK"
553 | "IFCSPHERE"
554 | "IFCRIGHTCIRCULARCYLINDER"
555 | "IFCRIGHTCIRCULARCONE"
556 | "IFCRECTANGULARPYRAMID" => lower_csg_primitive_node(session, id, frame),
557 _ => Err(session.unsupported(id, &type_name, detail_for(&type_name))),
558 }
559}
560
561fn detail_for(type_name: &str) -> &'static str {
563 PLANNED
564 .iter()
565 .find(|(name, _)| *name == type_name)
566 .map(|(_, detail)| *detail)
567 .unwrap_or("representation item family is not lowered yet")
568}
569
570#[cfg(test)]
571mod tests {
572 use super::*;
573
574 #[test]
575 fn implemented_and_planned_families_do_not_overlap() {
576 for name in IMPLEMENTED {
577 assert!(
578 !PLANNED.iter().any(|(planned, _)| planned == name),
579 "{name} is listed as both implemented and planned"
580 );
581 }
582 }
583
584 #[test]
585 fn every_planned_family_states_a_concrete_reason() {
586 for (name, detail) in PLANNED {
587 assert!(!detail.is_empty(), "{name} has no stated reason");
588 assert_ne!(
589 *detail, "unsupported",
590 "{name} must say what specifically is missing"
591 );
592 }
593 }
594}