Files
IfcOpenShell/src/ifcgeom/kernels/opencascade/sweep_along_curve.cpp
T
2025-11-30 09:56:27 +01:00

356 lines
12 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 "OpenCascadeKernel.h"
#include "base_utils.h"
#include "wire_utils.h"
#include <BRepOffsetAPI_MakePipeShell.hxx>
#include <Geom_Plane.hxx>
#include <ShapeAnalysis_Surface.hxx>
#include <BRepBuilderAPI_Transform.hxx>
#include <ShapeFix_Edge.hxx>
#include <BRepBuilderAPI_MakeFace.hxx>
#include <TopExp.hxx>
#include <Geom_Circle.hxx>
#include <BRepBuilderAPI_MakeSolid.hxx>
using namespace ifcopenshell::geometry;
using namespace ifcopenshell::geometry::kernels;
using namespace IfcGeom;
using namespace IfcGeom::util;
namespace {
bool wire_is_c1_continuous(const TopoDS_Wire& w, double tol) {
// NB Note that c0 continuity is NOT checked!
TopTools_IndexedDataMapOfShapeListOfShape map;
TopExp::MapShapesAndAncestors(w, TopAbs_VERTEX, TopAbs_EDGE, map);
for (int i = 1; i <= map.Extent(); ++i) {
const auto& li = map.FindFromIndex(i);
if (li.Extent() == 2) {
const TopoDS_Vertex& v = TopoDS::Vertex(map.FindKey(i));
const TopoDS_Edge& e0 = TopoDS::Edge(li.First());
const TopoDS_Edge& e1 = TopoDS::Edge(li.Last());
double u0 = BRep_Tool::Parameter(v, e0);
double u1 = BRep_Tool::Parameter(v, e1);
double _, __;
Handle(Geom_Curve) c0 = BRep_Tool::Curve(e0, _, __);
Handle(Geom_Curve) c1 = BRep_Tool::Curve(e1, _, __);
gp_Pnt p;
gp_Vec v0, v1;
c0->D1(u0, p, v0);
c1->D1(u1, p, v1);
if (1. - std::abs(v0.Normalized().Dot(v1.Normalized())) > tol) {
return false;
}
}
}
return true;
}
bool contains_circular_segments(const TopoDS_Wire& w) {
for (TopoDS_Iterator it(w); it.More(); it.Next()) {
const auto& e = TopoDS::Edge(it.Value());
double _, __;
auto crv = BRep_Tool::Curve(e, _, __);
if (crv && crv->DynamicType() == STANDARD_TYPE(Geom_Circle)) {
return true;
}
}
return false;
}
}
bool OpenCascadeKernel::convert(const taxonomy::sweep_along_curve::ptr scs, TopoDS_Shape& result) {
using namespace ifcopenshell::geometry;
bool applied_temporary_offset = false;
Eigen::Vector3d mean;
auto curve = scs->curve;
// Apply temporary offset if the geometry is far away from origin
// Re: https://github.com/IfcOpenShell/IfcOpenShell/issues/7408
// The norm2 that used as a treshold is actually really small though
// is this a coincedence that it solves the problem in this one test case?
if (curve->kind() == taxonomy::LOOP && std::dynamic_pointer_cast<taxonomy::loop>(curve)->is_polyhedron()) {
Eigen::Vector3d sum = Eigen::Vector3d::Zero();
size_t count = 0;
visit_2<taxonomy::point3, taxonomy::loop>(std::dynamic_pointer_cast<taxonomy::loop>(curve), [&](const taxonomy::point3::ptr& p) {
const Eigen::Vector3d& coords = p->ccomponents();
sum += coords;
++count;
});
mean = (count > 0) ? (sum / static_cast<double>(count)).eval() : Eigen::Vector3d::Zero().eval();
if (mean.norm() > 1.e2) {
curve = taxonomy::loop::ptr((taxonomy::loop*)scs->curve->clone_());
applied_temporary_offset = true;
std::set<taxonomy::point3::ptr> unique_points;
for (auto& e : std::dynamic_pointer_cast<taxonomy::loop>(curve)->children) {
auto* a = boost::get<taxonomy::point3::ptr>(&e->start);
auto* b = boost::get<taxonomy::point3::ptr>(&e->end);
if (a) {
unique_points.insert(*a);
}
if (b) {
unique_points.insert(*b);
}
}
for (auto& p : unique_points) {
p->components() -= mean;
}
}
}
auto w = convert_curve(scs->curve);
if (w.which() != 2) {
Logger::Error("Unsupported directrix");
return false;
}
TopoDS_Shape face_;
convert(taxonomy::cast<taxonomy::face>(scs->basis), face_);
TopoDS_Face face;
if (face_.ShapeType() == TopAbs_FACE) {
face = TopoDS::Face(face_);
} else if (face_.ShapeType() == TopAbs_WIRE) {
wire_tolerance_settings settings{
!settings_.get<settings::NoWireIntersectionCheck>().get(),
!settings_.get<settings::NoWireIntersectionTolerance>().get(),
0.,
settings_.get<settings::Precision>().get()
};
if (!IfcGeom::util::convert_wire_to_face(TopoDS::Wire(face_), face, settings)) {
return false;
}
} else {
return false;
}
Handle(Geom_Surface) surface;
if (scs->surface) {
surface = convert_surface(scs->surface);
}
gp_Trsf directrix;
TopoDS_Wire wire = boost::get<TopoDS_Wire>(w);
const bool is_plane = surface && surface->DynamicType() == STANDARD_TYPE(Geom_Plane);
gp_Pln pln;
gp_Pnt directrix_origin;
gp_Vec directrix_tangent;
bool directrix_on_plane = is_plane;
if (is_plane) {
pln = Handle(Geom_Plane)::DownCast(surface)->Pln();
// As per Informal propositions 2: The Directrix shall lie on the ReferenceSurface.
// This is not always the case with the test files in the repository. I am not sure
// how to deal with this and whether my interpretation of the propositions is
// correct. However, if it has been asserted that the vertices of the directrix do
// not conform to the ReferenceSurface, the ReferenceSurface is ignored.
{
for (TopExp_Explorer exp(wire, TopAbs_VERTEX); exp.More(); exp.Next()) {
if (pln.Distance(BRep_Tool::Pnt(TopoDS::Vertex(exp.Current()))) > ALMOST_ZERO) {
directrix_on_plane = false;
Logger::Message(Logger::LOG_WARNING, "The Directrix does not lie on the ReferenceSurface", scs->instance);
break;
}
}
}
}
{
TopoDS_Vertex v0, v1;
TopExp::Vertices(wire, v0, v1);
TopTools_IndexedDataMapOfShapeListOfShape m;
TopExp::MapShapesAndAncestors(wire, TopAbs_VERTEX, TopAbs_EDGE, m);
const TopoDS_Edge& edge = TopoDS::Edge(m.FindFromKey(v0).First());
double u0, u1;
Handle(Geom_Curve) crv = BRep_Tool::Curve(edge, u0, u1);
crv->D1(u0, directrix_origin, directrix_tangent);
}
if (is_plane && pln.Axis().Direction().IsNormal(directrix_tangent, 1.e-5) && directrix_on_plane) {
directrix.SetTransformation(gp_Ax3(directrix_origin, directrix_tangent, pln.Axis().Direction()), gp::XOY());
} else if (!is_plane && surface) {
ShapeAnalysis_Surface sas(surface);
auto pnt2d = sas.ValueOfUV(directrix_origin, settings_.get<settings::Precision>().get());
// @todo should we revisit this pre 0.7 code wrt orientation?
/*
BRepGProp_Face prop(surface_face);
gp_Pnt _;
gp_Vec surface_normal;
prop.Normal(pnt2d.X(), pnt2d.Y(), _, surface_normal);
*/
gp_Pnt _;
gp_Vec surface_normal_u, surface_normal_v;
surface->D1(pnt2d.X(), pnt2d.Y(), _, surface_normal_u, surface_normal_v);
// @todo check order
auto surface_normal = surface_normal_u.Crossed(surface_normal_v);
directrix.SetTransformation(gp_Ax3(directrix_origin, directrix_tangent, surface_normal), gp::XOY());
} else {
directrix.SetTransformation(gp_Ax3(directrix_origin, directrix_tangent), gp::XOY());
}
face = TopoDS::Face(BRepBuilderAPI_Transform(face, directrix));
TopoDS_Face surface_face;
if (surface) {
surface_face = BRepBuilderAPI_MakeFace(surface, settings_.get<settings::Precision>().get()).Face();
}
if (!is_plane && surface) {
TopExp_Explorer exp(wire, TopAbs_EDGE);
for (; exp.More(); exp.Next()) {
ShapeFix_Edge sfe;
sfe.FixAddPCurve(TopoDS::Edge(exp.Current()), surface_face, false, settings_.get<settings::Precision>().get());
}
}
BRep_Builder BB;
TopoDS_Shell comp;
BB.MakeShell(comp);
// NB: Note that StartParam and EndParam param are ignored and the assumption is
// made that the parametric range over which to be swept matches the IfcCurve in
// its entirety.
// BRepOffsetAPI_MakePipeShell does not support FACE, so we need to manually iterate
// over the wires, first processing the outer, then inner. Where the cap face is
// constructed using MakeFace.
auto outer = BRepTools::OuterWire(face);
std::unique_ptr<BRepBuilderAPI_MakeFace> mf0, mf1;
TopoDS_Face f0, f1;
for (int i = 0; i < 2; ++i) {
for (TopExp_Explorer exp(face, TopAbs_WIRE); exp.More(); exp.Next()) {
const auto& section = TopoDS::Wire(exp.Current());
if (section.IsSame(outer) != (i == 0)) {
continue;
}
BRepOffsetAPI_MakePipeShell builder(wire);
builder.Add(section);
builder.SetTransitionMode(contains_circular_segments(wire) && wire_is_c1_continuous(wire, 1.e-2) ? BRepBuilderAPI_Transformed : BRepBuilderAPI_RightCorner);
if (directrix_on_plane) {
builder.SetMode(pln.Axis().Direction());
} else if (!is_plane) {
builder.SetMode(surface_face);
}
builder.Build();
if (!builder.IsDone()) {
return false;
}
auto w0 = TopoDS::Wire(builder.FirstShape());
auto w1 = TopoDS::Wire(builder.LastShape());
if (mf0) {
mf0->Add(w0);
mf1->Add(w1);
} else {
f0 = BRepBuilderAPI_MakeFace(w0).Face();
f1 = BRepBuilderAPI_MakeFace(w1).Face();
if (f0.IsNull() || f1.IsNull()) {
return false;
}
mf0.reset(new BRepBuilderAPI_MakeFace(f0));
mf1.reset(new BRepBuilderAPI_MakeFace(f1));
}
for (TopExp_Explorer exp2(builder.Shape(), TopAbs_FACE); exp2.More(); exp2.Next()) {
BB.Add(comp, exp2.Current());
}
}
}
if (mf0->IsDone() && mf1->IsDone()) {
BB.Add(comp, mf0->Face());
BB.Add(comp, mf1->Face());
} else {
BB.Add(comp, f0);
BB.Add(comp, f1);
}
result = BRepBuilderAPI_MakeSolid(comp).Solid();
if (applied_temporary_offset) {
gp_Trsf trsf;
trsf.SetTranslation(gp_Vec(-mean.x(), -mean.y(), -mean.z()));
result.Move(trsf);
}
return true;
}
bool OpenCascadeKernel::convert_impl(const taxonomy::sweep_along_curve::ptr scs, IfcGeom::ConversionResults& results) {
TopoDS_Shape shape;
// For tiny radii occt will fail building the sweep, in which case we enlarge the inputs to occt, and add a scale matrix to the output
bool enlarged = false;
static double enlarge_factor = 1000.;
if (scs->basis->kind() == taxonomy::FACE) {
auto w = std::static_pointer_cast<taxonomy::face>(scs->basis)->children[0];
if (w->children.size() == 1 && w->children[0]->basis && w->children[0]->basis->kind() == taxonomy::CIRCLE) {
auto circ = std::static_pointer_cast<taxonomy::circle>(w->children[0]->basis);
enlarged = circ->radius < 1.e-4;
if (enlarged) {
// @todo immutability
circ->radius *= enlarge_factor;
auto crv = std::static_pointer_cast<taxonomy::geom_item>(scs->curve);
if (crv->matrix) {
crv->matrix = taxonomy::make<taxonomy::matrix4>(
Eigen::Scaling(enlarge_factor) *
crv->matrix->ccomponents()
);
} else {
crv->matrix = taxonomy::make<taxonomy::matrix4>();
crv->matrix->components().topLeftCorner<3, 3>() = Eigen::Scaling(enlarge_factor, enlarge_factor, enlarge_factor).toDenseMatrix();
}
}
}
}
if (!convert(scs, shape)) {
return false;
}
taxonomy::matrix4::ptr m;
if (enlarged) {
m = taxonomy::make<taxonomy::matrix4>(
Eigen::Scaling(1. / enlarge_factor) *
scs->matrix->ccomponents()
);
} else {
m = scs->matrix;
}
results.emplace_back(ConversionResult(
scs->instance->as<IfcUtil::IfcBaseEntity>()->id(),
m,
new OpenCascadeShape(shape),
scs->surface_style
));
return true;
}