Files
IfcOpenShell/src/ifcgeom/mapping/IfcCenterLineProfileDef.cpp
T
Petru Conduraru 2e3d4486ca 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.
2026-07-25 08:34:14 +03:00

131 lines
4.8 KiB
C++

/********************************************************************************
* *
* This file is part of IfcOpenShell. *
* *
* IfcOpenShell is free software: you can redistribute it and/or modify *
* it under the terms of the Lesser GNU General Public License as published by *
* the Free Software Foundation, either version 3.0 of the License, or *
* (at your option) any later version. *
* *
* IfcOpenShell is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* Lesser GNU General Public License for more details. *
* *
* You should have received a copy of the Lesser GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
* *
********************************************************************************/
#include "mapping.h"
#define mapping POSTFIX_SCHEMA(mapping)
using namespace ifcopenshell::geometry;
#include "../profile_helper.h"
taxonomy::ptr mapping::map_impl(const IfcSchema::IfcCenterLineProfileDef* inst) {
const double d = inst->Thickness() * length_unit_ / 2.;
const double eps = settings_.get<settings::Precision>().get();
if (d < eps) {
logger_.Warning("GEO", 403, "Thickness below precision for:", inst);
return nullptr;
}
auto mapped = map(inst->Curve());
if (!mapped) {
return nullptr;
}
auto crv = taxonomy::dcast<taxonomy::loop>(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<Eigen::Vector2d> 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<taxonomy::point3::ptr>(e->start)->ccomponents().head<2>();
Eigen::Vector2d q = boost::get<taxonomy::point3::ptr>(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<Eigen::Vector2d> 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<Eigen::Vector2d> 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<Eigen::Vector2d> 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<taxonomy::point3::ptr> ps;
ps.reserve(polygon.size() + 1);
for (auto& p : polygon) {
ps.push_back(taxonomy::make<taxonomy::point3>(p.x(), p.y(), 0.));
}
ps.push_back(ps.front());
auto loop = polygon_from_points(ps);
auto face = taxonomy::make<taxonomy::face>();
face->children = { loop };
return face;
}