From 2e3d4486caa66e9209bde8f598e7deab0e5a3ee3 Mon Sep 17 00:00:00 2001 From: Petru Conduraru Date: Sun, 19 Jul 2026 23:23:24 +0300 Subject: [PATCH] IfcCenterLineProfileDef: implement profile mapping The mapping for IfcCenterLineProfileDef was a stub returning nullptr, so any swept solid using this profile produced no geometry. The dispatch then fell through to the IfcArbitraryOpenProfileDef parent mapping, whose open zero-area loop fails the face cast in the sweep mappings anyway. The taxonomy offset_curve item sketched in the earlier commented-out attempt is not implemented by any kernel (AbstractKernel throws not_implemented_error, the OpenCASCADE loop converter throws for it), so the offset is instead computed at mapping time. The centerline is offset to both sides by Thickness/2 in the profile plane and closed with end caps, using straight miter joins at polyline vertices. This preserves the constant thickness along the curve that the profile definition prescribes, which the circular arc joins of a generic offset algorithm (the pre-0.8 BRepOffsetAPI_MakeOffset path) would violate, matching the design note from the earlier implementation. Centerlines with curved (arc or spline) segments are detected and rejected with a warning for now, keeping the previous no-geometry behavior for those. Verified with IFC4 and IFC2X3 test files, straight and L-shaped centerlines: output solids are watertight, mitered, and have exactly the expected thickness, extents and volume. This is the geometry-side prerequisite for the sheet-from-centerline authoring discussed in #2077. Generated with the assistance of an AI coding tool. --- .../mapping/IfcCenterLineProfileDef.cpp | 160 +++++++++++------- 1 file changed, 102 insertions(+), 58 deletions(-) diff --git a/src/ifcgeom/mapping/IfcCenterLineProfileDef.cpp b/src/ifcgeom/mapping/IfcCenterLineProfileDef.cpp index 64409e4263..c4b4060b27 100644 --- a/src/ifcgeom/mapping/IfcCenterLineProfileDef.cpp +++ b/src/ifcgeom/mapping/IfcCenterLineProfileDef.cpp @@ -21,66 +21,110 @@ #define mapping POSTFIX_SCHEMA(mapping) using namespace ifcopenshell::geometry; +#include "../profile_helper.h" + taxonomy::ptr mapping::map_impl(const IfcSchema::IfcCenterLineProfileDef* inst) { - return nullptr; - - /* const double d = inst->Thickness() * length_unit_ / 2.; - auto f = taxonomy::make(); - auto ofc = taxonomy::make(); - ofc->basis = map(inst->Curve()); - ofc->offset = d; - // @todo - // f->children.push_back(ofc); - return f; - */ + const double eps = settings_.get().get(); - // @todo we still need to handle this in the geometry libraries - - /* - TopoDS_Wire wire; - if (!convert_wire(inst->Curve(), wire)) return false; - - // BRepOffsetAPI_MakeOffset insists on creating circular arc - // segments for joining the curves that constitute the center - // line. This is probably not in accordance with the IFC spec. - // Although it does not specify a method to join segments - // explicitly, it does dictate 'a constant thickness along the - // curve'. Therefore for simple singular wires a quick - // alternative is provided that uses a straight join. - - TopExp_Explorer exp(wire, TopAbs_EDGE); - TopoDS_Edge edge = TopoDS::Edge(exp.Current()); - exp.Next(); - - if (!exp.More()) { - double u1, u2; - Handle(Geom_Curve) curve = BRep_Tool::Curve(edge, u1, u2); - - Handle(Geom_TrimmedCurve) trim = new Geom_TrimmedCurve(curve, u1, u2); - - Handle(Geom_OffsetCurve) c1 = new Geom_OffsetCurve(trim, d, gp::DZ()); - Handle(Geom_OffsetCurve) c2 = new Geom_OffsetCurve(trim, -d, gp::DZ()); - - gp_Pnt c1a, c1b, c2a, c2b; - c1->D0(c1->FirstParameter(), c1a); - c1->D0(c1->LastParameter(), c1b); - c2->D0(c2->FirstParameter(), c2a); - c2->D0(c2->LastParameter(), c2b); - - BRepBuilderAPI_MakeWire mw; - mw.Add(BRepBuilderAPI_MakeEdge(c1)); - mw.Add(BRepBuilderAPI_MakeEdge(c1a, c2a)); - mw.Add(BRepBuilderAPI_MakeEdge(c2)); - mw.Add(BRepBuilderAPI_MakeEdge(c2b, c1b)); - - face = BRepBuilderAPI_MakeFace(mw.Wire()); - } else { - BRepOffsetAPI_MakeOffset offset(BRepBuilderAPI_MakeFace(gp_Pln(gp::Origin(), gp::DZ()))); - offset.AddWire(wire); - offset.Perform(d); - face = BRepBuilderAPI_MakeFace(TopoDS::Wire(offset)); + if (d < eps) { + logger_.Warning("GEO", 403, "Thickness below precision for:", inst); + return nullptr; } - return true; - */ + + auto mapped = map(inst->Curve()); + if (!mapped) { + return nullptr; + } + auto crv = taxonomy::dcast(mapped); + if (!crv || crv->children.empty()) { + logger_.Warning("GEO", 404, "Unsupported centerline curve for:", inst); + return nullptr; + } + if (!crv->is_polyhedron()) { + logger_.Warning("GEO", 404, "Unsupported curved centerline segments for:", inst); + return nullptr; + } + + std::vector pts; + pts.reserve(crv->children.size() + 1); + for (auto& e : crv->children) { + if (e->start.which() != 1 || e->end.which() != 1) { + logger_.Warning("GEO", 404, "Unsupported parametric trims on centerline for:", inst); + return nullptr; + } + Eigen::Vector2d p = boost::get(e->start)->ccomponents().head<2>(); + Eigen::Vector2d q = boost::get(e->end)->ccomponents().head<2>(); + if (pts.empty()) { + pts.push_back(p); + } else if ((pts.back() - p).norm() > eps) { + logger_.Warning("GEO", 405, "Unsupported discontinuous centerline for:", inst); + return nullptr; + } + pts.push_back(q); + } + + if (pts.size() < 2 || (pts.front() - pts.back()).norm() < eps) { + logger_.Warning("GEO", 405, "Unsupported closed or degenerate centerline for:", inst); + return nullptr; + } + + const size_t n = pts.size(); + std::vector normals(n - 1); + for (size_t i = 0; i < n - 1; ++i) { + Eigen::Vector2d t = pts[i + 1] - pts[i]; + const double l = t.norm(); + if (l < eps) { + logger_.Warning("GEO", 405, "Degenerate centerline segment for:", inst); + return nullptr; + } + t /= l; + normals[i] = Eigen::Vector2d(-t.y(), t.x()); + } + + // Straight miter joins at the vertices, because the profile prescribes a + // constant thickness along the curve, which the arc joins of a generic + // offset algorithm would violate. + std::vector miters(n); + miters.front() = normals.front(); + miters.back() = normals.back(); + for (size_t i = 1; i < n - 1; ++i) { + const double denom = 1. + normals[i - 1].dot(normals[i]); + if (denom < 1.e-9) { + logger_.Warning("GEO", 405, "Centerline reverses onto itself for:", inst); + return nullptr; + } + miters[i] = (normals[i - 1] + normals[i]) / denom; + } + + std::vector polygon; + polygon.reserve(2 * n); + for (size_t i = 0; i < n; ++i) { + polygon.push_back(pts[i] - d * miters[i]); + } + for (size_t i = n; i-- > 0;) { + polygon.push_back(pts[i] + d * miters[i]); + } + + double twice_area = 0.; + for (size_t i = 0; i < polygon.size(); ++i) { + const auto& a = polygon[i]; + const auto& b = polygon[(i + 1) % polygon.size()]; + twice_area += a.x() * b.y() - b.x() * a.y(); + } + if (twice_area < 0.) { + std::reverse(polygon.begin(), polygon.end()); + } + + std::vector ps; + ps.reserve(polygon.size() + 1); + for (auto& p : polygon) { + ps.push_back(taxonomy::make(p.x(), p.y(), 0.)); + } + ps.push_back(ps.front()); + + auto loop = polygon_from_points(ps); + auto face = taxonomy::make(); + face->children = { loop }; + return face; }