/******************************************************************************** * * * 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 . * * * ********************************************************************************/ #include "OpenCascadeKernel.h" #include "base_utils.h" #include "wire_utils.h" #include #include #include #include #include #include #include #include #include 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(curve)->is_polyhedron()) { Eigen::Vector3d sum = Eigen::Vector3d::Zero(); size_t count = 0; visit_2(std::dynamic_pointer_cast(curve), [&](const taxonomy::point3::ptr& p) { const Eigen::Vector3d& coords = p->ccomponents(); sum += coords; ++count; }); mean = (count > 0) ? (sum / static_cast(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 unique_points; for (auto& e : std::dynamic_pointer_cast(curve)->children) { auto* a = boost::get(&e->start); auto* b = boost::get(&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(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().get(), !settings_.get().get(), 0., settings_.get().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(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().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().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().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 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(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(w->children[0]->basis); enlarged = circ->radius < 1.e-4; if (enlarged) { // @todo immutability circ->radius *= enlarge_factor; auto crv = std::static_pointer_cast(scs->curve); if (crv->matrix) { crv->matrix = taxonomy::make( Eigen::Scaling(enlarge_factor) * crv->matrix->ccomponents() ); } else { crv->matrix = taxonomy::make(); 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( Eigen::Scaling(1. / enlarge_factor) * scs->matrix->ccomponents() ); } else { m = scs->matrix; } results.emplace_back(ConversionResult( scs->instance->as()->id(), m, new OpenCascadeShape(shape), scs->surface_style )); return true; }