From 4dc3dc37a53478c954d79f9d55d2c0ac738fc80d Mon Sep 17 00:00:00 2001 From: Thomas Krijnen Date: Mon, 14 Nov 2022 10:31:00 +0100 Subject: [PATCH] Refactoring --- src/ifcgeom/ConversionSettings.cpp | 9 + src/ifcgeom/ConversionSettings.h | 61 + .../kernel_agnostic/AbstractKernel.cpp | 77 +- src/ifcgeom/kernel_agnostic/AbstractKernel.h | 31 +- src/ifcgeom/kernels/cgal/CgalKernel.h | 10 +- .../kernels/opencascade/IfcGeomCurves.cpp_ | 198 - .../kernels/opencascade/IfcGeomFaces.cpp_ | 1181 ------ .../kernels/opencascade/IfcGeomFunctions.cpp_ | 3586 ----------------- .../kernels/opencascade/IfcGeomHelpers.cpp_ | 419 -- .../opencascade/IfcGeomSerialisation.cpp_ | 660 --- .../kernels/opencascade/IfcGeomShapes.cpp | 838 +--- .../kernels/opencascade/IfcGeomShapes.cpp_ | 1289 ------ .../kernels/opencascade/IfcGeomWires.cpp_ | 929 ----- .../kernels/opencascade/OpenCascadeKernel.h | 90 +- .../kernels/opencascade/boolean_utils.cpp | 804 ++++ .../kernels/opencascade/boolean_utils.h | 91 + .../kernels/opencascade/face_definition.cpp | 14 +- .../kernels/opencascade/face_definition.h | 77 +- .../kernels/opencascade/faceset_helper.cpp | 280 ++ src/ifcgeom/kernels/opencascade/shell.cpp | 81 + .../kernels/opencascade/sweep_utils.cpp | 356 ++ src/ifcgeom/kernels/opencascade/sweep_utils.h | 59 + .../kernels/opencascade/wire_builder.cpp | 8 +- .../kernels/opencascade/wire_builder.h | 2 +- .../kernels/opencascade/wire_utils.cpp | 569 +++ src/ifcgeom/kernels/opencascade/wire_utils.h | 28 + src/ifcgeom/schema_agnostic/Converter.cpp | 3 +- src/ifcgeom/schema_agnostic/Converter.h | 43 +- 28 files changed, 2438 insertions(+), 9355 deletions(-) create mode 100644 src/ifcgeom/ConversionSettings.cpp create mode 100644 src/ifcgeom/ConversionSettings.h delete mode 100644 src/ifcgeom/kernels/opencascade/IfcGeomCurves.cpp_ delete mode 100644 src/ifcgeom/kernels/opencascade/IfcGeomFaces.cpp_ delete mode 100644 src/ifcgeom/kernels/opencascade/IfcGeomFunctions.cpp_ delete mode 100644 src/ifcgeom/kernels/opencascade/IfcGeomHelpers.cpp_ delete mode 100644 src/ifcgeom/kernels/opencascade/IfcGeomSerialisation.cpp_ delete mode 100644 src/ifcgeom/kernels/opencascade/IfcGeomShapes.cpp_ delete mode 100644 src/ifcgeom/kernels/opencascade/IfcGeomWires.cpp_ create mode 100644 src/ifcgeom/kernels/opencascade/boolean_utils.cpp create mode 100644 src/ifcgeom/kernels/opencascade/boolean_utils.h create mode 100644 src/ifcgeom/kernels/opencascade/faceset_helper.cpp create mode 100644 src/ifcgeom/kernels/opencascade/sweep_utils.cpp create mode 100644 src/ifcgeom/kernels/opencascade/sweep_utils.h create mode 100644 src/ifcgeom/kernels/opencascade/wire_utils.cpp create mode 100644 src/ifcgeom/kernels/opencascade/wire_utils.h diff --git a/src/ifcgeom/ConversionSettings.cpp b/src/ifcgeom/ConversionSettings.cpp new file mode 100644 index 0000000000..555b1e5e78 --- /dev/null +++ b/src/ifcgeom/ConversionSettings.cpp @@ -0,0 +1,9 @@ +#include "ConversionSettings.h" + +void ifcopenshell::geometry::ConversionSettings::setValue(GeomValue var, double value) { + values_[var] = value; +} + +double ifcopenshell::geometry::ConversionSettings::getValue(GeomValue var) const { + return values_[var]; +} diff --git a/src/ifcgeom/ConversionSettings.h b/src/ifcgeom/ConversionSettings.h new file mode 100644 index 0000000000..d17266179f --- /dev/null +++ b/src/ifcgeom/ConversionSettings.h @@ -0,0 +1,61 @@ +#ifndef CONVERSIONSETTINGS_H +#define CONVERSIONSETTINGS_H + +#include + +namespace ifcopenshell { namespace geometry { + + class NativeElement; + + class ConversionSettings { + public: + // Tolerances and settings for various geometrical operations: + enum GeomValue { + // Specifies the deflection of the mesher + // Default: 0.001m / 1mm + GV_DEFLECTION_TOLERANCE, + // Specifies the minimal area of a face to be included in an IfcConnectedFaceset + // Read-only + GV_MINIMAL_FACE_AREA, + // Specifies the threshold distance under which cartesian points are deemed equal + // Read-only + GV_POINT_EQUALITY_TOLERANCE, + // Specifies maximum number of faces for a shell to be reoriented. + // Default: -1 + GV_MAX_FACES_TO_ORIENT, + // The length unit used the creation of TopoDS_Shapes, primarily affects the + // interpretation of IfcCartesianPoints and IfcVector magnitudes + // DefaultL 1.0 + GV_LENGTH_UNIT, + // The plane angle unit used for the creation of TopoDS_Shapes, primarily affects + // the interpretation of IfcParamaterValues of IfcTrimmedCurves + // Default: -1.0 (= not set, fist try degrees, then radians) + GV_PLANEANGLE_UNIT, + // The precision used in boolean operations, setting this value too low results + // in artefacts and potentially modelling failures + // Default: 0.00001 (obtained from IfcGeometricRepresentationContext if available) + GV_PRECISION, + // Whether to process shapes of type Face or higher (1) Wire or lower (-1) or all (0) + GV_DIMENSIONALITY + }; + + void setValue(GeomValue var, double value); + + double getValue(GeomValue var) const; + + private: + std::array values_ = { + /* deflection_tolerance = */ 0.001, + /* wire_creation_tolerance = */ 0.0001, + /* point_equality_tolerance = */ 0.00001, + /* max_faces_to_sew = */ -1.0, + /* ifc_length_unit = */ 1.0, + /* ifc_planeangle_unit = */ -1.0, + /* modelling_precision = */ 0.00001, + /* dimensionality = */ 1., + }; + }; + +} } + +#endif \ No newline at end of file diff --git a/src/ifcgeom/kernel_agnostic/AbstractKernel.cpp b/src/ifcgeom/kernel_agnostic/AbstractKernel.cpp index 92f1e5865d..f0c9008f72 100644 --- a/src/ifcgeom/kernel_agnostic/AbstractKernel.cpp +++ b/src/ifcgeom/kernel_agnostic/AbstractKernel.cpp @@ -19,9 +19,9 @@ bool ifcopenshell::geometry::kernels::AbstractKernel::convert(const taxonomy::it ifcopenshell::geometry::kernels::AbstractKernel* ifcopenshell::geometry::kernels::construct(const std::string& geometry_library, IfcParse::IfcFile* file) { const std::string geometry_library_lower = boost::to_lower_copy(geometry_library); if (geometry_library_lower == "opencascade") { - return new OpenCascadeKernel; + return new OpenCascadeKernel(settings); } else if (geometry_library_lower == "cgal") { - return new CgalKernel; + return new CgalKernel(settings); } else { throw IfcParse::IfcException("No geometry kernel registered for " + geometry_library); } @@ -40,76 +40,3 @@ bool ifcopenshell::geometry::kernels::AbstractKernel::convert_impl(const taxonom } return r.size() > s; } - -//void ifcopenshell::geometry::kernels::AbstractKernel::set_conversion_placement_rel_to(const IfcParse::declaration* type) { -// placement_rel_to = type; -//} -// -//void ifcopenshell::geometry::kernels::AbstractKernel::setValue(GeomValue var, double value) { -// switch (var) { -// case GV_DEFLECTION_TOLERANCE: -// deflection_tolerance = value; -// break; -// case GV_POINT_EQUALITY_TOLERANCE: -// point_equality_tolerance = value; -// break; -// case GV_LENGTH_UNIT: -// ifc_length_unit = value; -// break; -// case GV_PLANEANGLE_UNIT: -// ifc_planeangle_unit = value; -// break; -// case GV_PRECISION: -// modelling_precision = value; -// break; -// case GV_DIMENSIONALITY: -// dimensionality = value; -// break; -// default: -// assert(!"never reach here"); -// } -//} -// -//double ifcopenshell::geometry::kernels::AbstractKernel::getValue(GeomValue var) const { -// switch (var) { -// case GV_DEFLECTION_TOLERANCE: -// return deflection_tolerance; -// case GV_MINIMAL_FACE_AREA: -// // Considering a right-angled triangle, this about the smallest -// // area you can obtain without the vertices being confused. -// return modelling_precision * modelling_precision / 2.; -// case GV_POINT_EQUALITY_TOLERANCE: -// return point_equality_tolerance; -// case GV_LENGTH_UNIT: -// return ifc_length_unit; -// break; -// case GV_PLANEANGLE_UNIT: -// return ifc_planeangle_unit; -// break; -// case GV_PRECISION: -// return modelling_precision; -// break; -// case GV_DIMENSIONALITY: -// return dimensionality; -// break; -// } -// assert(!"never reach here"); -// return 0; -//} -// -// -// -// -//template IFC_GEOM_API ifcopenshell::geometry::kernels::NativeElement* ifcopenshell::geometry::kernels::AbstractKernel::create_brep_for_representation_and_product( -// const IteratorSettings& settings, IfcSchema::IfcRepresentation* representation, IfcSchema::IfcProduct* product); -//template IFC_GEOM_API ifcopenshell::geometry::kernels::NativeElement* ifcopenshell::geometry::kernels::AbstractKernel::create_brep_for_representation_and_product( -// const IteratorSettings& settings, IfcSchema::IfcRepresentation* representation, IfcSchema::IfcProduct* product); -//template IFC_GEOM_API ifcopenshell::geometry::kernels::NativeElement* ifcopenshell::geometry::kernels::AbstractKernel::create_brep_for_representation_and_product( -// const IteratorSettings& settings, IfcSchema::IfcRepresentation* representation, IfcSchema::IfcProduct* product); -// -//template IFC_GEOM_API ifcopenshell::geometry::kernels::NativeElement* ifcopenshell::geometry::kernels::AbstractKernel::create_brep_for_processed_representation( -// const IteratorSettings& settings, IfcSchema::IfcRepresentation* representation, IfcSchema::IfcProduct* product, ifcopenshell::geometry::kernels::NativeElement* brep); -//template IFC_GEOM_API ifcopenshell::geometry::kernels::NativeElement* ifcopenshell::geometry::kernels::AbstractKernel::create_brep_for_processed_representation( -// const IteratorSettings& settings, IfcSchema::IfcRepresentation* representation, IfcSchema::IfcProduct* product, ifcopenshell::geometry::kernels::NativeElement* brep); -//template IFC_GEOM_API ifcopenshell::geometry::kernels::NativeElement* ifcopenshell::geometry::kernels::AbstractKernel::create_brep_for_processed_representation( -// const IteratorSettings& settings, IfcSchema::IfcRepresentation* representation, IfcSchema::IfcProduct* product, ifcopenshell::geometry::kernels::NativeElement* brep); \ No newline at end of file diff --git a/src/ifcgeom/kernel_agnostic/AbstractKernel.h b/src/ifcgeom/kernel_agnostic/AbstractKernel.h index 5eb2172194..9a2bee9dc9 100644 --- a/src/ifcgeom/kernel_agnostic/AbstractKernel.h +++ b/src/ifcgeom/kernel_agnostic/AbstractKernel.h @@ -5,6 +5,7 @@ #include "../../ifcgeom/schema_agnostic/ifc_geom_api.h" #include "../../ifcgeom/schema_agnostic/IfcGeomRepresentation.h" #include "../../ifcgeom/taxonomy.h" +#include "../../ifcgeom/ConversionSettings.h" static const double ALMOST_ZERO = 1.e-9; @@ -18,31 +19,15 @@ namespace ifcopenshell { namespace geometry { namespace kernels { class IFC_GEOM_API AbstractKernel { protected: // For stopping PlacementRelTo recursion in convert(const IfcSchema::IfcObjectPlacement* l, gp_Trsf& trsf) - const IfcParse::declaration* placement_rel_to; - - double deflection_tolerance; - double wire_creation_tolerance; - double point_equality_tolerance; - double max_faces_to_sew; - double ifc_length_unit; - double ifc_planeangle_unit; - double modelling_precision; - double dimensionality; - - std::string geometry_library; + const IfcParse::declaration* placement_rel_to = nullptr; + std::string geometry_library_; + ConversionSettings settings_; public: - AbstractKernel(const std::string& geometry_library) - : geometry_library(geometry_library) - , deflection_tolerance(0.001) - , wire_creation_tolerance(0.0001) - , point_equality_tolerance(0.00001) - , max_faces_to_sew(-1.0) - , ifc_length_unit(1.0) - , ifc_planeangle_unit(-1.0) - , modelling_precision(0.00001) - , dimensionality(1.) - , placement_rel_to(0) {} + AbstractKernel(const std::string& geometry_library, const ConversionSettings& settings) + : geometry_library_(geometry_library) + , settings_(settings) + {} bool convert(const taxonomy::item*, ifcopenshell::geometry::ConversionResults&); diff --git a/src/ifcgeom/kernels/cgal/CgalKernel.h b/src/ifcgeom/kernels/cgal/CgalKernel.h index 7fcbd0d1ac..f7bbacb324 100644 --- a/src/ifcgeom/kernels/cgal/CgalKernel.h +++ b/src/ifcgeom/kernels/cgal/CgalKernel.h @@ -81,14 +81,10 @@ namespace kernels { } public: - CgalKernel() - : AbstractKernel("cgal") - // @todo - , precision_(1.e-5) + CgalKernel(const ConversionSettings& settings) + : AbstractKernel("cgal", settings) , circle_segments_(16) - { - - } + {} void remove_duplicate_points_from_loop(cgal_wire_t& polygon); diff --git a/src/ifcgeom/kernels/opencascade/IfcGeomCurves.cpp_ b/src/ifcgeom/kernels/opencascade/IfcGeomCurves.cpp_ deleted file mode 100644 index 728fa4eabc..0000000000 --- a/src/ifcgeom/kernels/opencascade/IfcGeomCurves.cpp_ +++ /dev/null @@ -1,198 +0,0 @@ -/******************************************************************************** - * * - * 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 . * - * * - ********************************************************************************/ - -/******************************************************************************** - * * - * Implementations of the various conversion functions defined in IfcRegister.h * - * * - ********************************************************************************/ - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include -#include -#include - -#include - -#include - -#include "../../../ifcgeom/kernels/opencascade/IfcGeom.h" - -#ifdef SCHEMA_HAS_IfcBSplineCurveWithKnots -#include -#endif - -#define Kernel POSTFIX_SCHEMA(Kernel) - -bool IfcGeom::Kernel::convert(const IfcSchema::IfcCircle* l, Handle(Geom_Curve)& curve) { - const double r = l->Radius() * getValue(GV_LENGTH_UNIT); - if ( r < ALMOST_ZERO ) { - Logger::Message(Logger::LOG_ERROR, "Radius not greater than zero for:", l); - return false; - } - gp_Trsf trsf; - IfcSchema::IfcAxis2Placement* placement = l->Position(); - if (placement->declaration().is(IfcSchema::IfcAxis2Placement3D::Class())) { - IfcGeom::Kernel::convert((IfcSchema::IfcAxis2Placement3D*)placement,trsf); - } else { - gp_Trsf2d trsf2d; - IfcGeom::Kernel::convert((IfcSchema::IfcAxis2Placement2D*)placement,trsf2d); - trsf = trsf2d; - } - gp_Ax2 ax = gp_Ax2().Transformed(trsf); - curve = new Geom_Circle(ax, r); - return true; -} -bool IfcGeom::Kernel::convert(const IfcSchema::IfcEllipse* l, Handle(Geom_Curve)& curve) { - double x = l->SemiAxis1() * getValue(GV_LENGTH_UNIT); - double y = l->SemiAxis2() * getValue(GV_LENGTH_UNIT); - if (x < ALMOST_ZERO || y < ALMOST_ZERO) { - Logger::Message(Logger::LOG_ERROR, "Radius not greater than zero for:", l); - return false; - } - // Open Cascade does not allow ellipses of which the minor radius - // is greater than the major radius. Hence, in this case, the - // ellipse is rotated. Note that special care needs to be taken - // when creating a trimmed curve off of an ellipse like this. - const bool rotated = y > x; - gp_Trsf trsf; - IfcSchema::IfcAxis2Placement* placement = l->Position(); - if (placement->declaration().is(IfcSchema::IfcAxis2Placement3D::Class())) { - convert((IfcSchema::IfcAxis2Placement3D*)placement,trsf); - } else { - gp_Trsf2d trsf2d; - convert((IfcSchema::IfcAxis2Placement2D*)placement,trsf2d); - trsf = trsf2d; - } - gp_Ax2 ax = gp_Ax2(); - if (rotated) { - ax.Rotate(ax.Axis(), M_PI / 2.); - std::swap(x, y); - } - ax.Transform(trsf); - curve = new Geom_Ellipse(ax, x, y); - return true; -} -bool IfcGeom::Kernel::convert(const IfcSchema::IfcLine* l, Handle(Geom_Curve)& curve) { - gp_Pnt pnt;gp_Vec vec; - convert(l->Pnt(),pnt); - convert(l->Dir(),vec); - // See note at IfcGeomWires.cpp:237 - curve = new Geom_Line(pnt,vec); - return true; -} - -#ifdef SCHEMA_HAS_IfcBSplineCurveWithKnots -bool IfcGeom::Kernel::convert(const IfcSchema::IfcBSplineCurveWithKnots* l, Handle(Geom_Curve)& curve) { - - const bool is_rational = l->declaration().is(IfcSchema::IfcRationalBSplineCurveWithKnots::Class()); - - const IfcSchema::IfcCartesianPoint::list::ptr cps = l->ControlPointsList(); - const std::vector mults = l->KnotMultiplicities(); - const std::vector knots = l->Knots(); - - TColgp_Array1OfPnt Poles(0, cps->size() - 1); - TColStd_Array1OfReal Weights(0, cps->size() - 1); - TColStd_Array1OfReal Knots(0, (int)knots.size() - 1); - TColStd_Array1OfInteger Mults(0, (int)mults.size() - 1); - Standard_Integer Degree = l->Degree(); - Standard_Boolean Periodic = l->ClosedCurve(); - - int i; - - if (is_rational) { - IfcSchema::IfcRationalBSplineCurveWithKnots* rl = (IfcSchema::IfcRationalBSplineCurveWithKnots*)l; - std::vector weights = rl->WeightsData(); - - i = 0; - for (std::vector::const_iterator it = weights.begin(); it != weights.end(); ++it, ++i) { - Weights(i) = *it; - } - } - - i = 0; - for (IfcSchema::IfcCartesianPoint::list::it it = cps->begin(); it != cps->end(); ++it, ++i) { - gp_Pnt pnt; - if (!convert(*it, pnt)) return false; - Poles(i) = pnt; - } - - i = 0; - for (std::vector::const_iterator it = mults.begin(); it != mults.end(); ++it, ++i) { - Mults(i) = *it; - } - - i = 0; - for (std::vector::const_iterator it = knots.begin(); it != knots.end(); ++it, ++i) { - Knots(i) = *it; - } - - if (is_rational) { - curve = new Geom_BSplineCurve(Poles, Weights, Knots, Mults, Degree, Periodic); - } else { - curve = new Geom_BSplineCurve(Poles, Knots, Mults, Degree, Periodic); - } - return true; -} -#endif \ No newline at end of file diff --git a/src/ifcgeom/kernels/opencascade/IfcGeomFaces.cpp_ b/src/ifcgeom/kernels/opencascade/IfcGeomFaces.cpp_ deleted file mode 100644 index 58cd6dda47..0000000000 --- a/src/ifcgeom/kernels/opencascade/IfcGeomFaces.cpp_ +++ /dev/null @@ -1,1181 +0,0 @@ -/******************************************************************************** - * * - * 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 . * - * * - ********************************************************************************/ - -/******************************************************************************** - * * - * Implementations of the various conversion functions defined in IfcRegister.h * - * * - ********************************************************************************/ - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include - -#include -#include -#include -#include - -#include -#include - -#include - -#include -#include -#include - -#include - -#include -#include - -#include - -#include "../../../ifcgeom/kernels/opencascade/IfcGeom.h" - -#ifdef SCHEMA_HAS_IfcBSplineSurfaceWithKnots -#include -#include -#include -#include -#endif - -#define Kernel POSTFIX_SCHEMA(Kernel) - -namespace { - /* Returns whether wire conforms to a polyhedron, i.e. only edges with linear curves*/ - bool is_polyhedron(const TopoDS_Wire& wire) { - double a, b; - TopLoc_Location l; - - TopoDS_Iterator it(wire, false, false); - for (; it.More(); it.Next()) { - auto crv = BRep_Tool::Curve(TopoDS::Edge(it.Value()), l, a, b); - if (!crv || crv->DynamicType() != STANDARD_TYPE(Geom_Line)) { - return false; - } - } - - return true; - } - - /* A temporary structure to store the intermediate data for the face conversion */ - class face_definition { - private: - Handle(Geom_Surface) surface_; - std::vector wires_; - bool all_outer_; - public: - face_definition() : surface_(), all_outer_(false) {} - - typedef std::vector::const_iterator wire_it; - - bool& all_outer() { - return all_outer_; - } - - bool all_outer() const { - return all_outer_; - } - - Handle(Geom_Surface)& surface() { - return surface_; - } - - const Handle(Geom_Surface)& surface() const { - return surface_; - } - - std::vector& wires() { - return wires_; - } - - const TopoDS_Wire& outer_wire() const { - return wires_.front(); - } - - std::pair inner_wires() const { - return { wires_.begin() + 1, wires_.end() }; - } - }; -} - -bool IfcGeom::Kernel::convert(const IfcSchema::IfcFace* l, TopoDS_Shape& result) { - IfcSchema::IfcFaceBound::list::ptr bounds = l->Bounds(); - - face_definition fd; - - const bool is_face_surface = l->declaration().is(IfcSchema::IfcFaceSurface::Class()); - - if (is_face_surface) { - IfcSchema::IfcFaceSurface* fs = (IfcSchema::IfcFaceSurface*) l; - fs->FaceSurface(); - // FIXME: Surfaces are interpreted as a TopoDS_Shape - TopoDS_Shape surface_shape; - if (!convert_shape(fs->FaceSurface(), surface_shape)) return false; - - // FIXME: Assert this obtaines the only face - TopExp_Explorer exp(surface_shape, TopAbs_FACE); - if (!exp.More()) return false; - - TopoDS_Face surface = TopoDS::Face(exp.Current()); - fd.surface() = BRep_Tool::Surface(surface); - } - - const int num_bounds = bounds->size(); - int num_outer_bounds = 0; - - for (IfcSchema::IfcFaceBound::list::it it = bounds->begin(); it != bounds->end(); ++it) { - IfcSchema::IfcFaceBound* bound = *it; - if (bound->declaration().is(IfcSchema::IfcFaceOuterBound::Class())) num_outer_bounds ++; - } - - // The number of outer bounds should be one according to the schema. Also Open Cascade - // expects this, but it is not strictly checked. Regardless, if the number is greater, - // the face will still be processed as long as there are no holes. A compound of faces - // is returned in that case. - if (num_bounds > 1 && num_outer_bounds > 1 && num_bounds != num_outer_bounds) { - Logger::Message(Logger::LOG_ERROR, "Invalid configuration of boundaries for:", l); - return false; - } - - if (num_outer_bounds > 1) { - Logger::Message(Logger::LOG_WARNING, "Multiple outer boundaries for:", l); - fd.all_outer() = true; - } - - TopTools_DataMapOfShapeInteger wire_senses; - - for (int process_interior = 0; process_interior <= 1; ++process_interior) { - for (IfcSchema::IfcFaceBound::list::it it = bounds->begin(); it != bounds->end(); ++it) { - IfcSchema::IfcFaceBound* bound = *it; - IfcSchema::IfcLoop* loop = bound->Bound(); - - bool same_sense = bound->Orientation(); - const bool is_interior = - !bound->declaration().is(IfcSchema::IfcFaceOuterBound::Class()) && - (num_bounds > 1) && - (num_outer_bounds < num_bounds); - - // The exterior face boundary is processed first - if (is_interior == !process_interior) continue; - - TopoDS_Wire wire; - if (faceset_helper_ && loop->as()) { - if (!faceset_helper_->wire(loop->as(), wire)) { - Logger::Message(Logger::LOG_WARNING, "Face boundary loop not included", loop); - continue; - } - } else if (!convert_wire(loop, wire)) { - Logger::Message(Logger::LOG_ERROR, "Failed to process face boundary loop", loop); - return false; - } - - if (!same_sense) { - wire.Reverse(); - } - - wire_senses.Bind(wire.Oriented(TopAbs_FORWARD), same_sense ? TopAbs_FORWARD : TopAbs_REVERSED); - - fd.wires().emplace_back(wire); - } - } - - if (fd.wires().empty()) { - Logger::Warning("Face with no boundaries", l); - return false; - } - - if (fd.surface().IsNull()) { - // Use the first wire to find a plane manually for polygonal wires - const TopoDS_Wire& wire = fd.wires().front(); - if (is_polyhedron(wire)) { - TopExp_Explorer exp(wire, TopAbs_EDGE); - int count = 0; - TopoDS_Edge edges[2]; - for (; exp.More(); exp.Next(), count++) { - if (count < 2) { - edges[count] = TopoDS::Edge(exp.Current()); - } - } - - if (count == 3) { - // Help Open Cascade by finding the plane more efficiently - double _, __; - Handle(Geom_Line) c1 = Handle(Geom_Line)::DownCast(BRep_Tool::Curve(edges[0], _, __)); - Handle(Geom_Line) c2 = Handle(Geom_Line)::DownCast(BRep_Tool::Curve(edges[1], _, __)); - - const gp_Vec ab = c1->Position().Direction(); - const gp_Vec ac = c2->Position().Direction(); - const gp_Vec cross = ab.Crossed(ac); - - if (cross.SquareMagnitude() > ALMOST_ZERO) { - const gp_Dir n = cross; - fd.surface() = new Geom_Plane(c1->Position().Location(), n); - } - } else { - gp_Pln pln; - if (approximate_plane_through_wire(wire, pln)) { - fd.surface() = new Geom_Plane(pln); - } - } - } - } - - if (fd.surface().IsNull()) { - // BRepLib_FindSurface is used in case no surface is found or provided - - const TopoDS_Wire& wire = fd.wires().front(); - - BRepLib_FindSurface fs(wire, getValue(GV_PRECISION), true, true); - if (fs.Found()) { - fd.surface() = fs.Surface(); - ShapeFix_ShapeTolerance ftol; - ftol.SetTolerance(wire, fs.ToleranceReached(), TopAbs_WIRE); - } - } - - TopTools_ListOfShape face_list; - - if (fd.surface().IsNull()) { - // The set of wires is triangulated in case no surface can be found - Logger::Message(Logger::LOG_WARNING, "Triangulating face boundaries for face", l); - - if (fd.all_outer()) { - for (const auto& w : fd.wires()) { - TopTools_ListOfShape fl; - triangulate_wire({ w }, fl); - face_list.Append(fl); - } - } else { - triangulate_wire(fd.wires(), face_list); - } - } else if (!fd.all_outer()) { - BRepBuilderAPI_MakeFace mf(fd.surface(), fd.outer_wire()); - - if (mf.IsDone()) { - // Is this necessary - TopoDS_Face f = mf.Face(); - mf.Init(f); - - for (auto it = fd.inner_wires().first; it != fd.inner_wires().second; ++it) { - mf.Add(*it); - } - - face_list.Append(mf.Face()); - } - } else { - for (const auto& w : fd.wires()) { - BRepBuilderAPI_MakeFace mf(fd.surface(), w); - if (mf.IsDone()) { - face_list.Append(mf.Face()); - } - } - } - - if (!fd.surface().IsNull()) { - // Some fixes for orientation and p-curves. If we have no surface, it - // means the face has been triangulated in which case none of these - // fixes are necessary. - - if (fd.surface()->DynamicType() != STANDARD_TYPE(Geom_Plane)) { - // In case of (non-planar) face surface, p-curves need to be computed. - // For planar faces, Open Cascade generates p-curves on the fly. - - for (TopTools_ListIteratorOfListOfShape it(face_list); it.More(); it.Next()) { - // Small chance there are multiple faces - const TopoDS_Face& face = TopoDS::Face(it.Value()); - for (TopExp_Explorer exp2(face, TopAbs_EDGE); exp2.More(); exp2.Next()) { - const TopoDS_Edge& edge = TopoDS::Edge(exp2.Current()); - ShapeFix_Edge fix_edge; - fix_edge.FixAddPCurve(edge, face, false, getValue(GV_PRECISION)); - } - } - } - - for (TopTools_ListIteratorOfListOfShape it(face_list); it.More(); it.Next()) { - const TopoDS_Face& face = TopoDS::Face(it.Value()); - - ShapeFix_Face sfs(TopoDS::Face(face)); - TopTools_DataMapOfShapeListOfShape wire_map; - sfs.FixOrientation(wire_map); - - TopoDS_Iterator jt(face, false); - for (; jt.More(); jt.Next()) { - const TopoDS_Wire& w = TopoDS::Wire(jt.Value()); - // tfk: @todo if wire_map contains w, I would assume wire_senses also contains w, - // this is not the case in github issue #405. - if (wire_map.IsBound(w) && wire_senses.IsBound(w)) { - const TopTools_ListOfShape& shapes = wire_map.Find(w); - TopTools_ListIteratorOfListOfShape kt(shapes); - for (; kt.More(); kt.Next()) { - // Apparently the wire got reversed, so register it with opposite orientation in the map - wire_senses.Bind(kt.Value(), wire_senses.Find(w) == TopAbs_FORWARD ? TopAbs_REVERSED : TopAbs_FORWARD); - } - } - } - - it.Value() = sfs.Face(); - } - - for (TopTools_ListIteratorOfListOfShape it(face_list); it.More(); it.Next()) { - TopoDS_Face& face = TopoDS::Face(it.Value()); - - bool all_reversed = true; - TopoDS_Iterator jt(face, false); - for (; jt.More(); jt.Next()) { - const TopoDS_Wire& w = TopoDS::Wire(jt.Value()); - if (!wire_senses.IsBound(w.Oriented(TopAbs_FORWARD)) || (w.Orientation() == wire_senses.Find(w.Oriented(TopAbs_FORWARD)))) { - all_reversed = false; - } - } - - if (all_reversed) { - face.Reverse(); - } - } - } - - if (face_list.Extent() > 1) { - TopoDS_Compound compound; - BRep_Builder builder; - builder.MakeCompound(compound); - for (TopTools_ListIteratorOfListOfShape it(face_list); it.More(); it.Next()) { - TopoDS_Face& face = TopoDS::Face(it.Value()); - builder.Add(compound, face); - } - result = compound; - } else { - result = face_list.First(); - } - - return true; -} - -bool IfcGeom::Kernel::convert(const IfcSchema::IfcArbitraryClosedProfileDef* l, TopoDS_Shape& face) { - TopoDS_Wire wire; - if (!convert_wire(l->OuterCurve(), wire)) { - return false; - } - - assert_closed_wire(wire); - - TopoDS_Face f; - bool success = convert_wire_to_face(wire, f); - if (success) { - face = f; - } - return success; -} - -bool IfcGeom::Kernel::convert(const IfcSchema::IfcArbitraryProfileDefWithVoids* l, TopoDS_Shape& face) { - TopoDS_Wire profile; - if (!convert_wire(l->OuterCurve(), profile)) { - return false; - } - - assert_closed_wire(profile); - - BRepBuilderAPI_MakeFace mf(profile); - - IfcSchema::IfcCurve::list::ptr voids = l->InnerCurves(); - - for(IfcSchema::IfcCurve::list::it it = voids->begin(); it != voids->end(); ++it) { - TopoDS_Wire hole; - if (convert_wire(*it, hole)) { - assert_closed_wire(hole); - mf.Add(hole); - } - } - - ShapeFix_Shape sfs(mf.Face()); - sfs.Perform(); - face = sfs.Shape(); - - return true; -} - -bool IfcGeom::Kernel::convert(const IfcSchema::IfcRectangleProfileDef* l, TopoDS_Shape& face) { - const double x = l->XDim() / 2.0f * getValue(GV_LENGTH_UNIT); - const double y = l->YDim() / 2.0f * getValue(GV_LENGTH_UNIT); - - if ( x < ALMOST_ZERO || y < ALMOST_ZERO ) { - Logger::Message(Logger::LOG_NOTICE,"Skipping zero sized profile:",l); - return false; - } - - gp_Trsf2d trsf2d; - bool has_position = true; -#ifdef SCHEMA_IfcParameterizedProfileDef_Position_IS_OPTIONAL - has_position = l->hasPosition(); -#endif - if (has_position) { - IfcGeom::Kernel::convert(l->Position(), trsf2d); - } - - double coords[8] = {-x,-y,x,-y,x,y,-x,y}; - return profile_helper(4,coords,0,0,0,trsf2d,face); -} - -bool IfcGeom::Kernel::convert(const IfcSchema::IfcRoundedRectangleProfileDef* l, TopoDS_Shape& face) { - const double x = l->XDim() / 2.0f * getValue(GV_LENGTH_UNIT); - const double y = l->YDim() / 2.0f * getValue(GV_LENGTH_UNIT); - const double r = l->RoundingRadius() * getValue(GV_LENGTH_UNIT); - - if ( x < ALMOST_ZERO || y < ALMOST_ZERO || r < ALMOST_ZERO ) { - Logger::Message(Logger::LOG_NOTICE,"Skipping zero sized profile:",l); - return false; - } - - gp_Trsf2d trsf2d; - bool has_position = true; -#ifdef SCHEMA_IfcParameterizedProfileDef_Position_IS_OPTIONAL - has_position = l->hasPosition(); -#endif - if (has_position) { - IfcGeom::Kernel::convert(l->Position(), trsf2d); - } - - double coords[8] = {-x,-y, x,-y, x,y, -x,y}; - int fillets[4] = {0,1,2,3}; - double radii[4] = {r,r,r,r}; - return profile_helper(4,coords,4,fillets,radii,trsf2d,face); -} - -bool IfcGeom::Kernel::convert(const IfcSchema::IfcRectangleHollowProfileDef* l, TopoDS_Shape& face) { - const double x = l->XDim() / 2.0f * getValue(GV_LENGTH_UNIT); - const double y = l->YDim() / 2.0f * getValue(GV_LENGTH_UNIT); - const double d = l->WallThickness() * getValue(GV_LENGTH_UNIT); - - const bool fr1 = l->hasOuterFilletRadius(); - const bool fr2 = l->hasInnerFilletRadius(); - - const double r1 = fr1 ? l->OuterFilletRadius() * getValue(GV_LENGTH_UNIT) : 0.; - const double r2 = fr2 ? l->InnerFilletRadius() * getValue(GV_LENGTH_UNIT) : 0.; - - if ( x < ALMOST_ZERO || y < ALMOST_ZERO ) { - Logger::Message(Logger::LOG_NOTICE,"Skipping zero sized profile:",l); - return false; - } - - TopoDS_Face f1; - TopoDS_Face f2; - - gp_Trsf2d trsf2d; - bool has_position = true; -#ifdef SCHEMA_IfcParameterizedProfileDef_Position_IS_OPTIONAL - has_position = l->hasPosition(); -#endif - if (has_position) { - IfcGeom::Kernel::convert(l->Position(), trsf2d); - } - - double coords1[8] = {-x ,-y, x ,-y, x, y, -x, y }; - double coords2[8] = {-x+d,-y+d, x-d,-y+d, x-d,y-d, -x+d,y-d}; - double radii1[4] = {r1,r1,r1,r1}; - double radii2[4] = {r2,r2,r2,r2}; - int fillets[4] = {0,1,2,3}; - - bool s1 = profile_helper(4,coords1,fr1 ? 4 : 0,fillets,radii1,trsf2d,f1); - bool s2 = profile_helper(4,coords2,fr2 ? 4 : 0,fillets,radii2,trsf2d,f2); - - if (!s1 || !s2) return false; - - TopExp_Explorer exp1(f1, TopAbs_WIRE); - TopExp_Explorer exp2(f2, TopAbs_WIRE); - - TopoDS_Wire w1 = TopoDS::Wire(exp1.Current()); - TopoDS_Wire w2 = TopoDS::Wire(exp2.Current()); - - BRepBuilderAPI_MakeFace mf(w1, false); - mf.Add(w2); - - ShapeFix_Shape sfs(mf.Face()); - sfs.Perform(); - face = TopoDS::Face(sfs.Shape()); - return true; -} - -bool IfcGeom::Kernel::convert(const IfcSchema::IfcTrapeziumProfileDef* l, TopoDS_Shape& face) { - const double x1 = l->BottomXDim() / 2.0f * getValue(GV_LENGTH_UNIT); - const double w = l->TopXDim() * getValue(GV_LENGTH_UNIT); - const double dx = l->TopXOffset() * getValue(GV_LENGTH_UNIT); - const double y = l->YDim() / 2.0f * getValue(GV_LENGTH_UNIT); - - if ( x1 < ALMOST_ZERO || w < ALMOST_ZERO || y < ALMOST_ZERO ) { - Logger::Message(Logger::LOG_NOTICE,"Skipping zero sized profile:",l); - return false; - } - - gp_Trsf2d trsf2d; - bool has_position = true; -#ifdef SCHEMA_IfcParameterizedProfileDef_Position_IS_OPTIONAL - has_position = l->hasPosition(); -#endif - if (has_position) { - IfcGeom::Kernel::convert(l->Position(), trsf2d); - } - - double coords[8] = {-x1,-y, x1,-y, dx+w-x1,y, dx-x1,y}; - return profile_helper(4,coords,0,0,0,trsf2d,face); -} - -bool IfcGeom::Kernel::convert(const IfcSchema::IfcIShapeProfileDef* l, TopoDS_Shape& face) { - const double x1 = l->OverallWidth() / 2.0f * getValue(GV_LENGTH_UNIT); - const double y = l->OverallDepth() / 2.0f * getValue(GV_LENGTH_UNIT); - const double d1 = l->WebThickness() / 2.0f * getValue(GV_LENGTH_UNIT); - const double dy1 = l->FlangeThickness() * getValue(GV_LENGTH_UNIT); - - bool doFillet1 = l->hasFilletRadius(); - double f1 = 0.; - if ( doFillet1 ) { - f1 = l->FilletRadius() * getValue(GV_LENGTH_UNIT); - } - - bool doFillet2 = doFillet1; - double x2 = x1, dy2 = dy1, f2 = f1; - - if (l->declaration().is(IfcSchema::IfcAsymmetricIShapeProfileDef::Class())) { - IfcSchema::IfcAsymmetricIShapeProfileDef* assym = (IfcSchema::IfcAsymmetricIShapeProfileDef*) l; - x2 = assym->TopFlangeWidth() / 2. * getValue(GV_LENGTH_UNIT); - doFillet2 = assym->hasTopFlangeFilletRadius(); - if (doFillet2) { - f2 = assym->TopFlangeFilletRadius() * getValue(GV_LENGTH_UNIT); - } - if (assym->hasTopFlangeThickness()) { - dy2 = assym->TopFlangeThickness() * getValue(GV_LENGTH_UNIT); - } - } - - if ( x1 < ALMOST_ZERO || x2 < ALMOST_ZERO || y < ALMOST_ZERO || d1 < ALMOST_ZERO || dy1 < ALMOST_ZERO || dy2 < ALMOST_ZERO ) { - Logger::Message(Logger::LOG_NOTICE,"Skipping zero sized profile:",l); - return false; - } - - gp_Trsf2d trsf2d; - bool has_position = true; -#ifdef SCHEMA_IfcParameterizedProfileDef_Position_IS_OPTIONAL - has_position = l->hasPosition(); -#endif - if (has_position) { - IfcGeom::Kernel::convert(l->Position(), trsf2d); - } - - double coords[24] = {-x1,-y, x1,-y, x1,-y+dy1, d1,-y+dy1, d1,y-dy2, x2,y-dy2, x2,y, -x2,y, -x2,y-dy2, -d1,y-dy2, -d1,-y+dy1, -x1,-y+dy1}; - int fillets[4] = {3,4,9,10}; - double radii[4] = {f1,f1,f2,f2}; - return profile_helper(12,coords,(doFillet1||doFillet2) ? 4 : 0,fillets,radii,trsf2d,face); -} - -bool IfcGeom::Kernel::convert(const IfcSchema::IfcZShapeProfileDef* l, TopoDS_Shape& face) { - const double x = l->FlangeWidth() * getValue(GV_LENGTH_UNIT); - const double y = l->Depth() / 2.0f * getValue(GV_LENGTH_UNIT); - const double dx = l->WebThickness() / 2.0f * getValue(GV_LENGTH_UNIT); - const double dy = l->FlangeThickness() * getValue(GV_LENGTH_UNIT); - - bool doFillet = l->hasFilletRadius(); - bool doEdgeFillet = l->hasEdgeRadius(); - - double f1 = 0.; - double f2 = 0.; - - if ( doFillet ) { - f1 = l->FilletRadius() * getValue(GV_LENGTH_UNIT); - } - if ( doEdgeFillet ) { - f2 = l->EdgeRadius() * getValue(GV_LENGTH_UNIT); - } - - if ( x == 0.0f || y == 0.0f || dx == 0.0f || dy == 0.0f ) { - Logger::Message(Logger::LOG_NOTICE,"Skipping zero sized profile:",l); - return false; - } - - gp_Trsf2d trsf2d; - bool has_position = true; -#ifdef SCHEMA_IfcParameterizedProfileDef_Position_IS_OPTIONAL - has_position = l->hasPosition(); -#endif - if (has_position) { - IfcGeom::Kernel::convert(l->Position(), trsf2d); - } - - double coords[16] = {-dx,-y, x,-y, x,-y+dy, dx,-y+dy, dx,y, -x,y, -x,y-dy, -dx,y-dy}; - int fillets[4] = {2,3,6,7}; - double radii[4] = {f2,f1,f2,f1}; - return profile_helper(8,coords,(doFillet || doEdgeFillet) ? 4 : 0,fillets,radii,trsf2d,face); -} - -bool IfcGeom::Kernel::convert(const IfcSchema::IfcCShapeProfileDef* l, TopoDS_Shape& face) { - const double y = l->Depth() / 2.0f * getValue(GV_LENGTH_UNIT); - const double x = l->Width() / 2.0f * getValue(GV_LENGTH_UNIT); - const double d1 = l->WallThickness() * getValue(GV_LENGTH_UNIT); - const double d2 = l->Girth() * getValue(GV_LENGTH_UNIT); - bool doFillet = l->hasInternalFilletRadius(); - double f1 = 0; - double f2 = 0; - if ( doFillet ) { - f1 = l->InternalFilletRadius() * getValue(GV_LENGTH_UNIT); - f2 = f1 + d1; - } - - if ( x < ALMOST_ZERO || y < ALMOST_ZERO || d1 < ALMOST_ZERO || d2 < ALMOST_ZERO ) { - Logger::Message(Logger::LOG_NOTICE,"Skipping zero sized profile:",l); - return false; - } - - gp_Trsf2d trsf2d; - bool has_position = true; -#ifdef SCHEMA_IfcParameterizedProfileDef_Position_IS_OPTIONAL - has_position = l->hasPosition(); -#endif - if (has_position) { - IfcGeom::Kernel::convert(l->Position(), trsf2d); - } - - double coords[24] = {-x,-y,x,-y,x,-y+d2,x-d1,-y+d2,x-d1,-y+d1,-x+d1,-y+d1,-x+d1,y-d1,x-d1,y-d1,x-d1,y-d2,x,y-d2,x,y,-x,y}; - int fillets[8] = {0,1,4,5,6,7,10,11}; - double radii[8] = {f2,f2,f1,f1,f1,f1,f2,f2}; - return profile_helper(12,coords,doFillet ? 8 : 0,fillets,radii,trsf2d,face); -} - -bool IfcGeom::Kernel::convert(const IfcSchema::IfcLShapeProfileDef* l, TopoDS_Shape& face) { - const bool hasSlope = l->hasLegSlope(); - const bool doEdgeFillet = l->hasEdgeRadius(); - const bool doFillet = l->hasFilletRadius(); - - const double y = l->Depth() / 2.0f * getValue(GV_LENGTH_UNIT); - const double x = (l->hasWidth() ? l->Width() : l->Depth()) / 2.0f * getValue(GV_LENGTH_UNIT); - const double d = l->Thickness() * getValue(GV_LENGTH_UNIT); - const double slope = hasSlope ? (l->LegSlope() * getValue(GV_PLANEANGLE_UNIT)) : 0.; - - double f1 = 0.0f; - double f2 = 0.0f; - if (doFillet) { - f1 = l->FilletRadius() * getValue(GV_LENGTH_UNIT); - } - if ( doEdgeFillet) { - f2 = l->EdgeRadius() * getValue(GV_LENGTH_UNIT); - } - - if ( x < ALMOST_ZERO || y < ALMOST_ZERO || d < ALMOST_ZERO ) { - Logger::Message(Logger::LOG_NOTICE,"Skipping zero sized profile:",l); - return false; - } - - double xx = -x+d; - double xy = -y+d; - double dy1 = 0.; - double dy2 = 0.; - double dx1 = 0.; - double dx2 = 0.; - if (hasSlope) { - dy1 = tan(slope) * x; - dy2 = tan(slope) * (x - d); - dx1 = tan(slope) * y; - dx2 = tan(slope) * (y - d); - - const double x1s = x; const double y1s = -y + d - dy1; - const double x1e = -x + d; const double y1e = -y + d + dy2; - const double x2s = -x + d - dx1; const double y2s = y; - const double x2e = -x + d + dx2; const double y2e = -y + d; - - const double a1 = y1e - y1s; - const double b1 = x1s - x1e; - const double c1 = a1*x1s + b1*y1s; - - const double a2 = y2e - y2s; - const double b2 = x2s - x2e; - const double c2 = a2*x2s + b2*y2s; - - const double det = a1*b2 - a2*b1; - - if (ALMOST_THE_SAME(det, 0.)) { - Logger::Message(Logger::LOG_NOTICE, "Legs do not intersect for:",l); - return false; - } - - xx = (b2*c1 - b1*c2) / det; - xy = (a1*c2 - a2*c1) / det; - } - - gp_Trsf2d trsf2d; - bool has_position = true; -#ifdef SCHEMA_IfcParameterizedProfileDef_Position_IS_OPTIONAL - has_position = l->hasPosition(); -#endif - if (has_position) { - IfcGeom::Kernel::convert(l->Position(), trsf2d); - } - - double coords[12] = {-x,-y, x,-y, x,-y+d-dy1, xx, xy, -x+d-dx1,y, -x,y}; - int fillets[3] = {2,3,4}; - double radii[3] = {f2,f1,f2}; - return profile_helper(6,coords,doFillet ? 3 : 0,fillets,radii,trsf2d,face); -} - -bool IfcGeom::Kernel::convert(const IfcSchema::IfcUShapeProfileDef* l, TopoDS_Shape& face) { - const bool doEdgeFillet = l->hasEdgeRadius(); - const bool doFillet = l->hasFilletRadius(); - const bool hasSlope = l->hasFlangeSlope(); - - const double y = l->Depth() / 2.0f * getValue(GV_LENGTH_UNIT); - const double x = l->FlangeWidth() / 2.0f * getValue(GV_LENGTH_UNIT); - const double d1 = l->WebThickness() * getValue(GV_LENGTH_UNIT); - const double d2 = l->FlangeThickness() * getValue(GV_LENGTH_UNIT); - const double slope = hasSlope ? (l->FlangeSlope() * getValue(GV_PLANEANGLE_UNIT)) : 0.; - - double dy1 = 0.0f; - double dy2 = 0.0f; - double f1 = 0.0f; - double f2 = 0.0f; - - if (doFillet) { - f1 = l->FilletRadius() * getValue(GV_LENGTH_UNIT); - } - if (doEdgeFillet) { - f2 = l->EdgeRadius() * getValue(GV_LENGTH_UNIT); - } - - if (hasSlope) { - dy1 = (x - d1) * tan(slope); - dy2 = x * tan(slope); - } - - if ( x < ALMOST_ZERO || y < ALMOST_ZERO || d1 < ALMOST_ZERO || d2 < ALMOST_ZERO ) { - Logger::Message(Logger::LOG_NOTICE,"Skipping zero sized profile:",l); - return false; - } - - gp_Trsf2d trsf2d; - bool has_position = true; -#ifdef SCHEMA_IfcParameterizedProfileDef_Position_IS_OPTIONAL - has_position = l->hasPosition(); -#endif - if (has_position) { - IfcGeom::Kernel::convert(l->Position(), trsf2d); - } - - double coords[16] = {-x,-y, x,-y, x,-y+d2-dy2, -x+d1,-y+d2+dy1, -x+d1,y-d2-dy1, x,y-d2+dy2, x,y, -x,y}; - int fillets[4] = {2,3,4,5}; - double radii[4] = {f2,f1,f1,f2}; - return profile_helper(8, coords, (doFillet || doEdgeFillet) ? 4 : 0, fillets, radii, trsf2d, face); -} - -bool IfcGeom::Kernel::convert(const IfcSchema::IfcTShapeProfileDef* l, TopoDS_Shape& face) { - const bool doFlangeEdgeFillet = l->hasFlangeEdgeRadius(); - const bool doWebEdgeFillet = l->hasWebEdgeRadius(); - const bool doFillet = l->hasFilletRadius(); - const bool hasFlangeSlope = l->hasFlangeSlope(); - const bool hasWebSlope = l->hasWebSlope(); - - const double y = l->Depth() / 2.0f * getValue(GV_LENGTH_UNIT); - const double x = l->FlangeWidth() / 2.0f * getValue(GV_LENGTH_UNIT); - const double d1 = l->WebThickness() * getValue(GV_LENGTH_UNIT); - const double d2 = l->FlangeThickness() * getValue(GV_LENGTH_UNIT); - const double flangeSlope = hasFlangeSlope ? (l->FlangeSlope() * getValue(GV_PLANEANGLE_UNIT)) : 0.; - const double webSlope = hasWebSlope ? (l->WebSlope() * getValue(GV_PLANEANGLE_UNIT)) : 0.; - - if ( x < ALMOST_ZERO || y < ALMOST_ZERO || d1 < ALMOST_ZERO || d2 < ALMOST_ZERO ) { - Logger::Message(Logger::LOG_NOTICE,"Skipping zero sized profile:",l); - return false; - } - - double dy1 = 0.0f; - double dy2 = 0.0f; - double dx1 = 0.0f; - double dx2 = 0.0f; - double f1 = 0.0f; - double f2 = 0.0f; - double f3 = 0.0f; - - if (doFillet) { - f1 = l->FilletRadius() * getValue(GV_LENGTH_UNIT); - } - if (doWebEdgeFillet) { - f2 = l->WebEdgeRadius() * getValue(GV_LENGTH_UNIT); - } - if (doFlangeEdgeFillet) { - f3 = l->FlangeEdgeRadius() * getValue(GV_LENGTH_UNIT); - } - - double xx, xy; - if (hasFlangeSlope) { - dy1 = (x / 2. - d1) * tan(flangeSlope); - dy2 = x / 2. * tan(flangeSlope); - } - if (hasWebSlope) { - dx1 = (y - d2) * tan(webSlope); - dx2 = y * tan(webSlope); - } - if (hasWebSlope || hasFlangeSlope) { - const double x1s = d1/2. - dx2; const double y1s = -y; - const double x1e = d1/2. + dx1; const double y1e = y - d2; - const double x2s = x; const double y2s = y - d2 + dy2; - const double x2e = d1/2.; const double y2e = y - d2 - dy1; - - const double a1 = y1e - y1s; - const double b1 = x1s - x1e; - const double c1 = a1*x1s + b1*y1s; - - const double a2 = y2e - y2s; - const double b2 = x2s - x2e; - const double c2 = a2*x2s + b2*y2s; - - const double det = a1*b2 - a2*b1; - - if (ALMOST_THE_SAME(det, 0.)) { - Logger::Message(Logger::LOG_NOTICE, "Web and flange do not intersect for:",l); - return false; - } - - xx = (b2*c1 - b1*c2) / det; - xy = (a1*c2 - a2*c1) / det; - } else { - xx = d1 / 2; - xy = y - d2; - } - - gp_Trsf2d trsf2d; - bool has_position = true; -#ifdef SCHEMA_IfcParameterizedProfileDef_Position_IS_OPTIONAL - has_position = l->hasPosition(); -#endif - if (has_position) { - IfcGeom::Kernel::convert(l->Position(), trsf2d); - } - - double coords[16] = {d1/2.-dx2,-y, xx,xy, x,y-d2+dy2, x,y, -x,y, -x,y-d2+dy2, -xx,xy, -d1/2.+dx2,-y}; - int fillets[6] = {0,1,2,5,6,7}; - double radii[6] = {f2,f1,f3,f3,f1,f2}; - return profile_helper(8, coords, (doFillet || doWebEdgeFillet || doFlangeEdgeFillet) ? 6 : 0, fillets, radii, trsf2d, face); -} - -bool IfcGeom::Kernel::convert(const IfcSchema::IfcCircleProfileDef* l, TopoDS_Shape& face) { - const double r = l->Radius() * getValue(GV_LENGTH_UNIT); - if ( r == 0.0f ) { - Logger::Message(Logger::LOG_NOTICE,"Skipping zero sized profile:",l); - return false; - } - - gp_Trsf2d trsf2d; - bool has_position = true; -#ifdef SCHEMA_IfcParameterizedProfileDef_Position_IS_OPTIONAL - has_position = l->hasPosition(); -#endif - if (has_position) { - IfcGeom::Kernel::convert(l->Position(), trsf2d); - } - gp_Ax2 ax = gp_Ax2().Transformed(trsf2d); - - - Handle(Geom_Circle) circle = new Geom_Circle(ax, r); - TopoDS_Edge edge = BRepBuilderAPI_MakeEdge(circle); - - BRepBuilderAPI_MakeWire w; - w.Add(edge); - - TopoDS_Face f; - bool success = convert_wire_to_face(w, f); - if (success) face = f; - return success; -} - -bool IfcGeom::Kernel::convert(const IfcSchema::IfcCircleHollowProfileDef* l, TopoDS_Shape& face) { - const double r = l->Radius() * getValue(GV_LENGTH_UNIT); - const double t = l->WallThickness() * getValue(GV_LENGTH_UNIT); - - if ( r == 0.0f || t == 0.0f ) { - Logger::Message(Logger::LOG_NOTICE,"Skipping zero sized profile:",l); - return false; - } - - gp_Trsf2d trsf2d; - bool has_position = true; -#ifdef SCHEMA_IfcParameterizedProfileDef_Position_IS_OPTIONAL - has_position = l->hasPosition(); -#endif - if (has_position) { - IfcGeom::Kernel::convert(l->Position(), trsf2d); - } - - gp_Ax2 ax = gp_Ax2().Transformed(trsf2d); - - BRepBuilderAPI_MakeWire outer; - Handle(Geom_Circle) outerCircle = new Geom_Circle(ax, r); - outer.Add(BRepBuilderAPI_MakeEdge(outerCircle)); - BRepBuilderAPI_MakeFace mf(outer.Wire(), false); - - BRepBuilderAPI_MakeWire inner; - Handle(Geom_Circle) innerCirlce = new Geom_Circle(ax, r-t); - inner.Add(BRepBuilderAPI_MakeEdge(innerCirlce)); - mf.Add(inner); - - ShapeFix_Shape sfs(mf.Face()); - sfs.Perform(); - face = TopoDS::Face(sfs.Shape()); - return true; -} - -bool IfcGeom::Kernel::convert(const IfcSchema::IfcEllipseProfileDef* l, TopoDS_Shape& face) { - double rx = l->SemiAxis1() * getValue(GV_LENGTH_UNIT); - double ry = l->SemiAxis2() * getValue(GV_LENGTH_UNIT); - - if ( rx < ALMOST_ZERO || ry < ALMOST_ZERO ) { - Logger::Message(Logger::LOG_NOTICE,"Skipping zero sized profile:",l); - return false; - } - - const bool rotated = ry > rx; - - gp_Trsf2d trsf2d; - bool has_position = true; -#ifdef SCHEMA_IfcParameterizedProfileDef_Position_IS_OPTIONAL - has_position = l->hasPosition(); -#endif - if (has_position) { - IfcGeom::Kernel::convert(l->Position(), trsf2d); - } - - gp_Ax2 ax = gp_Ax2(); - if (rotated) { - ax.Rotate(ax.Axis(), M_PI / 2.); - std::swap(rx, ry); - } - ax.Transform(trsf2d); - - BRepBuilderAPI_MakeWire w; - Handle(Geom_Ellipse) ellipse = new Geom_Ellipse(ax, rx, ry); - TopoDS_Edge edge = BRepBuilderAPI_MakeEdge(ellipse); - w.Add(edge); - - TopoDS_Face f; - bool success = convert_wire_to_face(w, f); - if (success) face = f; - return success; -} - -bool IfcGeom::Kernel::convert(const IfcSchema::IfcCenterLineProfileDef* l, TopoDS_Shape& face) { - const double d = l->Thickness() * getValue(GV_LENGTH_UNIT) / 2.; - - TopoDS_Wire wire; - if (!convert_wire(l->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)); - } - return true; -} - -bool IfcGeom::Kernel::convert(const IfcSchema::IfcCompositeProfileDef* l, TopoDS_Shape& face) { - // BRepBuilderAPI_MakeFace mf; - - TopoDS_Compound compound; - BRep_Builder builder; - builder.MakeCompound(compound); - - IfcSchema::IfcProfileDef::list::ptr profiles = l->Profiles(); - //bool first = true; - for (IfcSchema::IfcProfileDef::list::it it = profiles->begin(); it != profiles->end(); ++it) { - TopoDS_Face f; - if (convert_face(*it, f)) { - builder.Add(compound, f); - /* TopExp_Explorer exp(f, TopAbs_WIRE); - for (; exp.More(); exp.Next()) { - const TopoDS_Wire& wire = TopoDS::Wire(exp.Current()); - if (first) { - mf.Init(BRepBuilderAPI_MakeFace(wire)); - } else { - mf.Add(wire); - } - first = false; - } */ - } - } - - face = compound; - return !face.IsNull(); -} - -bool IfcGeom::Kernel::convert(const IfcSchema::IfcDerivedProfileDef* l, TopoDS_Shape& face) { - TopoDS_Face f; - gp_Trsf2d trsf2d; - if (convert_face(l->ParentProfile(), f) && IfcGeom::Kernel::convert(l->Operator(), trsf2d)) { - gp_Trsf trsf = trsf2d; - face = TopoDS::Face(BRepBuilderAPI_Transform(f, trsf)); - return true; - } else { - return false; - } -} - -bool IfcGeom::Kernel::convert(const IfcSchema::IfcPlane* l, TopoDS_Shape& face) { - gp_Pln pln; - convert(l, pln); - Handle_Geom_Surface surf = new Geom_Plane(pln); -#if OCC_VERSION_HEX < 0x60502 - face = BRepBuilderAPI_MakeFace(surf); -#else - face = BRepBuilderAPI_MakeFace(surf, getValue(GV_PRECISION)); -#endif - return true; -} - -#ifdef SCHEMA_HAS_IfcBSplineSurfaceWithKnots - -bool IfcGeom::Kernel::convert(const IfcSchema::IfcBSplineSurfaceWithKnots* l, TopoDS_Shape& face) { - boost::shared_ptr< IfcTemplatedEntityListList > cps = l->ControlPointsList(); - std::vector uknots = l->UKnots(); - std::vector vknots = l->VKnots(); - std::vector umults = l->UMultiplicities(); - std::vector vmults = l->VMultiplicities(); - - TColgp_Array2OfPnt Poles (0, (int)cps->size() - 1, 0, (int)(*cps->begin()).size() - 1); - TColStd_Array1OfReal UKnots(0, (int)uknots.size() - 1); - TColStd_Array1OfReal VKnots(0, (int)vknots.size() - 1); - TColStd_Array1OfInteger UMults(0, (int)umults.size() - 1); - TColStd_Array1OfInteger VMults(0, (int)vmults.size() - 1); - Standard_Integer UDegree = l->UDegree(); - Standard_Integer VDegree = l->VDegree(); - - int i = 0, j; - for (IfcTemplatedEntityListList::outer_it it = cps->begin(); it != cps->end(); ++it, ++i) { - j = 0; - for (IfcTemplatedEntityListList::inner_it jt = (*it).begin(); jt != (*it).end(); ++jt, ++j) { - IfcSchema::IfcCartesianPoint* p = *jt; - gp_Pnt pnt; - if (!convert(p, pnt)) return false; - Poles(i, j) = pnt; - } - } - i = 0; - for (std::vector::const_iterator it = uknots.begin(); it != uknots.end(); ++it, ++i) { - UKnots(i) = *it; - } - i = 0; - for (std::vector::const_iterator it = vknots.begin(); it != vknots.end(); ++it, ++i) { - VKnots(i) = *it; - } - i = 0; - for (std::vector::const_iterator it = umults.begin(); it != umults.end(); ++it, ++i) { - UMults(i) = *it; - } - i = 0; - for (std::vector::const_iterator it = vmults.begin(); it != vmults.end(); ++it, ++i) { - VMults(i) = *it; - } - Handle_Geom_Surface surf = new Geom_BSplineSurface(Poles, UKnots, VKnots, UMults, VMults, UDegree, VDegree); - -#if OCC_VERSION_HEX < 0x60502 - face = BRepBuilderAPI_MakeFace(surf); -#else - face = BRepBuilderAPI_MakeFace(surf, getValue(GV_PRECISION)); -#endif - - return true; -} - -#endif \ No newline at end of file diff --git a/src/ifcgeom/kernels/opencascade/IfcGeomFunctions.cpp_ b/src/ifcgeom/kernels/opencascade/IfcGeomFunctions.cpp_ deleted file mode 100644 index afbc7b2588..0000000000 --- a/src/ifcgeom/kernels/opencascade/IfcGeomFunctions.cpp_ +++ /dev/null @@ -1,3586 +0,0 @@ -/******************************************************************************** - * * - * 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 . * - * * - ********************************************************************************/ - -/******************************************************************************** - * * - * Implementations of the various conversion functions defined in IfcGeom.h * - * * - ********************************************************************************/ - -#include -#include -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include -#include -#include -#include - -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#if OCC_VERSION_HEX >= 0x70200 -#include -#endif - -#include - -#include -#include -#include -#include - -#include -#include -#include -#include - -#include - -#include - -#include - -#include -#include - -#include -#include -#include - -#include - -#include - -#include -#include -#include - -#include -#include - -#include -#include -#include -#include - -#include -#include - -#include - -#include -#include - -#include - -#include -#include - -#include "../../../ifcparse/macros.h" -#include "../../../ifcparse/IfcSIPrefix.h" -#include "../../../ifcparse/IfcFile.h" -#include "../../../ifcgeom/kernels/opencascade/IfcGeom.h" -#include "../../../ifcgeom/kernels/opencascade/IfcGeomTree.h" - -#include - -#if OCC_VERSION_HEX < 0x60900 -#ifdef _MSC_VER -#pragma message("warning: You are linking against Open CASCADE version " OCC_VERSION_COMPLETE ". Version 6.9.0 introduces various improvements with relation to boolean operations. You are advised to upgrade.") -#else -#warning "You are linking against linking against an older version of Open CASCADE. Version 6.9.0 introduces various improvements with relation to boolean operations. You are advised to upgrade." -#endif -#endif - -namespace { - struct POSTFIX_SCHEMA(factory_t) { - IfcGeom::Kernel* operator()(IfcParse::IfcFile* file) const { - IfcGeom::POSTFIX_SCHEMA(Kernel)* k = new IfcGeom::POSTFIX_SCHEMA(Kernel); - if (file) { - double unit_magnitude = 1.; - - // Set unit information from file - - IfcSchema::IfcProject::list::ptr projects = file->instances_by_type(); - if (projects->size() == 1) { - IfcSchema::IfcProject* project = *projects->begin(); - std::pair unit_info = k->initializeUnits(project->UnitsInContext()); - unit_magnitude = unit_info.second; - } else { - Logger::Warning("A single IfcProject is expected (encountered " + boost::lexical_cast(projects->size()) + "); unable to read unit information."); - } - - // Set precision from file - - double lowest_precision_encountered = std::numeric_limits::infinity(); - bool any_precision_encountered = false; - - IfcSchema::IfcGeometricRepresentationContext::list::it it; - IfcSchema::IfcGeometricRepresentationContext::list::ptr contexts = - file->instances_by_type_excl_subtypes(); - - for (it = contexts->begin(); it != contexts->end(); ++it) { - IfcSchema::IfcGeometricRepresentationContext* context = *it; - if (context->hasPrecision() && context->Precision() < lowest_precision_encountered) { - // Some arbitrary factor that has proven to work better for the models in the set of test files. - lowest_precision_encountered = context->Precision() * unit_magnitude * 10.; - any_precision_encountered = true; - } - } - - double precision_to_set = 1.e-5; - - if (any_precision_encountered) { - if (lowest_precision_encountered < 1.e-7) { - Logger::Message(Logger::LOG_WARNING, "Precision lower than 0.0000001 meter not enforced"); - precision_to_set = 1.e-7; - } else { - precision_to_set = lowest_precision_encountered; - } - } - - k->setValue(IfcGeom::Kernel::GV_PRECISION, precision_to_set); - } - return k; - } - }; -} - -void MAKE_INIT_FN(KernelImplementation_opencascade_)(IfcGeom::impl::KernelFactoryImplementation* mapping) { - static const std::string schema_name = STRINGIFY(IfcSchema); - POSTFIX_SCHEMA(factory_t) factory; - mapping->bind(schema_name, "opencascade", factory); -} - -#define Kernel POSTFIX_SCHEMA(Kernel) - -namespace { - void copy_operand(const TopTools_ListOfShape& l, TopTools_ListOfShape& r) { -#if OCC_VERSION_HEX < 0x70000 - TopTools_ListIteratorOfListOfShape it(l); - for (; it.More(); it.Next()) { - r.Append(BRepBuilderAPI_Copy(it.Value())); - } -#else - // On OCCT 7.0 and higher BRepAlgoAPI_BuilderAlgo::SetNonDestructive(true) is - // called. Not entirely sure on the behaviour before 7.0, so overcautiously - // create copies. - r.Assign(l); -#endif - } - - TopoDS_Shape copy_operand(const TopoDS_Shape& s) { -#if OCC_VERSION_HEX < 0x70000 - return BRepBuilderAPI_Copy(s); -#else - return s; -#endif - } - - double min_edge_length(const TopoDS_Shape& a) { - double min_edge_len = std::numeric_limits::infinity(); - TopExp_Explorer exp(a, TopAbs_EDGE); - for (; exp.More(); exp.Next()) { - GProp_GProps prop; - BRepGProp::LinearProperties(exp.Current(), prop); - double l = prop.Mass(); - if (l < min_edge_len) { - min_edge_len = l; - } - } - return min_edge_len; - } - - double min_vertex_edge_distance(const TopoDS_Shape& a, double min_search, double max_search) { - double M = std::numeric_limits::infinity(); - - TopTools_IndexedMapOfShape vertices, edges; - - TopExp::MapShapes(a, TopAbs_VERTEX, vertices); - TopExp::MapShapes(a, TopAbs_EDGE, edges); - - IfcGeom::impl::tree tree; - - // Add edges to tree - for (int i = 1; i <= edges.Extent(); ++i) { - tree.add(i, edges(i)); - } - - for (int j = 1; j <= vertices.Extent(); ++j) { - const TopoDS_Vertex& v = TopoDS::Vertex(vertices(j)); - gp_Pnt p = BRep_Tool::Pnt(v); - - Bnd_Box b; - b.Add(p); - b.Enlarge(max_search); - - std::vector edge_idxs = tree.select_box(b, false); - std::vector::const_iterator it = edge_idxs.begin(); - for (; it != edge_idxs.end(); ++it) { - const TopoDS_Edge& e = TopoDS::Edge(edges(*it)); - TopoDS_Vertex v1, v2; - TopExp::Vertices(e, v1, v2); - - if (v.IsSame(v1) || v.IsSame(v2)) { - continue; - } - - BRepAdaptor_Curve crv(e); - Extrema_ExtPC ext(p, crv); - if (!ext.IsDone()) { - continue; - } - - for (int i = 1; i <= ext.NbExt(); ++i) { - const double m = sqrt(ext.SquareDistance(i)); - if (m < M && m > min_search) { - M = m; - } - } - } - } - - return M; - } - - class points_on_planar_face_generator { - private: - const TopoDS_Face& f_; - Handle(Geom_Surface) plane_; - BRepTopAdaptor_FClass2d cls_; - double u0, u1, v0, v1; - int i, j; - static const int N = 10; - - public: - points_on_planar_face_generator(const TopoDS_Face& f) - : f_(f) - , plane_(BRep_Tool::Surface(f_)) - , cls_(f_, BRep_Tool::Tolerance(f_)) - , i(0), j(0) - { - BRepTools::UVBounds(f_, u0, u1, v0, v1); - } - - void reset() { - i = j = 0; - } - - bool operator()(gp_Pnt& p) { - while (j < N) { - double u = u0 + (u1 - u0) * i / N; - double v = v0 + (v1 - v0) * j / N; - - i++; - if (i == N) { - i = 0; - j++; - } - - // Specifically does not consider ON - if (cls_.Perform(gp_Pnt2d(u, v)) == TopAbs_IN) { - plane_->D0(u, v, p); - return true; - } - } - - return false; - } - }; - - double min_face_face_distance(const TopoDS_Shape& a, double max_search) { - /* - NB: This is currently only implemented for planar surfaces. - */ - double M = std::numeric_limits::infinity(); - - TopTools_IndexedMapOfShape faces; - - TopExp::MapShapes(a, TopAbs_FACE, faces); - - IfcGeom::impl::tree tree; - - // Add faces to tree - for (int i = 1; i <= faces.Extent(); ++i) { - if (BRep_Tool::Surface(TopoDS::Face(faces(i)))->DynamicType() == STANDARD_TYPE(Geom_Plane)) { - tree.add(i, faces(i)); - } - } - - for (int j = 1; j <= faces.Extent(); ++j) { - const TopoDS_Face& f = TopoDS::Face(faces(j)); - const Handle(Geom_Surface)& fs = BRep_Tool::Surface(f); - - if (fs->DynamicType() != STANDARD_TYPE(Geom_Plane)) { - continue; - } - - points_on_planar_face_generator pgen(f); - - Bnd_Box b; - BRepBndLib::AddClose(f, b); - b.Enlarge(max_search); - - std::vector face_idxs = tree.select_box(b, false); - std::vector::const_iterator it = face_idxs.begin(); - for (; it != face_idxs.end(); ++it) { - if (*it == j) { - continue; - } - - const TopoDS_Face& g = TopoDS::Face(faces(*it)); - const Handle(Geom_Surface)& gs = BRep_Tool::Surface(g); - - auto p0 = Handle(Geom_Plane)::DownCast(fs); - auto p1 = Handle(Geom_Plane)::DownCast(gs); - - if (p0->Position().IsCoplanar(p1->Position(), max_search, asin(max_search))) { - pgen.reset(); - - BRepTopAdaptor_FClass2d cls(g, BRep_Tool::Tolerance(g)); - - gp_Pnt test; - while (pgen(test)) { - gp_Vec d = test.XYZ() - p1->Position().Location().XYZ(); - double u = d.Dot(p1->Position().XDirection()); - double v = d.Dot(p1->Position().YDirection()); - - // nb: TopAbs_ON is explicitly not considered to prevent matching adjacent faces - // with similar orientations. - if (cls.Perform(gp_Pnt2d(u, v)) == TopAbs_IN) { - gp_Pnt test2; - p1->D0(u, v, test2); - double w = gp_Vec(p1->Position().Direction().XYZ()).Dot(test2.XYZ() - test.XYZ()); - if (w < M) { - M = w; - } - } - } - } - } - } - - return M; - } - - void bounding_box_overlap(double p, const TopoDS_Shape& a, const TopTools_ListOfShape& b, TopTools_ListOfShape& c) { - Bnd_Box A; - BRepBndLib::Add(a, A); - - if (A.IsVoid()) { - return; - } - - TopTools_ListIteratorOfListOfShape it(b); - for (; it.More(); it.Next()) { - Bnd_Box B; - BRepBndLib::Add(it.Value(), B); - - if (B.IsVoid()) { - continue; - } - - if (A.Distance(B) < p) { - c.Append(it.Value()); - } - } - } - - TopoDS_Shape unify(const TopoDS_Shape& s, double tolerance) { - tolerance = (std::min)(min_edge_length(s) / 2., tolerance); - ShapeUpgrade_UnifySameDomain usd(s); - usd.SetSafeInputMode(true); - usd.SetLinearTolerance(tolerance); - usd.SetAngularTolerance(1.e-3); - usd.Build(); - return usd.Shape(); - } -} - -namespace { - int count_occt(const TopoDS_Shape& s, TopAbs_ShapeEnum t) { - IfcGeom::OpenCascadeShape Ss(s); - return IfcGeom::Kernel::count(&Ss, (int) t); - } - - int is_manifold_occt(const TopoDS_Shape& s) { - IfcGeom::OpenCascadeShape Ss(s); - return IfcGeom::Kernel::is_manifold(&Ss); - } -} - -bool IfcGeom::Kernel::create_solid_from_compound(const TopoDS_Shape& compound, TopoDS_Shape& shape) { - TopTools_ListOfShape face_list; - TopExp_Explorer exp(compound, TopAbs_FACE); - for (; exp.More(); exp.Next()) { - TopoDS_Face face = TopoDS::Face(exp.Current()); - face_list.Append(face); - } - - if (face_list.Extent() == 0) { - return false; - } - - return create_solid_from_faces(face_list, shape); -} - -bool IfcGeom::Kernel::create_solid_from_faces(const TopTools_ListOfShape& face_list, TopoDS_Shape& shape) { - bool valid_shell = false; - - if (face_list.Extent() == 1) { - shape = face_list.First(); - // A bit dubious what to return here. - return true; - } else if (face_list.Extent() == 0) { - return false; - } - - TopTools_ListIteratorOfListOfShape face_iterator; - - bool has_shared_edges = false; - TopTools_MapOfShape edge_set; - - // In case there are wire interesections or failures in non-planar wire triangulations - // the idea is to let occt do an exhaustive search of edge partners. But we have not - // found a case where this actually improves boolean ops later on. - // if (!faceset_helper_ || !faceset_helper_->non_manifold()) { - - for (face_iterator.Initialize(face_list); face_iterator.More(); face_iterator.Next()) { - // As soon as is detected one of the edges is shared, the assumption is made no - // additional sewing is necessary. - if (!has_shared_edges) { - TopExp_Explorer exp(face_iterator.Value(), TopAbs_EDGE); - for (; exp.More(); exp.Next()) { - if (edge_set.Contains(exp.Current())) { - has_shared_edges = true; - break; - } - edge_set.Add(exp.Current()); - } - } - } - - BRepOffsetAPI_Sewing sewing_builder; - sewing_builder.SetTolerance(getValue(GV_PRECISION)); - sewing_builder.SetMaxTolerance(getValue(GV_PRECISION)); - sewing_builder.SetMinTolerance(getValue(GV_PRECISION)); - - BRep_Builder builder; - TopoDS_Shell shell; - builder.MakeShell(shell); - - for (face_iterator.Initialize(face_list); face_iterator.More(); face_iterator.Next()) { - if (has_shared_edges) { - builder.Add(shell, face_iterator.Value()); - } else { - sewing_builder.Add(face_iterator.Value()); - } - } - - try { - if (has_shared_edges) { - ShapeFix_Shell fix; - fix.FixFaceOrientation(shell); - shape = fix.Shape(); - } else { - sewing_builder.Perform(); - shape = sewing_builder.SewedShape(); - } - - BRepCheck_Analyzer ana(shape); - valid_shell = ana.IsValid(); - - if (!valid_shell) { - ShapeFix_Shape sfs(shape); - sfs.Perform(); - shape = sfs.Shape(); - - BRepCheck_Analyzer reana(shape); - valid_shell = reana.IsValid(); - } - - valid_shell &= count_occt(shape, TopAbs_SHELL) > 0; - } catch (const Standard_Failure& e) { - if (e.GetMessageString() && strlen(e.GetMessageString())) { - Logger::Error(e.GetMessageString()); - } else { - Logger::Error("Unknown error sewing shell"); - } - } catch (...) { - Logger::Error("Unknown error sewing shell"); - } - - if (valid_shell) { - - TopoDS_Shape complete_shape; - TopExp_Explorer exp(shape, TopAbs_SHELL); - - for (; exp.More(); exp.Next()) { - TopoDS_Shape result_shape = exp.Current(); - - try { - ShapeFix_Solid solid; - solid.SetMaxTolerance(getValue(GV_PRECISION)); - TopoDS_Solid solid_shape = solid.SolidFromShell(TopoDS::Shell(exp.Current())); - // @todo: BRepClass3d_SolidClassifier::PerformInfinitePoint() is done by SolidFromShell - // and this is done again, to be able to catch errors during this process. - // This is double work that should be avoided. - if (!solid_shape.IsNull()) { - try { - BRepClass3d_SolidClassifier classifier(solid_shape); - result_shape = solid_shape; - classifier.PerformInfinitePoint(getValue(GV_PRECISION)); - if (classifier.State() == TopAbs_IN) { - shape.Reverse(); - } - } catch (const Standard_Failure& e) { - if (e.GetMessageString() && strlen(e.GetMessageString())) { - Logger::Error(e.GetMessageString()); - } else { - Logger::Error("Unknown error classifying solid"); - } - } catch (...) { - Logger::Error("Unknown error classifying solid"); - } - } - } catch (const Standard_Failure& e) { - if (e.GetMessageString() && strlen(e.GetMessageString())) { - Logger::Error(e.GetMessageString()); - } else { - Logger::Error("Unknown error creating solid"); - } - } catch (...) { - Logger::Error("Unknown error creating solid"); - } - - if (complete_shape.IsNull()) { - complete_shape = result_shape; - } else { - BRep_Builder B; - if (complete_shape.ShapeType() != TopAbs_COMPOUND) { - TopoDS_Compound C; - B.MakeCompound(C); - B.Add(C, complete_shape); - complete_shape = C; - Logger::Warning("Multiple components in IfcConnectedFaceSet"); - } - B.Add(complete_shape, result_shape); - } - } - - TopExp_Explorer loose_faces(shape, TopAbs_FACE, TopAbs_SHELL); - - for (; loose_faces.More(); loose_faces.Next()) { - BRep_Builder B; - if (complete_shape.ShapeType() != TopAbs_COMPOUND) { - TopoDS_Compound C; - B.MakeCompound(C); - B.Add(C, complete_shape); - complete_shape = C; - Logger::Warning("Loose faces in IfcConnectedFaceSet"); - } - B.Add(complete_shape, loose_faces.Current()); - } - - shape = complete_shape; - - } else { - Logger::Error("Failed to sew faceset"); - } - - return valid_shell; -} - -bool IfcGeom::Kernel::is_compound(const TopoDS_Shape& shape) { - bool has_solids = TopExp_Explorer(shape,TopAbs_SOLID).More() != 0; - bool has_shells = TopExp_Explorer(shape,TopAbs_SHELL).More() != 0; - bool has_compounds = TopExp_Explorer(shape,TopAbs_COMPOUND).More() != 0; - bool has_faces = TopExp_Explorer(shape,TopAbs_FACE).More() != 0; - return has_compounds && has_faces && !has_solids && !has_shells; -} - -const TopoDS_Shape& IfcGeom::Kernel::ensure_fit_for_subtraction(const TopoDS_Shape& shape, TopoDS_Shape& solid) { - const bool is_comp = is_compound(shape); - if (!is_comp) { - return solid = shape; - } - - if (!create_solid_from_compound(shape, solid)) { - return solid = shape; - } - - // If the SEW_SHELLS option had been set this precision had been applied - // at the end of the generic convert_shape() call. - const double precision = getValue(GV_PRECISION); - apply_tolerance(solid, precision); - - return solid; -} - -namespace { - struct opening_sorter { - bool operator()(const std::pair& a, const std::pair& b) const { - return a.first > b.first; - } - }; -} - -bool IfcGeom::Kernel::convert_openings(const IfcSchema::IfcProduct* entity, const IfcSchema::IfcRelVoidsElement::list::ptr& openings, - const IfcGeom::ConversionResults& entity_shapes, const ConversionResultPlacement* entity_place, IfcGeom::ConversionResults& cut_shapes) { - - const gp_Trsf entity_trsf = ((OpenCascadePlacement*)entity_place)->trsf().Trsf(); - - std::vector< std::pair > opening_vector; - - for (IfcSchema::IfcRelVoidsElement::list::it it = openings->begin(); it != openings->end(); ++it) { - IfcSchema::IfcRelVoidsElement* v = *it; - IfcSchema::IfcFeatureElementSubtraction* fes = v->RelatedOpeningElement(); - if (fes->declaration().is(IfcSchema::IfcOpeningElement::Class())) { - if (!fes->hasRepresentation()) continue; - - // Convert the IfcRepresentation of the IfcOpeningElement - gp_Trsf opening_trsf; - if (fes->hasObjectPlacement()) { - try { - convert(fes->ObjectPlacement(), opening_trsf); - } catch (const std::exception& e) { - Logger::Error(e); - } catch (...) { - Logger::Error("Failed to construct placement"); - } - } - - // Move the opening into the coordinate system of the IfcProduct - opening_trsf.PreMultiply(entity_trsf.Inverted()); - - IfcSchema::IfcProductRepresentation* prodrep = fes->Representation(); - IfcSchema::IfcRepresentation::list::ptr reps = prodrep->Representations(); - - IfcGeom::ConversionResults opening_shapes; - - for (IfcSchema::IfcRepresentation::list::it it2 = reps->begin(); it2 != reps->end(); ++it2) { - convert_shapes(*it2, opening_shapes); - } - - for (unsigned int i = 0; i < opening_shapes.size(); ++i) { - TopoDS_Shape opening_shape_solid; - const TopoDS_Shape& opening_shape_unlocated = ensure_fit_for_subtraction(*(OpenCascadeShape*)opening_shapes[i].Shape(), opening_shape_solid); - - gp_GTrsf gtrsf; - if (opening_shapes[i].Placement()) { - gtrsf = ((OpenCascadePlacement*)opening_shapes[i].Placement())->trsf(); - } - gtrsf.PreMultiply(opening_trsf); - TopoDS_Shape opening_shape = apply_transformation(opening_shape_unlocated, gtrsf); - opening_vector.push_back(std::make_pair(min_edge_length(opening_shape), opening_shape)); - } - - } - } - - std::sort(opening_vector.begin(), opening_vector.end(), opening_sorter()); - - // Iterate over the shapes of the IfcProduct - for ( IfcGeom::ConversionResults::const_iterator it3 = entity_shapes.begin(); it3 != entity_shapes.end(); ++ it3 ) { - TopoDS_Shape entity_shape_solid; - const TopoDS_Shape& entity_shape_unlocated = ensure_fit_for_subtraction(*(OpenCascadeShape*) it3->Shape(),entity_shape_solid); - const OpenCascadePlacement* entity_shape_gtrsf = (OpenCascadePlacement*)it3->Placement(); - TopoDS_Shape entity_shape = apply_transformation(entity_shape_unlocated, entity_shape_gtrsf); - - TopoDS_Shape result = entity_shape; - - auto it = opening_vector.begin(); - auto jt = it; - - for (;; ++it) { - if (it == opening_vector.end() || jt->first / it->first > 10.) { - - TopTools_ListOfShape opening_list; - for (auto kt = jt; kt < it; ++kt) { - opening_list.Append(kt->second); - } - - TopoDS_Shape intermediate_result; - if (boolean_operation(result, opening_list, BOPAlgo_CUT, intermediate_result)) { - result = intermediate_result; - } else { - Logger::Message(Logger::LOG_ERROR, "Opening subtraction failed for " + boost::lexical_cast(std::distance(jt, it)) + " openings", entity); - } - - jt = it; - } - - if (it == opening_vector.end()) { - break; - } - } - - cut_shapes.push_back(IfcGeom::ConversionResult(it3->ItemId(), new OpenCascadeShape(result), &it3->Style())); - } - return true; -} - -bool IfcGeom::Kernel::convert_wire_to_face(const TopoDS_Wire& w, TopoDS_Face& face) { - TopoDS_Wire wire = w; - - TopTools_ListOfShape results; - if (wire_intersections(wire, results)) { - Logger::Warning("Self-intersections with " + boost::lexical_cast(results.Extent()) + " cycles detected"); - select_largest(results, wire); - } - - bool is_2d = true; - TopExp_Explorer exp(wire, TopAbs_EDGE); - for (; exp.More(); exp.Next()) { - double a, b; - Handle(Geom_Curve) crv = BRep_Tool::Curve(TopoDS::Edge(exp.Current()), a, b); - if (crv->DynamicType() != STANDARD_TYPE(Geom_Line)) { - is_2d = false; - break; - } - Handle(Geom_Line) line = Handle(Geom_Line)::DownCast(crv); - if (line->Lin().Direction().Z() > ALMOST_ZERO) { - is_2d = false; - break; - } - } - - if (!is_2d) { - // For 2d wires (e.g. profiles) a higher tolerance for plane fitting is never required. - ShapeFix_ShapeTolerance FTol; - FTol.SetTolerance(wire, getValue(GV_PRECISION), TopAbs_WIRE); - } - - BRepBuilderAPI_MakeFace mf(wire, false); - BRepBuilderAPI_FaceError er = mf.Error(); - - if (er != BRepBuilderAPI_FaceDone) { - Logger::Error("Failed to create face."); - return false; - } - face = mf.Face(); - - return true; -} - -void IfcGeom::Kernel::assert_closed_wire(TopoDS_Wire& wire) { - if (wire.Closed() == 0) { - TopoDS_Vertex v0, v1; - TopExp::Vertices(wire, v0, v1); - - gp_Pnt p1 = BRep_Tool::Pnt(v0); - gp_Pnt p2 = BRep_Tool::Pnt(v1); - - if (p1.Distance(p2) > getValue(GV_PRECISION)) { - - BRepBuilderAPI_MakeWire mw; - mw.Add(wire); - mw.Add(BRepBuilderAPI_MakeEdge(v0, v1).Edge()); - wire = mw.Wire(); - - } - - Logger::Warning("Wire not closed:"); - } -} - -bool IfcGeom::Kernel::convert_curve_to_wire(const Handle(Geom_Curve)& curve, TopoDS_Wire& wire) { - try { - wire = BRepBuilderAPI_MakeWire(BRepBuilderAPI_MakeEdge(curve)); - return true; - } catch (const Standard_Failure& e) { - if (e.GetMessageString() && strlen(e.GetMessageString())) { - Logger::Error(e.GetMessageString()); - } else { - Logger::Error("Unknown error converting curve to wire"); - } - } catch (...) { - Logger::Error("Unknown error converting curve to wire"); - } - return false; -} - -bool IfcGeom::Kernel::profile_helper(int numVerts, double* verts, int numFillets, int* filletIndices, double* filletRadii, gp_Trsf2d trsf, TopoDS_Shape& face_shape) { - TopoDS_Vertex* vertices = new TopoDS_Vertex[numVerts]; - - for ( int i = 0; i < numVerts; i ++ ) { - gp_XY xy (verts[2*i],verts[2*i+1]); - trsf.Transforms(xy); - vertices[i] = BRepBuilderAPI_MakeVertex(gp_Pnt(xy.X(),xy.Y(),0.0f)); - } - - BRepBuilderAPI_MakeWire w; - for ( int i = 0; i < numVerts; i ++ ) - w.Add(BRepBuilderAPI_MakeEdge(vertices[i],vertices[(i+1)%numVerts])); - - TopoDS_Face face; - convert_wire_to_face(w.Wire(),face); - - if ( numFillets && *std::max_element(filletRadii, filletRadii + numFillets) > ALMOST_ZERO ) { - BRepFilletAPI_MakeFillet2d fillet (face); - for ( int i = 0; i < numFillets; i ++ ) { - const double radius = filletRadii[i]; - if ( radius <= ALMOST_ZERO ) continue; - fillet.AddFillet(vertices[filletIndices[i]],radius); - } - fillet.Build(); - if (fillet.IsDone()) { - face = TopoDS::Face(fillet.Shape()); - } else { - Logger::Error("Failed to process profile fillets"); - } - } - - face_shape = face; - - delete[] vertices; - return true; -} -double IfcGeom::Kernel::shape_volume(const TopoDS_Shape& s) { - GProp_GProps prop; - BRepGProp::VolumeProperties(s, prop); - return prop.Mass(); -} -double IfcGeom::Kernel::face_area(const TopoDS_Face& f) { - GProp_GProps prop; - BRepGProp::SurfaceProperties(f,prop); - return prop.Mass(); -} -bool IfcGeom::Kernel::is_convex(const TopoDS_Wire& wire) { - for ( TopExp_Explorer exp1(wire,TopAbs_VERTEX); exp1.More(); exp1.Next() ) { - TopoDS_Vertex V1 = TopoDS::Vertex(exp1.Current()); - gp_Pnt P1 = BRep_Tool::Pnt(V1); - // Store the neighboring points - std::vector neighbors; - for ( TopExp_Explorer exp3(wire,TopAbs_EDGE); exp3.More(); exp3.Next() ) { - TopoDS_Edge edge = TopoDS::Edge(exp3.Current()); - std::vector edge_points; - for ( TopExp_Explorer exp2(edge,TopAbs_VERTEX); exp2.More(); exp2.Next() ) { - TopoDS_Vertex V2 = TopoDS::Vertex(exp2.Current()); - gp_Pnt P2 = BRep_Tool::Pnt(V2); - edge_points.push_back(P2); - } - if ( edge_points.size() != 2 ) continue; - if ( edge_points[0].IsEqual(P1,getValue(GV_POINT_EQUALITY_TOLERANCE))) neighbors.push_back(edge_points[1]); - else if ( edge_points[1].IsEqual(P1, getValue(GV_POINT_EQUALITY_TOLERANCE))) neighbors.push_back(edge_points[0]); - } - // There should be two of these - if ( neighbors.size() != 2 ) return false; - // Now find the non neighboring points - std::vector non_neighbors; - for ( TopExp_Explorer exp2(wire,TopAbs_VERTEX); exp2.More(); exp2.Next() ) { - TopoDS_Vertex V2 = TopoDS::Vertex(exp2.Current()); - gp_Pnt P2 = BRep_Tool::Pnt(V2); - if ( P1.IsEqual(P2,getValue(GV_POINT_EQUALITY_TOLERANCE)) ) continue; - bool found = false; - for( std::vector::const_iterator it = neighbors.begin(); it != neighbors.end(); ++ it ) { - if ( (*it).IsEqual(P2,getValue(GV_POINT_EQUALITY_TOLERANCE)) ) { found = true; break; } - } - if ( ! found ) non_neighbors.push_back(P2); - } - // Calculate the angle between the two edges of the vertex - gp_Dir dir1(neighbors[0].XYZ() - P1.XYZ()); - gp_Dir dir2(neighbors[1].XYZ() - P1.XYZ()); - const double angle = acos(dir1.Dot(dir2)) + 0.0001; - // Now for the non-neighbors see whether a greater angle can be found with one of the edges - for ( std::vector::const_iterator it = non_neighbors.begin(); it != non_neighbors.end(); ++ it ) { - gp_Dir dir3((*it).XYZ() - P1.XYZ()); - const double angle2 = acos(dir3.Dot(dir1)); - const double angle3 = acos(dir3.Dot(dir2)); - if ( angle2 > angle || angle3 > angle ) return false; - } - } - return true; -} -TopoDS_Shape IfcGeom::Kernel::halfspace_from_plane(const gp_Pln& pln,const gp_Pnt& cent) { - TopoDS_Face face = BRepBuilderAPI_MakeFace(pln).Face(); - return BRepPrimAPI_MakeHalfSpace(face,cent).Solid(); -} -gp_Pln IfcGeom::Kernel::plane_from_face(const TopoDS_Face& face) { - BRepGProp_Face prop(face); - Standard_Real u1,u2,v1,v2; - prop.Bounds(u1,u2,v1,v2); - Standard_Real u = (u1+u2)/2.0; - Standard_Real v = (v1+v2)/2.0; - gp_Pnt p; - gp_Vec n; - prop.Normal(u,v,p,n); - return gp_Pln(p,n); -} -gp_Pnt IfcGeom::Kernel::point_above_plane(const gp_Pln& pln, bool agree) { - if ( agree ) { - return pln.Location().Translated(pln.Axis().Direction()); - } else { - return pln.Location().Translated(-pln.Axis().Direction()); - } -} - -void IfcGeom::Kernel::apply_tolerance(TopoDS_Shape& s, double t) { - /* - // This does not result in actionable error messages and has been disabled. - ShapeAnalysis_ShapeTolerance toler; - if (Logger::LOG_WARNING >= Logger::Verbosity()) { - if (toler.Tolerance(s, 0) > t * 10.) { - Handle_TopTools_HSequenceOfShape shapes = toler.OverTolerance(s, t * 10.); - for (int i = 1; i <= shapes->Length(); ++i) { - const TopoDS_Shape& sub = shapes->Value(i); - std::stringstream ss; - TopAbs::Print(sub.ShapeType(), ss); - Logger::Warning("Tolerance of " + boost::lexical_cast(toler.Tolerance(sub, 0)) + " on " + ss.str()); - } - } - } - */ - -#if OCC_VERSION_HEX < 0x60900 - // This tolerance hack is not required as the boolean ops use a fuzziness value - - ShapeFix_ShapeTolerance tol; - tol.LimitTolerance(s, t); -#else - (void)s; - (void)t; -#endif -} - -namespace { - - // Returns the vertex part of an TopoDS_Edge edge that is not TopoDS_Vertex vertex - TopoDS_Vertex find_other(const TopoDS_Edge& edge, const TopoDS_Vertex& vertex) { - TopExp_Explorer exp(edge, TopAbs_VERTEX); - while (exp.More()) { - if (!exp.Current().IsSame(vertex)) { - return TopoDS::Vertex(exp.Current()); - } - exp.Next(); - } - return TopoDS_Vertex(); - } - - TopoDS_Edge find_next(const TopTools_IndexedMapOfShape& edge_set, const TopTools_IndexedDataMapOfShapeListOfShape& vertex_to_edges, const TopoDS_Vertex& current, const TopoDS_Edge& previous_edge) { - const TopTools_ListOfShape& edges = vertex_to_edges.FindFromKey(current); - TopTools_ListIteratorOfListOfShape eit; - for (eit.Initialize(edges); eit.More(); eit.Next()) { - const TopoDS_Edge& edge = TopoDS::Edge(eit.Value()); - if (edge.IsSame(previous_edge)) continue; - if (edge_set.Contains(edge)) { - return edge; - } - } - return TopoDS_Edge(); - } - -} - -bool IfcGeom::Kernel::fill_nonmanifold_wires_with_planar_faces(TopoDS_Shape& shape) { - BRepOffsetAPI_Sewing sew; - sew.Add(shape); - - TopTools_IndexedDataMapOfShapeListOfShape edge_to_faces; - TopTools_IndexedDataMapOfShapeListOfShape vertex_to_edges; - std::set visited; - TopTools_IndexedMapOfShape edge_set; - - TopExp::MapShapesAndAncestors (shape, TopAbs_EDGE, TopAbs_FACE, edge_to_faces); - - const int num_edges = edge_to_faces.Extent(); - for (int i = 1; i <= num_edges; ++i) { - const TopTools_ListOfShape& faces = edge_to_faces.FindFromIndex(i); - const int count = faces.Extent(); - // Find only the non-manifold edges: Edges that are only part of a - // single face and therefore part of the wire(s) we want to fill. - if (count == 1) { - const TopoDS_Shape& edge = edge_to_faces.FindKey(i); - TopExp::MapShapesAndAncestors (edge, TopAbs_VERTEX, TopAbs_EDGE, vertex_to_edges); - edge_set.Add(edge); - } - } - - const int num_verts = vertex_to_edges.Extent(); - TopoDS_Vertex first, current; - TopoDS_Edge previous_edge; - - // Now loop over all the vertices that are part of the wire(s) to be filled - for (int i = 1; i <= num_verts; ++i) { - first = current = TopoDS::Vertex(vertex_to_edges.FindKey(i)); - // We keep track of the vertices we already used - if (visited.find(vertex_to_edges.FindIndex(current)) != visited.end()) { - continue; - } - // Given these vertices, try to find closed loops and create new - // wires out of them. - BRepBuilderAPI_MakeWire w; - for (;;) { - visited.insert(vertex_to_edges.FindIndex(current)); - // Find the edge that the current vertex is part of and points - // away from the previous vertex (null for the first vertex). - TopoDS_Edge edge = find_next(edge_set, vertex_to_edges, current, previous_edge); - if (edge.IsNull()) { - return false; - } - TopoDS_Vertex other = find_other(edge, current); - if (other.IsNull()) { - // Dealing with a conical edge probably, for some reason - // this works better than adding the edge directly. - double u1, u2; - Handle(Geom_Curve) crv = BRep_Tool::Curve(edge, u1, u2); - w.Add(BRepBuilderAPI_MakeEdge(crv, u1, u2)); - break; - } else { - w.Add(edge); - } - // See if the starting point of this loop has been reached. Note that - // additional wires after this one potentially will be created. - if (other.IsSame(first)) { - break; - } - previous_edge = edge; - current = other; - } - sew.Add(BRepBuilderAPI_MakeFace(w)); - previous_edge.Nullify(); - } - - sew.Perform(); - shape = sew.SewedShape(); - - try { - ShapeFix_Solid solid; - solid.LimitTolerance(getValue(GV_POINT_EQUALITY_TOLERANCE)); - shape = solid.SolidFromShell(TopoDS::Shell(shape)); - } catch (const Standard_Failure& e) { - if (e.GetMessageString() && strlen(e.GetMessageString())) { - Logger::Error(e.GetMessageString()); - } else { - Logger::Error("Unknown error creating solid"); - } - } catch (...) { - Logger::Error("Unknown error creating solid"); - } - - return true; -} - -bool IfcGeom::Kernel::flatten_shape_list(const IfcGeom::ConversionResults& shapes, TopoDS_Shape& result, bool fuse) { - TopoDS_Compound compound; - BRep_Builder builder; - builder.MakeCompound(compound); - - result = TopoDS_Shape(); - - for ( IfcGeom::ConversionResults::const_iterator it = shapes.begin(); it != shapes.end(); ++ it ) { - TopoDS_Shape merged; - const TopoDS_Shape& s = *(OpenCascadeShape*)it->Shape(); - if (fuse) { - ensure_fit_for_subtraction(s, merged); - } else { - merged = s; - } - const OpenCascadePlacement* trsf = (const OpenCascadePlacement*) it->Placement(); - const TopoDS_Shape moved_shape = apply_transformation(merged, trsf); - - if (shapes.size() == 1) { - result = moved_shape; - const double precision = getValue(GV_PRECISION); - apply_tolerance(result, precision); - return true; - } - - if (fuse) { - if (result.IsNull()) { - result = moved_shape; - } else { - BRepAlgoAPI_Fuse brep_fuse(result, moved_shape); - if ( brep_fuse.IsDone() ) { - TopoDS_Shape fused = brep_fuse; - - ShapeFix_Shape fix(result); - fix.Perform(); - result = fix.Shape(); - - bool is_valid = BRepCheck_Analyzer(result).IsValid() != 0; - if ( is_valid ) { - result = fused; - } - } - } - } else { - builder.Add(compound,moved_shape); - } - } - - if (!fuse) { - result = compound; - } - - const bool success = !result.IsNull(); - if (success) { - const double precision = getValue(GV_PRECISION); - apply_tolerance(result, precision); - } - - return success; -} - -void IfcGeom::Kernel::remove_duplicate_points_from_loop(TColgp_SequenceOfPnt& polygon, bool closed, double tol) { - if (tol <= 0.) tol = getValue(GV_PRECISION); - tol *= tol; - - for (;;) { - bool removed = false; - int n = polygon.Length() - (closed ? 0 : 1); - for (int i = 1; i <= n; ++i) { - // wrap around to the first point in case of a closed loop - int j = (i % polygon.Length()) + 1; - double dist = polygon.Value(i).SquareDistance(polygon.Value(j)); - if (dist < tol) { - // do not remove the first or last point to - // maintain connectivity with other wires - if ((closed && j == 1) || (!closed && j == n)) polygon.Remove(i); - else polygon.Remove(j); - removed = true; - break; - } - } - if (!removed) break; - } -} - -void IfcGeom::Kernel::remove_collinear_points_from_loop(TColgp_SequenceOfPnt& polygon, bool closed, double tol) { - if (tol <= 0.) tol = getValue(GV_PRECISION); - const int start = closed ? 1 : 2; - const int end = polygon.Length() - (closed ? 0 : 1); - std::vector to_remove(polygon.Length(), false); - for (int i = start; i <= end; ++i) { - const gp_Pnt& a = polygon.Value(((i - 2 + polygon.Length()) % polygon.Length()) + 1); - const gp_Pnt& b = polygon.Value(i); - const gp_Pnt& c = polygon.Value((i % polygon.Length()) + 1); - const gp_Vec d1 = c.XYZ() - a.XYZ(); - const gp_Vec d2 = b.XYZ() - a.XYZ(); - const double dt = d2.Dot(d1) / d1.Dot(d1); - const gp_Vec d3 = d1.Scaled(dt); - const gp_Pnt b2 = a.XYZ() + d3.XYZ(); - if (b.Distance(b2) < tol) { - to_remove[i-1] = true; - } - } - for (int i = (int) to_remove.size() - 1; i >= 0; --i) { - if (to_remove[i]) { - polygon.Remove(i+1); - } - } -} - -bool IfcGeom::Kernel::wire_to_sequence_of_point(const TopoDS_Wire& w, TColgp_SequenceOfPnt& p) { - TopExp_Explorer exp(w, TopAbs_EDGE); - for (; exp.More(); exp.Next()) { - double a, b; - Handle_Geom_Curve crv = BRep_Tool::Curve(TopoDS::Edge(exp.Current()), a, b); - if (crv->DynamicType() != STANDARD_TYPE(Geom_Line)) { - return false; - } - } - - exp.ReInit(); - - int i = 0; - for (; exp.More(); exp.Next(), ++i) { - TopoDS_Vertex v1, v2; - TopExp::Vertices(TopoDS::Edge(exp.Current()), v1, v2, true); - if (exp.More()) { - if (i == 0) { - p.Append(BRep_Tool::Pnt(v1)); - } - p.Append(BRep_Tool::Pnt(v2)); - } - } - - return true; -} - -void IfcGeom::Kernel::sequence_of_point_to_wire(const TColgp_SequenceOfPnt& p, TopoDS_Wire& w, bool close) { - BRepBuilderAPI_MakePolygon builder; - for (int i = 1; i <= p.Length(); ++i) { - builder.Add(p.Value(i)); - } - if (close) { - builder.Close(); - } - w = builder.Wire(); -} - -std::pair IfcGeom::Kernel::initializeUnits(IfcSchema::IfcUnitAssignment* unit_assignment) { - // Set default units, set length to meters, angles to undefined - setValue(IfcGeom::Kernel::GV_LENGTH_UNIT, 1.0); - setValue(IfcGeom::Kernel::GV_PLANEANGLE_UNIT, -1.0); - - std::string unit_name = "METER"; - double unit_magnitude = 1.; - - bool length_unit_encountered = false, angle_unit_encountered = false; - - try { - IfcEntityList::ptr units = unit_assignment->Units(); - if (!units || !units->size()) { - Logger::Warning("No unit information found"); - } else { - for (IfcEntityList::it it = units->begin(); it != units->end(); ++it) { - IfcUtil::IfcBaseClass* base = *it; - if (base->declaration().is(IfcSchema::IfcNamedUnit::Class())) { - IfcSchema::IfcNamedUnit* named_unit = base->as(); - if (named_unit->UnitType() == IfcSchema::IfcUnitEnum::IfcUnit_LENGTHUNIT || - named_unit->UnitType() == IfcSchema::IfcUnitEnum::IfcUnit_PLANEANGLEUNIT) - { - std::string current_unit_name; - const double current_unit_magnitude = IfcParse::get_SI_equivalent(named_unit); - if (current_unit_magnitude != 0.) { - if (named_unit->declaration().is(IfcSchema::IfcConversionBasedUnit::Class())) { - IfcSchema::IfcConversionBasedUnit* u = (IfcSchema::IfcConversionBasedUnit*)base; - current_unit_name = u->Name(); - } else if (named_unit->declaration().is(IfcSchema::IfcSIUnit::Class())) { - IfcSchema::IfcSIUnit* si_unit = named_unit->as(); - if (si_unit->hasPrefix()) { - current_unit_name = IfcSchema::IfcSIPrefix::ToString(si_unit->Prefix()) + unit_name; - } - current_unit_name += IfcSchema::IfcSIUnitName::ToString(si_unit->Name()); - } - if (named_unit->UnitType() == IfcSchema::IfcUnitEnum::IfcUnit_LENGTHUNIT) { - unit_name = current_unit_name; - unit_magnitude = current_unit_magnitude; - setValue(IfcGeom::Kernel::GV_LENGTH_UNIT, current_unit_magnitude); - length_unit_encountered = true; - } else { - setValue(IfcGeom::Kernel::GV_PLANEANGLE_UNIT, current_unit_magnitude); - angle_unit_encountered = true; - } - } - } - } - } - } - } catch (const IfcParse::IfcException& ex) { - std::stringstream ss; - ss << "Failed to determine unit information '" << ex.what() << "'"; - Logger::Message(Logger::LOG_ERROR, ss.str()); - } - - if (!length_unit_encountered) { - Logger::Warning("No length unit encountered"); - } - - if (!angle_unit_encountered) { - Logger::Warning("No plane angle unit encountered"); - } - - return std::pair(unit_name, unit_magnitude); -} - -bool IfcGeom::Kernel::convert_layerset(const IfcSchema::IfcProduct* product, std::vector& surfaces, std::vector& styles, std::vector& thicknesses) { - IfcSchema::IfcMaterialLayerSetUsage* usage = 0; - Handle_Geom_Surface reference_surface; - - IfcSchema::IfcRelAssociates::list::ptr associations = product->HasAssociations(); - for (IfcSchema::IfcRelAssociates::list::it it = associations->begin(); it != associations->end(); ++it) { - IfcSchema::IfcRelAssociatesMaterial* associates_material = (**it).as(); - if (associates_material) { - usage = associates_material->RelatingMaterial()->as(); - break; - } - } - - if (!usage) { - return false; - } - - IfcSchema::IfcRepresentation* body_representation = find_representation(product, "Body"); - - if (!body_representation) { - Logger::Warning("No body representation for product", product); - return false; - } - - if (product->declaration().is(IfcSchema::IfcWall::Class())) { - IfcSchema::IfcRepresentation* axis_representation = find_representation(product, "Axis"); - - if (!axis_representation) { - Logger::Message(Logger::LOG_WARNING, "No axis representation for:", product); - return false; - } - - ConversionResults axis_items; - { - Kernel temp = *this; - temp.setValue(GV_DIMENSIONALITY, -1.); - temp.convert_shapes(axis_representation, axis_items); - } - - TopoDS_Shape axis_shape; - flatten_shape_list(axis_items, axis_shape, false); - - TopExp_Explorer exp(axis_shape, TopAbs_EDGE); - TopoDS_Edge axis_edge; - int edge_count = 0; - - if (exp.More()) { - axis_edge = TopoDS::Edge(exp.Current()); - ++ edge_count; - } else { - Logger::Message(Logger::LOG_WARNING, "No edge found in axis representation:", product); - return false; - } - - double u1, u2; - Handle_Geom_Curve axis_curve = BRep_Tool::Curve(axis_edge, u1, u2); - - if (true) { /**< @todo Why always true? */ - if (axis_curve->DynamicType() == STANDARD_TYPE(Geom_Line)) { - Handle_Geom_Line axis_line = Handle_Geom_Line::DownCast(axis_curve); - // @todo note that this creates an offset into the wrong order, the cross product arguments should be - // reversed. This causes some inversions later on, e.g. if(positive) { reverse(); } - reference_surface = new Geom_Plane(axis_line->Lin().Location(), axis_line->Lin().Direction() ^ gp::DZ()); - } else if (axis_curve->DynamicType() == STANDARD_TYPE(Geom_Circle)) { - // @todo note that in this branch this inversion does not seem to take place. - Handle_Geom_Circle axis_line = Handle_Geom_Circle::DownCast(axis_curve); - reference_surface = new Geom_CylindricalSurface(axis_line->Position(), axis_line->Radius()); - } else { - Logger::Message(Logger::LOG_ERROR, "Unsupported underlying curve of Axis representation:", product); - return false; - } - } else { - // Unfortunately this does not work when its intersection - // is calculated later on when the layerset is applied. - reference_surface = new Geom_SurfaceOfLinearExtrusion(axis_curve, gp::DZ()); - } - - } else { - IfcSchema::IfcExtrudedAreaSolid::list::ptr extrusions = IfcParse::traverse(body_representation)->as(); - - if (extrusions->size() != 1) { - Logger::Message(Logger::LOG_WARNING, "No single extrusion found in body representation for:", product); - return false; - } - - IfcSchema::IfcExtrudedAreaSolid* extrusion = *extrusions->begin(); - - gp_Trsf extrusion_position; - - bool has_position = true; -#ifdef SCHEMA_IfcSweptAreaSolid_Position_IS_OPTIONAL - has_position = extrusion->hasPosition(); -#endif - if (has_position) { - if (!convert(extrusion->Position(), extrusion_position)) { - Logger::Message(Logger::LOG_ERROR, "Failed to convert placement for extrusion of:", product); - return false; - } - } - - gp_Dir extrusion_direction; - if (!convert(extrusion->ExtrudedDirection(), extrusion_direction)) { - Logger::Message(Logger::LOG_ERROR, "Failed to convert direction for extrusion of:", product); - return false; - } - - reference_surface = new Geom_Plane(extrusion_position.TranslationPart(), extrusion_direction); - } - - const IfcSchema::IfcMaterialLayerSet* layerset = usage->ForLayerSet(); - const bool positive = usage->DirectionSense() == IfcSchema::IfcDirectionSenseEnum::IfcDirectionSense_POSITIVE; - double offset = usage->OffsetFromReferenceLine() * getValue(GV_LENGTH_UNIT); - - IfcSchema::IfcMaterialLayer::list::ptr material_layers = layerset->MaterialLayers(); - - surfaces.push_back(new Geom_OffsetSurface(reference_surface, -offset)); - - for (IfcSchema::IfcMaterialLayer::list::it it = material_layers->begin(); it != material_layers->end(); ++it) { - styles.push_back(get_style((*it)->Material())); - - double thickness = (*it)->LayerThickness() * getValue(GV_LENGTH_UNIT); - - thicknesses.push_back(thickness); - - if (!positive) { - thickness *= -1; - } - - offset += thickness; - - if (fabs(offset) < 1.e-7) { - surfaces.push_back(reference_surface); - } else { - surfaces.push_back(new Geom_OffsetSurface(reference_surface, -offset)); - } - } - - if (positive) { - std::reverse(thicknesses.begin(), thicknesses.end()); - std::reverse(styles.begin(), styles.end()); - std::reverse(surfaces.begin(), surfaces.end()); - } - - return true; -} - -const Handle_Geom_Curve IfcGeom::Kernel::intersect(const Handle_Geom_Surface& a, const Handle_Geom_Surface& b) { - GeomAPI_IntSS x(a, b, 1.e-7); - if (x.IsDone() && x.NbLines() == 1) { - return x.Line(1); - } else { - return Handle_Geom_Curve(); - } -} - -const Handle_Geom_Curve IfcGeom::Kernel::intersect(const Handle_Geom_Surface& a, const TopoDS_Face& b) { - return intersect(a, BRep_Tool::Surface(b)); -} - -const Handle_Geom_Curve IfcGeom::Kernel::intersect(const TopoDS_Face& a, const Handle_Geom_Surface& b) { - return intersect(BRep_Tool::Surface(a), b); -} - -bool IfcGeom::Kernel::intersect(const Handle_Geom_Curve& a, const Handle_Geom_Surface& b, gp_Pnt& p) { - GeomAPI_IntCS x(a, b); - if (x.IsDone() && x.NbPoints() == 1) { - p = x.Point(1); - return true; - } else { - return false; - } -} - -bool IfcGeom::Kernel::intersect(const Handle_Geom_Curve& a, const TopoDS_Face& b, gp_Pnt &c) { - return intersect(a, BRep_Tool::Surface(b), c); -} - -bool IfcGeom::Kernel::intersect(const Handle_Geom_Curve& a, const TopoDS_Shape& b, std::vector& out) { - TopExp_Explorer exp(b, TopAbs_FACE); - gp_Pnt p; - for (; exp.More(); exp.Next()) { - if (intersect(a, TopoDS::Face(exp.Current()), p)) { - out.push_back(p); - } - } - return !out.empty(); -} - -bool IfcGeom::Kernel::intersect(const Handle_Geom_Surface& a, const TopoDS_Shape& b, std::vector< std::pair >& out) { - TopExp_Explorer exp(b, TopAbs_FACE); - for (; exp.More(); exp.Next()) { - const TopoDS_Face& f = TopoDS::Face(exp.Current()); - const Handle_Geom_Surface& s = BRep_Tool::Surface(f); - Handle_Geom_Curve crv = intersect(a, s); - if (!crv.IsNull()) { - out.push_back(std::make_pair(s, crv)); - } - } - return !out.empty(); -} - -bool IfcGeom::Kernel::closest(const gp_Pnt& a, const std::vector& b, gp_Pnt& c) { - double minimal_distance = std::numeric_limits::infinity(); - for (std::vector::const_iterator it = b.begin(); it != b.end(); ++it) { - const double d = a.Distance(*it); - if (d < minimal_distance) { - minimal_distance = d; - c = *it; - } - } - return minimal_distance != std::numeric_limits::infinity(); -} - -bool IfcGeom::Kernel::project(const Handle_Geom_Curve& crv, const gp_Pnt& pt, gp_Pnt& p, double& u, double& d) { - ShapeAnalysis_Curve sac; - sac.Project(crv, pt, 1e-3, p, u, false); - d = pt.Distance(p); - return true; -} - -bool IfcGeom::Kernel::find_wall_end_points(const IfcSchema::IfcWall* wall, gp_Pnt& start, gp_Pnt& end) { - IfcSchema::IfcRepresentation* axis_representation = find_representation(wall, "Axis"); - if (!axis_representation) { - return false; - } - - ConversionResults items; - { - Kernel temp = *this; - temp.setValue(GV_DIMENSIONALITY, -1.); - temp.convert_shapes(axis_representation, items); - } - - TopoDS_Vertex a, b; - for (ConversionResults::const_iterator it = items.begin(); it != items.end(); ++it) { - TopExp_Explorer exp(*(OpenCascadeShape*)it->Shape(), TopAbs_VERTEX); - for (; exp.More(); exp.Next()) { - b = TopoDS::Vertex(exp.Current()); - if (a.IsNull()) { - a = b; - } - } - } - - if (a.IsNull() || b.IsNull()) { - return false; - } - - start = BRep_Tool::Pnt(a); - end = BRep_Tool::Pnt(b); - - return true; -} - -bool IfcGeom::Kernel::fold_layers(const IfcSchema::IfcWall* wall, const ConversionResults& items, const std::vector& surfaces, const std::vector& thicknesses, std::vector< std::vector >& result) { - bool folds_made = false; - - IfcSchema::IfcRelConnectsPathElements::list::ptr connections(new IfcSchema::IfcRelConnectsPathElements::list); - connections->push(wall->ConnectedFrom()->as()); - connections->push( wall->ConnectedTo()->as()); - - typedef std::vector surfaces_t; - typedef std::pair curve_on_surface; - typedef std::vector curves_on_surfaces_t; - typedef std::vector< std::pair< std::pair, const IfcSchema::IfcProduct*> > endpoint_connections_t; - typedef std::vector< std::vector > result_t; - endpoint_connections_t endpoint_connections; - - for (IfcSchema::IfcRelConnectsPathElements::list::it it = connections->begin(); it != connections->end(); ++it) { - IfcSchema::IfcRelConnectsPathElements* connection = *it; - IfcSchema::IfcConnectionTypeEnum::Value own_type = connection->RelatedElement() == wall - ? connection->RelatedConnectionType() - : connection->RelatingConnectionType(); - IfcSchema::IfcConnectionTypeEnum::Value other_type = connection->RelatedElement() == wall - ? connection->RelatingConnectionType() - : connection->RelatedConnectionType(); - if (other_type != IfcSchema::IfcConnectionTypeEnum::IfcConnectionType_ATPATH && - (own_type == IfcSchema::IfcConnectionTypeEnum::IfcConnectionType_ATEND || - own_type == IfcSchema::IfcConnectionTypeEnum::IfcConnectionType_ATSTART)) - { - IfcSchema::IfcElement* other = connection->RelatedElement() == wall - ? connection->RelatingElement() - : connection->RelatedElement(); - if (other->as()) { - endpoint_connections.push_back(std::make_pair(std::make_pair(own_type, other_type), other)); - } - } - } - - if (endpoint_connections.size() == 0) { - return false; - } - - int connection_type_count[2] = {0,0}; - for (endpoint_connections_t::const_iterator it = endpoint_connections.begin(); it != endpoint_connections.end(); ++it) { - const int idx = it->first.first == IfcSchema::IfcConnectionTypeEnum::IfcConnectionType_ATSTART; - connection_type_count[idx] ++; - } - - gp_Trsf local; - if (!convert(wall->ObjectPlacement(), local)) { - return false; - } - local.Invert(); - - { - // Copy the unfolded surfaces - result.resize(surfaces.size()); - std::vector< std::vector >::iterator result_it = result.begin() + 1; - std::vector::const_iterator input_it = surfaces.begin() + 1; - for(; input_it != surfaces.end() - 1; ++result_it, ++input_it) { - result_it->push_back(*input_it); - } - } - - gp_Pnt own_axis_start, own_axis_end; - find_wall_end_points(wall, own_axis_start, own_axis_end); - - for (int idx = 0; idx < 2; ++idx) { - if (connection_type_count[idx] <= 1) { - continue; - } - - /* - IfcSchema::IfcConnectionTypeEnum::Value connection_type = idx == 1 - ? IfcSchema::IfcConnectionTypeEnum::IfcConnectionType_ATSTART - : IfcSchema::IfcConnectionTypeEnum::IfcConnectionType_ATEND; - */ - - std::set others; - endpoint_connections_t::iterator it = endpoint_connections.begin(); - while (it != endpoint_connections.end()) { - const IfcSchema::IfcProduct* other = it->second; - if (others.find(other) != others.end()) { - it = endpoint_connections.erase(it); - -- connection_type_count[idx]; - } else { - others.insert(other); - ++it; - } - } - - /* - Additionally one could check whether the end points are of the wall are really ~1 LayerThickness away from each other - for (endpoint_connections_t::const_iterator it = endpoint_connections.begin(); it != endpoint_connections.end(); ++it) { - IfcSchema::IfcConnectionTypeEnum::Value relating_connection_type = it->first.first; - IfcSchema::IfcConnectionTypeEnum::Value related_connection_type = it->first.second; - - if (connection_type != relating_connection_type) { - continue; - } - - gp_Pnt other_axis_start, other_axis_end; - find_wall_end_points(it->second->as(), other_axis_start, other_axis_end); - - gp_Trsf other; - if (!convert(it->second->ObjectPlacement(), other)) { - continue; - } - - other.Transforms(other_axis_start.ChangeCoord()); - local.Transforms(other_axis_start.ChangeCoord()); - other.Transforms(other_axis_end.ChangeCoord()); - local.Transforms(other_axis_end.ChangeCoord()); - - const gp_Pnt& a = relating_connection_type == IfcSchema::IfcConnectionTypeEnum::IfcConnectionType_ATSTART - ? own_axis_start - : own_axis_end; - - const gp_Pnt& b = related_connection_type == IfcSchema::IfcConnectionTypeEnum::IfcConnectionType_ATSTART - ? other_axis_start - : other_axis_end; - - const double d = a.Distance(b); - } - */ - } - - for (endpoint_connections_t::const_iterator it = endpoint_connections.begin(); it != endpoint_connections.end(); ++it) { - IfcSchema::IfcConnectionTypeEnum::Value connection_type = it->first.first; - - // If more than one wall connects to this start/end -point assume layers do not need to be folded - const int idx = connection_type == IfcSchema::IfcConnectionTypeEnum::IfcConnectionType_ATSTART; - if (connection_type_count[idx] > 1) continue; - - const gp_Pnt& own_end_point = connection_type == IfcSchema::IfcConnectionTypeEnum::IfcConnectionType_ATEND - ? own_axis_end - : own_axis_start; - const IfcSchema::IfcProduct* other_wall = it->second; - - gp_Trsf other; - if (!convert(other_wall->ObjectPlacement(), other)) { - Logger::Error("Failed to convert placement", other_wall); - continue; - } - - IfcSchema::IfcRepresentation* axis_representation = find_representation(other_wall, "Axis"); - - if (!axis_representation) { - Logger::Warning("Joined wall has no axis representation", other_wall); - continue; - } - - ConversionResults axis_items; - { - Kernel temp = *this; - temp.setValue(GV_DIMENSIONALITY, -1.); - temp.convert_shapes(axis_representation, axis_items); - } - - TopoDS_Shape axis_shape; - flatten_shape_list(axis_items, axis_shape, false); - - // local and other are IfcLocalPlacements and therefore have a unit - // scale factor that can be applied by means of TopoDS_Shape::Move() - axis_shape.Move(other); - axis_shape.Move(local); - - TopoDS_Shape body_shape; - flatten_shape_list(items, body_shape, false); - - Handle_Geom_Curve axis_curve; - double axis_u1, axis_u2; - - { - TopExp_Explorer exp(axis_shape, TopAbs_EDGE); - if (!exp.More()) { - return false; - } - - TopoDS_Edge axis_edge = TopoDS::Edge(exp.Current()); - axis_curve = BRep_Tool::Curve(axis_edge, axis_u1, axis_u2); - - gp_Pnt other_a_1, other_a_2; - axis_curve->D0(axis_u1, other_a_1); - axis_curve->D0(axis_u2, other_a_2); - - if (axis_u2 < axis_u1) { - std::swap(axis_u1, axis_u2); - } - exp.Next(); - - for (; exp.More(); exp.Next()) { - TopoDS_Edge axis_edge2 = TopoDS::Edge(exp.Current()); - TopExp_Explorer exp2(axis_edge2, TopAbs_VERTEX); - for (; exp2.More(); exp2.Next()) { - gp_Pnt p = BRep_Tool::Pnt(TopoDS::Vertex(exp2.Current())); - gp_Pnt pp; - double u, d; - if (project(axis_curve, p, pp, u, d)) { - if (u < axis_u1) axis_u1 = u; - if (u > axis_u2) axis_u2 = u; - } - } - } - } - - double layer_offset = 0; - - std::vector::const_iterator thickness = thicknesses.begin(); - result_t::iterator result_vector = result.begin() + 1; - - for (surfaces_t::const_iterator jt = surfaces.begin() + 1; jt != surfaces.end() - 1; ++jt, ++result_vector) { - layer_offset += *thickness++; - - bool found_intersection = false; - boost::optional point_outside_param_range; - //double param; - - const Handle_Geom_Surface& surface = *jt; - - GeomAPI_IntCS intersections(axis_curve, surface); - if (intersections.IsDone() && intersections.NbPoints() == 1) { - const gp_Pnt& p = intersections.Point(1); - double u, v, w; - intersections.Parameters(1, u, v, w); - if (w < axis_u1 || w > axis_u2) { - point_outside_param_range = p; - //param = w; - } else { - // Found an intersection. Layer end point is covered by connecting wall - found_intersection = true; - break; - } - } - - if (!found_intersection && point_outside_param_range) { - - /* - Is there a bug in Open Cascade related to the intersection - of offset surfaces constructed from linear extrusions? - Handle_Geom_Surface xy = new Geom_Plane(gp::Origin(), gp::DZ()); - // Handle_Geom_Surface yz = new Geom_Plane(gp::Origin(), gp::DX()); - // Handle_Geom_Surface yz2 = new Geom_OffsetSurface(yz, 1.); - Handle_Geom_Curve ln = new Geom_Line(gp::Origin(), gp::DX()); - Handle_Geom_Surface yz = new Geom_SurfaceOfLinearExtrusion(ln, gp::DZ()); - Handle_Geom_Surface yz2 = new Geom_OffsetSurface(yz, 1.); - intersect(xy, yz2); - */ - - Handle_Geom_Surface plane = new Geom_Plane(*point_outside_param_range, gp::DZ()); - - curves_on_surfaces_t layer_ends; - intersect(surface, body_shape, layer_ends); - - Handle_Geom_Curve layer_body_intersection; - Handle_Geom_Surface body_surface; - double mind = std::numeric_limits::infinity(); - for (curves_on_surfaces_t::const_iterator kt = layer_ends.begin(); kt != layer_ends.end(); ++kt) { - gp_Pnt p; - gp_Vec v; - double u, d; - kt->second->D1(0., p, v); - if (ALMOST_THE_SAME(0., v.Dot(gp::DZ()))) { - // Filter horizontal curves - continue; - } - if (project(kt->second, own_end_point, p, u, d)) { - if (d < mind) { - body_surface = kt->first; - layer_body_intersection = kt->second; - mind = d; - } - } - } - - GeomAPI_IntCS intersection2(layer_body_intersection, plane); - if (intersection2.IsDone() && intersection2.NbPoints() == 1) { - const gp_Pnt& layer_end_point = intersection2.Point(1); - GeomAPI_IntSS intersection3(surface, plane, 1.e-7); - if (intersection3.IsDone() && intersection3.NbLines() == 1) { - Handle_Geom_Curve layer_line = intersection3.Line(1); - GeomAdaptor_Curve layer_line_adaptor(layer_line); - ShapeAnalysis_Curve sac; - gp_Pnt layer_end_point_projected; double layer_end_point_param; - sac.Project(layer_line, layer_end_point, 1e-3, layer_end_point_projected, layer_end_point_param, false); - - GCPnts_AbscissaPoint dst(layer_line_adaptor, layer_offset, layer_end_point_param); - if (dst.IsDone()) { - gp_Pnt layer_fold_point; - layer_line->D0(dst.Parameter(), layer_fold_point); - - GeomAPI_IntSS intersection4(body_surface, plane, 1.e-7); - if (intersection4.IsDone() && intersection4.NbLines() == 1) { - Handle_Geom_Curve body_trim_curve = intersection4.Line(1); - ShapeAnalysis_Curve sac2; - gp_Pnt layer_fold_point_projected; double layer_fold_point_param; - sac2.Project(body_trim_curve, layer_fold_point, 1.e-7, layer_fold_point_projected, layer_fold_point_param, false); - Handle_Geom_Curve fold_curve = new Geom_OffsetCurve(body_trim_curve->Reversed(), layer_fold_point_projected.Distance(layer_fold_point), gp::DZ()); - - Handle_Geom_Surface fold_surface = new Geom_SurfaceOfLinearExtrusion(fold_curve, gp::DZ()); - result_vector->push_back(fold_surface); - folds_made = true; - } - } - } - } - - } - - } - } - - return folds_made; -} - -namespace { - -#if OCC_VERSION_HEX >= 0x70200 - bool split(IfcGeom::Kernel&, const TopoDS_Shape& input, const TopTools_ListOfShape& operands, double eps, std::vector& slices) { - if (operands.Extent() < 2) { - // Needs to have at least two cutting surfaces for the ordering based on surface containment to work. - return false; - } - - BRepAlgoAPI_Splitter split; - TopTools_ListOfShape input_list; - input_list.Append(input); - split.SetArguments(input_list); - split.SetTools(operands); - split.SetNonDestructive(true); - split.SetFuzzyValue(eps); - split.Build(); - - if (!split.IsDone()) { - return false; - } else { - - std::map surfaces; - - // NB 1, since first surface has been excluded - int i = 1; - for (TopTools_ListIteratorOfListOfShape it(operands); it.More(); it.Next(), ++i) { - TopExp_Explorer exp(it.Value(), TopAbs_FACE); - for (; exp.More(); exp.Next()) { - surfaces.insert(std::make_pair(BRep_Tool::Surface(TopoDS::Face(exp.Current())).get(), i)); - } - } - - // Count subshapes - size_t n = 0; - TopoDS_Iterator sit(split.Shape()); - for (; sit.More(); sit.Next()) { - ++n; - } - - // Initialize storage - slices.resize(n); - - sit.Initialize(split.Shape()); - for (; sit.More(); sit.Next()) { - - // Iterate over the faces of solid to find correspondence to original - // splitting surfaces. For the outmost slices, there will be a single - // corresponding surface, because the outmost surfaces that align with - // the body geometry have not been added as operands. For intermediate - // slices, two surface indices should be find that should be next to - // each other in the array of input surfaces. - - TopExp_Explorer exp(sit.Value(), TopAbs_FACE); - int min = std::numeric_limits::max(); - int max = std::numeric_limits::min(); - for (; exp.More(); exp.Next()) { - auto ssrf = BRep_Tool::Surface(TopoDS::Face(exp.Current())); - auto it = surfaces.find(ssrf.get()); - if (it != surfaces.end()) { - if (it->second < min) { - min = it->second; - - } - if (it->second > max) { - max = it->second; - } - } - } - - int idx = std::numeric_limits::max(); - if (min != std::numeric_limits::max()) { - if (min == 1 && max == 1) { - idx = 0; - } else if (min + 1 == max || min == max) { - idx = min; - } - } - - if (idx < (int) slices.size()) { - if (slices[idx].IsNull()) { - slices[idx] = sit.Value(); - continue; - } - } - - Logger::Error("Unable to map layer geometry to material index"); - return false; - } - } - - return true; - } -#else - bool split(IfcGeom::Kernel& k, const TopoDS_Shape& input, const TopTools_ListOfShape& operands, double, std::vector& slices) { - TopTools_ListIteratorOfListOfShape it(operands); - TopoDS_Shape i = input; - for (; it.More(); it.Next()) { - const TopoDS_Shape& s = it.Value(); - TopoDS_Shape a, b; - - Handle(Geom_Surface) surf; - if (s.ShapeType() == TopAbs_FACE) { - surf = BRep_Tool::Surface(TopoDS::Face(s)); - } - - if ((s.ShapeType() == TopAbs_FACE && k.split_solid_by_surface(i, surf, a, b)) || - (s.ShapeType() == TopAbs_SHELL && k.split_solid_by_shell(i, s, a, b))) - { - slices.push_back(b); - i = a; - } else { - return false; - } - } - slices.push_back(i); - return true; - } -#endif -} - -bool IfcGeom::Kernel::apply_folded_layerset(const ConversionResults& items, const std::vector< std::vector >& surfaces, const std::vector& styles, ConversionResults& result) { - Bnd_Box bb; - TopoDS_Shape input; - flatten_shape_list(items, input, false); - - typedef std::vector< std::vector > folded_surfaces_t; - typedef std::vector< std::pair< TopoDS_Face, std::pair > > faces_with_mass_t; - - TopTools_ListOfShape shells; - - for (folded_surfaces_t::const_iterator it = surfaces.begin(); it != surfaces.end(); ++it) { - if (it->empty()) { - continue; - } else if (it->size() == 1) { - const Handle_Geom_Surface& surface = (*it)[0]; - double u1, v1, u2, v2; - if (!project(surface, input, u1, v1, u2, v2)) { - continue; - } - shells.Append(BRepBuilderAPI_MakeShell(surface, u1, v1, u2, v2).Shell()); - } else { - faces_with_mass_t solids; - for (folded_surfaces_t::value_type::const_iterator jt = it->begin(); jt != it->end(); ++jt) { - const Handle_Geom_Surface& surface = *jt; - double u1, v1, u2, v2; - if (!project(surface, input, u1, v1, u2, v2)) { - continue; - } - TopoDS_Face face = BRepBuilderAPI_MakeFace(surface, u1, u2, v1, v2, 1.e-7).Face(); - gp_Pnt p, p1, p2; gp_Vec vu, vv, n; - surface->D1((u1+u2)/2., (v1+v2)/2., p, vu, vv); - n = vu ^ vv; - p1 = p.Translated( n); - p2 = p.Translated(-n); - solids.push_back(std::make_pair(face, std::make_pair(p1, p2))); - } - - - if (solids.empty()) { - continue; - } - - faces_with_mass_t::iterator jt = solids.begin(); - TopoDS_Face& A = jt->first; - TopoDS_Shape An = BRepPrimAPI_MakeHalfSpace(A, jt->second.second).Solid(); - for (++jt; jt != solids.end(); ++jt) { - TopoDS_Face& B = jt->first; - TopoDS_Shape Bn = BRepPrimAPI_MakeHalfSpace(B, jt->second.second).Solid(); - - TopoDS_Shape a = BRepAlgoAPI_Cut(A, Bn); - if (count_occt(a, TopAbs_FACE) == 1) { - A = TopoDS::Face(TopExp_Explorer(a, TopAbs_FACE).Current()); - } - - TopoDS_Shape b = BRepAlgoAPI_Cut(B, An); - if (count_occt(b, TopAbs_FACE) == 1) { - B = TopoDS::Face(TopExp_Explorer(b, TopAbs_FACE).Current()); - } - } - - BRepOffsetAPI_Sewing builder; - for (faces_with_mass_t::const_iterator kt = solids.begin(); kt != solids.end(); ++kt) { - builder.Add(kt->first); - } - - builder.Perform(); - shells.Append(TopoDS::Shell(builder.SewedShape())); - } - } - - if (shells.Extent() == 0) { - - return false; - - } else if (shells.Extent() == 1) { - - for (ConversionResults::const_iterator it = items.begin(); it != items.end(); ++it) { - TopoDS_Shape a,b; - if (split_solid_by_shell(*(OpenCascadeShape*)it->Shape(), shells.First(), a, b)) { - result.push_back(ConversionResult(it->ItemId(), it->Placement()->clone(), new OpenCascadeShape(b), styles[0] ? styles[0] : &it->Style())); - result.push_back(ConversionResult(it->ItemId(), it->Placement()->clone(), new OpenCascadeShape(a), styles[1] ? styles[1] : &it->Style())); - } else { - continue; - } - } - - return true; - - } else { - - for (ConversionResults::const_iterator it = items.begin(); it != items.end(); ++it) { - - const TopoDS_Shape& s = *(OpenCascadeShape*)it->Shape(); - TopoDS_Solid sld; - ensure_fit_for_subtraction(s, sld); - - std::vector slices; - if (split(*this, *(OpenCascadeShape*)it->Shape(), shells, getValue(GV_PRECISION), slices) && slices.size() == styles.size()) { - for (size_t i = 0; i < slices.size(); ++i) { - result.push_back(ConversionResult(it->ItemId(), it->Placement()->clone(), new OpenCascadeShape(slices[i]), styles[i] ? styles[i] : &it->Style())); - } - } else { - return false; - } - } - - return true; - - } - -} - -bool IfcGeom::Kernel::apply_layerset(const ConversionResults& items, const std::vector& surfaces, const std::vector& styles, ConversionResults& result) { - if (surfaces.size() < 3) { - - return false; - - } else if (surfaces.size() == 3) { - - for (ConversionResults::const_iterator it = items.begin(); it != items.end(); ++it) { - TopoDS_Shape a,b; - if (split_solid_by_surface(*(OpenCascadeShape*)it->Shape(), surfaces[1], a, b)) { - result.push_back(ConversionResult(it->ItemId(), it->Placement()->clone(), new OpenCascadeShape(b), styles[0] ? styles[0] : &it->Style())); - result.push_back(ConversionResult(it->ItemId(), it->Placement()->clone(), new OpenCascadeShape(a), styles[1] ? styles[1] : &it->Style())); - } else { - continue; - } - } - - return true; - - } else { - - /* - // Determine whether sequence of surfaces is consistent with surface normal, so that - // layer operations are applied in the correct order. This seems to be always the case. - Bnd_Box bb; - for (ConversionResults::const_iterator it = items.begin(); it != items.end(); ++it) { - BRepBndLib::Add(it->Shape(), bb); - } - - double x1, y1, z1, x2, y2, z2; - bb.Get(x1, y1, z1, x2, y2, z2); - gp_Pnt p1(x1, y1, z1); - gp_Pnt p2(x2, y2, z2); - gp_Pnt avg = (p1.XYZ() + p2.XYZ()) / 2.; - - ShapeAnalysis_Surface sas1(surfaces[0]); - ShapeAnalysis_Surface sas2(surfaces[1]); - const gp_Pnt2d uv = sas1.ValueOfUV(avg, 1e-3); - - gp_Pnt ps1, ps2, mass; - gp_Vec du1, dv1, du2, dv2; - surfaces[0]->D1(uv.X(), uv.Y(), ps1, du1, dv1); - const gp_Vec n1 = dv1.XYZ() ^ du1.XYZ(); - - const bool reversed = gp_Dir(ps2.XYZ() - ps1.XYZ()).Dot(n1) < 0.; - - surfaces[surfaces.size() - 1]->D0(uv.X(), uv.Y(), mass); - mass.ChangeCoord() += n1.XYZ(); - */ - - for (ConversionResults::const_iterator it = items.begin(); it != items.end(); ++it) { - - const TopoDS_Shape& s = *(OpenCascadeShape*)it->Shape(); - TopoDS_Solid sld; - ensure_fit_for_subtraction(s, sld); - - TopTools_ListOfShape operands; - for (unsigned i = 1; i < surfaces.size() - 1; ++i) { - double u1, v1, u2, v2; - if (!project(surfaces[i], sld, u1, v1, u2, v2)) { - return false; - } - - TopoDS_Face face = BRepBuilderAPI_MakeFace(surfaces[i], u1, u2, v1, v2, 1.e-7).Face(); - - operands.Append(face); - } - - std::vector slices; - if (split(*this, *(OpenCascadeShape*)it->Shape(), operands, getValue(GV_PRECISION), slices) && slices.size() == styles.size()) { - for (size_t i = 0; i < slices.size(); ++i) { - result.push_back(ConversionResult(it->ItemId(), it->Placement()->clone(), new OpenCascadeShape(slices[i]), styles[i] ? styles[i] : &it->Style())); - } - } else { - return false; - } - } - - return true; - } -} - -IfcSchema::IfcRepresentation* IfcGeom::Kernel::find_representation(const IfcSchema::IfcProduct* product, const std::string& identifier) { - if (!product->hasRepresentation()) return 0; - IfcSchema::IfcProductRepresentation* prod_rep = product->Representation(); - IfcSchema::IfcRepresentation::list::ptr reps = prod_rep->Representations(); - for (IfcSchema::IfcRepresentation::list::it it = reps->begin(); it != reps->end(); ++it) { - if ((**it).hasRepresentationIdentifier() && (**it).RepresentationIdentifier() == identifier) { - return *it; - } - } - return 0; -} - -bool IfcGeom::Kernel::split_solid_by_surface(const TopoDS_Shape& input, const Handle_Geom_Surface& surface, TopoDS_Shape& front, TopoDS_Shape& back) { - // Use an unbounded surface, that isolate part of the input shape, - // to split this shape into two parts. Make sure that the addition - // of the two result volumes matches that of the input. - - double u1, v1, u2, v2; - if (!project(surface, input, u1, v1, u2, v2)) { - return false; - } - - TopoDS_Face face = BRepBuilderAPI_MakeFace(surface, u1, u2, v1, v2, 1.e-7).Face(); - gp_Pnt p, p1, p2; gp_Vec vu, vv, n; - surface->D1((u1+u2)/2., (v1+v2)/2., p, vu, vv); - n = vu ^ vv; - p1 = p.Translated(-n); - TopoDS_Solid solid = BRepPrimAPI_MakeHalfSpace(face, p1).Solid(); - - const bool b = split_solid_by_shell(input, solid, front, back); - return b; -} - -bool IfcGeom::Kernel::split_solid_by_shell(const TopoDS_Shape& input, const TopoDS_Shape& shell, TopoDS_Shape& front, TopoDS_Shape& back) { - // Use a shell, typically one or more connected faces, that isolate part - // of the input shape, to split this shape into two parts. Make sure that - // the addition of the two result volumes matches that of the input. - - TopoDS_Solid solid; - if (shell.ShapeType() == TopAbs_SHELL) { - solid = BRepBuilderAPI_MakeSolid(TopoDS::Shell(shell)).Solid(); - } else if (shell.ShapeType() == TopAbs_SOLID) { - solid = TopoDS::Solid(shell); - } else { - return false; - } - apply_tolerance(solid, getValue(GV_PRECISION)); - -#if OCC_VERSION_HEX >= 0x70300 - TopTools_ListOfShape shapes; -#else - BOPCol_ListOfShape shapes; -#endif - shapes.Append(input); - shapes.Append(solid); - BOPAlgo_PaveFiller filler(new NCollection_IncAllocator); // TODO: Does this need to be freed? - filler.SetArguments(shapes); - filler.Perform(); - front = BRepAlgoAPI_Cut(input, solid, filler); - back = BRepAlgoAPI_Common(input, solid, filler); - - bool is_null[2]; - - for (int i = 0; i < 2; ++i) { - TopoDS_Shape& shape = i == 0 ? front : back; - const bool result_is_null = is_null[i] = shape.IsNull() != 0; - if (result_is_null) { - continue; - } - try { - ShapeFix_Shape fix(shape); - if (fix.Perform()) { - shape = fix.Shape(); - } - } catch (const Standard_Failure& e) { - if (e.GetMessageString() && strlen(e.GetMessageString())) { - Logger::Error(e.GetMessageString()); - } else { - Logger::Error("Unknown error performing fixes"); - } - } catch (...) { - Logger::Error("Unknown error performing fixes"); - } - BRepCheck_Analyzer analyser(shape); - bool is_valid = analyser.IsValid() != 0; - if (!is_valid) { - return false; - } - } - - if (is_null[0] || is_null[1]) { - Logger::Message(Logger::LOG_ERROR, "Null result obtained from layerset slicing"); - if (is_null[0] && is_null[1]) { - return false; - } - } - - const double ab = shape_volume(input); - const double a = shape_volume(front); - const double b = shape_volume(back); - - return ALMOST_THE_SAME(ab, a+b, 1.e-3); -} - -bool IfcGeom::Kernel::project(const Handle_Geom_Surface& srf, const TopoDS_Shape& shp, double& u1, double& v1, double& u2, double& v2, double widen) { - // @todo std::unique_ptr for C++11 - ShapeAnalysis_Surface* sas = 0; - Handle(Geom_Plane) pln; - - if (srf->DynamicType() == STANDARD_TYPE(Geom_Plane)) { - // Optimize projection for specific cases - pln = Handle(Geom_Plane)::DownCast(srf); - } else if (srf->DynamicType() == STANDARD_TYPE(Geom_OffsetSurface) && Handle(Geom_OffsetSurface)::DownCast(srf)->BasisSurface()->DynamicType() == STANDARD_TYPE(Geom_Plane)) { - // For an offset planar surface the projected UV coords are the same as the basis surface - pln = Handle(Geom_Plane)::DownCast(Handle(Geom_OffsetSurface)::DownCast(srf)->BasisSurface()); - } else { - sas = new ShapeAnalysis_Surface(srf); - } - - u1 = v1 = +std::numeric_limits::infinity(); - u2 = v2 = -std::numeric_limits::infinity(); - - gp_Pnt median; - int vertex_count = 0; - for (TopExp_Explorer exp(shp, TopAbs_VERTEX); exp.More(); exp.Next(), ++vertex_count) { - gp_Pnt p = BRep_Tool::Pnt(TopoDS::Vertex(exp.Current())); - median.ChangeCoord() += p.XYZ(); - - gp_Pnt2d uv; - if (sas) { - uv = sas->ValueOfUV(p, 1e-3); - } else { - gp_Vec d = p.XYZ() - pln->Position().Location().XYZ(); - uv.SetX(d.Dot(pln->Position().XDirection())); - uv.SetY(d.Dot(pln->Position().YDirection())); - } - - if (uv.X() < u1) u1 = uv.X(); - if (uv.Y() < v1) v1 = uv.Y(); - if (uv.X() > u2) u2 = uv.X(); - if (uv.Y() > v2) v2 = uv.Y(); - } - - if (vertex_count > 0) { - - // Add a little bit of resolution so that the median is shifted towards the mass - // of the curve. This helps to find the parameter ordering for conic surfaces. - for (TopExp_Explorer exp(shp, TopAbs_EDGE); exp.More(); exp.Next(), ++vertex_count) { - const TopoDS_Edge& e = TopoDS::Edge(exp.Current()); - - double a, b; - Handle_Geom_Curve crv = BRep_Tool::Curve(e, a, b); - gp_Pnt p; - crv->D0((a + b) / 2., p); - - median.ChangeCoord() += p.XYZ(); - } - - median.ChangeCoord().Divide(vertex_count); - gp_Pnt2d uv; - if (sas) { - uv = sas->ValueOfUV(median, 1e-3); - } else { - gp_Vec d = median.XYZ() - pln->Position().Location().XYZ(); - uv.SetX(d.Dot(pln->Position().XDirection())); - uv.SetY(d.Dot(pln->Position().YDirection())); - } - - if (uv.X() < u1 || uv.X() > u2) { - std::swap(u1, u2); - } - - u1 -= widen; - u2 += widen; - v1 -= widen; - v2 += widen; - - } - - delete sas; - return vertex_count > 0; -} - -bool IfcGeom::Kernel::is_identity_transform(const IfcUtil::IfcBaseClass* l) { - const IfcSchema::IfcAxis2Placement2D* ax2d; - const IfcSchema::IfcAxis2Placement3D* ax3d; - - const IfcSchema::IfcCartesianTransformationOperator2D* op2d; - const IfcSchema::IfcCartesianTransformationOperator3D* op3d; - const IfcSchema::IfcCartesianTransformationOperator2DnonUniform* op2dnonu; - const IfcSchema::IfcCartesianTransformationOperator3DnonUniform* op3dnonu; - - if((op2dnonu = l->as()) != 0) { - gp_GTrsf2d gtrsf2d; - convert(op2dnonu, gtrsf2d); - return gtrsf2d.Form() == gp_Identity; - } else if ((op2d = l->as()) != 0) { - gp_Trsf2d trsf2d; - convert(op2d, trsf2d); - return trsf2d.Form() == gp_Identity; - } else if((op3dnonu = l->as()) != 0) { - gp_GTrsf gtrsf; - convert(op3dnonu, gtrsf); - return gtrsf.Form() == gp_Identity; - } else if ((op3d = l->as()) != 0) { - gp_Trsf trsf; - convert(op3d, trsf); - return trsf.Form() == gp_Identity; - } else if((ax2d = l->as()) != 0) { - gp_Trsf2d trsf2d; - convert(ax2d, trsf2d); - return trsf2d.Form() == gp_Identity; - } else if ((ax3d = l->as()) != 0) { - gp_Trsf trsf; - convert(ax3d, trsf); - return trsf.Form() == gp_Identity; - } else { - throw IfcParse::IfcException("Invalid valuation for IfcAxis2Placement / IfcCartesianTransformationOperator"); - } -} - -bool IfcGeom::Kernel::approximate_plane_through_wire(const TopoDS_Wire& wire, gp_Pln& plane, double eps) { - // Newell's Method is used for the normal calculation - // as a simple edge cross product can give opposite results - // for a concave face boundary. - // Reference: Graphics Gems III p. 231 - - const double eps_ = eps < 1. ? getValue(GV_PRECISION) : eps; - const double eps2 = eps_ * eps_; - - double x = 0, y = 0, z = 0; - gp_Pnt current, previous, first; - gp_XYZ center; - int n = 0; - - BRepTools_WireExplorer exp(wire); - - for (;; exp.Next()) { - const bool has_more = exp.More() != 0; - if (has_more) { - const TopoDS_Vertex& v = exp.CurrentVertex(); - current = BRep_Tool::Pnt(v); - center += current.XYZ(); - } else { - current = first; - } - if (n) { - const double& xn = previous.X(); - const double& yn = previous.Y(); - const double& zn = previous.Z(); - const double& xn1 = current.X(); - const double& yn1 = current.Y(); - const double& zn1 = current.Z(); - x += (yn - yn1)*(zn + zn1); - y += (xn + xn1)*(zn - zn1); - z += (xn - xn1)*(yn + yn1); - } else { - first = current; - } - if (!has_more) { - break; - } - previous = current; - ++n; - } - - if (n < 3) { - return false; - } - - plane = gp_Pln(center / n, gp_Dir(x, y, z)); - - exp.Init(wire); - for (; exp.More(); exp.Next()) { - const TopoDS_Vertex& v = exp.CurrentVertex(); - current = BRep_Tool::Pnt(v); - if (plane.SquareDistance(current) > eps2) { - return false; - } - } - - return true; -} - -bool IfcGeom::Kernel::flatten_wire(TopoDS_Wire& wire) { - gp_Pln pln; - if (!approximate_plane_through_wire(wire, pln)) { - return false; - } - TopoDS_Face face = BRepBuilderAPI_MakeFace(pln).Face(); - BRepAlgo_NormalProjection proj(face); - proj.Add(wire); - proj.Build(); - if (!proj.IsDone()) { - return false; - } - TopTools_ListOfShape list; - proj.BuildWire(list); - if (list.Extent() != 1) { - return false; - } - wire = TopoDS::Wire(list.First()); - return true; -} - -bool IfcGeom::Kernel::triangulate_wire(const std::vector& wires, TopTools_ListOfShape& faces) { - // This is a bit of a precarious approach, but seems to work for the - // versions of OCCT tested for. OCCT has a Delaunay triangulation function - // BRepMesh_Delaun, but it is notoriously hard to interpret the results - // (due to the Bowyer-Watson super triangle perhaps?). Therefore - // alternatively we use the regular OCCT incremental mesher on a new face - // created from the UV coordinates of the original wire. Pray to our gods - // that the vertex coordinates are unaffected by the meshing algorithm and - // map them back to 3d coordinates when iterating over the mesh triangles. - - // In addition, to maintain a manifold shell, we need to make sure that - // every edge from the input wire is used exactly once in the list of - // resulting faces. And that other internal edges are used twice. - - typedef std::pair uv_node; - - gp_Pln pln; - if (!approximate_plane_through_wire(wires.front(), pln, std::numeric_limits::infinity())) { - return false; - } - - const gp_XYZ& udir = pln.Position().XDirection().XYZ(); - const gp_XYZ& vdir = pln.Position().YDirection().XYZ(); - const gp_XYZ& pnt = pln.Position().Location().XYZ(); - - std::map mapping; - std::map, TopoDS_Edge> existing_edges, new_edges; - - std::unique_ptr mf; - - for (auto it = wires.begin(); it != wires.end(); ++it) { - const TopoDS_Wire& wire = *it; - BRepTools_WireExplorer exp(wire); - BRepBuilderAPI_MakePolygon mp; - - // Add UV coordinates to a newly created polygon - for (; exp.More(); exp.Next()) { - // Project onto plane - const TopoDS_Vertex& V = exp.CurrentVertex(); - gp_Pnt p = BRep_Tool::Pnt(V); - double u = (p.XYZ() - pnt).Dot(udir); - double v = (p.XYZ() - pnt).Dot(vdir); - mp.Add(gp_Pnt(u, v, 0.)); - - mapping.insert(std::make_pair(std::make_pair(u, v), V)); - - // Store existing edges in a map so that triangles can - // actually reference the preexisting edges. - const TopoDS_Edge& e = exp.Current(); - TopoDS_Vertex V0, V1; - TopExp::Vertices(e, V0, V1, true); - gp_Pnt p0 = BRep_Tool::Pnt(V0); - gp_Pnt p1 = BRep_Tool::Pnt(V1); - double u0 = (p0.XYZ() - pnt).Dot(udir); - double v0 = (p0.XYZ() - pnt).Dot(vdir); - double u1 = (p1.XYZ() - pnt).Dot(udir); - double v1 = (p1.XYZ() - pnt).Dot(vdir); - uv_node uv0 = std::make_pair(u0, v0); - uv_node uv1 = std::make_pair(u1, v1); - existing_edges.insert(std::make_pair(std::make_pair(uv0, uv1), e)); - existing_edges.insert(std::make_pair(std::make_pair(uv1, uv0), TopoDS::Edge(e.Reversed()))); - } - - // Not closed by default - mp.Close(); - - if (mf) { - if (it - 1 == wires.begin()) { - // @todo is this necessary? - TopoDS_Face f = mf->Face(); - mf->Init(f); - } - mf->Add(mp.Wire()); - } else { - mf.reset(new BRepBuilderAPI_MakeFace(mp.Wire())); - } - } - - const TopoDS_Face& face = mf->Face(); - - // Create a triangular mesh from the face - BRepMesh_IncrementalMesh(face, Precision::Confusion()); - - int n123[3]; - TopLoc_Location loc; - Handle_Poly_Triangulation tri = BRep_Tool::Triangulation(face, loc); - - if (!tri.IsNull()) { - const TColgp_Array1OfPnt& nodes = tri->Nodes(); - - const Poly_Array1OfTriangle& triangles = tri->Triangles(); - for (int i = 1; i <= triangles.Length(); ++i) { - if (face.Orientation() == TopAbs_REVERSED) - triangles(i).Get(n123[2], n123[1], n123[0]); - else triangles(i).Get(n123[0], n123[1], n123[2]); - - // Create polygons from the mesh vertices - BRepBuilderAPI_MakeWire mp2; - for (int j = 0; j < 3; ++j) { - - uv_node uvnodes[2]; - TopoDS_Vertex vs[2]; - - for (int k = 0; k < 2; ++k) { - const gp_Pnt& uv = nodes.Value(n123[(j + k) % 3]); - uvnodes[k] = std::make_pair(uv.X(), uv.Y()); - - auto it = mapping.find(uvnodes[k]); - if (it == mapping.end()) { - Logger::Error("Internal error: unable to unproject uv-mesh"); - return false; - } - - vs[k] = it->second; - } - - auto it = existing_edges.find(std::make_pair(uvnodes[0], uvnodes[1])); - if (it != existing_edges.end()) { - // This is a boundary edge, reuse existing edge from wire - mp2.Add(it->second); - } else { - auto jt = new_edges.find(std::make_pair(uvnodes[0], uvnodes[1])); - if (jt != new_edges.end()) { - // We have already added the reverse as part of another - // triangle, reuse this edge. - mp2.Add(TopoDS::Edge(jt->second)); - } else { - // This is a new internal edge. Register the reverse - // for reuse later. We need to be sure to reuse vertices - // for the edge construction because otherwise the wire - // builder will use geometrical proximity for vertex - // connections in which case the edge will be copied - // and no longer partner with other edges from the shell. - TopoDS_Edge ne = BRepBuilderAPI_MakeEdge(vs[0], vs[1]); - mp2.Add(ne); - // Store the reverse to be picked up later. - new_edges.insert(std::make_pair(std::make_pair(uvnodes[1], uvnodes[0]), TopoDS::Edge(ne.Reversed()))); - } - } - } - - BRepBuilderAPI_MakeFace mft(mp2.Wire()); - if (mft.IsDone()) { - TopoDS_Face triangle_face = mft.Face(); - TopoDS_Iterator jt(triangle_face, false); - for (; jt.More(); jt.Next()) { - const TopoDS_Wire& w = TopoDS::Wire(jt.Value()); - if (w.Orientation() != wires.front().Orientation()) { - triangle_face.Reverse(); - } - } - faces.Append(triangle_face); - } else { - Logger::Error("Internal error: missing face"); - return false; - } - } - } - - TopTools_IndexedDataMapOfShapeListOfShape mape, mapn; - for (auto& wire : wires) { - TopExp::MapShapesAndAncestors(wire, TopAbs_EDGE, TopAbs_WIRE, mape); - } - TopTools_ListIteratorOfListOfShape it(faces); - for (; it.More(); it.Next()) { - TopExp::MapShapesAndAncestors(it.Value(), TopAbs_EDGE, TopAbs_WIRE, mapn); - } - - // Validation - - for (int i = 1; i <= mape.Extent(); ++i) { -#if OCC_VERSION_HEX >= 0x70000 - TopTools_ListOfShape val; - if (!mapn.FindFromKey(mape.FindKey(i), val)) { -#else - bool contains = false; - try { - TopTools_ListOfShape val = mapn.FindFromKey(mape.FindKey(i)); - contains = true; - } catch (Standard_NoSuchObject&) {} - if (!contains) { -#endif - // All existing edges need to exist in the new faces - Logger::Error("Internal error, missing edge from triangulation"); - if (faceset_helper_ != nullptr) { - faceset_helper_->non_manifold() = true; - } - } - } - - for (int i = 1; i <= mapn.Extent(); ++i) { - const TopoDS_Shape& v = mapn.FindKey(i); - int n = mapn.FindFromIndex(i).Extent(); - // Existing edges are boundaries with use 1 - // New edges are internal with use 2 - if (n != (mape.Contains(v) ? 1 : 2)) { - Logger::Error("Internal error, non-manifold result from triangulation"); - if (faceset_helper_ != nullptr) { - faceset_helper_->non_manifold() = true; - } - } - } - - return true; -} - -TopoDS_Shape IfcGeom::Kernel::apply_transformation(const TopoDS_Shape& s, const OpenCascadePlacement* t) { - if (t == nullptr) { - return s; - } else { - return apply_transformation(s, t->trsf()); - } -} - -TopoDS_Shape IfcGeom::Kernel::apply_transformation(const TopoDS_Shape& s, const gp_GTrsf& t) { - if (t.Form() == gp_Other) { - Logger::Message(Logger::LOG_WARNING, "Applying non uniform transformation"); - return BRepBuilderAPI_GTransform(s, t, true); - } else { - return apply_transformation(s, t.Trsf()); - } -} - -TopoDS_Shape IfcGeom::Kernel::apply_transformation(const TopoDS_Shape& s, const gp_Trsf& t) { - /// @todo set to 1. and exactly 1. or use epsilon? - if (t.ScaleFactor() != 1.) { - return BRepBuilderAPI_Transform(s, t, true); - } else { - return s.Moved(t); - } -} - -namespace { - - /* - * A small helper utility to wrap around a numeric range - */ - class bounded_int { - private: - int i; - size_t n; - public: - bounded_int(int i, size_t n) : i(i), n(n) {} - - bounded_int& operator--() { - --i; - if (i == -1) { - i = n - 1; - } - return *this; - } - - bounded_int& operator++() { - ++i; - if (i == (int) n) { - i = 0; - } - return *this; - } - - operator int() { return i; } - }; - - inline std::string format_pnt(const gp_Pnt& p) { - std::stringstream ss; - ss << std::fixed << std::setprecision(4) << p.X() << " " << p.Y() << " " << p.Z(); - return ss.str(); - } - - inline std::string format_edge(const TopoDS_Edge& e) { - std::stringstream ss; - TopoDS_Vertex v1, v2; - TopExp::Vertices(e, v1, v2); - gp_Pnt p1 = BRep_Tool::Pnt(v1); - gp_Pnt p2 = BRep_Tool::Pnt(v2); - ss << "edge " << format_pnt(p1) << " -> " << format_pnt(p2); - return ss.str(); - } - -} - -bool IfcGeom::Kernel::wire_intersections(const TopoDS_Wire& wire, TopTools_ListOfShape& wires) { - if (!wire.Closed()) { - wires.Append(wire); - return false; - } - - int n = count_occt(wire, TopAbs_EDGE); - if (n < 3) { - wires.Append(wire); - return false; - } - - // Note: initialize empty - Handle(ShapeExtend_WireData) wd = new ShapeExtend_WireData(); - - // ... to be sure to get consecutive edges - BRepTools_WireExplorer exp(wire); - IfcGeom::impl::tree tree; - - int edge_idx = 0; - for (; exp.More(); exp.Next()) { - wd->Add(exp.Current()); - if (n > 64) { - // tfk: indices in tree are 0-based vd 1-based in wiredata - tree.add(edge_idx++, exp.Current()); - } - } - - if (wd->NbEdges() != n) { - // If the number of edges differs, BRepTools_WireExplorer did not - // reach every edge, probably due to loops exactly at vertex locations. - // This is not supported by this algorithm which only elimates loops - // due to edge crossings. - - throw geometry_exception("Invalid loop"); - } - - bool intersected = false; - - // tfk: Extrema on infinite curves proved to be more robust. - // TopoDS_Face face = BRepBuilderAPI_MakeFace(wire, true).Face(); - // ShapeAnalysis_Wire saw(wd, face, getValue(GV_PRECISION)); - - const double eps = faceset_helper_ - ? faceset_helper_->epsilon() - : (std::min)(min_edge_length(wire) / 2., getValue(GV_PRECISION) * 10.); - - for (int i = 2; i < n; ++i) { - - std::vector js; - if (n > 64) { - Bnd_Box b; - BRepBndLib::Add(wd->Edge(i + 1), b); - b.Enlarge(eps); - js = tree.select_box(b, false); - } else { - boost::push_back(js, boost::irange(0, i - 1)); - } - - for(std::vector::const_iterator it = js.begin(); it != js.end(); ++it) { - int j = *it; - - if (n > 64) { - if (j > i) { - continue; - } - if ((std::max)(i, j) - (std::min)(i, j) <= 1) { - continue; - } - } - - // Only check non-consecutive edges - if (i == n - 1 && j == 0) continue; - - double u11, u12, u21, u22, U1, U2; - GeomAPI_ExtremaCurveCurve ecc( - BRep_Tool::Curve(wd->Edge(i + 1), u11, u12), - BRep_Tool::Curve(wd->Edge(j + 1), u21, u22) - ); - - // @todo: extend this to work in case of multiple extrema and curved segments. - const bool unbounded_intersects = (ecc.NbExtrema() == 1 && ecc.Distance(1) < eps); - if (unbounded_intersects) { - ecc.Parameters(1, U1, U2); - - if (u11 > u12) { - std::swap(u11, u12); - } - if (u21 > u22) { - std::swap(u21, u22); - } - - /// @todo: tfk: probably need different thresholds on non-linear curves - u11 -= eps; - u12 += eps; - u21 -= eps; - u22 += eps; - - // tfk: code below is for ShapeAnalysis_Wire::CheckIntersectingEdges() - // IntRes2d_SequenceOfIntersectionPoint points2d; - // TColgp_SequenceOfPnt points3d; - // TColStd_SequenceOfReal errors; - // if (saw.CheckIntersectingEdges(i + 1, j + 1, points2d, points3d, errors)) { - - if (u11 < U1 && U1 < u12 && u21 < U2 && U2 < u22) { - - intersected = true; - - // Explore a forward and backward cycle from the intersection point - for (int fb = 0; fb <= 1; ++fb) { - const bool forward = fb == 0; - - BRepBuilderAPI_MakeWire mw; - bool first = true; - - for (bounded_int k(j, n);;) { - bool intersecting = k == j || k == i; - if (intersecting) { - TopoDS_Edge e = wd->Edge(k + 1); - - TopoDS_Vertex v1, v2; - TopExp::Vertices(e, v1, v2, true); - const TopoDS_Vertex* v = first == forward ? &v2 : &v1; - - // gp_Pnt p2 = points3d.Value(1); - - gp_Pnt p1 = BRep_Tool::Pnt(*v); - gp_Pnt pp1, pp2; - ecc.Points(1, pp1, pp2); - const gp_Pnt& p2 = k == i ? pp1 : pp2; - - // Substitute with a new edge from/to the intersection point - if (p1.Distance(p2) > getValue(GV_PRECISION) * 2) { - double _, __; - Handle_Geom_Curve crv = BRep_Tool::Curve(e, _, __); - BRepBuilderAPI_MakeEdge me(crv, p1, p2); - TopoDS_Edge ed = me.Edge(); - mw.Add(ed); - } - - first = false; - } else { - // Re-use original edge - mw.Add(wd->Edge(k + 1)); - } - - if (k == i) { - break; - } - - if (forward) { - ++k; - } else { - --k; - } - } - - // Recursively process both cuts - wire_intersections(mw.Wire(), wires); - } - - return true; - } - - } - } - } - - // No intersections found, append original wire - if (!intersected) { - wires.Append(wire); - } - - return intersected; -} - -void IfcGeom::Kernel::select_largest(const TopTools_ListOfShape& shapes, TopoDS_Shape& largest) { - double mass = 0.; - TopTools_ListIteratorOfListOfShape it(shapes); - for (; it.More(); it.Next()) { - /* - // tfk: bounding box is more efficient probably - const TopoDS_Wire& w = TopoDS::Wire(it.Value()); - TopoDS_Face face = BRepBuilderAPI_MakeFace(w).Face(); - const double m = face_area(face); - */ - - Bnd_Box bb; - BRepBndLib::AddClose(it.Value(), bb); - double xyz_min[3], xyz_max[3]; - bb.Get(xyz_min[0], xyz_min[1], xyz_min[2], xyz_max[0], xyz_max[1], xyz_max[2]); - const double eps = getValue(GV_PRECISION); - - double m = 1.; - for (int i = 0; i < 3; ++i) { - if (Precision::IsNegativeInfinite(xyz_min[i])) { - xyz_min[i] = 0.; - } - if (Precision::IsInfinite(xyz_max[i])) { - xyz_max[i] = 0.; - } - m *= (xyz_max[i] + eps) - (xyz_min[i] - eps); - } - - if (m > mass) { - mass = m; - largest = it.Value(); - } - } -} - -bool IfcGeom::Kernel::fit_halfspace(const TopoDS_Shape& a, const TopoDS_Shape& b, TopoDS_Shape& box, double& height) { - TopExp_Explorer exp(b, TopAbs_FACE); - if (!exp.More()) { - return false; - } - - TopoDS_Face face = TopoDS::Face(exp.Current()); - exp.Next(); - - if (exp.More()) { - return false; - } - - Handle(Geom_Surface) surf = BRep_Tool::Surface(face); - - // const gp_XYZ xyz = a.Location().Transformation().TranslationPart(); - // std::cout << "dz " << xyz.Z() << std::endl; - - if (surf->DynamicType() != STANDARD_TYPE(Geom_Plane)) { - return false; - } - - Bnd_Box bb; - BRepBndLib::Add(a, bb); - - if (bb.IsVoid()) { - return false; - } - - double xs[2], ys[2], zs[2]; - bb.Get(xs[0], ys[0], zs[0], xs[1], ys[1], zs[1]); - - gp_Pln pln = Handle(Geom_Plane)::DownCast(surf)->Pln(); - - gp_Pnt P = pln.Position().Location(); - gp_Vec z = pln.Position().Direction(); - gp_Vec x = pln.Position().XDirection(); - gp_Vec y = pln.Position().YDirection(); - - if (face.Orientation() != TopAbs_REVERSED) { - z.Reverse(); - } - - double D, Umin, Umax, Vmin, Vmax; - D = 0.; - Umin = Vmin = +std::numeric_limits::infinity(); - Umax = Vmax = -std::numeric_limits::infinity(); - - for (int i = 0; i < 2; ++i) { - for (int j = 0; j < 2; ++j) { - for (int k = 0; k < 2; ++k) { - gp_Pnt p(xs[i], ys[j], zs[k]); - - gp_Vec d = p.XYZ() - P.XYZ(); - const double u = d.Dot(x); - const double v = d.Dot(y); - const double w = d.Dot(z); - - if (w > D) { - D = w; - } - if (u < Umin) { - Umin = u; - } - if (u > Umax) { - Umax = u; - } - if (v < Vmin) { - Vmin = v; - } - if (v > Vmax) { - Vmax = v; - } - } - } - } - - const double eps = getValue(GV_PRECISION) * 1000.; - - BRepBuilderAPI_MakePolygon poly; - poly.Add(P.XYZ() + x.XYZ() * (Umin - eps) + y.XYZ() * (Vmin - eps)); - poly.Add(P.XYZ() + x.XYZ() * (Umax + eps) + y.XYZ() * (Vmin - eps)); - poly.Add(P.XYZ() + x.XYZ() * (Umax + eps) + y.XYZ() * (Vmax + eps)); - poly.Add(P.XYZ() + x.XYZ() * (Umin - eps) + y.XYZ() * (Vmax + eps)); - poly.Close(); - - BRepBuilderAPI_MakeFace mf(surf, poly.Wire(), true); - - gp_Vec vec = gp_Vec(z.XYZ() * (D + eps)); - - BRepPrimAPI_MakePrism mp(mf.Face(), vec); - box = mp.Shape(); - - height = D; - return true; -} - -#if OCC_VERSION_HEX < 0x60900 -bool IfcGeom::Kernel::boolean_operation(const TopoDS_Shape& a, const TopTools_ListOfShape& b, BOPAlgo_Operation op, TopoDS_Shape& result) { - result = a; - TopTools_ListIteratorOfListOfShape it(b); - for (; it.More(); it.Next()) { - TopoDS_Shape r; - if (!boolean_operation(result, it.Value(), op, r)) { - return false; - } - result = r; - } - return true; -} -bool IfcGeom::Kernel::boolean_operation(const TopoDS_Shape& a, const TopoDS_Shape& b, BOPAlgo_Operation op, TopoDS_Shape& result) { - bool succesful = true; - BRepAlgoAPI_BooleanOperation* builder; - if (op == BOPAlgo_CUT) { - builder = new BRepAlgoAPI_Cut(a, b); - } else if (op == BOPAlgo_COMMON) { - builder = new BRepAlgoAPI_Common(a, b); - } else if (op == BOPAlgo_FUSE) { - builder = new BRepAlgoAPI_Fuse(a, b); - } else { - return false; - } - if (builder->IsDone()) { - TopoDS_Shape r = *builder; - succesful = BRepCheck_Analyzer(r).IsValid() != 0; - if (succesful) { - result = r; - - ShapeFix_Shape fix(result); - try { - fix.Perform(); - result = fix.Shape(); - } catch (...) { - Logger::Error("Shape healing failed on boolean result"); - } - - } else { - // Increase tolerance max 3 times until succesful - TopoDS_Shape a2 = a; - TopoDS_Shape b2 = b; - ShapeAnalysis_ShapeTolerance tolerance; - const double t1 = tolerance.Tolerance(a, 1) * 10.; - const double t2 = tolerance.Tolerance(b, 1) * 10.; - if (((std::max)(t1, t2) + 1e-15) > getValue(GV_PRECISION) * 1000.) { - return false; - } - apply_tolerance(a2, t1); - apply_tolerance(b2, t2); - succesful = boolean_operation(a2, b2, op, result); - } - } - delete builder; - return succesful; -} -#else - -bool IfcGeom::Kernel::boolean_operation(const TopoDS_Shape& a_, const TopTools_ListOfShape& b__, BOPAlgo_Operation op, TopoDS_Shape& result, double fuzziness) { - - if (fuzziness < 0.) { - fuzziness = getValue(GV_PRECISION) / 10.; - } - - // @todo, it does seem a bit odd, we first triangulate non-planar faces - // to later unify them again. Can we make this a bit more intelligent? - TopoDS_Shape a = unify(a_, fuzziness); - TopTools_ListOfShape b_; - { - TopTools_ListIteratorOfListOfShape it(b__); - for (; it.More(); it.Next()) { - b_.Append(unify(it.Value(), fuzziness)); - } - } - - bool success = false; - BRepAlgoAPI_BooleanOperation* builder; - TopTools_ListOfShape B, b; - if (op == BOPAlgo_CUT) { - builder = new BRepAlgoAPI_Cut(); - bounding_box_overlap(getValue(GV_PRECISION), a, b_, b); - } else if (op == BOPAlgo_COMMON) { - builder = new BRepAlgoAPI_Common(); - b = b_; - } else if (op == BOPAlgo_FUSE) { - builder = new BRepAlgoAPI_Fuse(); - b = b_; - } else { - return false; - } - - if (b.Extent() == 0) { - result = a; - return true; - } - - // Find a sensible value for the fuzziness, based on precision - // and limited by edge lengths and vertex-edge distances. - const double len_a = min_edge_length(a_); - double min_length_orig = (std::min)(len_a, min_vertex_edge_distance(a_, getValue(GV_PRECISION), len_a)); - TopTools_ListIteratorOfListOfShape it(b__); - for (; it.More(); it.Next()) { - double d = min_edge_length(it.Value()); - if (d < min_length_orig) { - min_length_orig = d; - } - d = min_vertex_edge_distance(it.Value(), getValue(GV_PRECISION), d); - if (d < min_length_orig) { - min_length_orig = d; - } - } - - const double fuzz = (std::min)(min_length_orig / 3., fuzziness); - - TopTools_ListOfShape s1s; - s1s.Append(copy_operand(a)); -#if OCC_VERSION_HEX >= 0x70000 - builder->SetNonDestructive(true); -#endif - builder->SetFuzzyValue(fuzz); - builder->SetArguments(s1s); - copy_operand(b, B); - builder->SetTools(B); - builder->Build(); - if (builder->IsDone()) { - TopoDS_Shape r = *builder; - - ShapeFix_Shape fix(r); - try { - fix.SetMinTolerance(fuzz); - fix.SetMaxTolerance(fuzz); - fix.SetPrecision(fuzz); - fix.Perform(); - r = fix.Shape(); - } catch (...) { - Logger::Error("Shape healing failed on boolean result"); - } - - success = BRepCheck_Analyzer(r).IsValid() != 0; - - if (success) { - - success = !is_manifold_occt(a) || is_manifold_occt(r); - - if (success) { - - // when there are edges or vertex-edge distances close to the used fuzziness, the - // output is not trusted and the operation is attempted with a higher fuzziness. - int reason = 0; - double v; - if ((v = min_edge_length(r)) < fuzziness * 3.) { - reason = 0; - success = false; - } else if ((v = min_vertex_edge_distance(r, getValue(GV_PRECISION), fuzziness * 3.)) < fuzziness * 3.) { - reason = 1; - success = false; - } else if ((v = min_face_face_distance(r, fuzziness * 3.)) < fuzziness * 3.) { - reason = 2; - success = false; - } - - if (success) { - result = r; - } else { - static const char* const reason_strings[] = { "edge length", "vertex-edge", "face-face" }; - std::stringstream str; - str << "Boolean operation result failing " << reason_strings[reason] << " interference check, with fuzziness " << fuzziness << " with length " << v; - Logger::Notice(str.str()); - } - } else { - Logger::Notice("Boolean operation yields non-manifold result"); - } - } else { - Logger::Notice("Boolean operation yields invalid result"); - } - } else { - std::stringstream str; -#if OCC_VERSION_HEX >= 0x70000 - builder->DumpErrors(str); -#else - str << "Error code: " << builder->ErrorStatus(); -#endif - std::string str_str = str.str(); - if (str_str.size()) { - Logger::Notice(str_str); - } - } - delete builder; - if (!success) { - const double new_fuzziness = fuzziness * 10.; - if (new_fuzziness - 1e-15 <= getValue(GV_PRECISION) * 10000. && new_fuzziness < min_length_orig) { - return boolean_operation(a, b, op, result, new_fuzziness); - } else { - Logger::Notice("No longer attempting boolean operation with higher fuzziness"); - } - } - return success; -} - -bool IfcGeom::Kernel::boolean_operation(const TopoDS_Shape& a, const TopoDS_Shape& b, BOPAlgo_Operation op, TopoDS_Shape& result, double fuzziness) { - TopTools_ListOfShape bs; - bs.Append(b); - return boolean_operation(a, bs, op, result, fuzziness); -} -#endif - -namespace { - void find_neighbours(IfcGeom::impl::tree& tree, std::vector>& pnts, std::set& visited, int p, double eps) { - visited.insert(p); - - Bnd_Box b; - b.Set(*pnts[p].get()); - b.Enlarge(eps); - - std::vector js = tree.select_box(b, false); - for (int j : js) { - visited.insert(j); -#ifdef FACESET_HELPER_RECURSIVE - if (visited.find(j) == visited.end()) { - // @todo, making this recursive removes the dependence on the initial ordering, but will - // likely result in empty results when all vertices are within 1 eps from another point. - find_neighbours(tree, pnts, visited, j, eps); - } -#endif - } - } -} - -IfcGeom::Kernel::faceset_helper::~faceset_helper() { - kernel_->faceset_helper_ = nullptr; -} - -IfcGeom::Kernel::faceset_helper::faceset_helper(Kernel* kernel, const IfcSchema::IfcConnectedFaceSet* l) - : kernel_(kernel) - , non_manifold_(false) -{ - kernel->faceset_helper_ = this; - - IfcSchema::IfcCartesianPoint::list::ptr points = IfcParse::traverse((IfcUtil::IfcBaseClass*) l)->as(); - std::vector> pnts(std::distance(points->begin(), points->end())); - std::vector vertices(pnts.size()); - - IfcGeom::impl::tree tree; - - BRep_Builder B; - - Bnd_Box box; - for (size_t i = 0; i < points->size(); ++i) { - gp_Pnt* p = new gp_Pnt(); - if (kernel->convert(*(points->begin() + i), *p)) { - pnts[i].reset(p); - B.MakeVertex(vertices[i], *p, Precision::Confusion()); - tree.add(i, vertices[i]); - box.Add(*p); - } else { - delete p; - } - } - - // Use the bbox diagonal to influence local epsilon - // double bdiff = std::sqrt(box.SquareExtent()); - - // Find the minimal bounding box edge - double bmin[3], bmax[3]; - box.Get(bmin[0], bmin[1], bmin[2], bmax[0], bmax[1], bmax[2]); - double bdiff = std::numeric_limits::infinity(); - for (size_t i = 0; i < 3; ++i) { - const double d = bmax[i] - bmin[i]; - if (d > kernel->getValue(GV_PRECISION) * 10. && d < bdiff) { - bdiff = d; - } - } - - eps_ = kernel->getValue(GV_PRECISION) * 10. * (std::min)(1.0, bdiff); - - if (eps_ < Precision::Confusion()) { - // occt uses some hard coded precision values, don't go smaller than that. - // @todo, can be reset though with BRepLib::Precision(double) - eps_ = Precision::Confusion(); - } - - std::map, int> edge_use; - - for (int i = 0; i < (int) pnts.size(); ++i) { - if (pnts[i]) { - std::set vs; - find_neighbours(tree, pnts, vs, i, eps_); - - for (int v : vs) { - auto pt = *(points->begin() + v); - // NB: insert() ignores duplicate keys - vertex_mapping_.insert({ pt->data().id() , i }); - } - } - } - - // @todo, there a tiny possibility that the duplicate faces are triggered - // for an internal boundary, that is also present as an external boundary. - // This will result in non-manifold configuration then, but this is deemed - // such as corner-case that it is not considered. - IfcSchema::IfcPolyLoop::list::ptr loops = IfcParse::traverse((IfcUtil::IfcBaseClass*)l)->as(); - - size_t loops_removed = 0, non_manifold = 0, duplicate_faces = 0; - - typedef std::array edge_t; - typedef std::set edge_set_t; - std::set edge_sets; - - for (auto& loop : *loops) { - auto ps = loop->Polygon(); - - std::vector > segments; - edge_set_t segment_set; - - loop_(ps, [&segments, &segment_set](int C, int D, bool) { - segment_set.insert({{ C, D }}); - segments.push_back({ C, D }); - }); - - if (edge_sets.find(segment_set) != edge_sets.end()) { - duplicate_faces++; - duplicates_.insert(loop); - continue; - } - edge_sets.insert(segment_set); - - if (segments.size() >= 3) { - for (auto& p : segments) { - edge_use[p] ++; - } - } else { - loops_removed += 1; - } - } - - for (auto& p : edge_use) { - int a, b; - std::tie(a, b) = p.first; - edges_[p.first] = BRepBuilderAPI_MakeEdge(vertices[a], vertices[b]); - - if (p.second != 2) { - non_manifold += 1; - } - } - - if (loops_removed || (non_manifold && l->declaration().is(IfcSchema::IfcClosedShell::Class()))) { - Logger::Warning(boost::lexical_cast(duplicate_faces) + " duplicate faces removed, " + boost::lexical_cast(loops_removed) + " loops removed and " + boost::lexical_cast(non_manifold) + " non-manifold edges for:", l); - } -} - -bool IfcGeom::Kernel::apply_layerset(const IfcSchema::IfcProduct* product, IfcGeom::ConversionResults& shapes) { - IfcGeom::ConversionResults shapes2; - - bool success = false; - - TopoDS_Shape merge; - if (flatten_shape_list(shapes, merge, false)) { - if (count_occt(merge, TopAbs_FACE) > 0) { - std::vector thickness; - std::vector layers; - std::vector< std::vector > folded_layers; - std::vector styles; - if (convert_layerset(product, layers, styles, thickness)) { - if (styles.size() > 1) { - // If there's only a single layer there is no need to manipulate geometries. - success = true; - if (product->as() && fold_layers(product->as(), shapes, layers, thickness, folded_layers)) { - if (apply_folded_layerset(shapes, folded_layers, styles, shapes2)) { - std::swap(shapes, shapes2); - success = true; - } - } else { - if (apply_layerset(shapes, layers, styles, shapes2)) { - std::swap(shapes, shapes2); - success = true; - } - } - - if (!success) { - Logger::Error("Failed processing layerset"); - } - } - } - } - } - - return success; -} - -bool IfcGeom::Kernel::validate_quantities(const IfcSchema::IfcProduct* product, const IfcGeom::Representation::BRep& brep) { - auto rels = product->IsDefinedBy(); - for (auto& rel : *rels) { - if (rel->as()) { - auto pdef = rel->as()->RelatingPropertyDefinition(); - if (pdef->as()) { - std::string organization_name; - try { - // A couple of files are not according to the schema here. - organization_name = pdef->as()->OwnerHistory()->OwningApplication()->ApplicationDeveloper()->Name(); - } catch (...) {} - if (organization_name == "IfcOpenShell") { - auto qs = pdef->as()->Quantities(); - for (auto& q : *qs) { - if (q->as() && q->Name() == "Total Surface Area") { - double a_calc; - double a_file = q->as()->AreaValue(); - if (brep.calculate_surface_area(a_calc)) { - double diff = std::abs(a_calc - a_file); - if (diff / std::sqrt(a_file) > getValue(GV_PRECISION)) { - Logger::Error("Validation of surface area failed for:", product); - } else { - Logger::Notice("Validation of surface area succeeded for:", product); - } - } else { - Logger::Error("Validation of surface area failed for:", product); - } - } else if (q->as() && q->Name() == "Volume") { - double v_calc; - double v_file = q->as()->VolumeValue(); - if (brep.calculate_volume(v_calc)) { - double diff = std::abs(v_calc - v_file); - if (diff / std::sqrt(v_file) > getValue(GV_PRECISION)) { - Logger::Error("Validation of volume failed for:", product); - } else { - Logger::Notice("Validation of volume succeeded for:", product); - } - } else { - Logger::Error("Validation of volume failed for:", product); - } - } else if (q->as() && q->Name() == "Shape Validation Properties") { - auto qs2 = q->as()->HasQuantities(); - bool all_succeeded = qs2->size() > 0; - for (auto& q2 : *qs2) { - if (q2->as() && q2->Name() == "Surface Genus" && q2->hasDescription()) { - int item_id = boost::lexical_cast(q2->Description().substr(1)); - int genus = q2->as()->CountValue(); - for (auto& part : brep) { - if (part.ItemId() == item_id) { - if (surface_genus(part.Shape()) != genus) { - all_succeeded = false; - } - } - } - } - } - if (!all_succeeded) { - Logger::Error("Validation of surface genus failed for:", product); - } else { - Logger::Notice("Validation of surface genus succeeded for:", product); - } - } - } - } - } - } - } - - return true; -} \ No newline at end of file diff --git a/src/ifcgeom/kernels/opencascade/IfcGeomHelpers.cpp_ b/src/ifcgeom/kernels/opencascade/IfcGeomHelpers.cpp_ deleted file mode 100644 index 4be6bae286..0000000000 --- a/src/ifcgeom/kernels/opencascade/IfcGeomHelpers.cpp_ +++ /dev/null @@ -1,419 +0,0 @@ -/******************************************************************************** - * * - * 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 . * - * * - ********************************************************************************/ - -/******************************************************************************** - * * - * Implementations of the various conversion functions defined in IfcRegister.h * - * * - ********************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include -#include -#include - -#include - -#include - -#include "../../../ifcgeom/kernels/opencascade/IfcGeom.h" - -#define Kernel POSTFIX_SCHEMA(Kernel) - -namespace { - - // Helper functions (re)set gp_(G)Trsf(2d) forms explicitly to 'Identity' - // so that it can be easily identified in the IfcMappedItem processing - - // For axis placements detect equality early in order for the - // relatively computionaly expensive gp_Trsf calculation to be skipped - template - bool axis_equal(const T& a, const T& b, double tolerance); - template <> - bool axis_equal(const gp_Ax3& a, const gp_Ax3& b, double tolerance) { - if (!a.Location().IsEqual(b.Location(), tolerance)) return false; - // Note that the tolerance below is angular, above is linear. Since architectural - // objects are about 1m'ish in scale, it should be somewhat equivalent. Besides, - // this is mostly a filter for NULL or default values in the placements. - if (!a.Direction().IsEqual(b.Direction(), tolerance)) return false; - if (!a.XDirection().IsEqual(b.XDirection(), tolerance)) return false; - if (!a.YDirection().IsEqual(b.YDirection(), tolerance)) return false; - return true; - } - - bool axis_equal(const gp_Ax2d& a, const gp_Ax2d& b, double tolerance) { - if (!a.Location().IsEqual(b.Location(), tolerance)) return false; - if (!a.Direction().IsEqual(b.Direction(), tolerance)) return false; - return true; - } - - template struct dimension_count {}; - template <> struct dimension_count { static const int n = 2; }; - template <> struct dimension_count { static const int n = 2; }; - template <> struct dimension_count < gp_Trsf > { static const int n = 3; }; - template <> struct dimension_count < gp_GTrsf > { static const int n = 3; }; - - template - bool is_identity(const T& t, double tolerance) { - // Note the {1, n+1} range due to Open Cascade's 1-based indexing - // Note the {1, n+2} range due to the translation part of the matrix - for (int i = 1; i < dimension_count::n + 2; ++i) { - for (int j = 1; j < dimension_count::n + 1; ++j) { - const double iden_value = i == j ? 1. : 0.; - const double trsf_value = t.Value(j, i); - if (fabs(trsf_value - iden_value) > tolerance) { - return false; - } - } - } - return true; - } -} - -bool IfcGeom::Kernel::convert(const IfcSchema::IfcCartesianPoint* l, gp_Pnt& point) { - IN_CACHE(IfcCartesianPoint,l,gp_Pnt,point) - std::vector xyz = l->Coordinates(); - point = gp_Pnt( - xyz.size() ? (xyz[0]*getValue(GV_LENGTH_UNIT)) : 0.0f, - xyz.size() > 1 ? (xyz[1]*getValue(GV_LENGTH_UNIT)) : 0.0f, - xyz.size() > 2 ? (xyz[2]*getValue(GV_LENGTH_UNIT)) : 0.0f - ); - CACHE(IfcCartesianPoint,l,point) - return true; -} - -bool IfcGeom::Kernel::convert(const IfcSchema::IfcDirection* l, gp_Dir& dir) { - IN_CACHE(IfcDirection,l,gp_Dir,dir) - std::vector xyz = l->DirectionRatios(); - dir = gp_Dir( - xyz.size() ? xyz[0] : 0.0f, - xyz.size() > 1 ? xyz[1] : 0.0f, - xyz.size() > 2 ? xyz[2] : 0.0f - ); - CACHE(IfcDirection,l,dir) - return true; -} - -bool IfcGeom::Kernel::convert(const IfcSchema::IfcVector* l, gp_Vec& v) { - IN_CACHE(IfcVector,l,gp_Vec,v) - gp_Dir d; - IfcGeom::Kernel::convert(l->Orientation(),d); - v = l->Magnitude() * getValue(GV_LENGTH_UNIT) * d; - CACHE(IfcVector,l,v) - return true; -} - -bool IfcGeom::Kernel::convert(const IfcSchema::IfcAxis2Placement3D* l, gp_Trsf& trsf) { - IN_CACHE(IfcAxis2Placement3D, l, gp_Trsf, trsf) - - gp_Pnt o; - gp_Dir axis(0, 0, 1); - gp_Dir refDirection; - - IfcGeom::Kernel::convert(l->Location(), o); - const bool hasAxis = l->hasAxis(); - const bool hasRef = l->hasRefDirection(); - - if (hasAxis != hasRef) { - Logger::Warning("Axis and RefDirection should be specified together", l); - } - - if (hasAxis) { - IfcGeom::Kernel::convert(l->Axis(), axis); - } - - if (hasRef) { - IfcGeom::Kernel::convert(l->RefDirection(), refDirection); - } else { - if (!axis.IsParallel(gp::DX(), 1.e-5)) { - refDirection = gp::DX(); - } else { - refDirection = gp::DZ(); - } - gp_Vec Xvec = axis.Dot(refDirection) * axis; - gp_Vec Xaxis = refDirection.XYZ() - Xvec.XYZ(); - refDirection = Xaxis; - } - - gp_Ax3 ax3(o, axis, refDirection); - - if (!axis_equal(ax3, (gp_Ax3) gp::XOY(), getValue(GV_PRECISION))) { - trsf.SetTransformation(ax3, gp::XOY()); - } - - CACHE(IfcAxis2Placement3D,l,trsf) - return true; -} - -bool IfcGeom::Kernel::convert(const IfcSchema::IfcAxis1Placement* l, gp_Ax1& ax) { - IN_CACHE(IfcAxis1Placement,l,gp_Ax1,ax) - gp_Pnt o;gp_Dir axis = gp_Dir(0,0,1); - IfcGeom::Kernel::convert(l->Location(),o); - if ( l->hasAxis() ) IfcGeom::Kernel::convert(l->Axis(), axis); - ax = gp_Ax1(o, axis); - CACHE(IfcAxis1Placement,l,ax) - return true; -} - -bool IfcGeom::Kernel::convert(const IfcSchema::IfcCartesianTransformationOperator3D* l, gp_Trsf& trsf) { - IN_CACHE(IfcCartesianTransformationOperator3D,l,gp_Trsf,trsf) - gp_Pnt origin; - IfcGeom::Kernel::convert(l->LocalOrigin(),origin); - gp_Dir axis1 (1.,0.,0.); - gp_Dir axis2 (0.,1.,0.); - gp_Dir axis3 (0.,0.,1.); - if ( l->hasAxis1() ) IfcGeom::Kernel::convert(l->Axis1(),axis1); - if ( l->hasAxis2() ) IfcGeom::Kernel::convert(l->Axis2(),axis2); - if ( l->hasAxis3() ) IfcGeom::Kernel::convert(l->Axis3(),axis3); - gp_Ax3 ax3 (origin,axis3,axis1); - if ( axis2.Dot(ax3.YDirection()) < 0 ) ax3.YReverse(); - - if (!axis_equal(ax3, (gp_Ax3) gp::XOY(), getValue(GV_PRECISION))) { - trsf.SetTransformation(ax3); - trsf.Invert(); - } - - if (l->hasScale() && !ALMOST_THE_SAME(l->Scale(), 1.)) { - trsf.SetScaleFactor(l->Scale()); - } - - CACHE(IfcCartesianTransformationOperator3D,l,trsf) - return true; -} - -bool IfcGeom::Kernel::convert(const IfcSchema::IfcCartesianTransformationOperator2D* l, gp_Trsf2d& trsf) { - IN_CACHE(IfcCartesianTransformationOperator2D,l,gp_Trsf2d,trsf) - - gp_Pnt origin; - gp_Dir axis1 (1.,0.,0.); - gp_Dir axis2 (0.,1.,0.); - - IfcGeom::Kernel::convert(l->LocalOrigin(),origin); - if ( l->hasAxis1() ) IfcGeom::Kernel::convert(l->Axis1(),axis1); - if ( l->hasAxis2() ) IfcGeom::Kernel::convert(l->Axis2(),axis2); - - const gp_Pnt2d origin2d(origin.X(), origin.Y()); - const gp_Dir2d axis12d(axis1.X(), axis1.Y()); - const gp_Dir2d axis22d(axis2.X(), axis2.Y()); - - // A better match to represent the IfcCartesianTransformationOperator2D would - // be the gp_Ax22d, but to my knowledge no easy way exists to convert it into - // a gp_Trsf2d. Easiest would probably be to simply update the underlying - // gp_Mat2d directly. - - const gp_Ax2d ax2d (origin2d, axis12d); - trsf.SetTransformation(ax2d); - - if ( ax2d.Direction().Rotated(M_PI / 2.).Dot(axis22d) < 0. ) { - gp_Trsf2d mirror; mirror.SetMirror(ax2d); - trsf.Multiply(mirror); - } - - trsf.Invert(); - if ( l->hasScale() && !ALMOST_THE_SAME(l->Scale(), 1.) ) trsf.SetScaleFactor(l->Scale()); - - if (is_identity(trsf, getValue(GV_PRECISION))) { - trsf = gp_Trsf2d(); - } - - CACHE(IfcCartesianTransformationOperator2D,l,trsf) - return true; -} - -bool IfcGeom::Kernel::convert(const IfcSchema::IfcCartesianTransformationOperator3DnonUniform* l, gp_GTrsf& gtrsf) { - IN_CACHE(IfcCartesianTransformationOperator3DnonUniform,l,gp_GTrsf,gtrsf) - gp_Trsf trsf; - gp_Pnt origin; - IfcGeom::Kernel::convert(l->LocalOrigin(),origin); - gp_Dir axis1 (1.,0.,0.); - gp_Dir axis2 (0.,1.,0.); - gp_Dir axis3 (0.,0.,1.); - if ( l->hasAxis1() ) IfcGeom::Kernel::convert(l->Axis1(),axis1); - if ( l->hasAxis2() ) IfcGeom::Kernel::convert(l->Axis2(),axis2); - if ( l->hasAxis3() ) IfcGeom::Kernel::convert(l->Axis3(),axis3); - gp_Ax3 ax3 (origin,axis3,axis1); - if ( axis2.Dot(ax3.YDirection()) < 0 ) ax3.YReverse(); - trsf.SetTransformation(ax3); - trsf.Invert(); - const double scale1 = l->hasScale() ? l->Scale() : 1.0f; - const double scale2 = l->hasScale2() ? l->Scale2() : scale1; - const double scale3 = l->hasScale3() ? l->Scale3() : scale1; - gtrsf = gp_GTrsf(); - gtrsf.SetValue(1,1,scale1); - gtrsf.SetValue(2,2,scale2); - gtrsf.SetValue(3,3,scale3); - gtrsf.PreMultiply(trsf); - - if (is_identity(gtrsf, getValue(GV_PRECISION))) { - gtrsf = gp_GTrsf(); - } - - CACHE(IfcCartesianTransformationOperator3DnonUniform,l,gtrsf) - return true; -} - -bool IfcGeom::Kernel::convert(const IfcSchema::IfcCartesianTransformationOperator2DnonUniform* l, gp_GTrsf2d& gtrsf) { - IN_CACHE(IfcCartesianTransformationOperator2DnonUniform,l,gp_GTrsf2d,gtrsf) - - gp_Trsf2d trsf; - gp_Pnt origin; - gp_Dir axis1 (1.,0.,0.); - gp_Dir axis2 (0.,1.,0.); - - IfcGeom::Kernel::convert(l->LocalOrigin(),origin); - if ( l->hasAxis1() ) IfcGeom::Kernel::convert(l->Axis1(),axis1); - if ( l->hasAxis2() ) IfcGeom::Kernel::convert(l->Axis2(),axis2); - - const gp_Pnt2d origin2d(origin.X(), origin.Y()); - const gp_Dir2d axis12d(axis1.X(), axis1.Y()); - const gp_Dir2d axis22d(axis2.X(), axis2.Y()); - - const gp_Ax2d ax2d (origin2d, axis12d); - trsf.SetTransformation(ax2d); - - if ( ax2d.Direction().Rotated(M_PI / 2.).Dot(axis22d) < 0. ) { - gp_Trsf2d mirror; mirror.SetMirror(ax2d); - trsf.Multiply(mirror); - } - - trsf.Invert(); - - const double scale1 = l->hasScale() ? l->Scale() : 1.0f; - const double scale2 = l->hasScale2() ? l->Scale2() : scale1; - gtrsf = gp_GTrsf2d(); - gtrsf.SetValue(1,1,scale1); - gtrsf.SetValue(2,2,scale2); - gtrsf.Multiply(trsf); - - if (is_identity(gtrsf, getValue(GV_PRECISION))) { - gtrsf = gp_GTrsf2d(); - } - - CACHE(IfcCartesianTransformationOperator2DnonUniform,l,gtrsf) - return true; -} - -bool IfcGeom::Kernel::convert(const IfcSchema::IfcPlane* pln, gp_Pln& plane) { - IN_CACHE(IfcPlane,pln,gp_Pln,plane) - IfcSchema::IfcAxis2Placement3D* l = pln->Position(); - gp_Pnt o;gp_Dir axis = gp_Dir(0,0,1);gp_Dir refDirection; - IfcGeom::Kernel::convert(l->Location(),o); - bool hasRef = l->hasRefDirection(); - if ( l->hasAxis() ) IfcGeom::Kernel::convert(l->Axis(),axis); - if ( hasRef ) IfcGeom::Kernel::convert(l->RefDirection(),refDirection); - gp_Ax3 ax3; - if ( hasRef ) ax3 = gp_Ax3(o,axis,refDirection); - else ax3 = gp_Ax3(o,axis); - plane = gp_Pln(ax3); - CACHE(IfcPlane,pln,plane) - return true; -} - -bool IfcGeom::Kernel::convert(const IfcSchema::IfcAxis2Placement2D* l, gp_Trsf2d& trsf) { - IN_CACHE(IfcAxis2Placement2D,l,gp_Trsf2d,trsf) - gp_Pnt P; gp_Dir V (1,0,0); - IfcGeom::Kernel::convert(l->Location(),P); - if ( l->hasRefDirection() ) - IfcGeom::Kernel::convert(l->RefDirection(),V); - - gp_Ax2d axis(gp_Pnt2d(P.X(),P.Y()), gp_Dir2d(V.X(),V.Y())); - - if (!axis_equal(axis, gp_Ax2d(), getValue(GV_PRECISION))) { - trsf.SetTransformation(axis, gp_Ax2d()); - } - - CACHE(IfcAxis2Placement2D,l,trsf) - return true; -} - -bool IfcGeom::Kernel::convert(const IfcSchema::IfcObjectPlacement* l, gp_Trsf& trsf) { - IN_CACHE(IfcObjectPlacement,l,gp_Trsf,trsf) - if ( ! l->declaration().is(IfcSchema::IfcLocalPlacement::Class()) ) { - Logger::Message(Logger::LOG_ERROR, "Unsupported IfcObjectPlacement:", l); - return false; - } - IfcSchema::IfcLocalPlacement* current = (IfcSchema::IfcLocalPlacement*)l; - for (;;) { - gp_Trsf trsf2; - IfcSchema::IfcAxis2Placement* relplacement = current->RelativePlacement(); - if ( relplacement->declaration().is(IfcSchema::IfcAxis2Placement3D::Class()) ) { - IfcGeom::Kernel::convert((IfcSchema::IfcAxis2Placement3D*)relplacement,trsf2); - trsf.PreMultiply(trsf2); - } - if ( current->hasPlacementRelTo() ) { - IfcSchema::IfcObjectPlacement* parent = current->PlacementRelTo(); - IfcSchema::IfcProduct::list::ptr parentPlaces = parent->PlacesObject(); - bool parentPlacesType = false; - for ( IfcSchema::IfcProduct::list::it iter = parentPlaces->begin(); - iter != parentPlaces->end(); ++iter) { - if ( (*iter)->declaration().is(*placement_rel_to) ) parentPlacesType = true; - } - - if ( parentPlacesType ) break; - else if ( parent->declaration().is(IfcSchema::IfcLocalPlacement::Class()) ) - current = (IfcSchema::IfcLocalPlacement*)current->PlacementRelTo(); - else break; - } else break; - } - CACHE(IfcObjectPlacement,l,trsf) - return true; -} diff --git a/src/ifcgeom/kernels/opencascade/IfcGeomSerialisation.cpp_ b/src/ifcgeom/kernels/opencascade/IfcGeomSerialisation.cpp_ deleted file mode 100644 index def77c5064..0000000000 --- a/src/ifcgeom/kernels/opencascade/IfcGeomSerialisation.cpp_ +++ /dev/null @@ -1,660 +0,0 @@ -#include -#include -#include -#include - -#include -#include -#include - -#include - -#include -#include -#include -#include - -#include "IfcGeom.h" - -template -int convert_to_ifc(const T& t, U*& u, bool /*advanced*/) { - std::vector coords(3); - coords[0] = t.X(); coords[1] = t.Y(); coords[2] = t.Z(); - u = new U(coords); - return 1; -} - -template <> -int convert_to_ifc(const TopoDS_Vertex& v, IfcSchema::IfcCartesianPoint*& p, bool advanced) { - gp_Pnt pnt = BRep_Tool::Pnt(v); - return convert_to_ifc(pnt, p, advanced); -} - -template <> -int convert_to_ifc(const TopoDS_Vertex& v, IfcSchema::IfcVertex*& vertex, bool advanced) { - IfcSchema::IfcCartesianPoint* p; - convert_to_ifc(v, p, advanced); - vertex = new IfcSchema::IfcVertexPoint(p); - return 1; -} - -template <> -int convert_to_ifc(const gp_Ax2& a, IfcSchema::IfcAxis2Placement3D*& ax, bool advanced) { - IfcSchema::IfcCartesianPoint* p; - IfcSchema::IfcDirection *x, *z; - if (!(convert_to_ifc(a.Location(), p, advanced) && convert_to_ifc(a.Direction(), z, advanced) && convert_to_ifc(a.XDirection(), x, advanced))) { - ax = 0; - return 0; - } - ax = new IfcSchema::IfcAxis2Placement3D(p, z, x); - return 1; -} - -template -void opencascade_array_to_vector(T& t, std::vector& u) { - u.reserve(t.Length()); - for (int i = t.Lower(); i <= t.Upper(); ++i) { - u.push_back(t.Value(i)); - } -} - -template -void opencascade_array_to_vector2(T& t, std::vector< std::vector >& u) { - u.reserve(t.RowLength()); - for (int j = t.LowerRow(); j <= t.UpperRow(); ++j) { - std::vector v; - v.reserve(t.ColLength()); - for (int i = t.LowerCol(); i <= t.UpperCol(); ++i) { - v.push_back(t.Value(j, i)); - } - u.push_back(v); - } -} - -#ifdef USE_IFC4 -IfcSchema::IfcKnotType::Value opencascade_knotspec_to_ifc(GeomAbs_BSplKnotDistribution bspline_knot_spec) { - IfcSchema::IfcKnotType::Value knot_spec = IfcSchema::IfcKnotType::IfcKnotType_UNSPECIFIED; - if (bspline_knot_spec == GeomAbs_Uniform) { - knot_spec = IfcSchema::IfcKnotType::IfcKnotType_UNIFORM_KNOTS; - } else if (bspline_knot_spec == GeomAbs_QuasiUniform) { - knot_spec = IfcSchema::IfcKnotType::IfcKnotType_QUASI_UNIFORM_KNOTS; - } else if (bspline_knot_spec == GeomAbs_PiecewiseBezier) { - knot_spec = IfcSchema::IfcKnotType::IfcKnotType_PIECEWISE_BEZIER_KNOTS; - } - return knot_spec; -} -#endif - -template <> -int convert_to_ifc(const Handle_Geom_Curve& c, IfcSchema::IfcCurve*& curve, bool advanced) { - if (c->DynamicType() == STANDARD_TYPE(Geom_Line)) { - IfcSchema::IfcDirection* d; - IfcSchema::IfcCartesianPoint* p; - - Handle_Geom_Line line = Handle_Geom_Line::DownCast(c); - - if (!convert_to_ifc(line->Position().Location(), p, advanced)) { - return 0; - } - if (!convert_to_ifc(line->Position().Direction(), d, advanced)) { - return 0; - } - - IfcSchema::IfcVector* v = new IfcSchema::IfcVector(d, 1.); - curve = new IfcSchema::IfcLine(p, v); - - return 1; - } else if (c->DynamicType() == STANDARD_TYPE(Geom_Circle)) { - IfcSchema::IfcAxis2Placement3D* ax; - - Handle_Geom_Circle circle = Handle_Geom_Circle::DownCast(c); - - convert_to_ifc(circle->Position(), ax, advanced); - curve = new IfcSchema::IfcCircle(ax, circle->Radius()); - - return 1; - } else if (c->DynamicType() == STANDARD_TYPE(Geom_Ellipse)) { - IfcSchema::IfcAxis2Placement3D* ax; - - Handle_Geom_Ellipse ellipse = Handle_Geom_Ellipse::DownCast(c); - - convert_to_ifc(ellipse->Position(), ax, advanced); - curve = new IfcSchema::IfcEllipse(ax, ellipse->MajorRadius(), ellipse->MinorRadius()); - - return 1; - } -#ifdef USE_IFC4 - else if (c->DynamicType() == STANDARD_TYPE(Geom_BSplineCurve)) { - Handle_Geom_BSplineCurve bspline = Handle_Geom_BSplineCurve::DownCast(c); - - IfcSchema::IfcCartesianPoint::list::ptr points(new IfcSchema::IfcCartesianPoint::list); - TColgp_Array1OfPnt poles(1, bspline->NbPoles()); - bspline->Poles(poles); - for (int i = 1; i <= bspline->NbPoles(); ++i) { - IfcSchema::IfcCartesianPoint* p; - if (!convert_to_ifc(poles.Value(i), p, advanced)) { - return 0; - } - points->push(p); - } - IfcSchema::IfcKnotType::Value knot_spec = opencascade_knotspec_to_ifc(bspline->KnotDistribution()); - - std::vector mults; - std::vector knots; - std::vector weights; - - TColStd_Array1OfInteger bspline_mults(1, bspline->NbKnots()); - TColStd_Array1OfReal bspline_knots(1, bspline->NbKnots()); - TColStd_Array1OfReal bspline_weights(1, bspline->NbPoles()); - - bspline->Multiplicities(bspline_mults); - bspline->Knots(bspline_knots); - bspline->Weights(bspline_weights); - - opencascade_array_to_vector(bspline_mults, mults); - opencascade_array_to_vector(bspline_knots, knots); - opencascade_array_to_vector(bspline_weights, weights); - - bool rational = false; - for (std::vector::const_iterator it = weights.begin(); it != weights.end(); ++it) { - if ((*it) != 1.) { - rational = true; - break; - } - } - - if (rational) { - curve = new IfcSchema::IfcRationalBSplineCurveWithKnots( - bspline->Degree(), - points, - IfcSchema::IfcBSplineCurveForm::IfcBSplineCurveForm_UNSPECIFIED, - bspline->IsClosed() != 0, - false, - mults, - knots, - knot_spec, - weights - ); - } else { - curve = new IfcSchema::IfcBSplineCurveWithKnots( - bspline->Degree(), - points, - IfcSchema::IfcBSplineCurveForm::IfcBSplineCurveForm_UNSPECIFIED, - bspline->IsClosed() != 0, - false, - mults, - knots, - knot_spec - ); - } - - return 1; - } -#endif - return 0; -} - -template <> -int convert_to_ifc(const Handle_Geom_Surface& s, IfcSchema::IfcSurface*& surface, bool advanced) { - if (s->DynamicType() == STANDARD_TYPE(Geom_Plane)) { - Handle_Geom_Plane plane = Handle_Geom_Plane::DownCast(s); - IfcSchema::IfcAxis2Placement3D* place; - /// @todo: Note that the Ax3 is converted to an Ax2 here - if (!convert_to_ifc(plane->Position().Ax2(), place, advanced)) { - return 0; - } - surface = new IfcSchema::IfcPlane(place); - return 1; - } -#ifdef USE_IFC4 - else if (s->DynamicType() == STANDARD_TYPE(Geom_CylindricalSurface)) { - Handle_Geom_CylindricalSurface cyl = Handle_Geom_CylindricalSurface::DownCast(s); - IfcSchema::IfcAxis2Placement3D* place; - /// @todo: Note that the Ax3 is converted to an Ax2 here - if (!convert_to_ifc(cyl->Position().Ax2(), place, advanced)) { - return 0; - } - surface = new IfcSchema::IfcCylindricalSurface(place, cyl->Radius()); - return 1; - } else if (s->DynamicType() == STANDARD_TYPE(Geom_BSplineSurface)) { - typedef IfcTemplatedEntityListList points_t; - - Handle_Geom_BSplineSurface bspline = Handle_Geom_BSplineSurface::DownCast(s); - points_t::ptr points(new points_t); - - TColgp_Array2OfPnt poles(1, bspline->NbUPoles(), 1, bspline->NbVPoles()); - bspline->Poles(poles); - for (int i = 1; i <= bspline->NbUPoles(); ++i) { - std::vector ps; - ps.reserve(bspline->NbVPoles()); - for (int j = 1; j <= bspline->NbVPoles(); ++j) { - IfcSchema::IfcCartesianPoint* p; - if (!convert_to_ifc(poles.Value(i, j), p, advanced)) { - return 0; - } - ps.push_back(p); - } - points->push(ps); - } - - IfcSchema::IfcKnotType::Value knot_spec_u = opencascade_knotspec_to_ifc(bspline->UKnotDistribution()); - IfcSchema::IfcKnotType::Value knot_spec_v = opencascade_knotspec_to_ifc(bspline->VKnotDistribution()); - - if (knot_spec_u != knot_spec_v) { - knot_spec_u = IfcSchema::IfcKnotType::IfcKnotType_UNSPECIFIED; - } - - std::vector umults; - std::vector vmults; - std::vector uknots; - std::vector vknots; - std::vector< std::vector > weights; - - TColStd_Array1OfInteger bspline_umults(1, bspline->NbUKnots()); - TColStd_Array1OfInteger bspline_vmults(1, bspline->NbVKnots()); - TColStd_Array1OfReal bspline_uknots(1, bspline->NbUKnots()); - TColStd_Array1OfReal bspline_vknots(1, bspline->NbVKnots()); - TColStd_Array2OfReal bspline_weights(1, bspline->NbUPoles(), 1, bspline->NbVPoles()); - - bspline->UMultiplicities(bspline_umults); - bspline->VMultiplicities(bspline_vmults); - bspline->UKnots(bspline_uknots); - bspline->VKnots(bspline_vknots); - bspline->Weights(bspline_weights); - - opencascade_array_to_vector(bspline_umults, umults); - opencascade_array_to_vector(bspline_vmults, vmults); - opencascade_array_to_vector(bspline_uknots, uknots); - opencascade_array_to_vector(bspline_vknots, vknots); - opencascade_array_to_vector2(bspline_weights, weights); - - bool rational = false; - for (std::vector< std::vector >::const_iterator it = weights.begin(); it != weights.end(); ++it) { - for (std::vector::const_iterator jt = it->begin(); jt != it->end(); ++jt) { - if ((*jt) != 1.) { - rational = true; - break; - } - } - } - - if (rational) { - surface = new IfcSchema::IfcRationalBSplineSurfaceWithKnots( - bspline->UDegree(), - bspline->VDegree(), - points, - IfcSchema::IfcBSplineSurfaceForm::IfcBSplineSurfaceForm_UNSPECIFIED, - bspline->IsUClosed() != 0, - bspline->IsVClosed() != 0, - false, - umults, - vmults, - uknots, - vknots, - knot_spec_u, - weights - ); - } else { - surface = new IfcSchema::IfcBSplineSurfaceWithKnots( - bspline->UDegree(), - bspline->VDegree(), - points, - IfcSchema::IfcBSplineSurfaceForm::IfcBSplineSurfaceForm_UNSPECIFIED, - bspline->IsUClosed() != 0, - bspline->IsVClosed() != 0, - false, - umults, - vmults, - uknots, - vknots, - knot_spec_u - ); - } - - return 1; - } -#endif - return 0; -} - -template <> -int convert_to_ifc(const TopoDS_Edge& e, IfcSchema::IfcCurve*& c, bool advanced) { - double a, b; - IfcSchema::IfcCurve* base; - - Handle_Geom_Curve crv = BRep_Tool::Curve(e, a, b); - if (!convert_to_ifc(crv, base, advanced)) { - return 0; - } - - IfcEntityList::ptr trim1(new IfcEntityList); - IfcEntityList::ptr trim2(new IfcEntityList); - trim1->push(new IfcSchema::IfcParameterValue(a)); - trim2->push(new IfcSchema::IfcParameterValue(b)); - - c = new IfcSchema::IfcTrimmedCurve(base, trim1, trim2, true, IfcSchema::IfcTrimmingPreference::IfcTrimmingPreference_PARAMETER); - - return 1; -} - -template <> -int convert_to_ifc(const TopoDS_Edge& e, IfcSchema::IfcEdge*& edge, bool advanced) { - double a, b; - - TopExp_Explorer exp(e, TopAbs_VERTEX); - if (!exp.More()) return 0; - TopoDS_Vertex v1 = TopoDS::Vertex(exp.Current()); - exp.Next(); - if (!exp.More()) return 0; - TopoDS_Vertex v2 = TopoDS::Vertex(exp.Current()); - - IfcSchema::IfcVertex *vertex1, *vertex2; - if (!(convert_to_ifc(v1, vertex1, advanced) && convert_to_ifc(v2, vertex2, advanced))) { - return 0; - } - - Handle_Geom_Curve crv = BRep_Tool::Curve(e, a, b); - - if (crv.IsNull()) { - return 0; - } - - if (crv->DynamicType() == STANDARD_TYPE(Geom_Line) && !advanced) { - IfcSchema::IfcEdge* edge2 = new IfcSchema::IfcEdge(vertex1, vertex2); - edge = new IfcSchema::IfcOrientedEdge(edge2, true); - return 1; - } else { - IfcSchema::IfcCurve* curve; - if (!convert_to_ifc(crv, curve, advanced)) { - return 0; - } - /// @todo probably not correct - const bool sense = e.Orientation() == TopAbs_FORWARD; - IfcSchema::IfcEdge* edge2 = new IfcSchema::IfcEdgeCurve(vertex1, vertex2, curve, true); - edge = new IfcSchema::IfcOrientedEdge(edge2, sense); - return 1; - } -} - -template <> -int convert_to_ifc(const TopoDS_Wire& wire, IfcSchema::IfcLoop*& loop, bool advanced) { - bool polygonal = true; - for (TopExp_Explorer exp(wire, TopAbs_EDGE); exp.More(); exp.Next()) { - double a, b; - Handle_Geom_Curve crv = BRep_Tool::Curve(TopoDS::Edge(exp.Current()), a, b); - if (crv.IsNull()) { - continue; - } - if (crv->DynamicType() != STANDARD_TYPE(Geom_Line)) { - polygonal = false; - break; - } - } - if (!polygonal && !advanced) { - return 0; - } else if (polygonal && !advanced) { - IfcSchema::IfcCartesianPoint::list::ptr points(new IfcSchema::IfcCartesianPoint::list); - BRepTools_WireExplorer exp(wire); - IfcSchema::IfcCartesianPoint* p; - for (; exp.More(); exp.Next()) { - if (convert_to_ifc(exp.CurrentVertex(), p, advanced)) { - points->push(p); - } else { - return 0; - } - } - loop = new IfcSchema::IfcPolyLoop(points); - return 1; - } else { - IfcSchema::IfcOrientedEdge::list::ptr edges(new IfcSchema::IfcOrientedEdge::list); - BRepTools_WireExplorer exp(wire); - for (; exp.More(); exp.Next()) { - IfcSchema::IfcEdge* edge; - // With advanced set to true convert_to_ifc(TopoDS_Edge&) will always create an IfcOrientedEdge - if (!convert_to_ifc(exp.Current(), edge, true)) { - double a, b; - if (BRep_Tool::Curve(TopoDS::Edge(exp.Current()), a, b).IsNull()) { - continue; - } else { - return 0; - } - } - edges->push(edge->as()); - } - loop = new IfcSchema::IfcEdgeLoop(edges); - return 1; - } -} - -template <> -int convert_to_ifc(const TopoDS_Face& f, IfcSchema::IfcFace*& face, bool advanced) { - Handle_Geom_Surface surf = BRep_Tool::Surface(f); - TopExp_Explorer exp(f, TopAbs_WIRE); - IfcSchema::IfcFaceBound::list::ptr bounds(new IfcSchema::IfcFaceBound::list); - int index = 0; - for (; exp.More(); exp.Next(), ++index) { - IfcSchema::IfcLoop* loop; - if (!convert_to_ifc(TopoDS::Wire(exp.Current()), loop, advanced)) { - return 0; - } - IfcSchema::IfcFaceBound* bnd; - if (index == 0) { - bnd = new IfcSchema::IfcFaceOuterBound(loop, true); - } else { - bnd = new IfcSchema::IfcFaceBound(loop, true); - } - bounds->push(bnd); - } - - const bool is_planar = surf->DynamicType() == STANDARD_TYPE(Geom_Plane); - - if (!is_planar && !advanced) { - return 0; - } - if (is_planar && !advanced) { - face = new IfcSchema::IfcFace(bounds); - return 1; - } else { -#ifdef USE_IFC4 - IfcSchema::IfcSurface* surface; - if (!convert_to_ifc(surf, surface, advanced)) { - return 0; - } - face = new IfcSchema::IfcAdvancedFace(bounds, surface, f.Orientation() == TopAbs_FORWARD); - return 1; -#else - // No IfcAdvancedFace in Ifc2x3 - return 0; -#endif - } -} - -template -int convert_to_ifc(const TopoDS_Shape& s, U*& item, bool advanced) { - IfcSchema::IfcFace::list::ptr faces(new IfcSchema::IfcFace::list); - IfcSchema::IfcFace* f; - - for (TopExp_Explorer exp(s, TopAbs_FACE); exp.More(); exp.Next()) { - if (convert_to_ifc(TopoDS::Face(exp.Current()), f, advanced)) { - faces->push(f); - } else { - /// Cleanup: - for (IfcSchema::IfcFace::list::it it = faces->begin(); it != faces->end(); ++it) { - IfcEntityList::ptr data = IfcParse::traverse(*it)->unique(); - for (IfcEntityList::it jt = data->begin(); jt != data->end(); ++jt) { - delete *jt; - } - } - return 0; - } - } - - item = new U(faces); - return faces->size(); -} - -IfcUtil::IfcBaseClass* IfcGeom::POSTFIX_SCHEMA(serialise_)(const TopoDS_Shape& shape, bool advanced) { -#ifndef USE_IFC4 - advanced = false; -#endif - - for (TopExp_Explorer exp(shape, TopAbs_COMPSOLID); exp.More();) { - /// @todo CompSolids are not supported - return 0; - } - - IfcSchema::IfcRepresentation* rep = 0; - IfcSchema::IfcRepresentationItem::list::ptr items(new IfcSchema::IfcRepresentationItem::list); - - // First check if there is a solid with one or more shells - for (TopExp_Explorer exp(shape, TopAbs_SOLID); exp.More(); exp.Next()) { - IfcSchema::IfcClosedShell* outer = 0; - IfcSchema::IfcClosedShell::list::ptr inner(new IfcSchema::IfcClosedShell::list); - for (TopExp_Explorer exp2(exp.Current(), TopAbs_SHELL); exp2.More(); exp2.Next()) { - IfcSchema::IfcClosedShell* shell; - if (!convert_to_ifc(exp2.Current(), shell, advanced)) { - return 0; - } - /// @todo Are shells always in this order or does Orientation() needs to be checked? - if (outer) { - inner->push(shell); - } else { - outer = shell; - } - } - -#ifdef USE_IFC4 - if (advanced) { - if (inner->size()) { - items->push(new IfcSchema::IfcAdvancedBrepWithVoids(outer, inner)); - } else { - items->push(new IfcSchema::IfcAdvancedBrep(outer)); - } - } else -#endif - - /// @todo this is not necessarily correct as the shell is not necessarily facetted. - if (inner->size()) { - items->push(new IfcSchema::IfcFacetedBrepWithVoids(outer, inner)); - } else { - items->push(new IfcSchema::IfcFacetedBrep(outer)); - } - - } - - if (items->size() > 0) { - rep = new IfcSchema::IfcShapeRepresentation(0, std::string("Body"), std::string("Brep"), items); - } else { - - // If not, see if there is a shell - IfcSchema::IfcOpenShell::list::ptr shells(new IfcSchema::IfcOpenShell::list); - for (TopExp_Explorer exp(shape, TopAbs_SHELL); exp.More(); exp.Next()) { - IfcSchema::IfcOpenShell* shell; - if (!convert_to_ifc(exp.Current(), shell, advanced)) { - return 0; - } - shells->push(shell); - } - - if (shells->size() > 0) { - items->push(new IfcSchema::IfcShellBasedSurfaceModel(shells->generalize())); - rep = new IfcSchema::IfcShapeRepresentation(0, std::string("Body"), std::string("Brep"), items); - } else { - - // If not, see if there is are one of more faces. Note that they will be grouped into a shell. - IfcSchema::IfcOpenShell* shell; - int face_count = convert_to_ifc(shape, shell, advanced); - - if (face_count > 0) { - items->push(shell); - rep = new IfcSchema::IfcShapeRepresentation(0, std::string("Body"), std::string("Brep"), items); - } else { - - // If not, see if there are any edges. Note that wires are skipped as - // they are not commonly top-level geometrical descriptions in IFC. - // Also note that edges are written as trimmed curves rather than edges. - - IfcEntityList::ptr edges(new IfcEntityList); - - for (TopExp_Explorer exp(shape, TopAbs_EDGE); exp.More(); exp.Next()) { - IfcSchema::IfcCurve* c; - if (!convert_to_ifc(TopoDS::Edge(exp.Current()), c, advanced)) { - return 0; - } - edges->push(c); - } - - if (edges->size() == 0) { - return 0; - } else if (edges->size() == 1) { - rep = new IfcSchema::IfcShapeRepresentation(0, std::string("Axis"), std::string("Curve2D"), edges->as()); - } else { - // A geometric set is created as that probably (?) makes more sense in IFC - IfcSchema::IfcGeometricCurveSet* curves = new IfcSchema::IfcGeometricCurveSet(edges); - items->push(curves); - rep = new IfcSchema::IfcShapeRepresentation(0, std::string("Axis"), std::string("GeometricCurveSet"), items->as()); - } - - } - } - } - - IfcSchema::IfcRepresentation::list::ptr reps(new IfcSchema::IfcRepresentation::list); - reps->push(rep); - return new IfcSchema::IfcProductDefinitionShape(boost::none, boost::none, reps); -} - -IfcUtil::IfcBaseClass* IfcGeom::POSTFIX_SCHEMA(tesselate_)(const TopoDS_Shape& shape, double deflection) { - BRepMesh_IncrementalMesh(shape, deflection); - - IfcSchema::IfcFace::list::ptr faces(new IfcSchema::IfcFace::list); - - for (TopExp_Explorer exp(shape, TopAbs_FACE); exp.More(); exp.Next()) { - const TopoDS_Face& face = TopoDS::Face(exp.Current()); - TopLoc_Location loc; - Handle(Poly_Triangulation) tri = BRep_Tool::Triangulation(face, loc); - - if (!tri.IsNull()) { - const TColgp_Array1OfPnt& nodes = tri->Nodes(); - std::vector vertices; - for (int i = 1; i <= nodes.Length(); ++i) { - gp_Pnt pnt = nodes(i).Transformed(loc); - std::vector xyz; xyz.push_back(pnt.X()); xyz.push_back(pnt.Y()); xyz.push_back(pnt.Z()); - IfcSchema::IfcCartesianPoint* cpnt = new IfcSchema::IfcCartesianPoint(xyz); - vertices.push_back(cpnt); - } - const Poly_Array1OfTriangle& triangles = tri->Triangles(); - for (int i = 1; i <= triangles.Length(); ++i) { - int n1, n2, n3; - triangles(i).Get(n1, n2, n3); - IfcSchema::IfcCartesianPoint::list::ptr points(new IfcSchema::IfcCartesianPoint::list); - points->push(vertices[n1 - 1]); - points->push(vertices[n2 - 1]); - points->push(vertices[n3 - 1]); - IfcSchema::IfcPolyLoop* loop = new IfcSchema::IfcPolyLoop(points); - IfcSchema::IfcFaceOuterBound* bound = new IfcSchema::IfcFaceOuterBound(loop, face.Orientation() != TopAbs_REVERSED); - IfcSchema::IfcFaceBound::list::ptr bounds(new IfcSchema::IfcFaceBound::list); - bounds->push(bound); - IfcSchema::IfcFace* face2 = new IfcSchema::IfcFace(bounds); - faces->push(face2); - } - } - } - IfcSchema::IfcOpenShell* shell = new IfcSchema::IfcOpenShell(faces); - IfcSchema::IfcConnectedFaceSet::list::ptr shells(new IfcSchema::IfcConnectedFaceSet::list); - shells->push(shell); - IfcSchema::IfcFaceBasedSurfaceModel* surface_model = new IfcSchema::IfcFaceBasedSurfaceModel(shells); - - IfcSchema::IfcRepresentation::list::ptr reps(new IfcSchema::IfcRepresentation::list); - IfcSchema::IfcRepresentationItem::list::ptr items(new IfcSchema::IfcRepresentationItem::list); - - items->push(surface_model); - - IfcSchema::IfcShapeRepresentation* rep = new IfcSchema::IfcShapeRepresentation( - 0, std::string("Facetation"), std::string("SurfaceModel"), items); - - reps->push(rep); - IfcSchema::IfcProductDefinitionShape* shapedef = new IfcSchema::IfcProductDefinitionShape(boost::none, boost::none, reps); - - return shapedef; -} diff --git a/src/ifcgeom/kernels/opencascade/IfcGeomShapes.cpp b/src/ifcgeom/kernels/opencascade/IfcGeomShapes.cpp index 379cd4e67c..18b2eda801 100644 --- a/src/ifcgeom/kernels/opencascade/IfcGeomShapes.cpp +++ b/src/ifcgeom/kernels/opencascade/IfcGeomShapes.cpp @@ -107,6 +107,7 @@ #include "../../../ifcparse/IfcLogger.h" #include "../../../ifcgeom/kernels/opencascade/OpenCascadeConversionResult.h" +#include "IfcGeomTree.h" using namespace ifcopenshell::geometry; using namespace ifcopenshell::geometry::kernels; @@ -116,7 +117,7 @@ using namespace ifcopenshell::geometry::kernels; #include #include #include - +#include #include @@ -134,354 +135,6 @@ using namespace ifcopenshell::geometry::kernels; #include -bool OpenCascadeKernel::approximate_plane_through_wire(const TopoDS_Wire& wire, gp_Pln& plane, double eps) { - // Newell's Method is used for the normal calculation - // as a simple edge cross product can give opposite results - // for a concave face boundary. - // Reference: Graphics Gems III p. 231 - - const double eps_ = eps < 1. ? precision_ : eps; - const double eps2 = eps_ * eps_; - - double x = 0, y = 0, z = 0; - gp_Pnt current, previous, first; - gp_XYZ center; - int n = 0; - - BRepTools_WireExplorer exp(wire); - - for (;; exp.Next()) { - const bool has_more = exp.More() != 0; - if (has_more) { - const TopoDS_Vertex& v = exp.CurrentVertex(); - current = BRep_Tool::Pnt(v); - center += current.XYZ(); - } else { - current = first; - } - if (n) { - const double& xn = previous.X(); - const double& yn = previous.Y(); - const double& zn = previous.Z(); - const double& xn1 = current.X(); - const double& yn1 = current.Y(); - const double& zn1 = current.Z(); - x += (yn - yn1)*(zn + zn1); - y += (xn + xn1)*(zn - zn1); - z += (xn - xn1)*(yn + yn1); - } else { - first = current; - } - if (!has_more) { - break; - } - previous = current; - ++n; - } - - if (n < 3) { - return false; - } - - plane = gp_Pln(center / n, gp_Dir(x, y, z)); - - exp.Init(wire); - for (; exp.More(); exp.Next()) { - const TopoDS_Vertex& v = exp.CurrentVertex(); - current = BRep_Tool::Pnt(v); - if (plane.SquareDistance(current) > eps2) { - return false; - } - } - - return true; -} - - -bool OpenCascadeKernel::triangulate_wire(const std::vector& wires, TopTools_ListOfShape& faces) { - // This is a bit of a precarious approach, but seems to work for the - // versions of OCCT tested for. OCCT has a Delaunay triangulation function - // BRepMesh_Delaun, but it is notoriously hard to interpret the results - // (due to the Bowyer-Watson super triangle perhaps?). Therefore - // alternatively we use the regular OCCT incremental mesher on a new face - // created from the UV coordinates of the original wire. Pray to our gods - // that the vertex coordinates are unaffected by the meshing algorithm and - // map them back to 3d coordinates when iterating over the mesh triangles. - - // In addition, to maintain a manifold shell, we need to make sure that - // every edge from the input wire is used exactly once in the list of - // resulting faces. And that other internal edges are used twice. - - typedef std::pair uv_node; - - gp_Pln pln; - if (!approximate_plane_through_wire(wires.front(), pln, std::numeric_limits::infinity())) { - return false; - } - - const gp_XYZ& udir = pln.Position().XDirection().XYZ(); - const gp_XYZ& vdir = pln.Position().YDirection().XYZ(); - const gp_XYZ& pnt = pln.Position().Location().XYZ(); - - std::map mapping; - std::map, TopoDS_Edge> existing_edges, new_edges; - - std::unique_ptr mf; - - for (auto it = wires.begin(); it != wires.end(); ++it) { - const TopoDS_Wire& wire = *it; - BRepTools_WireExplorer exp(wire); - BRepBuilderAPI_MakePolygon mp; - - // Add UV coordinates to a newly created polygon - for (; exp.More(); exp.Next()) { - // Project onto plane - const TopoDS_Vertex& V = exp.CurrentVertex(); - gp_Pnt p = BRep_Tool::Pnt(V); - double u = (p.XYZ() - pnt).Dot(udir); - double v = (p.XYZ() - pnt).Dot(vdir); - mp.Add(gp_Pnt(u, v, 0.)); - - mapping.insert(std::make_pair(std::make_pair(u, v), V)); - - // Store existing edges in a map so that triangles can - // actually reference the preexisting edges. - const TopoDS_Edge& e = exp.Current(); - TopoDS_Vertex V0, V1; - TopExp::Vertices(e, V0, V1, true); - gp_Pnt p0 = BRep_Tool::Pnt(V0); - gp_Pnt p1 = BRep_Tool::Pnt(V1); - double u0 = (p0.XYZ() - pnt).Dot(udir); - double v0 = (p0.XYZ() - pnt).Dot(vdir); - double u1 = (p1.XYZ() - pnt).Dot(udir); - double v1 = (p1.XYZ() - pnt).Dot(vdir); - uv_node uv0 = std::make_pair(u0, v0); - uv_node uv1 = std::make_pair(u1, v1); - existing_edges.insert(std::make_pair(std::make_pair(uv0, uv1), e)); - existing_edges.insert(std::make_pair(std::make_pair(uv1, uv0), TopoDS::Edge(e.Reversed()))); - } - - // Not closed by default - mp.Close(); - - if (mf) { - if (it - 1 == wires.begin()) { - // @todo is this necessary? - TopoDS_Face f = mf->Face(); - mf->Init(f); - } - mf->Add(mp.Wire()); - } else { - mf.reset(new BRepBuilderAPI_MakeFace(mp.Wire())); - } - } - - const TopoDS_Face& face = mf->Face(); - - // Create a triangular mesh from the face - BRepMesh_IncrementalMesh(face, Precision::Confusion()); - - int n123[3]; - TopLoc_Location loc; - Handle_Poly_Triangulation tri = BRep_Tool::Triangulation(face, loc); - - if (!tri.IsNull()) { - const TColgp_Array1OfPnt& nodes = tri->Nodes(); - - const Poly_Array1OfTriangle& triangles = tri->Triangles(); - for (int i = 1; i <= triangles.Length(); ++i) { - if (face.Orientation() == TopAbs_REVERSED) - triangles(i).Get(n123[2], n123[1], n123[0]); - else triangles(i).Get(n123[0], n123[1], n123[2]); - - // Create polygons from the mesh vertices - BRepBuilderAPI_MakeWire mp2; - for (int j = 0; j < 3; ++j) { - - uv_node uvnodes[2]; - TopoDS_Vertex vs[2]; - - for (int k = 0; k < 2; ++k) { - const gp_Pnt& uv = nodes.Value(n123[(j + k) % 3]); - uvnodes[k] = std::make_pair(uv.X(), uv.Y()); - - auto it = mapping.find(uvnodes[k]); - if (it == mapping.end()) { - Logger::Error("Internal error: unable to unproject uv-mesh"); - return false; - } - - vs[k] = it->second; - } - - auto it = existing_edges.find(std::make_pair(uvnodes[0], uvnodes[1])); - if (it != existing_edges.end()) { - // This is a boundary edge, reuse existing edge from wire - mp2.Add(it->second); - } else { - auto jt = new_edges.find(std::make_pair(uvnodes[0], uvnodes[1])); - if (jt != new_edges.end()) { - // We have already added the reverse as part of another - // triangle, reuse this edge. - mp2.Add(TopoDS::Edge(jt->second)); - } else { - // This is a new internal edge. Register the reverse - // for reuse later. We need to be sure to reuse vertices - // for the edge construction because otherwise the wire - // builder will use geometrical proximity for vertex - // connections in which case the edge will be copied - // and no longer partner with other edges from the shell. - TopoDS_Edge ne = BRepBuilderAPI_MakeEdge(vs[0], vs[1]); - mp2.Add(ne); - // Store the reverse to be picked up later. - new_edges.insert(std::make_pair(std::make_pair(uvnodes[1], uvnodes[0]), TopoDS::Edge(ne.Reversed()))); - } - } - } - - BRepBuilderAPI_MakeFace mft(mp2.Wire()); - if (mft.IsDone()) { - TopoDS_Face triangle_face = mft.Face(); - TopoDS_Iterator jt(triangle_face, false); - for (; jt.More(); jt.Next()) { - const TopoDS_Wire& w = TopoDS::Wire(jt.Value()); - if (w.Orientation() != wires.front().Orientation()) { - triangle_face.Reverse(); - } - } - faces.Append(triangle_face); - } else { - Logger::Error("Internal error: missing face"); - return false; - } - } - } - - TopTools_IndexedDataMapOfShapeListOfShape mape, mapn; - for (auto& wire : wires) { - TopExp::MapShapesAndAncestors(wire, TopAbs_EDGE, TopAbs_WIRE, mape); - } - TopTools_ListIteratorOfListOfShape it(faces); - for (; it.More(); it.Next()) { - TopExp::MapShapesAndAncestors(it.Value(), TopAbs_EDGE, TopAbs_WIRE, mapn); - } - - // Validation - - for (int i = 1; i <= mape.Extent(); ++i) { -#if OCC_VERSION_HEX >= 0x70000 - TopTools_ListOfShape val; - if (!mapn.FindFromKey(mape.FindKey(i), val)) { -#else - bool contains = false; - try { - TopTools_ListOfShape val = mapn.FindFromKey(mape.FindKey(i)); - contains = true; - } catch (Standard_NoSuchObject&) {} - if (!contains) { -#endif - // All existing edges need to exist in the new faces - Logger::Error("Internal error, missing edge from triangulation"); - if (faceset_helper_ != nullptr) { - faceset_helper_->non_manifold() = true; - } - } - } - - for (int i = 1; i <= mapn.Extent(); ++i) { - const TopoDS_Shape& v = mapn.FindKey(i); - int n = mapn.FindFromIndex(i).Extent(); - // Existing edges are boundaries with use 1 - // New edges are internal with use 2 - if (n != (mape.Contains(v) ? 1 : 2)) { - Logger::Error("Internal error, non-manifold result from triangulation"); - if (faceset_helper_ != nullptr) { - faceset_helper_->non_manifold() = true; - } - } - } - - return true; -} - -bool OpenCascadeKernel::convert(const taxonomy::shell* l, TopoDS_Shape& shape) { - std::unique_ptr helper_scope; - helper_scope.reset(new faceset_helper(this, l)); - - auto faces = l->children_as(); - double minimal_face_area = precision_ * precision_ * 0.5; - - double min_face_area = faceset_helper_ - ? (faceset_helper_->epsilon() * faceset_helper_->epsilon() / 20.) - : minimal_face_area; - - TopTools_ListOfShape face_list; - for (auto& face : faces) { - bool success = false; - TopoDS_Face occ_face; - - try { - success = convert(face, occ_face); - } catch (const std::exception& e) { - Logger::Error(e); - } catch (const Standard_Failure& e) { - if (e.GetMessageString() && strlen(e.GetMessageString())) { - Logger::Error(e.GetMessageString()); - } else { - Logger::Error("Unknown error creating face"); - } - } catch (...) { - Logger::Error("Unknown error creating face"); - } - - if (!success) { - Logger::Message(Logger::LOG_WARNING, "Failed to convert face:", face->instance); - continue; - } - - if (occ_face.ShapeType() == TopAbs_COMPOUND) { - TopoDS_Iterator face_it(occ_face, false); - for (; face_it.More(); face_it.Next()) { - if (face_it.Value().ShapeType() == TopAbs_FACE) { - // This should really be the case. This is not asserted. - const TopoDS_Face& triangle = TopoDS::Face(face_it.Value()); - if (face_area(triangle) > min_face_area) { - face_list.Append(triangle); - } else { - Logger::Message(Logger::LOG_WARNING, "Degenerate face:", face->instance); - } - } - } - } else { - if (face_area(occ_face) > min_face_area) { - face_list.Append(occ_face); - } else { - Logger::Message(Logger::LOG_WARNING, "Degenerate face:", face->instance); - } - } - } - - if (face_list.Extent() == 0) { - return false; - } - - // @todo - /* face_list.Extent() > getValue(GV_MAX_FACES_TO_ORIENT) || */ - - if (!create_solid_from_faces(face_list, shape)) { - TopoDS_Compound compound; - BRep_Builder builder; - builder.MakeCompound(compound); - - TopTools_ListIteratorOfListOfShape face_iterator; - for (face_iterator.Initialize(face_list); face_iterator.More(); face_iterator.Next()) { - builder.Add(compound, face_iterator.Value()); - } - shape = compound; - } - - return true; -} #include #include @@ -550,9 +203,9 @@ bool OpenCascadeKernel::create_solid_from_faces(const TopTools_ListOfShape& face } BRepOffsetAPI_Sewing sewing_builder; - sewing_builder.SetTolerance(precision_); - sewing_builder.SetMaxTolerance(precision_); - sewing_builder.SetMinTolerance(precision_); + sewing_builder.SetTolerance(settings_.getValue(ConversionSettings::GV_PRECISION)); + sewing_builder.SetMaxTolerance(settings_.getValue(ConversionSettings::GV_PRECISION)); + sewing_builder.SetMinTolerance(settings_.getValue(ConversionSettings::GV_PRECISION)); BRep_Builder builder; TopoDS_Shell shell; @@ -609,7 +262,7 @@ bool OpenCascadeKernel::create_solid_from_faces(const TopTools_ListOfShape& face try { ShapeFix_Solid solid; - solid.SetMaxTolerance(precision_); + solid.SetMaxTolerance(settings_.getValue(ConversionSettings::GV_PRECISION)); TopoDS_Solid solid_shape = solid.SolidFromShell(TopoDS::Shell(exp.Current())); // @todo: BRepClass3d_SolidClassifier::PerformInfinitePoint() is done by SolidFromShell // and this is done again, to be able to catch errors during this process. @@ -618,7 +271,7 @@ bool OpenCascadeKernel::create_solid_from_faces(const TopTools_ListOfShape& face try { BRepClass3d_SolidClassifier classifier(solid_shape); result_shape = solid_shape; - classifier.PerformInfinitePoint(precision_); + classifier.PerformInfinitePoint(settings_.getValue(ConversionSettings::GV_PRECISION)); if (classifier.State() == TopAbs_IN) { shape.Reverse(); } @@ -695,460 +348,33 @@ int OpenCascadeKernel::count(const TopoDS_Shape& s, TopAbs_ShapeEnum t, bool uni } } -OpenCascadeKernel::faceset_helper::~faceset_helper() { - kernel_->faceset_helper_ = nullptr; -} - -#include "IfcGeomTree.h" - -namespace { - void find_neighbours(ifcopenshell::geometry::impl::tree& tree, std::vector>& pnts, std::set& visited, int p, double eps) { - visited.insert(p); - - Bnd_Box b; - b.Set(*pnts[p].get()); - b.Enlarge(eps); - - std::vector js = tree.select_box(b, false); - for (int j : js) { - visited.insert(j); -#ifdef FACESET_HELPER_RECURSIVE - if (visited.find(j) == visited.end()) { - // @todo, making this recursive removes the dependence on the initial ordering, but will - // likely result in empty results when all vertices are within 1 eps from another point. - find_neighbours(tree, pnts, visited, j, eps); - } -#endif - } - } -} - -OpenCascadeKernel::faceset_helper::faceset_helper(OpenCascadeKernel* kernel, const taxonomy::shell* shell) - : kernel_(kernel) - , non_manifold_(false) { - kernel->faceset_helper_ = this; - - // @todo use pointers? - std::vector points; - std::vector loops; - - for (auto& f : shell->children_as()) { - for (auto& l : f->children_as()) { - loops.push_back(l); - for (auto& e : l->children_as()) { - // @todo make sure only cartesian points are provided here - points.push_back(boost::get(e->start)); - } - } - } - - std::vector> pnts(points.size()); - std::vector vertices(pnts.size()); - - // @todo - impl::tree tree; - - BRep_Builder B; - - Bnd_Box box; - for (size_t i = 0; i < points.size(); ++i) { - gp_Pnt* p = new gp_Pnt(convert_xyz(points[i])); - pnts[i].reset(p); - B.MakeVertex(vertices[i], *p, Precision::Confusion()); - tree.add(i, vertices[i]); - box.Add(*p); - } - - // Use the bbox diagonal to influence local epsilon - // double bdiff = std::sqrt(box.SquareExtent()); - - // @todo the bounding box diagonal is not used (see above) - // because we're explicitly interested in the miminal - // dimension of the element to limit the tolerance (for sheet- - // like elements for example). But the way below is very - // dependent on orientation due to the usage of the - // axis-aligned bounding box. Use PCA to find three non-aligned - // set of dimensions and use the one with the smallest eigenvalue. - - // Find the minimal bounding box edge - double bmin[3], bmax[3]; - box.Get(bmin[0], bmin[1], bmin[2], bmax[0], bmax[1], bmax[2]); - double bdiff = std::numeric_limits::infinity(); - for (size_t i = 0; i < 3; ++i) { - const double d = bmax[i] - bmin[i]; - if (d > kernel->precision_ * 10. && d < bdiff) { - bdiff = d; - } - } - - eps_ = kernel->precision_ * 10. * (std::min)(1.0, bdiff); - - // @todo, there a tiny possibility that the duplicate faces are triggered - // for an internal boundary, that is also present as an external boundary. - // This will result in non-manifold configuration then, but this is deemed - // such as corner-case that it is not considered. - - size_t loops_removed, non_manifold, duplicate_faces; - - std::map, int> edge_use; - - for (int i = 0; i < 3; ++i) { - // Some times files, have large tolerance values specified collapsing too many vertices. - // This case we detect below and re-run the loop with smaller epsilon. Normally - // the body of this loop would only be executed once. - - loops_removed = 0; - non_manifold = 0; - duplicate_faces = 0; - - vertex_mapping_.clear(); - duplicates_.clear(); - - edge_use.clear(); - - if (eps_ < Precision::Confusion()) { - // occt uses some hard coded precision values, don't go smaller than that. - // @todo, can be reset though with BRepLib::Precision(double) - eps_ = Precision::Confusion(); - } - - for (int i = 0; i < (int)pnts.size(); ++i) { - if (pnts[i]) { - std::set vs; - find_neighbours(tree, pnts, vs, i, eps_); - - for (int v : vs) { - auto& pt = points[v]; - // NB: insert() ignores duplicate keys - vertex_mapping_.insert({ pt.instance->data().id() , i }); - } - } - } - - typedef std::array edge_t; - typedef std::set edge_set_t; - std::set edge_sets; - - for (auto& loop : loops) { - std::vector > segments; - edge_set_t segment_set; - - loop_(loop, [&segments, &segment_set](int C, int D, bool) { - segment_set.insert({ { C, D } }); - segments.push_back({ C, D }); - }); - - if (edge_sets.find(segment_set) != edge_sets.end()) { - duplicate_faces++; - duplicates_.insert(loop->instance->data().id()); - continue; - } - edge_sets.insert(segment_set); - - if (segments.size() >= 3) { - for (auto& p : segments) { - edge_use[p] ++; - } - } else { - loops_removed += 1; - } - } - - if (edge_use.size() != 0) { - break; - } else { - eps_ /= 10.; - } - } - - for (auto& p : edge_use) { - int a, b; - std::tie(a, b) = p.first; - edges_[p.first] = BRepBuilderAPI_MakeEdge(vertices[a], vertices[b]); - - if (p.second != 2) { - non_manifold += 1; - } - } - - if (loops_removed || (non_manifold && shell->closed.get_value_or(false))) { - Logger::Warning(boost::lexical_cast(duplicate_faces) + " duplicate faces removed, " + boost::lexical_cast(loops_removed) + " loops removed and " + boost::lexical_cast(non_manifold) + " non-manifold edges for:", shell->instance); - } -} - -#include -#include -#include - -namespace { - void copy_operand(const TopTools_ListOfShape& l, TopTools_ListOfShape& r) { -#if OCC_VERSION_HEX < 0x70000 - TopTools_ListIteratorOfListOfShape it(l); +bool is_manifold_occt(const TopoDS_Shape& a) { + if (a.ShapeType() == TopAbs_COMPOUND || a.ShapeType() == TopAbs_SOLID) { + TopoDS_Iterator it(a); for (; it.More(); it.Next()) { - r.Append(BRepBuilderAPI_Copy(it.Value())); + if (!is_manifold_occt(it.Value())) { + return false; + } } -#else - // On OCCT 7.0 and higher BRepAlgoAPI_BuilderAlgo::SetNonDestructive(true) is - // called. Not entirely sure on the behaviour before 7.0, so overcautiously - // create copies. - r.Assign(l); -#endif + return true; + } else { + TopTools_IndexedDataMapOfShapeListOfShape map; + TopExp::MapShapesAndAncestors(a, TopAbs_EDGE, TopAbs_FACE, map); + + for (int i = 1; i <= map.Extent(); ++i) { + if (map.FindFromIndex(i).Extent() != 2) { + return false; + } + } + + return true; } - - TopoDS_Shape copy_operand(const TopoDS_Shape& s) { -#if OCC_VERSION_HEX < 0x70000 - return BRepBuilderAPI_Copy(s); -#else - return s; -#endif - } - - double min_edge_length(const TopoDS_Shape& a) { - double min_edge_len = std::numeric_limits::infinity(); - TopExp_Explorer exp(a, TopAbs_EDGE); - for (; exp.More(); exp.Next()) { - GProp_GProps prop; - BRepGProp::LinearProperties(exp.Current(), prop); - double l = prop.Mass(); - if (l < min_edge_len) { - min_edge_len = l; - } - } - return min_edge_len; - } - - double min_vertex_edge_distance(const TopoDS_Shape& a, double min_search, double max_search) { - double M = std::numeric_limits::infinity(); - - TopTools_IndexedMapOfShape vertices, edges; - - TopExp::MapShapes(a, TopAbs_VERTEX, vertices); - TopExp::MapShapes(a, TopAbs_EDGE, edges); - - impl::tree tree; - - // Add edges to tree - for (int i = 1; i <= edges.Extent(); ++i) { - tree.add(i, edges(i)); - } - - for (int j = 1; j <= vertices.Extent(); ++j) { - const TopoDS_Vertex& v = TopoDS::Vertex(vertices(j)); - gp_Pnt p = BRep_Tool::Pnt(v); - - Bnd_Box b; - b.Add(p); - b.Enlarge(max_search); - - std::vector edge_idxs = tree.select_box(b, false); - std::vector::const_iterator it = edge_idxs.begin(); - for (; it != edge_idxs.end(); ++it) { - const TopoDS_Edge& e = TopoDS::Edge(edges(*it)); - TopoDS_Vertex v1, v2; - TopExp::Vertices(e, v1, v2); - - if (v.IsSame(v1) || v.IsSame(v2)) { - continue; - } - - BRepAdaptor_Curve crv(e); - Extrema_ExtPC ext(p, crv); - if (!ext.IsDone()) { - continue; - } - - for (int i = 1; i <= ext.NbExt(); ++i) { - const double m = sqrt(ext.SquareDistance(i)); - if (m < M && m > min_search) { - M = m; - } - } - } - } - - return M; - } - - class points_on_planar_face_generator { - private: - const TopoDS_Face& f_; - Handle(Geom_Surface) plane_; - BRepTopAdaptor_FClass2d cls_; - double u0, u1, v0, v1; - int i, j; - static const int N = 10; - - public: - points_on_planar_face_generator(const TopoDS_Face& f) - : f_(f) - , plane_(BRep_Tool::Surface(f_)) - , cls_(f_, BRep_Tool::Tolerance(f_)) - , i(0), j(0) { - BRepTools::UVBounds(f_, u0, u1, v0, v1); - } - - void reset() { - i = j = 0; - } - - bool operator()(gp_Pnt& p) { - while (j < N) { - double u = u0 + (u1 - u0) * i / N; - double v = v0 + (v1 - v0) * j / N; - - i++; - if (i == N) { - i = 0; - j++; - } - - // Specifically does not consider ON - if (cls_.Perform(gp_Pnt2d(u, v)) == TopAbs_IN) { - plane_->D0(u, v, p); - return true; - } - } - - return false; - } - }; - - double min_face_face_distance(const TopoDS_Shape& a, double max_search) { - /* - NB: This is currently only implemented for planar surfaces. - */ - double M = std::numeric_limits::infinity(); - - TopTools_IndexedMapOfShape faces; - - TopExp::MapShapes(a, TopAbs_FACE, faces); - - ifcopenshell::geometry::impl::tree tree; - - // Add faces to tree - for (int i = 1; i <= faces.Extent(); ++i) { - if (BRep_Tool::Surface(TopoDS::Face(faces(i)))->DynamicType() == STANDARD_TYPE(Geom_Plane)) { - tree.add(i, faces(i)); - } - } - - for (int j = 1; j <= faces.Extent(); ++j) { - const TopoDS_Face& f = TopoDS::Face(faces(j)); - const Handle(Geom_Surface)& fs = BRep_Tool::Surface(f); - - if (fs->DynamicType() != STANDARD_TYPE(Geom_Plane)) { - continue; - } - - points_on_planar_face_generator pgen(f); - - Bnd_Box b; - BRepBndLib::AddClose(f, b); - b.Enlarge(max_search); - - std::vector face_idxs = tree.select_box(b, false); - std::vector::const_iterator it = face_idxs.begin(); - for (; it != face_idxs.end(); ++it) { - if (*it == j) { - continue; - } - - const TopoDS_Face& g = TopoDS::Face(faces(*it)); - const Handle(Geom_Surface)& gs = BRep_Tool::Surface(g); - - auto p0 = Handle(Geom_Plane)::DownCast(fs); - auto p1 = Handle(Geom_Plane)::DownCast(gs); - - if (p0->Position().IsCoplanar(p1->Position(), max_search, asin(max_search))) { - pgen.reset(); - - BRepTopAdaptor_FClass2d cls(g, BRep_Tool::Tolerance(g)); - - gp_Pnt test; - while (pgen(test)) { - gp_Vec d = test.XYZ() - p1->Position().Location().XYZ(); - double u = d.Dot(p1->Position().XDirection()); - double v = d.Dot(p1->Position().YDirection()); - - // nb: TopAbs_ON is explicitly not considered to prevent matching adjacent faces - // with similar orientations. - if (cls.Perform(gp_Pnt2d(u, v)) == TopAbs_IN) { - gp_Pnt test2; - p1->D0(u, v, test2); - double w = gp_Vec(p1->Position().Direction().XYZ()).Dot(test2.XYZ() - test.XYZ()); - if (w < M) { - M = w; - } - } - } - } - } - } - - return M; - } - - void bounding_box_overlap(double p, const TopoDS_Shape& a, const TopTools_ListOfShape& b, TopTools_ListOfShape& c) { - Bnd_Box A; - BRepBndLib::Add(a, A); - - if (A.IsVoid()) { - return; - } - - TopTools_ListIteratorOfListOfShape it(b); - for (; it.More(); it.Next()) { - Bnd_Box B; - BRepBndLib::Add(it.Value(), B); - - if (B.IsVoid()) { - continue; - } - - if (A.Distance(B) < p) { - c.Append(it.Value()); - } - } - } - - TopoDS_Shape unify(const TopoDS_Shape& s, double tolerance) { - tolerance = (std::min)(min_edge_length(s) / 2., tolerance); - ShapeUpgrade_UnifySameDomain usd(s); - usd.SetSafeInputMode(true); - usd.SetLinearTolerance(tolerance); - usd.SetAngularTolerance(1.e-3); - usd.Build(); - return usd.Shape(); - } - - bool is_manifold_occt(const TopoDS_Shape& a) { - if (a.ShapeType() == TopAbs_COMPOUND || a.ShapeType() == TopAbs_SOLID) { - TopoDS_Iterator it(a); - for (; it.More(); it.Next()) { - if (!is_manifold_occt(it.Value())) { - return false; - } - } - return true; - } else { - TopTools_IndexedDataMapOfShapeListOfShape map; - TopExp::MapShapesAndAncestors(a, TopAbs_EDGE, TopAbs_FACE, map); - - for (int i = 1; i <= map.Extent(); ++i) { - if (map.FindFromIndex(i).Extent() != 2) { - return false; - } - } - - return true; - } - } } bool OpenCascadeKernel::boolean_operation(const TopoDS_Shape& a_, const TopTools_ListOfShape& b__, BOPAlgo_Operation op, TopoDS_Shape& result, double fuzziness) { if (fuzziness < 0.) { - fuzziness = precision_; + fuzziness = settings_.getValue(ConversionSettings::GV_PRECISION); } // @todo, it does seem a bit odd, we first triangulate non-planar faces @@ -1167,7 +393,7 @@ bool OpenCascadeKernel::boolean_operation(const TopoDS_Shape& a_, const TopTools TopTools_ListOfShape B, b; if (op == BOPAlgo_CUT) { builder = new BRepAlgoAPI_Cut(); - bounding_box_overlap(precision_, a, b_, b); + bounding_box_overlap(settings_.getValue(ConversionSettings::GV_PRECISION), a, b_, b); } else if (op == BOPAlgo_COMMON) { builder = new BRepAlgoAPI_Common(); b = b_; @@ -1186,14 +412,14 @@ bool OpenCascadeKernel::boolean_operation(const TopoDS_Shape& a_, const TopTools // Find a sensible value for the fuzziness, based on precision // and limited by edge lengths and vertex-edge distances. const double len_a = min_edge_length(a_); - double min_length_orig = (std::min)(len_a, min_vertex_edge_distance(a_, precision_, len_a)); + double min_length_orig = (std::min)(len_a, min_vertex_edge_distance(a_, settings_.getValue(ConversionSettings::GV_PRECISION), len_a)); TopTools_ListIteratorOfListOfShape it(b__); for (; it.More(); it.Next()) { double d = min_edge_length(it.Value()); if (d < min_length_orig) { min_length_orig = d; } - d = min_vertex_edge_distance(it.Value(), precision_, d); + d = min_vertex_edge_distance(it.Value(), settings_.getValue(ConversionSettings::GV_PRECISION), d); if (d < min_length_orig) { min_length_orig = d; } @@ -1240,7 +466,7 @@ bool OpenCascadeKernel::boolean_operation(const TopoDS_Shape& a_, const TopTools if ((v = min_edge_length(r)) < fuzziness * 3.) { reason = 0; success = false; - } else if ((v = min_vertex_edge_distance(r, precision_, fuzziness * 3.)) < fuzziness * 3.) { + } else if ((v = min_vertex_edge_distance(r, settings_.getValue(ConversionSettings::GV_PRECISION), fuzziness * 3.)) < fuzziness * 3.) { reason = 1; success = false; } else if ((v = min_face_face_distance(r, fuzziness * 3.)) < fuzziness * 3.) { @@ -1277,7 +503,7 @@ bool OpenCascadeKernel::boolean_operation(const TopoDS_Shape& a_, const TopTools delete builder; if (!success) { const double new_fuzziness = fuzziness * 10.; - if (new_fuzziness - 1e-15 <= precision_ * 10000. && new_fuzziness < min_length_orig) { + if (new_fuzziness - 1e-15 <= settings_.getValue(ConversionSettings::GV_PRECISION) * 10000. && new_fuzziness < min_length_orig) { return boolean_operation(a, b, op, result, new_fuzziness); } else { Logger::Notice("No longer attempting boolean operation with higher fuzziness"); @@ -1446,8 +672,6 @@ TopoDS_Shape OpenCascadeKernel::apply_transformation(const TopoDS_Shape& s, cons } } -#include - TopoDS_Shape OpenCascadeKernel::apply_transformation(const TopoDS_Shape& s, const gp_GTrsf& t) { if (t.Form() == gp_Other) { Logger::Message(Logger::LOG_WARNING, "Applying non uniform transformation"); diff --git a/src/ifcgeom/kernels/opencascade/IfcGeomShapes.cpp_ b/src/ifcgeom/kernels/opencascade/IfcGeomShapes.cpp_ deleted file mode 100644 index 5001639a6d..0000000000 --- a/src/ifcgeom/kernels/opencascade/IfcGeomShapes.cpp_ +++ /dev/null @@ -1,1289 +0,0 @@ -/******************************************************************************** - * * - * 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 . * - * * - ********************************************************************************/ - -/******************************************************************************** - * * - * Implementations of the various conversion functions defined in IfcRegister.h * - * * - ********************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include - -#include - -#include -#include - -#include - -#include - -#include "../../../ifcgeom/kernels/opencascade/IfcGeom.h" - -#include - -#define Kernel POSTFIX_SCHEMA(Kernel) - -bool IfcGeom::Kernel::convert(const IfcSchema::IfcExtrudedAreaSolid* l, TopoDS_Shape& shape) { - const double height = l->Depth() * getValue(GV_LENGTH_UNIT); - if (height < getValue(GV_PRECISION)) { - Logger::Message(Logger::LOG_ERROR, "Non-positive extrusion height encountered for:", l); - return false; - } - - TopoDS_Shape face; - if ( !convert_face(l->SweptArea(),face) ) return false; - - gp_Trsf trsf; - bool has_position = true; -#ifdef SCHEMA_IfcSweptAreaSolid_Position_IS_OPTIONAL - has_position = l->hasPosition(); -#endif - if (has_position) { - IfcGeom::Kernel::convert(l->Position(), trsf); - } - - gp_Dir dir; - convert(l->ExtrudedDirection(),dir); - - shape.Nullify(); - - if (face.ShapeType() == TopAbs_COMPOUND) { - - // For compounds (most likely the result of a IfcCompositeProfileDef) - // create a compound solid shape. - - TopExp_Explorer exp(face, TopAbs_FACE); - - TopoDS_CompSolid compound; - BRep_Builder builder; - builder.MakeCompSolid(compound); - - int num_faces_extruded = 0; - for (; exp.More(); exp.Next(), ++num_faces_extruded) { - builder.Add(compound, BRepPrimAPI_MakePrism(exp.Current(), height*dir)); - } - - if (num_faces_extruded) { - shape = compound; - } - - } - - if (shape.IsNull()) { - shape = BRepPrimAPI_MakePrism(face, height*dir); - } - - if (has_position && !shape.IsNull()) { - // IfcSweptAreaSolid.Position (trsf) is an IfcAxis2Placement3D - // and therefore has a unit scale factor - shape.Move(trsf); - } - - return !shape.IsNull(); -} - -#ifdef SCHEMA_HAS_IfcExtrudedAreaSolidTapered -bool IfcGeom::Kernel::convert(const IfcSchema::IfcExtrudedAreaSolidTapered* l, TopoDS_Shape& shape) { - const double height = l->Depth() * getValue(GV_LENGTH_UNIT); - if (height < getValue(GV_PRECISION)) { - Logger::Message(Logger::LOG_ERROR, "Non-positive extrusion height encountered for:", l); - return false; - } - - TopoDS_Shape face1, face2; - if (!convert_face(l->SweptArea(), face1)) return false; - if (!convert_face(l->EndSweptArea(), face2)) return false; - - gp_Trsf trsf; - bool has_position = true; -#ifdef SCHEMA_IfcSweptAreaSolid_Position_IS_OPTIONAL - has_position = l->hasPosition(); -#endif - if (has_position) { - IfcGeom::Kernel::convert(l->Position(), trsf); - } - - gp_Dir dir; - convert(l->ExtrudedDirection(), dir); - - gp_Trsf end_profile; - end_profile.SetTranslation(height * dir); - - TopoDS_Edge spine_edge = BRepBuilderAPI_MakeEdge(gp_Pnt(), gp_Pnt((height * dir).XYZ())).Edge(); - TopoDS_Wire wire = BRepBuilderAPI_MakeWire(spine_edge).Wire(); - - shape.Nullify(); - - TopExp_Explorer exp1(face1, TopAbs_WIRE); - TopExp_Explorer exp2(face2, TopAbs_WIRE); - - TopoDS_Vertex v1, v2; - TopExp::Vertices(wire, v1, v2); - - TopoDS_Shape shell; - TopoDS_Compound compound; - BRep_Builder compound_builder; - - for (; exp1.More() && exp2.More(); exp1.Next(), exp2.Next()) { - const TopoDS_Wire& w1 = TopoDS::Wire(exp1.Current()); - const TopoDS_Wire& w2 = TopoDS::Wire(exp2.Current()); - - BRepOffsetAPI_MakePipeShell builder(wire); - builder.Add(w1, v1); - builder.Add(w2.Moved(end_profile), v2); - - TopoDS_Shape result = builder.Shape(); - - BRepOffsetAPI_Sewing sewer; - sewer.SetTolerance(getValue(GV_PRECISION)); - sewer.SetMaxTolerance(getValue(GV_PRECISION)); - sewer.SetMinTolerance(getValue(GV_PRECISION)); - - sewer.Add(result); - sewer.Add(BRepBuilderAPI_MakeFace(w1).Face()); - sewer.Add(BRepBuilderAPI_MakeFace(w2).Face().Moved(end_profile)); - - sewer.Perform(); - - result = sewer.SewedShape(); - - if (shell.IsNull()) { - shell = result; - } else if (l->SweptArea()->declaration().is(IfcSchema::IfcCircleHollowProfileDef::Class()) || - l->SweptArea()->declaration().is(IfcSchema::IfcRectangleHollowProfileDef::Class())) - { - /// @todo a bit of of a hack, should be sufficient - shell = BRepAlgoAPI_Cut(shell, result).Shape(); - break; - } else { - if (compound.IsNull()) { - compound_builder.MakeCompound(compound); - compound_builder.Add(compound, shell); - } - compound_builder.Add(compound, result); - } - } - - if (!compound.IsNull()) { - shell = compound; - } - - shape = shell; - - if (exp1.More() != exp2.More()) { - Logger::Message(Logger::LOG_ERROR, "Inconsistent profiles encountered for:", l); - } - - if (has_position && !shape.IsNull()) { - // IfcSweptAreaSolid.Position (trsf) is an IfcAxis2Placement3D - // and therefore has a unit scale factor - shape.Move(trsf); - } - - return !shape.IsNull(); -} -#endif - -bool IfcGeom::Kernel::convert(const IfcSchema::IfcSurfaceOfLinearExtrusion* l, TopoDS_Shape& shape) { - TopoDS_Wire wire; - if ( !convert_wire(l->SweptCurve(), wire) ) { - TopoDS_Face face; - if ( !convert_face(l->SweptCurve(),face) ) return false; - TopExp_Explorer exp(face, TopAbs_WIRE); - wire = TopoDS::Wire(exp.Current()); - } - const double height = l->Depth() * getValue(GV_LENGTH_UNIT); - - gp_Trsf trsf; - bool has_position = true; -#ifdef SCHEMA_IfcSweptSurface_Position_IS_OPTIONAL - has_position = l->hasPosition(); -#endif - if (has_position) { - IfcGeom::Kernel::convert(l->Position(), trsf); - } - - gp_Dir dir; - convert(l->ExtrudedDirection(),dir); - - shape = BRepPrimAPI_MakePrism(wire, height*dir); - - if (has_position) { - // IfcSweptSurface.Position (trsf) is an IfcAxis2Placement3D - // and therefore has a unit scale factor - shape.Move(trsf); - } - - return !shape.IsNull(); -} - -bool IfcGeom::Kernel::convert(const IfcSchema::IfcSurfaceOfRevolution* l, TopoDS_Shape& shape) { - TopoDS_Wire wire; - if ( !convert_wire(l->SweptCurve(), wire) ) { - TopoDS_Face face; - if ( !convert_face(l->SweptCurve(),face) ) return false; - TopExp_Explorer exp(face, TopAbs_WIRE); - wire = TopoDS::Wire(exp.Current()); - } - - gp_Ax1 ax1; - IfcGeom::Kernel::convert(l->AxisPosition(), ax1); - - gp_Trsf trsf; - bool has_position = true; -#ifdef SCHEMA_IfcSweptSurface_Position_IS_OPTIONAL - has_position = l->hasPosition(); -#endif - if (has_position) { - IfcGeom::Kernel::convert(l->Position(), trsf); - } - - shape = BRepPrimAPI_MakeRevol(wire, ax1); - - if (has_position) { - // IfcSweptSurface.Position (trsf) is an IfcAxis2Placement3D - // and therefore has a unit scale factor - shape.Move(trsf); - } - - return !shape.IsNull(); -} - -bool IfcGeom::Kernel::convert(const IfcSchema::IfcRevolvedAreaSolid* l, TopoDS_Shape& shape) { - const double ang = l->Angle() * getValue(GV_PLANEANGLE_UNIT); - - TopoDS_Face face; - if ( ! convert_face(l->SweptArea(),face) ) return false; - - gp_Ax1 ax1; - IfcGeom::Kernel::convert(l->Axis(), ax1); - - gp_Trsf trsf; - bool has_position = true; -#ifdef SCHEMA_IfcSweptAreaSolid_Position_IS_OPTIONAL - has_position = l->hasPosition(); -#endif - if (has_position) { - IfcGeom::Kernel::convert(l->Position(), trsf); - } - - if (ang >= M_PI * 2. - ALMOST_ZERO) { - shape = BRepPrimAPI_MakeRevol(face, ax1); - } else { - shape = BRepPrimAPI_MakeRevol(face, ax1, ang); - } - - if (has_position) { - // IfcSweptAreaSolid.Position (trsf) is an IfcAxis2Placement3D - // and therefore has a unit scale factor - shape.Move(trsf); - } - - return !shape.IsNull(); -} - -bool IfcGeom::Kernel::convert(const IfcSchema::IfcManifoldSolidBrep* l, ConversionResults& shape) { - TopoDS_Shape s; - const SurfaceStyle* collective_style = get_style(l); - if (convert_shape(l->Outer(),s) ) { - const SurfaceStyle* indiv_style = get_style(l->Outer()); - - IfcSchema::IfcClosedShell::list::ptr voids(new IfcSchema::IfcClosedShell::list); - if (l->declaration().is(IfcSchema::IfcFacetedBrepWithVoids::Class())) { - voids = l->as()->Voids(); - } -#ifdef SCHEMA_HAS_IfcAdvancedBrepWithVoids - if (l->declaration().is(IfcSchema::IfcAdvancedBrepWithVoids::Class())) { - voids = l->as()->Voids(); - } -#endif - - for (IfcSchema::IfcClosedShell::list::it it = voids->begin(); it != voids->end(); ++it) { - TopoDS_Shape s2; - /// @todo No extensive shapefixing since shells should be disjoint. - /// @todo Awaiting generalized boolean ops module with appropriate checking - if (convert_shape(l->Outer(), s2)) { - s = BRepAlgoAPI_Cut(s, s2).Shape(); - } - } - - shape.push_back(ConversionResult(l->data().id(), new OpenCascadeShape(s), indiv_style ? indiv_style : collective_style)); - return true; - } - return false; -} - -bool IfcGeom::Kernel::convert(const IfcSchema::IfcFaceBasedSurfaceModel* l, ConversionResults& shapes) { - bool part_success = false; - IfcSchema::IfcConnectedFaceSet::list::ptr facesets = l->FbsmFaces(); - const SurfaceStyle* collective_style = get_style(l); - for( IfcSchema::IfcConnectedFaceSet::list::it it = facesets->begin(); it != facesets->end(); ++ it ) { - TopoDS_Shape s; - const SurfaceStyle* shell_style = get_style(*it); - if (convert_shape(*it,s)) { - shapes.push_back(ConversionResult(l->data().id(), new OpenCascadeShape(s), shell_style ? shell_style : collective_style)); - part_success |= true; - } - } - return part_success; -} - -bool IfcGeom::Kernel::convert(const IfcSchema::IfcHalfSpaceSolid* l, TopoDS_Shape& shape) { - IfcSchema::IfcSurface* surface = l->BaseSurface(); - if ( ! surface->declaration().is(IfcSchema::IfcPlane::Class()) ) { - Logger::Message(Logger::LOG_ERROR, "Unsupported BaseSurface:", surface); - return false; - } - gp_Pln pln; - IfcGeom::Kernel::convert((IfcSchema::IfcPlane*)surface,pln); - const gp_Pnt pnt = pln.Location().Translated( l->AgreementFlag() ? -pln.Axis().Direction() : pln.Axis().Direction()); - shape = BRepPrimAPI_MakeHalfSpace(BRepBuilderAPI_MakeFace(pln),pnt).Solid(); - return true; -} - -bool IfcGeom::Kernel::convert(const IfcSchema::IfcPolygonalBoundedHalfSpace* l, TopoDS_Shape& shape) { - TopoDS_Shape halfspace; - if ( ! IfcGeom::Kernel::convert((IfcSchema::IfcHalfSpaceSolid*)l,halfspace) ) return false; - - TopoDS_Wire wire; - if ( ! convert_wire(l->PolygonalBoundary(),wire) || ! wire.Closed() ) return false; - - gp_Trsf trsf; - if ( ! convert(l->Position(),trsf) ) return false; - - TColgp_SequenceOfPnt points; - if (wire_to_sequence_of_point(wire, points)) { - // Boolean subtractions not very robust for narrow operands, - // increase minimal point spacing to eliminate such shapes. - const double t = getValue(GV_PRECISION) * 10.; - remove_duplicate_points_from_loop(points, wire.Closed() != 0, t); // Note: wire always closed, as per if statement above - remove_collinear_points_from_loop(points, wire.Closed() != 0, t); - if (points.Length() < 3) { - Logger::Message(Logger::LOG_ERROR, "Not enough points retained from:", l->PolygonalBoundary()); - return false; - } - sequence_of_point_to_wire(points, wire, wire.Closed() != 0); - } - - TopoDS_Shape prism = BRepPrimAPI_MakePrism(BRepBuilderAPI_MakeFace(wire),gp_Vec(0,0,200)); - gp_Trsf down; down.SetTranslation(gp_Vec(0,0,-100.0)); - - // `trsf` and `down` both have a unit scale factor - prism.Move(trsf*down); - - shape = BRepAlgoAPI_Common(halfspace,prism); - return true; -} - -bool IfcGeom::Kernel::convert(const IfcSchema::IfcShellBasedSurfaceModel* l, ConversionResults& shapes) { - IfcEntityList::ptr shells = l->SbsmBoundary(); - const SurfaceStyle* collective_style = get_style(l); - for( IfcEntityList::it it = shells->begin(); it != shells->end(); ++ it ) { - TopoDS_Shape s; - const SurfaceStyle* shell_style = 0; - if ((*it)->declaration().is(IfcSchema::IfcRepresentationItem::Class())) { - shell_style = get_style((IfcSchema::IfcRepresentationItem*)*it); - } - if (convert_shape(*it,s)) { - shapes.push_back(ConversionResult(l->data().id(), new OpenCascadeShape(s), shell_style ? shell_style : collective_style)); - } - } - return true; -} - -bool IfcGeom::Kernel::convert(const IfcSchema::IfcBooleanResult* l, TopoDS_Shape& shape) { - - TopoDS_Shape s1, s2; - ConversionResults items1; - TopoDS_Wire boundary_wire; - IfcSchema::IfcBooleanOperand* operand1 = l->FirstOperand(); - IfcSchema::IfcBooleanOperand* operand2 = l->SecondOperand(); - bool has_halfspace_operand = false; - - BOPAlgo_Operation occ_op; - - const IfcSchema::IfcBooleanOperator::Value op = l->Operator(); - if (op == IfcSchema::IfcBooleanOperator::IfcBooleanOperator_DIFFERENCE) { - occ_op = BOPAlgo_CUT; - } else if (op == IfcSchema::IfcBooleanOperator::IfcBooleanOperator_INTERSECTION) { - occ_op = BOPAlgo_COMMON; - } else if (op == IfcSchema::IfcBooleanOperator::IfcBooleanOperator_UNION) { - occ_op = BOPAlgo_FUSE; - } else { - return false; - } - - std::vector second_operands; - second_operands.push_back(operand2); - - if (occ_op == BOPAlgo_CUT) { - bool process_as_list = true; - while (true) { - auto res1 = operand1->as(); - if (res1) { - if (res1->Operator() == op) { - operand1 = res1->FirstOperand(); - second_operands.push_back(res1->SecondOperand()); - } else { - process_as_list = false; - break; - } - } else { - break; - } - } - - if (!process_as_list) { - operand1 = l->FirstOperand(); - second_operands = { operand2 }; - } - } - - if ( shape_type(operand1) == ST_SHAPELIST ) { - if (!(convert_shapes(operand1, items1) && flatten_shape_list(items1, s1, true))) { - return false; - } - } else if ( shape_type(operand1) == ST_SHAPE ) { - if ( ! convert_shape(operand1, s1) ) { - return false; - } - { TopoDS_Solid temp_solid; - s1 = ensure_fit_for_subtraction(s1, temp_solid); } - } else { - Logger::Message(Logger::LOG_ERROR, "Invalid representation item for boolean operation", operand1); - return false; - } - - const double first_operand_volume = shape_volume(s1); - if (first_operand_volume <= ALMOST_ZERO) { - Logger::Message(Logger::LOG_WARNING, "Empty solid for:", l->FirstOperand()); - } - - TopTools_ListOfShape second_operand_shapes; - - for (auto& op2 : second_operands) { - TopoDS_Shape s2; - - bool shape2_processed = false; - - bool is_halfspace = op2->declaration().is(IfcSchema::IfcHalfSpaceSolid::Class()); - bool is_unbounded_halfspace = is_halfspace && !op2->declaration().is(IfcSchema::IfcPolygonalBoundedHalfSpace::Class()); - has_halfspace_operand |= is_halfspace; - - { - if (shape_type(op2) == ST_SHAPELIST) { - ConversionResults items2; - shape2_processed = convert_shapes(op2, items2) && flatten_shape_list(items2, s2, true); - } else if (shape_type(op2) == ST_SHAPE) { - shape2_processed = convert_shape(op2, s2); - if (shape2_processed) { - TopoDS_Solid temp_solid; - s2 = ensure_fit_for_subtraction(s2, temp_solid); - } - } else { - Logger::Message(Logger::LOG_ERROR, "Invalid representation item for boolean operation", op2); - } - } - - if (is_unbounded_halfspace) { - TopoDS_Shape temp; - double d; - if (fit_halfspace(s1, s2, temp, d)) { - if (d < getValue(GV_PRECISION)) { - Logger::Message(Logger::LOG_WARNING, "Halfspace subtraction yields unchanged volume:", l); - continue; - } else { - s2 = temp; - } - } - } - - if (!shape2_processed) { - Logger::Message(Logger::LOG_ERROR, "Failed to convert SecondOperand:", op2); - continue; - } - - if (op2->declaration().is(IfcSchema::IfcHalfSpaceSolid::Class())) { - const double second_operand_volume = shape_volume(s2); - if (second_operand_volume <= ALMOST_ZERO) { - Logger::Message(Logger::LOG_WARNING, "Empty solid for:", op2); - } - } - - second_operand_shapes.Append(s2); - } - - /* - // TK: A little debugging trick to output both operands for visual inspection - - BRep_Builder builder; - TopoDS_Compound compound; - builder.MakeCompound(compound); - builder.Add(compound, s1); - for (const auto& s2 : second_operand_shapes) { - builder.Add(compound, s2); - } - shape = compound; - return true; - */ - -#if OCC_VERSION_HEX < 0x60900 - // @todo: this currently does not compile anymore, do we still need this? - bool valid_result = boolean_operation(s1, s2, occ_op, shape); -#else - bool valid_result = boolean_operation(s1, second_operand_shapes, occ_op, shape); -#endif - - if (op == IfcSchema::IfcBooleanOperator::IfcBooleanOperator_DIFFERENCE) { - // In case of a subtraction, a check on volume is performed. - if (valid_result) { - const double volume_after_subtraction = shape_volume(shape); - if ( ALMOST_THE_SAME(first_operand_volume,volume_after_subtraction) ) - Logger::Message(Logger::LOG_WARNING,"Subtraction yields unchanged volume:",l); - } else { - Logger::Message(Logger::LOG_ERROR,"Failed to process subtraction:",l); - shape = s1; - } - // NB: After issuing error the first operand is returned! - return true; - } else { - return valid_result; - } - return false; -} - -bool IfcGeom::Kernel::convert(const IfcSchema::IfcConnectedFaceSet* l, TopoDS_Shape& shape) { - std::unique_ptr helper_scope; - helper_scope.reset(new faceset_helper(this, l)); - - IfcSchema::IfcFace::list::ptr faces = l->CfsFaces(); - - double min_face_area = faceset_helper_ - ? (faceset_helper_->epsilon() * faceset_helper_->epsilon() / 20.) - : getValue(GV_MINIMAL_FACE_AREA); - - TopTools_ListOfShape face_list; - for (IfcSchema::IfcFace::list::it it = faces->begin(); it != faces->end(); ++it) { - bool success = false; - TopoDS_Face face; - - try { - success = convert_face(*it, face); - } catch (const std::exception& e) { - Logger::Error(e); - } catch (const Standard_Failure& e) { - if (e.GetMessageString() && strlen(e.GetMessageString())) { - Logger::Error(e.GetMessageString()); - } else { - Logger::Error("Unknown error creating face"); - } - } catch (...) { - Logger::Error("Unknown error creating face"); - } - - if (!success) { - Logger::Message(Logger::LOG_WARNING, "Failed to convert face:", (*it)); - continue; - } - - if (face.ShapeType() == TopAbs_COMPOUND) { - TopoDS_Iterator face_it(face, false); - for (; face_it.More(); face_it.Next()) { - if (face_it.Value().ShapeType() == TopAbs_FACE) { - // This should really be the case. This is not asserted. - const TopoDS_Face& triangle = TopoDS::Face(face_it.Value()); - if (face_area(triangle) > min_face_area) { - face_list.Append(triangle); - } else { - Logger::Message(Logger::LOG_WARNING, "Degenerate face:", (*it)); - } - } - } - } else { - if (face_area(face) > min_face_area) { - face_list.Append(face); - } else { - Logger::Message(Logger::LOG_WARNING, "Degenerate face:", (*it)); - } - } - } - - if (face_list.Extent() == 0) { - return false; - } - - if (face_list.Extent() > getValue(GV_MAX_FACES_TO_ORIENT) || !create_solid_from_faces(face_list, shape)) { - TopoDS_Compound compound; - BRep_Builder builder; - builder.MakeCompound(compound); - - TopTools_ListIteratorOfListOfShape face_iterator; - for (face_iterator.Initialize(face_list); face_iterator.More(); face_iterator.Next()) { - builder.Add(compound, face_iterator.Value()); - } - shape = compound; - } - - return true; -} - -bool IfcGeom::Kernel::convert(const IfcSchema::IfcMappedItem* l, ConversionResults& shapes) { - gp_GTrsf gtrsf; - IfcSchema::IfcCartesianTransformationOperator* transform = l->MappingTarget(); - if ( transform->declaration().is(IfcSchema::IfcCartesianTransformationOperator3DnonUniform::Class()) ) { - IfcGeom::Kernel::convert((IfcSchema::IfcCartesianTransformationOperator3DnonUniform*)transform,gtrsf); - } else if ( transform->declaration().is(IfcSchema::IfcCartesianTransformationOperator2DnonUniform::Class()) ) { - Logger::Message(Logger::LOG_ERROR, "Unsupported MappingTarget:", transform); - return false; - } else if ( transform->declaration().is(IfcSchema::IfcCartesianTransformationOperator3D::Class()) ) { - gp_Trsf trsf; - IfcGeom::Kernel::convert((IfcSchema::IfcCartesianTransformationOperator3D*)transform,trsf); - gtrsf = trsf; - } else if ( transform->declaration().is(IfcSchema::IfcCartesianTransformationOperator2D::Class()) ) { - gp_Trsf2d trsf_2d; - IfcGeom::Kernel::convert((IfcSchema::IfcCartesianTransformationOperator2D*)transform,trsf_2d); - gtrsf = (gp_Trsf) trsf_2d; - } - IfcSchema::IfcRepresentationMap* map = l->MappingSource(); - IfcSchema::IfcAxis2Placement* placement = map->MappingOrigin(); - gp_Trsf trsf; - if (placement->declaration().is(IfcSchema::IfcAxis2Placement3D::Class())) { - IfcGeom::Kernel::convert((IfcSchema::IfcAxis2Placement3D*)placement,trsf); - } else { - gp_Trsf2d trsf_2d; - IfcGeom::Kernel::convert((IfcSchema::IfcAxis2Placement2D*)placement,trsf_2d); - trsf = trsf_2d; - } - gtrsf.Multiply(trsf); - - const IfcGeom::SurfaceStyle* mapped_item_style = get_style(l); - - const size_t previous_size = shapes.size(); - bool b = convert_shapes(map->MappedRepresentation(), shapes); - - for (size_t i = previous_size; i < shapes.size(); ++ i ) { - OpenCascadePlacement p(gtrsf); - shapes[i].prepend(&p); - - // Apply styles assigned to the mapped item only if on - // a more granular level no styles have been applied - if (!shapes[i].hasStyle()) { - shapes[i].setStyle(mapped_item_style); - } - } - - return b; -} - -bool IfcGeom::Kernel::convert(const IfcSchema::IfcRepresentation* l, ConversionResults& shapes) { - IfcSchema::IfcRepresentationItem::list::ptr items = l->Items(); - bool part_succes = false; - if ( items->size() ) { - for ( IfcSchema::IfcRepresentationItem::list::it it = items->begin(); it != items->end(); ++ it ) { - IfcSchema::IfcRepresentationItem* representation_item = *it; - if ( shape_type(representation_item) == ST_SHAPELIST ) { - part_succes |= convert_shapes(*it, shapes); - } else { - TopoDS_Shape s; - if (convert_shape(representation_item,s)) { - shapes.push_back(ConversionResult(representation_item->data().id(), new OpenCascadeShape(s), get_style(representation_item))); - part_succes |= true; - } - } - } - } - return part_succes; -} - -bool IfcGeom::Kernel::convert(const IfcSchema::IfcGeometricSet* l, ConversionResults& shapes) { - IfcEntityList::ptr elements = l->Elements(); - if ( !elements->size() ) return false; - bool part_succes = false; - const IfcGeom::SurfaceStyle* parent_style = get_style(l); - for ( IfcEntityList::it it = elements->begin(); it != elements->end(); ++ it ) { - IfcSchema::IfcGeometricSetSelect* element = *it; - TopoDS_Shape s; - if (convert_shape(element, s)) { - part_succes = true; - const IfcGeom::SurfaceStyle* style = 0; - if (element->declaration().is(IfcSchema::IfcPoint::Class())) { - style = get_style((IfcSchema::IfcPoint*) element); - } else if (element->declaration().is(IfcSchema::IfcCurve::Class())) { - style = get_style((IfcSchema::IfcCurve*) element); - } else if (element->declaration().is(IfcSchema::IfcSurface::Class())) { - style = get_style((IfcSchema::IfcSurface*) element); - } - shapes.push_back(ConversionResult(l->data().id(), new OpenCascadeShape(s), style ? style : parent_style)); - } - } - return part_succes; -} - -bool IfcGeom::Kernel::convert(const IfcSchema::IfcBlock* l, TopoDS_Shape& shape) { - const double dx = l->XLength() * getValue(GV_LENGTH_UNIT); - const double dy = l->YLength() * getValue(GV_LENGTH_UNIT); - const double dz = l->ZLength() * getValue(GV_LENGTH_UNIT); - - BRepPrimAPI_MakeBox builder(dx, dy, dz); - gp_Trsf trsf; - IfcGeom::Kernel::convert(l->Position(),trsf); - - // IfcCsgPrimitive3D.Position has unit scale factor - shape = builder.Solid().Moved(trsf); - - return true; -} - -bool IfcGeom::Kernel::convert(const IfcSchema::IfcRectangularPyramid* l, TopoDS_Shape& shape) { - const double dx = l->XLength() * getValue(GV_LENGTH_UNIT); - const double dy = l->YLength() * getValue(GV_LENGTH_UNIT); - const double dz = l->Height() * getValue(GV_LENGTH_UNIT); - - BRepPrimAPI_MakeWedge builder(dx, dz, dy, dx / 2., dy / 2., dx / 2., dy / 2.); - - gp_Trsf trsf1, trsf2; - trsf2.SetValues( - 1, 0, 0, 0, - 0, 0, 1, 0, - 0, 1, 0, 0 -#if OCC_VERSION_HEX < 0x60800 - , Precision::Angular(), Precision::Confusion() -#endif - ); - - IfcGeom::Kernel::convert(l->Position(), trsf1); - shape = BRepBuilderAPI_Transform(builder.Solid(), trsf1 * trsf2); - return true; -} - -bool IfcGeom::Kernel::convert(const IfcSchema::IfcRightCircularCylinder* l, TopoDS_Shape& shape) { - const double r = l->Radius() * getValue(GV_LENGTH_UNIT); - const double h = l->Height() * getValue(GV_LENGTH_UNIT); - - BRepPrimAPI_MakeCylinder builder(r, h); - gp_Trsf trsf; - IfcGeom::Kernel::convert(l->Position(),trsf); - - // IfcCsgPrimitive3D.Position has unit scale factor - shape = builder.Solid().Moved(trsf); - - return true; -} - -bool IfcGeom::Kernel::convert(const IfcSchema::IfcRightCircularCone* l, TopoDS_Shape& shape) { - const double r = l->BottomRadius() * getValue(GV_LENGTH_UNIT); - const double h = l->Height() * getValue(GV_LENGTH_UNIT); - - BRepPrimAPI_MakeCone builder(r, 0., h); - gp_Trsf trsf; - IfcGeom::Kernel::convert(l->Position(),trsf); - - // IfcCsgPrimitive3D.Position has unit scale factor - shape = builder.Solid().Moved(trsf); - - return true; -} - -bool IfcGeom::Kernel::convert(const IfcSchema::IfcSphere* l, TopoDS_Shape& shape) { - const double r = l->Radius() * getValue(GV_LENGTH_UNIT); - - BRepPrimAPI_MakeSphere builder(r); - gp_Trsf trsf; - IfcGeom::Kernel::convert(l->Position(),trsf); - - // IfcCsgPrimitive3D.Position has unit scale factor - shape = builder.Solid().Moved(trsf); - - return true; -} - -bool IfcGeom::Kernel::convert(const IfcSchema::IfcCsgSolid* l, TopoDS_Shape& shape) { - return convert_shape(l->TreeRootExpression(), shape); -} - -bool IfcGeom::Kernel::convert(const IfcSchema::IfcCurveBoundedPlane* l, TopoDS_Shape& face) { - gp_Pln pln; - if (!IfcGeom::Kernel::convert(l->BasisSurface(), pln)) { - return false; - } - - gp_Trsf trsf; - trsf.SetTransformation(pln.Position(), gp::XOY()); - - TopoDS_Wire outer; - if (!convert_wire(l->OuterBoundary(), outer)) { - return false; - } - - BRepBuilderAPI_MakeFace mf(outer); - - if (!mf.IsDone() || mf.Shape().IsNull()) { - Logger::Error("Invalid outer boundary:", l->OuterBoundary()); - return false; - } - - IfcSchema::IfcCurve::list::ptr boundaries = l->InnerBoundaries(); - - for (IfcSchema::IfcCurve::list::it it = boundaries->begin(); it != boundaries->end(); ++it) { - TopoDS_Wire inner; - if (convert_wire(*it, inner)) { - mf.Add(inner); - } - } - - ShapeFix_Shape sfs(mf.Face()); - sfs.Perform(); - - // `trsf` consitutes the placement of the plane and therefore has unit scale factor - face = TopoDS::Face(sfs.Shape()).Moved(trsf); - - return true; -} - -bool IfcGeom::Kernel::convert(const IfcSchema::IfcRectangularTrimmedSurface* l, TopoDS_Shape& face) { - if (!l->BasisSurface()->declaration().is(IfcSchema::IfcPlane::Class())) { - Logger::Message(Logger::LOG_ERROR, "Unsupported BasisSurface:", l->BasisSurface()); - return false; - } - gp_Pln pln; - IfcGeom::Kernel::convert((IfcSchema::IfcPlane*) l->BasisSurface(), pln); - - BRepBuilderAPI_MakeFace mf(pln, l->U1(), l->U2(), l->V1(), l->V2()); - - face = mf.Face(); - - return true; -} - -bool IfcGeom::Kernel::convert(const IfcSchema::IfcSurfaceCurveSweptAreaSolid* l, TopoDS_Shape& shape) { - gp_Trsf directrix; - TopoDS_Shape face; - TopoDS_Wire wire, section; - - if (!l->ReferenceSurface()->declaration().is(IfcSchema::IfcPlane::Class())) { - Logger::Message(Logger::LOG_WARNING, "Reference surface not supported", l->ReferenceSurface()); - return false; - } - - gp_Trsf trsf; - bool has_position = true; -#ifdef SCHEMA_IfcSweptAreaSolid_Position_IS_OPTIONAL - has_position = l->hasPosition(); -#endif - if (has_position) { - IfcGeom::Kernel::convert(l->Position(), trsf); - } - - if (!convert_face(l->SweptArea(), face) || - !convert_wire(l->Directrix(), wire) ) { - return false; - } - - gp_Pln pln; - gp_Pnt directrix_origin; - gp_Vec directrix_tangent; - bool directrix_on_plane = true; - IfcGeom::Kernel::convert((IfcSchema::IfcPlane*) l->ReferenceSurface(), 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", l); - break; - } - } - } - - { - TopExp_Explorer exp(wire, TopAbs_EDGE); - TopoDS_Edge edge = TopoDS::Edge(exp.Current()); - double u0, u1; - Handle(Geom_Curve) crv = BRep_Tool::Curve(edge, u0, u1); - crv->D1(u0, directrix_origin, directrix_tangent); - } - - if (pln.Axis().Direction().IsNormal(directrix_tangent, Precision::Approximation()) && directrix_on_plane) { - directrix.SetTransformation(gp_Ax3(directrix_origin, directrix_tangent, pln.Axis().Direction()), gp::XOY()); - } else { - directrix.SetTransformation(gp_Ax3(directrix_origin, directrix_tangent), gp::XOY()); - } - face = BRepBuilderAPI_Transform(face, directrix); - - // 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 builder(wire); - - { TopExp_Explorer exp(face, TopAbs_WIRE); - section = TopoDS::Wire(exp.Current()); } - - builder.Add(section); - builder.SetTransitionMode(BRepBuilderAPI_RightCorner); - if (directrix_on_plane) { - builder.SetMode(pln.Axis().Direction()); - } - builder.Build(); - builder.MakeSolid(); - shape = builder.Shape(); - - if (has_position) { - // IfcSweptAreaSolid.Position (trsf) is an IfcAxis2Placement3D - // and therefore has a unit scale factor - shape.Move(trsf); - } - - return true; -} - -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 IfcGeom::Kernel::convert(const IfcSchema::IfcSweptDiskSolid* l, TopoDS_Shape& shape) { - TopoDS_Wire wire, section1, section2; - - bool hasInnerRadius = l->hasInnerRadius(); - - if (!convert_wire(l->Directrix(), wire)) { - return false; - } - - gp_Ax2 directrix; - { - gp_Pnt directrix_origin; - gp_Vec directrix_tangent; - - TopoDS_Edge edge; - - // Find first edge - TopoDS_Vertex v0, v1; - TopExp::Vertices(wire, v0, v1); - TopTools_IndexedDataMapOfShapeListOfShape map; - TopExp::MapShapesAndAncestors(wire, TopAbs_VERTEX, TopAbs_EDGE, map); - if (map.Contains(v0) && map.FindFromKey(v0).Extent() == 1) { - edge = TopoDS::Edge(map.FindFromKey(v0).First()); - } else { - Logger::Error("Unable to locate first edge of:", l->Directrix()); - return false; - } - - double u0, u1; - Handle(Geom_Curve) crv = BRep_Tool::Curve(edge, u0, u1); - crv->D1(u0, directrix_origin, directrix_tangent); - directrix = gp_Ax2(directrix_origin, directrix_tangent); - } - - const double r1 = l->Radius() * getValue(GV_LENGTH_UNIT); - Handle(Geom_Circle) circle = new Geom_Circle(directrix, r1); - section1 = BRepBuilderAPI_MakeWire(BRepBuilderAPI_MakeEdge(circle)); - - if (hasInnerRadius) { - const double r2 = l->InnerRadius() * getValue(GV_LENGTH_UNIT); - if (r2 < getValue(GV_PRECISION)) { - // Subtraction of pipes with small radii is unstable. - hasInnerRadius = false; - } else { - Handle(Geom_Circle) circle2 = new Geom_Circle(directrix, r2); - section2 = BRepBuilderAPI_MakeWire(BRepBuilderAPI_MakeEdge(circle2)); - } - } - - // This is not used anymore, BRepBuilderAPI_RightCorner is always used now. - // const bool is_continuous = wire_is_c1_continuous(wire, 1.e-3); - - // 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 builder(wire); - builder.Add(section1); - builder.SetTransitionMode(BRepBuilderAPI_RightCorner); - builder.Build(); - builder.MakeSolid(); - shape = builder.Shape(); } - - if (hasInnerRadius) { - BRepOffsetAPI_MakePipeShell builder(wire); - builder.Add(section2); - builder.SetTransitionMode(BRepBuilderAPI_RightCorner); - builder.Build(); - builder.MakeSolid(); - TopoDS_Shape inner = builder.Shape(); - - BRepAlgoAPI_Cut brep_cut(shape, inner); - bool is_valid = false; - if (brep_cut.IsDone()) { - TopoDS_Shape result = brep_cut; - - ShapeFix_Shape fix(result); - fix.Perform(); - result = fix.Shape(); - - is_valid = BRepCheck_Analyzer(result).IsValid() != 0; - if (is_valid) { - shape = result; - } - } - - if (!is_valid) { - Logger::Message(Logger::LOG_WARNING, "Failed to subtract inner radius void for:", l); - } - } - - return true; -} - -#ifdef SCHEMA_HAS_IfcCylindricalSurface - -bool IfcGeom::Kernel::convert(const IfcSchema::IfcCylindricalSurface* l, TopoDS_Shape& face) { - gp_Trsf trsf; - IfcGeom::Kernel::convert(l->Position(),trsf); - - // IfcElementarySurface.Position has unit scale factor -#if OCC_VERSION_HEX < 0x60502 - face = BRepBuilderAPI_MakeFace(new Geom_CylindricalSurface(gp::XOY(), l->Radius() * getValue(GV_LENGTH_UNIT))).Face().Moved(trsf); -#else - face = BRepBuilderAPI_MakeFace(new Geom_CylindricalSurface(gp::XOY(), l->Radius() * getValue(GV_LENGTH_UNIT)), getValue(GV_PRECISION)).Face().Moved(trsf); -#endif - return true; -} - -#endif - -#ifdef SCHEMA_HAS_IfcAdvancedBrep - -bool IfcGeom::Kernel::convert(const IfcSchema::IfcAdvancedBrep* l, TopoDS_Shape& shape) { - return convert(l->Outer(), shape); -} - -#endif - -#ifdef SCHEMA_HAS_IfcTriangulatedFaceSet - -bool IfcGeom::Kernel::convert(const IfcSchema::IfcTriangulatedFaceSet* l, TopoDS_Shape& shape) { - IfcSchema::IfcCartesianPointList3D* point_list = l->Coordinates(); - const std::vector< std::vector > coordinates = point_list->CoordList(); - std::vector points; - points.reserve(coordinates.size()); - for (std::vector< std::vector >::const_iterator it = coordinates.begin(); it != coordinates.end(); ++it) { - const std::vector& coords = *it; - if (coords.size() != 3) { - Logger::Message(Logger::LOG_ERROR, "Invalid dimensions encountered on Coordinates", l); - return false; - } - points.push_back(gp_Pnt(coords[0] * getValue(GV_LENGTH_UNIT), - coords[1] * getValue(GV_LENGTH_UNIT), - coords[2] * getValue(GV_LENGTH_UNIT))); - } - - std::vector< std::vector > indices = l->CoordIndex(); - - std::vector faces; - faces.reserve(indices.size()); - - for(std::vector< std::vector >::const_iterator it = indices.begin(); it != indices.end(); ++ it) { - const std::vector& tri = *it; - if (tri.size() != 3) { - Logger::Message(Logger::LOG_ERROR, "Invalid dimensions encountered on CoordIndex", l); - return false; - } - - const int min_index = *std::min_element(tri.begin(), tri.end()); - const int max_index = *std::max_element(tri.begin(), tri.end()); - - if (min_index < 1 || max_index > (int) points.size()) { - Logger::Message(Logger::LOG_ERROR, "Contents of CoordIndex out of bounds", l); - return false; - } - - const gp_Pnt& a = points[tri[0] - 1]; // account for zero- vs - const gp_Pnt& b = points[tri[1] - 1]; // one-based indices in - const gp_Pnt& c = points[tri[2] - 1]; // c++ and express - - TopoDS_Wire wire = BRepBuilderAPI_MakePolygon(a, b, c, true).Wire(); - TopoDS_Face face = BRepBuilderAPI_MakeFace(wire).Face(); - - TopoDS_Iterator face_it(face, false); - const TopoDS_Wire& w = TopoDS::Wire(face_it.Value()); - const bool reversed = w.Orientation() == TopAbs_REVERSED; - if (reversed) { - face.Reverse(); - } - - if (face_area(face) > getValue(GV_MINIMAL_FACE_AREA)) { - faces.push_back(face); - } - } - - if (faces.empty()) return false; - - bool valid_shell = false; - - // @todo Do this more efficiently by creating proper half-edge pairs. - BRepOffsetAPI_Sewing sewing_builder; - sewing_builder.SetTolerance(getValue(GV_POINT_EQUALITY_TOLERANCE)); - sewing_builder.SetMaxTolerance(getValue(GV_POINT_EQUALITY_TOLERANCE)); - sewing_builder.SetMinTolerance(getValue(GV_POINT_EQUALITY_TOLERANCE)); - - for (std::vector::const_iterator it = faces.begin(); it != faces.end(); ++it) { - sewing_builder.Add(*it); - } - - try { - sewing_builder.Perform(); - shape = sewing_builder.SewedShape(); - valid_shell = BRepCheck_Analyzer(shape).IsValid(); - } catch(...) {} - - if (valid_shell) { - try { - ShapeFix_Solid solid; - solid.LimitTolerance(getValue(GV_POINT_EQUALITY_TOLERANCE)); - TopoDS_Solid solid_shape = solid.SolidFromShell(TopoDS::Shell(shape)); - if (!solid_shape.IsNull()) { - try { - BRepClass3d_SolidClassifier classifier(solid_shape); - shape = solid_shape; - } catch (...) {} - } - } catch(...) {} - } else { - Logger::Message(Logger::LOG_WARNING, "Failed to sew faceset:", l); - } - - if (!valid_shell) { - TopoDS_Compound compound; - BRep_Builder builder; - builder.MakeCompound(compound); - - for (std::vector::const_iterator it = faces.begin(); it != faces.end(); ++it) { - builder.Add(compound, *it); - } - - shape = compound; - } - - return true; -} - -#endif diff --git a/src/ifcgeom/kernels/opencascade/IfcGeomWires.cpp_ b/src/ifcgeom/kernels/opencascade/IfcGeomWires.cpp_ deleted file mode 100644 index 383691072c..0000000000 --- a/src/ifcgeom/kernels/opencascade/IfcGeomWires.cpp_ +++ /dev/null @@ -1,929 +0,0 @@ -/******************************************************************************** - * * - * 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 . * - * * - ********************************************************************************/ - -/******************************************************************************** - * * - * Implementations of the various conversion functions defined in IfcRegister.h * - * * - ********************************************************************************/ - -#define _USE_MATH_DEFINES -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include -#include -#include -#include - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include - -#include -#include -#include - -#include -#include -#include -#include -#include - -#include -#include -#include - -#include "../../../ifcgeom/kernels/opencascade/IfcGeom.h" - -#define Kernel POSTFIX_SCHEMA(Kernel) - -namespace { - // Returns the other vertex of an edge - TopoDS_Vertex other(const TopoDS_Edge& e, const TopoDS_Vertex& v) { - TopoDS_Vertex a, b; - TopExp::Vertices(e, a, b); - return v.IsSame(b) ? a : b; - } - - TopoDS_Edge first_edge(const TopoDS_Wire& w) { - TopoDS_Vertex v1, v2; - TopExp::Vertices(w, v1, v2); - TopTools_IndexedDataMapOfShapeListOfShape wm; - TopExp::MapShapesAndAncestors(w, TopAbs_VERTEX, TopAbs_EDGE, wm); - return TopoDS::Edge(wm.FindFromKey(v1).First()); - } - - // Returns new wire with the edge replaced by a linear edge with the vertex v moved to p - TopoDS_Wire adjust(const TopoDS_Wire& w, const TopoDS_Vertex& v, const gp_Pnt& p) { - TopTools_IndexedDataMapOfShapeListOfShape map; - TopExp::MapShapesAndAncestors(w, TopAbs_VERTEX, TopAbs_EDGE, map); - - bool all_linear = true, single_circle = false, first = true; - - const TopTools_ListOfShape& edges = map.FindFromKey(v); - TopTools_ListIteratorOfListOfShape it(edges); - for (; it.More(); it.Next()) { - const TopoDS_Edge& e = TopoDS::Edge(it.Value()); - double _, __; - Handle(Geom_Curve) crv = BRep_Tool::Curve(e, _, __); - const bool is_line = crv->DynamicType() == STANDARD_TYPE(Geom_Line); - const bool is_circle = crv->DynamicType() == STANDARD_TYPE(Geom_Circle); - all_linear = all_linear && is_line; - single_circle = first && is_circle; - } - - if (all_linear) { - BRep_Builder b; - TopoDS_Vertex v2; - b.MakeVertex(v2, p, BRep_Tool::Tolerance(v)); - - ShapeBuild_ReShape reshape; - reshape.Replace(v.Oriented(TopAbs_FORWARD), v2); - - return TopoDS::Wire(reshape.Apply(w)); - } else if (single_circle) { - TopoDS_Vertex v1, v2; - TopExp::Vertices(w, v1, v2); - - gp_Pnt p1, p2, p3; - p1 = v.IsEqual(v1) ? p : BRep_Tool::Pnt(v1); - p3 = v.IsEqual(v2) ? p : BRep_Tool::Pnt(v2); - - double a, b; - Handle(Geom_Curve) crv = BRep_Tool::Curve(TopoDS::Edge(edges.First()), a, b); - crv->D0((a + b) / 2., p2); - - GC_MakeCircle mc(p1, p2, p3); - if (!mc.IsDone()) { - throw IfcGeom::geometry_exception("Failed to adjust circle"); - } - - TopoDS_Edge edge = BRepBuilderAPI_MakeEdge(mc.Value(), p1, p3).Edge(); - BRepBuilderAPI_MakeWire builder; - builder.Add(edge); - return builder.Wire(); - } else { - throw IfcGeom::geometry_exception("Unexpected wire to adjust"); - } - } - - // A wrapper around BRepBuilderAPI_MakeWire that makes sure segments are connected either by moving end points or by adding intermediate segments - class wire_builder { - private: - BRepBuilderAPI_MakeWire mw_; - double p_; - bool override_next_; - gp_Pnt next_override_; - const IfcUtil::IfcBaseClass* inst_; - - public: - wire_builder(double p, const IfcUtil::IfcBaseClass* inst = 0) : p_(p), override_next_(false), inst_(inst) {} - - void operator()(const TopoDS_Shape& a) { - const TopoDS_Wire& w = TopoDS::Wire(a); - if (override_next_) { - override_next_ = false; - TopoDS_Edge e = first_edge(w); - mw_.Add(adjust(w, TopExp::FirstVertex(e, true), next_override_)); - } else { - mw_.Add(w); - } - } - - void operator()(const TopoDS_Shape& a, const TopoDS_Shape& b, bool last) { - TopoDS_Wire w1 = TopoDS::Wire(a); - const TopoDS_Wire& w2 = TopoDS::Wire(b); - - if (override_next_) { - override_next_ = false; - TopoDS_Edge e = first_edge(w1); - w1 = adjust(w1, TopExp::FirstVertex(e, true), next_override_); - } - - TopoDS_Vertex w11, w12, w21, w22; - TopExp::Vertices(w1, w11, w12); - TopExp::Vertices(w2, w21, w22); - - gp_Pnt p1 = BRep_Tool::Pnt(w12); - gp_Pnt p2 = BRep_Tool::Pnt(w21); - - double dist = p1.Distance(p2); - - // Distance is within tolerance, this is fine - if (dist < p_) { - mw_.Add(w1); - goto check; - } - - // Distance is too large for attempting to move end points, add intermediate edge - if (dist > 1000. * p_) { - mw_.Add(w1); - mw_.Add(BRepBuilderAPI_MakeEdge(p1, p2)); - Logger::Warning("Added additional segment to close gap with length " + boost::lexical_cast(dist) + " to:", inst_); - goto check; - } - - { - TopTools_IndexedDataMapOfShapeListOfShape wmap1, wmap2; - - // Find edges connected to end- and begin vertex - TopExp::MapShapesAndAncestors(w1, TopAbs_VERTEX, TopAbs_EDGE, wmap1); - TopExp::MapShapesAndAncestors(w2, TopAbs_VERTEX, TopAbs_EDGE, wmap2); - - const TopTools_ListOfShape& last_edges = wmap1.FindFromKey(w12); - const TopTools_ListOfShape& first_edges = wmap2.FindFromKey(w21); - - double _, __; - if (last_edges.Extent() == 1 && first_edges.Extent() == 1) { - Handle(Geom_Curve) c1 = BRep_Tool::Curve(TopoDS::Edge(last_edges.First()), _, __); - Handle(Geom_Curve) c2 = BRep_Tool::Curve(TopoDS::Edge(first_edges.First()), _, __); - - const bool is_line1 = c1->DynamicType() == STANDARD_TYPE(Geom_Line); - const bool is_line2 = c2->DynamicType() == STANDARD_TYPE(Geom_Line); - - const bool is_circle1 = c1->DynamicType() == STANDARD_TYPE(Geom_Circle); - const bool is_circle2 = c2->DynamicType() == STANDARD_TYPE(Geom_Circle); - - // Preferably adjust the segment that is linear - if (is_line1 || (is_circle1 && !is_line2)) { - mw_.Add(adjust(w1, w12, p2)); - Logger::Notice("Adjusted edge end-point with distance " + boost::lexical_cast(dist) + " on:", inst_); - } else if ((is_line2 || is_circle2) && !last) { - mw_.Add(w1); - override_next_ = true; - next_override_ = p1; - Logger::Notice("Adjusted edge end-point with distance " + boost::lexical_cast(dist) + " on:", inst_); - } else { - // In all other cases an edge is added - mw_.Add(w1); - mw_.Add(BRepBuilderAPI_MakeEdge(p1, p2)); - Logger::Warning("Added additional segment to close gap with length " + boost::lexical_cast(dist) + " to:", inst_); - } - } else { - Logger::Error("Internal error, inconsistent wire segments", inst_); - mw_.Add(w1); - } - } - - check: - if (mw_.Error() == BRepBuilderAPI_NonManifoldWire) { - Logger::Error("Non-manifold curve segments:", inst_); - } else if (mw_.Error() == BRepBuilderAPI_DisconnectedWire) { - Logger::Error("Failed to join curve segments:", inst_); - } - } - - const TopoDS_Wire& wire() { return mw_.Wire(); } - }; - - template - void shape_pair_enumerate(TopTools_ListIteratorOfListOfShape& it, Fn& fn, bool closed) { - bool is_first = true; - TopoDS_Shape first, previous, current; - for (; it.More(); it.Next(), is_first = false) { - current = it.Value(); - if (is_first) { - first = current; - } else { - fn(previous, current, false); - } - previous = current; - } - if (closed) { - fn(current, first, true); - } else { - fn(current); - } - } -} - -bool IfcGeom::Kernel::convert(const IfcSchema::IfcCompositeCurve* l, TopoDS_Wire& wire) { - if ( getValue(GV_PLANEANGLE_UNIT)<0 ) { - Logger::Message(Logger::LOG_WARNING,"Creating a composite curve without unit information:",l); - - // Temporarily pretend we do have unit information - setValue(GV_PLANEANGLE_UNIT,1.0); - - bool succes_radians = false; - bool succes_degrees = false; - bool use_radians = false; - bool use_degrees = false; - - // First try radians - TopoDS_Wire wire_radians, wire_degrees; - try { - succes_radians = IfcGeom::Kernel::convert(l,wire_radians); - } catch (const std::exception& e) { - Logger::Notice(e); - } catch (const Standard_Failure& e) { - if (e.GetMessageString() && strlen(e.GetMessageString())) { - Logger::Notice(e.GetMessageString()); - } else { - Logger::Notice("Unknown error using radians"); - } - } catch (...) { - Logger::Notice("Unknown error using radians"); - } - - // Now try degrees - setValue(GV_PLANEANGLE_UNIT,0.0174532925199433); - try { - succes_degrees = IfcGeom::Kernel::convert(l,wire_degrees); - } catch (const std::exception& e) { - Logger::Notice(e); - } catch (const Standard_Failure& e) { - if (e.GetMessageString() && strlen(e.GetMessageString())) { - Logger::Notice(e.GetMessageString()); - } else { - Logger::Notice("Unknown error using degrees"); - } - } catch (...) { - Logger::Notice("Unknown error using degrees"); - } - - // Restore to unknown unit state - setValue(GV_PLANEANGLE_UNIT,-1.0); - - if ( succes_degrees && ! succes_radians ) { - use_degrees = true; - } else if ( succes_radians && ! succes_degrees ) { - use_radians = true; - } else if ( succes_radians && succes_degrees ) { - if ( wire_degrees.Closed() && ! wire_radians.Closed() ) { - use_degrees = true; - } else if ( wire_radians.Closed() && ! wire_degrees.Closed() ) { - use_radians = true; - } else { - // No heuristic left to prefer the one over the other, - // apparently both variants are equally successful. - // The curve might be composed of only straight segments. - // Let's go with the wire created using radians as that - // at least is a SI unit. - use_radians = true; - } - } - - if ( use_radians ) { - Logger::Message(Logger::LOG_NOTICE,"Used radians to create composite curve"); - wire = wire_radians; - } else if ( use_degrees ) { - Logger::Message(Logger::LOG_NOTICE,"Used degrees to create composite curve"); - wire = wire_degrees; - } - - return use_radians || use_degrees; - } - - - IfcSchema::IfcCompositeCurveSegment::list::ptr segments = l->Segments(); - - TopTools_ListOfShape converted_segments; - - for (IfcSchema::IfcCompositeCurveSegment::list::it it = segments->begin(); it != segments->end(); ++it) { - - IfcSchema::IfcCurve* curve = (*it)->ParentCurve(); - TopoDS_Wire segment; - - if (!convert_wire(curve, segment)) { - Logger::Message(Logger::LOG_ERROR, "Failed to convert curve:", curve); - continue; - } - - if (!(*it)->SameSense()) { - segment.Reverse(); - } - - ShapeFix_ShapeTolerance FTol; - FTol.SetTolerance(segment, getValue(GV_PRECISION), TopAbs_WIRE); - - converted_segments.Append(segment); - - } - - if (converted_segments.Extent() == 0) { - Logger::Message(Logger::LOG_ERROR, "No segment succesfully converted:", l); - return false; - } - - BRepBuilderAPI_MakeWire w; - TopoDS_Vertex wire_first_vertex, wire_last_vertex, edge_first_vertex, edge_last_vertex; - - TopTools_ListIteratorOfListOfShape it(converted_segments); - - IfcEntityList::ptr profile = l->data().getInverse(&IfcSchema::IfcProfileDef::Class(), -1); - const bool force_close = profile && profile->size() > 0; - - wire_builder bld(getValue(GV_PRECISION), l); - shape_pair_enumerate(it, bld, force_close); - wire = bld.wire(); - - return true; -} - -namespace { - - /* - Below is code to deduce the formula below in SageMath - - | R, b = var('R b') - | - | Bxy = R * cos(b), R * sin(b) - | Cxy = R * cos(b/2), R * sin(b/2) - | - | def dot(v, w): - | return v[0] * w[0] + v[1] * w[1] - | - | def norm(v): - | l = sqrt(v[0]^2 + v[1]^2) - | return v[0] / l, v[1] / l - | - | (R - R*dot(norm(Cxy), norm(Bxy))).full_simplify() - */ - - double deflection_for_approximating_circle(double radius, double param) { - return -radius * std::cos(1. / 2. * param) * std::cos(param) - radius * std::sin(1. / 2. * param) * std::sin(param) + radius; - } -} - -bool IfcGeom::Kernel::convert(const IfcSchema::IfcTrimmedCurve* l, TopoDS_Wire& wire) { - IfcSchema::IfcCurve* basis_curve = l->BasisCurve(); - bool isConic = basis_curve->declaration().is(IfcSchema::IfcConic::Class()); - double parameterFactor = isConic ? getValue(GV_PLANEANGLE_UNIT) : getValue(GV_LENGTH_UNIT); - - Handle(Geom_Curve) curve; - if (shape_type(basis_curve) == ST_CURVE) { - if (!convert_curve(basis_curve, curve)) return false; - } else if (shape_type(basis_curve) == ST_WIRE) { - Logger::Warning("Approximating BasisCurve due to possible discontinuities", l); - TopoDS_Wire w; - if (!convert_wire(basis_curve, w)) return false; - BRepAdaptor_CompCurve cc(w, true); - Handle(Adaptor3d_HCurve) hcc = Handle(Adaptor3d_HCurve)(new BRepAdaptor_HCompCurve(cc)); - // @todo, arbitrary numbers here, note they cannot be too high as contiguous memory is allocated based on them. - Approx_Curve3d approx(hcc, getValue(GV_PRECISION), GeomAbs_C0, 10, 10); - curve = approx.Curve(); - } else { - Logger::Error("Unknown BasisCurve", l); - return false; - } - - bool trim_cartesian = l->MasterRepresentation() != IfcSchema::IfcTrimmingPreference::IfcTrimmingPreference_PARAMETER; - IfcEntityList::ptr trims1 = l->Trim1(); - IfcEntityList::ptr trims2 = l->Trim2(); - - unsigned sense_agreement = l->SenseAgreement() ? 0 : 1; - double flts[2]; - gp_Pnt pnts[2]; - bool has_flts[2] = {false,false}; - bool has_pnts[2] = {false,false}; - - TopoDS_Edge e; - - for ( IfcEntityList::it it = trims1->begin(); it != trims1->end(); it ++ ) { - IfcUtil::IfcBaseClass* i = *it; - if ( i->declaration().is(IfcSchema::IfcCartesianPoint::Class()) ) { - IfcGeom::Kernel::convert((IfcSchema::IfcCartesianPoint*)i, pnts[sense_agreement] ); - has_pnts[sense_agreement] = true; - } else if ( i->declaration().is(IfcSchema::IfcParameterValue::Class()) ) { - const double value = *((IfcSchema::IfcParameterValue*)i); - flts[sense_agreement] = value * parameterFactor; - has_flts[sense_agreement] = true; - } - } - - for ( IfcEntityList::it it = trims2->begin(); it != trims2->end(); it ++ ) { - IfcUtil::IfcBaseClass* i = *it; - if ( i->declaration().is(IfcSchema::IfcCartesianPoint::Class()) ) { - IfcGeom::Kernel::convert((IfcSchema::IfcCartesianPoint*)i, pnts[1-sense_agreement] ); - has_pnts[1-sense_agreement] = true; - } else if ( i->declaration().is(IfcSchema::IfcParameterValue::Class()) ) { - const double value = *((IfcSchema::IfcParameterValue*)i); - flts[1-sense_agreement] = value * parameterFactor; - has_flts[1-sense_agreement] = true; - } - } - - trim_cartesian &= has_pnts[0] && has_pnts[1]; - bool trim_cartesian_failed = !trim_cartesian; - if ( trim_cartesian ) { - if ( pnts[0].Distance(pnts[1]) < 2 * getValue(GV_PRECISION) ) { - Logger::Message(Logger::LOG_WARNING,"Skipping segment with length below tolerance level:",l); - return false; - } - ShapeFix_ShapeTolerance FTol; - TopoDS_Vertex v1 = BRepBuilderAPI_MakeVertex(pnts[0]); - TopoDS_Vertex v2 = BRepBuilderAPI_MakeVertex(pnts[1]); - FTol.SetTolerance(v1, getValue(GV_PRECISION), TopAbs_VERTEX); - FTol.SetTolerance(v2, getValue(GV_PRECISION), TopAbs_VERTEX); - BRepBuilderAPI_MakeEdge me (curve,v1,v2); - if (!me.IsDone()) { - BRepBuilderAPI_EdgeError err = me.Error(); - if ( err == BRepBuilderAPI_PointProjectionFailed ) { - Logger::Message(Logger::LOG_WARNING,"Point projection failed for:",l); - trim_cartesian_failed = true; - } - } else { - e = me.Edge(); - } - } - - if ( (!trim_cartesian || trim_cartesian_failed) && (has_flts[0] && has_flts[1]) ) { - // The Geom_Line is constructed from a gp_Pnt and gp_Dir, whereas the IfcLine - // is defined by an IfcCartesianPoint and an IfcVector with Magnitude. Because - // the vector is normalised when passed to Geom_Line constructor the magnitude - // needs to be factored in with the IfcParameterValue here. - if ( basis_curve->declaration().is(IfcSchema::IfcLine::Class()) ) { - IfcSchema::IfcLine* line = static_cast(basis_curve); - const double magnitude = line->Dir()->Magnitude(); - flts[0] *= magnitude; flts[1] *= magnitude; - } - if ( basis_curve->declaration().is(IfcSchema::IfcEllipse::Class()) ) { - IfcSchema::IfcEllipse* ellipse = static_cast(basis_curve); - double x = ellipse->SemiAxis1() * getValue(GV_LENGTH_UNIT); - double y = ellipse->SemiAxis2() * getValue(GV_LENGTH_UNIT); - const bool rotated = y > x; - if (rotated) { - flts[0] -= M_PI / 2.; - flts[1] -= M_PI / 2.; - } - } - if ( isConic && ALMOST_THE_SAME(fmod(flts[1]-flts[0],M_PI*2.),0.) ) { - e = BRepBuilderAPI_MakeEdge(curve).Edge(); - } else { - BRepBuilderAPI_MakeEdge me (curve,flts[0],flts[1]); - e = me.Edge(); - } - } else if ( trim_cartesian_failed && (has_pnts[0] && has_pnts[1]) ) { - e = BRepBuilderAPI_MakeEdge(pnts[0], pnts[1]).Edge(); - } - - if (isConic) { - // Tiny circle segnments can cause issues later on, for example - // when the comp curve is used as the sweeping directrix. - double a, b; - Handle(Geom_Curve) crv = BRep_Tool::Curve(e, a, b); - double radius = -1.; - if (crv->DynamicType() == STANDARD_TYPE(Geom_Circle)) { - radius = Handle(Geom_Circle)::DownCast(crv)->Radius(); - } else if (crv->DynamicType() == STANDARD_TYPE(Geom_Ellipse)) { - // The formula above is for circles, but probably good enough - radius = Handle(Geom_Ellipse)::DownCast(crv)->MajorRadius(); - } - if (radius > 0. && deflection_for_approximating_circle(radius, b - a) < getValue(GV_PRECISION)) { - TopoDS_Vertex v0, v1; - TopExp::Vertices(e, v0, v1); - e = TopoDS::Edge(BRepBuilderAPI_MakeEdge(v0, v1).Edge().Oriented(e.Orientation())); - Logger::Warning("Subsituted edge with linear approximation", l); - } - } - - BRepBuilderAPI_MakeWire w; - w.Add(e); - - if (w.IsDone()) { - wire = w.Wire(); - - // When SenseAgreement == .F. the vertices above have been reversed to - // comply with the direction of conical curves. The ordering of the - // vertices then still needs to be reversed in order to have begin and - // end vertex consistent with IFC. - if (sense_agreement != 0) { // .F. - wire.Reverse(); - } - - return true; - } else { - return false; - } -} - -bool IfcGeom::Kernel::convert(const IfcSchema::IfcPolyline* l, TopoDS_Wire& result) { - IfcSchema::IfcCartesianPoint::list::ptr points = l->Points(); - - // Parse and store the points in a sequence - TColgp_SequenceOfPnt polygon; - for(IfcSchema::IfcCartesianPoint::list::it it = points->begin(); it != points->end(); ++ it) { - gp_Pnt pnt; - IfcGeom::Kernel::convert(*it, pnt); - polygon.Append(pnt); - } - - const double eps = getValue(GV_PRECISION) * 10; - const bool closed_by_proximity = polygon.Length() >= 3 && polygon.First().Distance(polygon.Last()) < eps; - if (closed_by_proximity) { - // tfk: note 1-based - polygon.Remove(polygon.Length()); - } - - // Remove points that are too close to one another - remove_duplicate_points_from_loop(polygon, closed_by_proximity, eps); - - if (polygon.Length() < 2) { - return false; - } - - BRepBuilderAPI_MakePolygon w; - for (int i = 1; i <= polygon.Length(); ++i) { - w.Add(polygon.Value(i)); - } - - if (closed_by_proximity) { - w.Close(); - } - - result = w.Wire(); - return true; -} - -bool IfcGeom::Kernel::convert(const IfcSchema::IfcPolyLoop* l, TopoDS_Wire& result) { - IfcSchema::IfcCartesianPoint::list::ptr points = l->Polygon(); - - // Parse and store the points in a sequence - TColgp_SequenceOfPnt polygon; - for(IfcSchema::IfcCartesianPoint::list::it it = points->begin(); it != points->end(); ++ it) { - gp_Pnt pnt; - IfcGeom::Kernel::convert(*it, pnt); - polygon.Append(pnt); - } - - // A loop should consist of at least three vertices - int original_count = polygon.Length(); - if (original_count < 3) { - Logger::Message(Logger::LOG_ERROR, "Not enough edges for:", l); - return false; - } - - // Remove points that are too close to one another - const double eps = getValue(GV_PRECISION) * 10; - remove_duplicate_points_from_loop(polygon, true, eps); - - int count = polygon.Length(); - if (original_count - count != 0) { - std::stringstream ss; ss << (original_count - count) << " edges removed for:"; - Logger::Message(Logger::LOG_WARNING, ss.str(), l); - } - - if (count < 3) { - Logger::Message(Logger::LOG_ERROR, "Not enough edges for:", l); - return false; - } - - BRepBuilderAPI_MakePolygon w; - for (int i = 1; i <= polygon.Length(); ++i) { - w.Add(polygon.Value(i)); - } - w.Close(); - - result = w.Wire(); - - TopTools_ListOfShape results; - if (wire_intersections(result, results)) { - Logger::Error("Self-intersections with " + boost::lexical_cast(results.Extent()) + " cycles detected", l); - select_largest(results, result); - } - - return true; -} - -bool IfcGeom::Kernel::convert(const IfcSchema::IfcArbitraryOpenProfileDef* l, TopoDS_Wire& result) { - return convert_wire(l->Curve(), result); -} - -bool IfcGeom::Kernel::convert(const IfcSchema::IfcEdgeCurve* l, TopoDS_Wire& result) { - IfcSchema::IfcPoint* pnt1 = ((IfcSchema::IfcVertexPoint*) l->EdgeStart())->VertexGeometry(); - IfcSchema::IfcPoint* pnt2 = ((IfcSchema::IfcVertexPoint*) l->EdgeEnd())->VertexGeometry(); - if (!pnt1->declaration().is(IfcSchema::IfcCartesianPoint::Class()) || !pnt2->declaration().is(IfcSchema::IfcCartesianPoint::Class())) { - Logger::Message(Logger::LOG_ERROR, "Only IfcCartesianPoints are supported for VertexGeometry", l); - return false; - } - - gp_Pnt p1, p2; - if (!IfcGeom::Kernel::convert(((IfcSchema::IfcCartesianPoint*)pnt1), p1) || - !IfcGeom::Kernel::convert(((IfcSchema::IfcCartesianPoint*)pnt2), p2)) - { - return false; - } - - BRepBuilderAPI_MakeWire mw; - Handle_Geom_Curve crv; - - // The lack of a clear separation between topological and geometrical entities - // is starting to get problematic. If the underlying curve is bounded it is - // assumed that a topological wire can be crafted from it. After which an - // attempt is made to reconstruct it from the individual curves and the vertices - // of the IfcEdgeCurve. - const bool is_bounded = l->EdgeGeometry()->declaration().is(IfcSchema::IfcBoundedCurve::Class()); - - if (!is_bounded && convert_curve(l->EdgeGeometry(), crv)) { - BRepBuilderAPI_MakeEdge me(crv, p1, p2); - if (!me.IsDone()) { - return false; - } - mw.Add(me.Edge()); - result = mw; - return true; - } else if (is_bounded && convert_wire(l->EdgeGeometry(), result)) { - if (!l->SameSense()) { - result.Reverse(); - } - - bool first = true; - TopExp_Explorer exp(result, TopAbs_EDGE); - - while (exp.More()) { - const TopoDS_Edge& ed = TopoDS::Edge(exp.Current()); - Standard_Real u1, u2; - Handle(Geom_Curve) ecrv = BRep_Tool::Curve(ed, u1, u2); - exp.Next(); - const bool last = !exp.More(); - - gp_Pnt a, b; - - if (first && last) { - a = p1; - b = p2; - } else if (first) { - a = p1; - ecrv->D0(u2, b); - } else if (last) { - ecrv->D0(u1, a); - b = p2; - } else { - BRepBuilderAPI_MakeEdge me(ecrv, u1, u2); - if (!me.IsDone()) { - return false; - } - mw.Add(me.Edge()); - first = false; - continue; - } - - BRep_Builder builder; - TopoDS_Vertex v1, v2; - /// @todo project first and emit warnings accordingly - builder.MakeVertex(v1, a, getValue(GV_PRECISION)); - builder.MakeVertex(v2, b, getValue(GV_PRECISION)); - - mw.Add(BRepBuilderAPI_MakeEdge(ecrv, v1, v2)); - - first = false; - } - result = mw; - return true; - } else { - return false; - } -} - -bool IfcGeom::Kernel::convert(const IfcSchema::IfcEdgeLoop* l, TopoDS_Wire& result) { - IfcSchema::IfcOrientedEdge::list::ptr li = l->EdgeList(); - BRepBuilderAPI_MakeWire mw; - for (IfcSchema::IfcOrientedEdge::list::it it = li->begin(); it != li->end(); ++it) { - TopoDS_Wire w; - if (convert_wire(*it, w)) { - mw.Add(TopoDS::Edge(TopoDS_Iterator(w).Value())); - } - } - if (!mw.IsDone()) { - return false; - } - result = mw.Wire(); - return true; -} - -bool IfcGeom::Kernel::convert(const IfcSchema::IfcEdge* l, TopoDS_Wire& result) { - if (!l->EdgeStart()->declaration().is(IfcSchema::IfcVertexPoint::Class()) || !l->EdgeEnd()->declaration().is(IfcSchema::IfcVertexPoint::Class())) { - Logger::Message(Logger::LOG_ERROR, "Only IfcVertexPoints are supported for EdgeStart and -End", l); - return false; - } - - IfcSchema::IfcPoint* pnt1 = ((IfcSchema::IfcVertexPoint*) l->EdgeStart())->VertexGeometry(); - IfcSchema::IfcPoint* pnt2 = ((IfcSchema::IfcVertexPoint*) l->EdgeEnd())->VertexGeometry(); - if (!pnt1->declaration().is(IfcSchema::IfcCartesianPoint::Class()) || !pnt2->declaration().is(IfcSchema::IfcCartesianPoint::Class())) { - Logger::Message(Logger::LOG_ERROR, "Only IfcCartesianPoints are supported for VertexGeometry", l); - return false; - } - - gp_Pnt p1, p2; - if (!convert(((IfcSchema::IfcCartesianPoint*)pnt1), p1) || - !convert(((IfcSchema::IfcCartesianPoint*)pnt2), p2)) - { - return false; - } - - BRepBuilderAPI_MakeWire mw; - mw.Add(BRepBuilderAPI_MakeEdge(p1, p2)); - - result = mw.Wire(); - return true; -} - -bool IfcGeom::Kernel::convert(const IfcSchema::IfcOrientedEdge* l, TopoDS_Wire& result) { - if (convert_wire(l->EdgeElement(), result)) { - if (!l->Orientation()) { - result.Reverse(); - } - return true; - } else { - return false; - } -} - -bool IfcGeom::Kernel::convert(const IfcSchema::IfcSubedge* l, TopoDS_Wire& result) { - TopoDS_Wire temp; - if (convert_wire(l->ParentEdge(), result) && convert((IfcSchema::IfcEdge*) l, temp)) { - TopExp_Explorer exp(result, TopAbs_EDGE); - TopoDS_Edge edge = TopoDS::Edge(exp.Current()); - Standard_Real u1, u2; - Handle(Geom_Curve) crv = BRep_Tool::Curve(edge, u1, u2); - TopoDS_Vertex v1, v2; - TopExp::Vertices(temp, v1, v2); - BRepBuilderAPI_MakeWire mw; - mw.Add(BRepBuilderAPI_MakeEdge(crv, v1, v2)); - result = mw.Wire(); - return true; - } else { - return false; - } -} - -#ifdef SCHEMA_HAS_IfcIndexedPolyCurve - -bool IfcGeom::Kernel::convert(const IfcSchema::IfcIndexedPolyCurve* l, TopoDS_Wire& result) { - - IfcSchema::IfcCartesianPointList* point_list = l->Points(); - std::vector< std::vector > coordinates; - if (point_list->as()) { - coordinates = point_list->as()->CoordList(); - } else if (point_list->as()) { - coordinates = point_list->as()->CoordList(); - } - - std::vector points; - points.reserve(coordinates.size()); - for (std::vector< std::vector >::const_iterator it = coordinates.begin(); it != coordinates.end(); ++it) { - const std::vector& coords = *it; - points.push_back(gp_Pnt( - coords.size() < 1 ? 0. : coords[0] * getValue(GV_LENGTH_UNIT), - coords.size() < 2 ? 0. : coords[1] * getValue(GV_LENGTH_UNIT), - coords.size() < 3 ? 0. : coords[2] * getValue(GV_LENGTH_UNIT))); - } - - int max_index = points.size(); - - BRepBuilderAPI_MakeWire w; - - if(l->hasSegments()) { - IfcEntityList::ptr segments = l->Segments(); - for (IfcEntityList::it it = segments->begin(); it != segments->end(); ++it) { - IfcUtil::IfcBaseClass* segment = *it; - if (segment->declaration().is(IfcSchema::IfcLineIndex::Class())) { - IfcSchema::IfcLineIndex* line = (IfcSchema::IfcLineIndex*) segment; - std::vector indices = *line; - gp_Pnt previous; - for (std::vector::const_iterator jt = indices.begin(); jt != indices.end(); ++jt) { - if (*jt < 1 || *jt > max_index) { - throw IfcParse::IfcException("IfcIndexedPolyCurve index out of bounds for index " + boost::lexical_cast(*jt)); - } - const gp_Pnt& current = points[*jt - 1]; - if (jt != indices.begin()) { - w.Add(BRepBuilderAPI_MakeEdge(previous, current)); - } - previous = current; - } - } else if (segment->declaration().is(IfcSchema::IfcArcIndex::Class())) { - IfcSchema::IfcArcIndex* arc = (IfcSchema::IfcArcIndex*) segment; - std::vector indices = *arc; - if (indices.size() != 3) { - throw IfcParse::IfcException("Invalid IfcArcIndex encountered"); - } - for (int i = 0; i < 3; ++i) { - const int& idx = indices[i]; - if (idx < 1 || idx > max_index) { - throw IfcParse::IfcException("IfcIndexedPolyCurve index out of bounds for index " + boost::lexical_cast(idx)); - } - } - const gp_Pnt& a = points[indices[0] - 1]; - const gp_Pnt& b = points[indices[1] - 1]; - const gp_Pnt& c = points[indices[2] - 1]; - Handle(Geom_Circle) circ = GC_MakeCircle(a, b, c).Value(); - w.Add(BRepBuilderAPI_MakeEdge(circ, a, c)); - } else { - throw IfcParse::IfcException("Unexpected IfcIndexedPolyCurve segment of type " + segment->declaration().name()); - } - } - } else if (points.begin() < points.end()) { - std::vector::const_iterator previous = points.begin(); - for (std::vector::const_iterator current = previous+1; current < points.end(); ++current){ - w.Add(BRepBuilderAPI_MakeEdge(*previous, *current)); - previous = current; - } - } - - result = w.Wire(); - return true; -} - -#endif diff --git a/src/ifcgeom/kernels/opencascade/OpenCascadeKernel.h b/src/ifcgeom/kernels/opencascade/OpenCascadeKernel.h index f02ec73b4f..c2b9694151 100644 --- a/src/ifcgeom/kernels/opencascade/OpenCascadeKernel.h +++ b/src/ifcgeom/kernels/opencascade/OpenCascadeKernel.h @@ -52,6 +52,7 @@ #include "../../../ifcgeom/schema_agnostic/ifc_geom_api.h" #include "../../../ifcgeom/taxonomy.h" +#include "../../../ifcgeom/ConversionSettings.h" // Define this in case you want to conserve memory usage at all cost. This has been // benchmarked extensively: https://github.com/IfcOpenShell/IfcOpenShell/pull/47 @@ -99,27 +100,7 @@ namespace kernels { double eps_; bool non_manifold_; - template - void loop_(const taxonomy::loop* ps, const Fn& callback) { - if (ps->children.size() < 3) { - return; - } - - auto a = boost::get(((taxonomy::edge*) ps->children.back())->start).instance; - auto A = a->data().id(); - for (auto& b : ps->children) { - auto B = boost::get(((taxonomy::edge*) b)->start).instance->data().id(); - auto C = vertex_mapping_[A], D = vertex_mapping_[B]; - bool fwd = C < D; - if (!fwd) { - std::swap(C, D); - } - if (C != D) { - callback(C, D, fwd); - A = B; - } - } - } + void loop_(const taxonomy::loop* ps, const std::function& callback); public: faceset_helper(OpenCascadeKernel* kernel, const taxonomy::shell* l); @@ -128,60 +109,11 @@ namespace kernels { bool non_manifold() const { return non_manifold_; } bool& non_manifold() { return non_manifold_; } - bool edge(const taxonomy::point3& a, const taxonomy::point3& b, TopoDS_Edge& e) { - int A = vertex_mapping_[a.instance->data().id()]; - int B = vertex_mapping_[b.instance->data().id()]; - if (A == B) { - return false; - } + bool edge(int A, int B, TopoDS_Edge& e); - return edge(A, B, e); - } + bool wire(const taxonomy::loop* loop, TopoDS_Wire& wire); - bool edge(int A, int B, TopoDS_Edge& e) { - auto it = edges_.find({ A, B }); - if (it == edges_.end()) { - return false; - } - e = it->second; - return true; - } - - bool wire(const taxonomy::loop* loop, TopoDS_Wire& wire) { - if (duplicates_.find(loop->instance->data().id()) != duplicates_.end()) { - return false; - } - BRep_Builder builder; - builder.MakeWire(wire); - int count = 0; - loop_(loop, [this, &builder, &wire, &count](int A, int B, bool fwd) { - TopoDS_Edge e; - if (edge(A, B, e)) { - if (!fwd) { - e.Reverse(); - } - builder.Add(wire, e); - count += 1; - } - }); - if (count >= 3) { - wire.Closed(true); - - /* - @todo - TopTools_ListOfShape results; - if (kernel_->wire_intersections(wire, results)) { - Logger::Warning("Self-intersections with " + boost::lexical_cast(results.Extent()) + " cycles detected", loop); - kernel_->select_largest(results, wire); - non_manifold_ = true; - } - */ - - return true; - } else { - return false; - } - } + bool wires(const taxonomy::loop* loop, TopTools_ListOfShape& wires); double epsilon() const { return eps_; @@ -195,17 +127,15 @@ namespace kernels { */ faceset_helper* faceset_helper_; - double precision_; public: - OpenCascadeKernel() - : AbstractKernel("opencascade") + OpenCascadeKernel(ConversionSettings& settings) + : AbstractKernel("opencascade", settings) , faceset_helper_(nullptr) - // @todo - , precision_(1.e-5) {} + {} OpenCascadeKernel(const OpenCascadeKernel& other) - : AbstractKernel("opencascade") { + : AbstractKernel("opencascade", other.settings_) { *this = other; } @@ -222,8 +152,6 @@ namespace kernels { bool convert(const taxonomy::matrix4*, gp_GTrsf&); bool convert(const taxonomy::shell*, TopoDS_Shape&); - bool approximate_plane_through_wire(const TopoDS_Wire& wire, gp_Pln& plane, double eps = -1.); - bool triangulate_wire(const std::vector& wires, TopTools_ListOfShape& faces); bool boolean_operation(const TopoDS_Shape& a_, const TopTools_ListOfShape& b__, BOPAlgo_Operation op, TopoDS_Shape& result, double fuzziness = -1.); const TopoDS_Shape& ensure_fit_for_subtraction(const TopoDS_Shape& shape, TopoDS_Shape& solid); bool flatten_shape_list(const ifcopenshell::geometry::ConversionResults& shapes, TopoDS_Shape& result, bool fuse); diff --git a/src/ifcgeom/kernels/opencascade/boolean_utils.cpp b/src/ifcgeom/kernels/opencascade/boolean_utils.cpp new file mode 100644 index 0000000000..f4d8437da6 --- /dev/null +++ b/src/ifcgeom/kernels/opencascade/boolean_utils.cpp @@ -0,0 +1,804 @@ +#include "boolean_utils.h" + +#include "IfcGeomTree.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +void ifcopenshell::geometry::util::copy_operand(const TopTools_ListOfShape & l, TopTools_ListOfShape & r) { +#if OCC_VERSION_HEX < 0x70000 + r.Clear(); + TopTools_ListIteratorOfListOfShape it(l); + for (; it.More(); it.Next()) { + r.Append(BRepBuilderAPI_Copy(it.Value())); + } +#else + // On OCCT 7.0 and higher BRepAlgoAPI_BuilderAlgo::SetNonDestructive(true) is + // called. Not entirely sure on the behaviour before 7.0, so overcautiously + // create copies. + r.Assign(l); +#endif +} + +TopoDS_Shape ifcopenshell::geometry::util::copy_operand(const TopoDS_Shape & s) { +#if OCC_VERSION_HEX < 0x70000 + return BRepBuilderAPI_Copy(s); +#else + return s; +#endif +} + +double ifcopenshell::geometry::util::min_edge_length(const TopoDS_Shape & a) { + double min_edge_len = std::numeric_limits::infinity(); + TopExp_Explorer exp(a, TopAbs_EDGE); + for (; exp.More(); exp.Next()) { + const TopoDS_Edge& e = TopoDS::Edge(exp.Current()); + + TopoDS_Vertex v0, v1; + TopExp::Vertices(e, v0, v1); + if (!v0.IsNull() && !v1.IsNull() && v0.IsSame(v1)) { + // Don't consider a 3d-degenerate edge (for example cone apex) + // in calculating overall shape min edge length. + continue; + } + + GProp_GProps prop; + BRepGProp::LinearProperties(e, prop); + double l = prop.Mass(); + if (l < min_edge_len) { + min_edge_len = l; + } + } + return min_edge_len; +} + +double ifcopenshell::geometry::util::min_vertex_edge_distance(const TopoDS_Shape & a, double min_search, double max_search) { + double M = std::numeric_limits::infinity(); + + TopTools_IndexedMapOfShape vertices, edges; + + TopExp::MapShapes(a, TopAbs_VERTEX, vertices); + TopExp::MapShapes(a, TopAbs_EDGE, edges); + + impl::tree tree; + + // Add edges to tree + for (int i = 1; i <= edges.Extent(); ++i) { + tree.add(i, edges(i)); + } + + for (int j = 1; j <= vertices.Extent(); ++j) { + const TopoDS_Vertex& v = TopoDS::Vertex(vertices(j)); + gp_Pnt p = BRep_Tool::Pnt(v); + + Bnd_Box b; + b.Add(p); + b.Enlarge(max_search); + + std::vector edge_idxs = tree.select_box(b, false); + std::vector::const_iterator it = edge_idxs.begin(); + for (; it != edge_idxs.end(); ++it) { + const TopoDS_Edge& e = TopoDS::Edge(edges(*it)); + TopoDS_Vertex v1, v2; + TopExp::Vertices(e, v1, v2); + + if (v.IsSame(v1) || v.IsSame(v2)) { + continue; + } + + BRepAdaptor_Curve crv(e); + Extrema_ExtPC ext(p, crv); + if (!ext.IsDone()) { + continue; + } + + for (int i = 1; i <= ext.NbExt(); ++i) { + const double m = sqrt(ext.SquareDistance(i)); + if (m < M && m > min_search) { + M = m; + } + } + } + } + + return M; +} + +bool ifcopenshell::geometry::util::faces_overlap(const TopoDS_Face & f, const TopoDS_Face & g) { + points_on_planar_face_generator pgen(f); + + BRep_Builder B; + gp_Pnt test; + double eps = BRep_Tool::Tolerance(f) + BRep_Tool::Tolerance(g); + + BRepExtrema_DistShapeShape x; + x.LoadS1(g); + + while (pgen(test)) { + TopoDS_Vertex V; + B.MakeVertex(V, test, Precision::Confusion()); + x.LoadS2(V); + x.Perform(); + if (x.IsDone() && x.NbSolution() == 1) { + if (x.Value() > eps) { + return false; + } + } + } + + return true; +} + +double ifcopenshell::geometry::util::min_face_face_distance(const TopoDS_Shape & a, double max_search) { + /* + NB: This is currently only implemented for planar surfaces. + */ + double M = std::numeric_limits::infinity(); + + TopTools_IndexedMapOfShape faces; + + TopExp::MapShapes(a, TopAbs_FACE, faces); + + impl::tree tree; + + // Add faces to tree + for (int i = 1; i <= faces.Extent(); ++i) { + if (BRep_Tool::Surface(TopoDS::Face(faces(i)))->DynamicType() == STANDARD_TYPE(Geom_Plane)) { + tree.add(i, faces(i)); + } + } + + for (int j = 1; j <= faces.Extent(); ++j) { + const TopoDS_Face& f = TopoDS::Face(faces(j)); + const Handle(Geom_Surface)& fs = BRep_Tool::Surface(f); + + if (fs->DynamicType() != STANDARD_TYPE(Geom_Plane)) { + continue; + } + + points_on_planar_face_generator pgen(f); + + Bnd_Box b; + BRepBndLib::AddClose(f, b); + b.Enlarge(max_search); + + std::vector face_idxs = tree.select_box(b, false); + std::vector::const_iterator it = face_idxs.begin(); + for (; it != face_idxs.end(); ++it) { + if (*it == j) { + continue; + } + + const TopoDS_Face& g = TopoDS::Face(faces(*it)); + const Handle(Geom_Surface)& gs = BRep_Tool::Surface(g); + + auto p0 = Handle(Geom_Plane)::DownCast(fs); + auto p1 = Handle(Geom_Plane)::DownCast(gs); + + if (p0->Position().IsCoplanar(p1->Position(), max_search, asin(max_search))) { + pgen.reset(); + + BRepTopAdaptor_FClass2d cls(g, BRep_Tool::Tolerance(g)); + + gp_Pnt test; + while (pgen(test)) { + gp_Vec d = test.XYZ() - p1->Position().Location().XYZ(); + double u = d.Dot(p1->Position().XDirection()); + double v = d.Dot(p1->Position().YDirection()); + + // nb: TopAbs_ON is explicitly not considered to prevent matching adjacent faces + // with similar orientations. + if (cls.Perform(gp_Pnt2d(u, v)) == TopAbs_IN) { + gp_Pnt test2; + p1->D0(u, v, test2); + double w = std::abs(gp_Vec(p1->Position().Direction().XYZ()).Dot(test2.XYZ() - test.XYZ())); + if (w < M) { + M = w; + } + } + } + } + } + } + + return M; +} + +int ifcopenshell::geometry::util::bounding_box_overlap(double p, const TopoDS_Shape & a, const TopTools_ListOfShape & b, TopTools_ListOfShape & c) { + int N = 0; + + Bnd_Box A; + BRepBndLib::Add(a, A); + + if (A.IsVoid()) { + return 0; + } + + TopTools_ListIteratorOfListOfShape it(b); + for (; it.More(); it.Next()) { + Bnd_Box B; + BRepBndLib::Add(it.Value(), B); + + if (B.IsVoid()) { + continue; + } + + if (A.Distance(B) < p) { + c.Append(it.Value()); + } else { + ++N; + } + } + + return N; +} + +bool ifcopenshell::geometry::util::get_edge_axis(const TopoDS_Edge & e, gp_Ax1 & ax) { + double _, __; + + auto crv = BRep_Tool::Curve(e, _, __); + auto line = Handle_Geom_Line::DownCast(crv); + auto bsple = Handle_Geom_BSplineCurve::DownCast(crv); + + if (line) { + ax = line->Position(); + return true; + } else if (bsple) { + if (bsple->NbPoles() == 2 && bsple->Degree() == 1) { + gp_Dir V(bsple->Poles().Last().XYZ() - bsple->Poles().First().XYZ()); + ax = gp_Ax1(bsple->Poles().First(), V); + return true; + } + } + + return false; +} + +bool ifcopenshell::geometry::util::is_subset(const TopTools_IndexedMapOfShape & lhs, const TopTools_IndexedMapOfShape & rhs) { + if (rhs.Extent() < lhs.Extent()) { + return false; + } + for (int i = 1; i < lhs.Extent(); ++i) { + auto& s = lhs.FindKey(i); + if (!rhs.Contains(s)) { + return false; + } + } + return true; +} + +bool ifcopenshell::geometry::util::is_extrusion(const gp_Vec & v, const TopoDS_Shape & s, TopoDS_Face & base, std::pair& interval) { + // This assumes UnifySameDomain has been processed on s, so that + // the extrusion top and bottom are a single face. + + TopTools_IndexedDataMapOfShapeListOfShape mapping; + TopExp::MapShapesAndAncestors(s, TopAbs_EDGE, TopAbs_FACE, mapping); + TopExp::MapShapesAndAncestors(s, TopAbs_VERTEX, TopAbs_FACE, mapping); + + TopTools_ListOfShape parallel; + TopTools_IndexedMapOfShape curved_orthogonal; + gp_Ax1 ax; + gp_Ax1 V(gp::Origin(), v); + + // Segment edges in parallel to extrusion direction, and orthogonal or curved, + // where the latter two categories have to make the edges part of the base or + // top face. When neither of these categories the shape is not a extrusion + // or the extrusion direction is not orthogonal to its basis. + for (int i = 1; i < mapping.Extent(); ++i) { + auto& s = mapping.FindKey(i); + if (s.ShapeType() != TopAbs_EDGE) { + continue; + } + + // @todo use a linear tolernace and the face extrimities, see #2218 + const TopoDS_Edge& e = TopoDS::Edge(s); + if (!get_edge_axis(e, ax)) { + // curved + curved_orthogonal.Add(e); + } else if (ax.IsParallel(V, 1.e-7)) { + parallel.Append(e); + } else if (ax.IsNormal(V, 1.e-7)) { + // ortho + curved_orthogonal.Add(e); + } else { + return false; + } + } + + // Select the two faces for which their edges are subsets + // of the ortho/curved edges + TopTools_IndexedMapOfShape ortho_faces; + for (TopExp_Explorer exp(s, TopAbs_FACE); exp.More(); exp.Next()) { + TopTools_IndexedMapOfShape face_edges; + TopExp::MapShapes(exp.Current(), TopAbs_EDGE, face_edges); + if (is_subset(face_edges, curved_orthogonal)) { + ortho_faces.Add(exp.Current()); + } + } + + // There should be a basis and top face + if (ortho_faces.Extent() != 2) { + return false; + } + + // For the parallel edges assert that its two vertices are part + // of both the basis and the top face. + for (TopTools_ListIteratorOfListOfShape it(parallel); + it.More(); it.Next()) { + TopoDS_Vertex v01[2]; + TopExp::Vertices(TopoDS::Edge(it.Value()), v01[0], v01[1]); + + TopTools_IndexedMapOfShape v_ortho_faces; + int nb_ortho_faces[2] = { 0,0 }; + + for (int i = 0; i < 2; ++i) { + auto& faces = mapping.FindFromKey(v01[i]); + + for (TopTools_ListIteratorOfListOfShape jt(faces); + jt.More(); jt.Next()) { + if (ortho_faces.Contains(jt.Value())) { + nb_ortho_faces[i] ++; + v_ortho_faces.Add(jt.Value()); + } + } + } + + bool sets_equal = v_ortho_faces.Size() == ortho_faces.Size() && is_subset(v_ortho_faces, ortho_faces); + if (!sets_equal) { + return false; + } + } + + // Assert the base/top faces are planar and get the interval + // (dot products along axis) for which the extrusion is defined + // If necessary swap the two faces so that the basis face has + // the smallest dot product along the axis. + auto f0 = TopoDS::Face(ortho_faces.FindKey(1)); + auto f1 = TopoDS::Face(ortho_faces.FindKey(2)); + + const Handle(Geom_Surface)& f0_s = BRep_Tool::Surface(f0); + const Handle(Geom_Surface)& f1_s = BRep_Tool::Surface(f1); + + auto p0 = Handle(Geom_Plane)::DownCast(f0_s); + auto p1 = Handle(Geom_Plane)::DownCast(f1_s); + + if (p0.IsNull() || p1.IsNull()) { + return false; + } + + auto dot0 = p0->Location().XYZ().Dot(v.XYZ()); + auto dot1 = p1->Location().XYZ().Dot(v.XYZ()); + + if (dot0 > dot1) { + std::swap(dot0, dot1); + std::swap(f0, f1); + } + + base = f0; + interval = { dot0, dot1 }; + + return true; +} + +int ifcopenshell::geometry::util::eliminate_touching_operands(double prec, const TopoDS_Shape & a, const TopTools_ListOfShape & bs, TopTools_ListOfShape & c) { + TopTools_IndexedMapOfShape a_faces; + TopExp::MapShapes(a, TopAbs_FACE, a_faces); + + // Check if any of the faces in a are non-planar, which is + // not supported by this quick check. + for (int i = 1; i <= a_faces.Extent(); ++i) { + auto surf = BRep_Tool::Surface(TopoDS::Face(a_faces(i))); + if (surf->DynamicType() != STANDARD_TYPE(Geom_Plane)) { + return 0; + } + } + + TopTools_IndexedMapOfShape a_vertices; + TopExp::MapShapes(a, TopAbs_VERTEX, a_vertices); + + ifcopenshell::geometry::impl::tree tree; + + // Add faces to tree + for (int i = 1; i <= a_faces.Extent(); ++i) { + tree.add(i, a_faces(i)); + } + + int N = 0; + + TopTools_ListIteratorOfListOfShape it(bs); + for (; it.More(); it.Next()) { + bool is_touching = false; + + auto& b = it.Value(); + + TopTools_IndexedMapOfShape b_faces; + TopExp::MapShapes(b, TopAbs_FACE, b_faces); + + // Check if any of the faces in b are non-planar, which is + // not supported by this quick check. + for (int i = 1; i <= b_faces.Extent(); ++i) { + auto surf = BRep_Tool::Surface(TopoDS::Face(b_faces(i))); + if (surf->DynamicType() != STANDARD_TYPE(Geom_Plane)) { + continue; + } + } + + TopTools_IndexedMapOfShape b_vertices; + TopExp::MapShapes(b, TopAbs_VERTEX, b_vertices); + + for (int k = 1; k <= b_faces.Extent(); ++k) { + const TopoDS_Face& f_b = TopoDS::Face(b_faces(k)); + Bnd_Box B; + BRepBndLib::Add(f_b, B); + + // Query tree using b_face bounding box + for (auto& i : tree.select_box(B, false)) { + const TopoDS_Face& f_a = TopoDS::Face(a_faces(i)); + + TopTools_IndexedMapOfShape f_a_vertices; + TopExp::MapShapes(f_a, TopAbs_VERTEX, f_a_vertices); + + BRepGProp_Face prop_a(f_a); + BRepGProp_Face prop_b(f_b); + + gp_Pnt p_a, p_b; + gp_Vec v_a, v_b; + + double u0, u1, v0, v1; + prop_a.Bounds(u0, u1, v0, v1); + prop_a.Normal((u0 + u1) / 2., (u0 + u1) / 2., p_a, v_a); + + prop_b.Bounds(u0, u1, v0, v1); + prop_b.Normal((u0 + u1) / 2., (u0 + u1) / 2., p_b, v_b); + + bool all_vertices_behind_f_a = true; + + // Check if all 'other' vertices in a are pointing + // away from the face in a, so that there is no geometry + // from a in front of the face that could participate + // in the boolean subtraction. + for (int j = 1; j <= a_vertices.Extent(); ++j) { + if (!f_a_vertices.Contains(a_vertices(j))) { + auto p = BRep_Tool::Pnt(TopoDS::Vertex(a_vertices(j))); + if ((p.XYZ() - p_a.XYZ()).Dot(v_a.XYZ()) > prec) { + all_vertices_behind_f_a = false; + break; + } + } + } + + if (!all_vertices_behind_f_a) { + continue; + } + + // Check if surface normals are opposite + if (v_a.IsOpposite(v_b, 1.e-5)) { + // Check if faces are co-planar + if ((p_b.XYZ() - p_a.XYZ()).Dot(v_a.XYZ()) <= prec) { + + TopTools_IndexedMapOfShape f_b_vertices; + TopExp::MapShapes(f_b, TopAbs_VERTEX, f_b_vertices); + + bool all_vertices_behind_f_b = true; + + // Check if all 'other' vertices in b are pointing + // away from the face in a. So that a boolean subtraction + // would not alter a. + for (int j = 1; j <= b_vertices.Extent(); ++j) { + if (!f_b_vertices.Contains(b_vertices(j))) { + auto p = BRep_Tool::Pnt(TopoDS::Vertex(b_vertices(j))); + if ((p.XYZ() - p_a.XYZ()).Dot(v_a.XYZ()) < prec * 10.) { + all_vertices_behind_f_b = false; + break; + } + } + } + + if (all_vertices_behind_f_b) { + is_touching = true; + break; + } + + } + } + } + + if (is_touching) { + break; + } + } + + if (!is_touching) { + c.Append(it.Value()); + } else { + ++N; + } + } + + return N; +} + +TopoDS_Shape ifcopenshell::geometry::util::unify(const TopoDS_Shape & s, double tolerance) { + tolerance = (std::min)(min_edge_length(s) / 2., tolerance); + ShapeUpgrade_UnifySameDomain usd(s); +#if OCC_VERSION_HEX >= 0x70200 + usd.SetSafeInputMode(true); +#endif +#if OCC_VERSION_HEX >= 0x70100 + usd.SetLinearTolerance(tolerance); + usd.SetAngularTolerance(1.e-3); +#endif + usd.Build(); + return usd.Shape(); +} + +bool ifcopenshell::geometry::util::boolean_subtraction_2d_using_builder(const TopoDS_Shape & a_input, const TopTools_ListOfShape & b_input, TopoDS_Shape & result, double eps) { + ifcopenshell::geometry::impl::tree edge_tree; + + TopTools_ListOfShape ab_input = b_input; + ab_input.Prepend(a_input); + + TopTools_ListIteratorOfListOfShape it(ab_input); + int shape_index = 0; + int edge_index = 0; + std::map edge_index_to_shape_index; + + std::vector shapes; + std::vector> edges; + // First is the outer wire + std::vector wires; + + for (; it.More(); it.Next(), ++shape_index) { + if (it.Value().ShapeType() != TopAbs_FACE) { + return false; + } + + const TopoDS_Face& f = TopoDS::Face(it.Value()); + TopoDS_Wire outer_wire; + + if (shape_index == 0) { + outer_wire = BRepTools::OuterWire(f); + wires.push_back(outer_wire); + } + + size_t num_wires = 0; + TopoDS_Iterator it2(it.Value()); + for (; it2.More(); it2.Next()) { + ++num_wires; + + if (outer_wire.IsNull() || !it2.Value().IsSame(outer_wire)) { + wires.push_back(TopoDS::Wire(it2.Value())); + + if (shape_index == 0 && num_wires > 0) { + // An inner wire on the first operand face: reverse, because + // MakeFace expects inner boundaries to be added as bounded + // areas. + wires.back().Reverse(); + } + } + } + + if (num_wires > 1 && shape_index != 0) { + // The first operand can have inner wires, but the others + // can't because a inner wire would result in an additional + // outer wire for the result. + return false; + } + + shapes.push_back(it.Value()); + TopExp_Explorer exp(it.Value(), TopAbs_EDGE); + for (; exp.More(); exp.Next(), ++edge_index) { + edge_tree.add(edge_index, exp.Current()); + edge_index_to_shape_index[edge_index] = shape_index; + edges.push_back({ shape_index, TopoDS::Edge(exp.Current()) }); + } + } + + { + TopoDS_Compound C; + BRep_Builder BB; + BB.MakeCompound(C); + + for (auto& w : wires) { + BB.Add(C, w); + } + + BRepTools::Write(C, "debug.brep"); + } + + shape_index = 0; + edge_index = 0; + + it.Initialize(ab_input); + for (; it.More(); it.Next(), ++shape_index) { + TopExp_Explorer exp(it.Value(), TopAbs_EDGE); + for (; exp.More(); exp.Next(), ++edge_index) { + Bnd_Box b; + BRepBndLib::Add(exp.Current(), b); + b.Enlarge(eps); + + for (auto& i : edge_tree.select_box(b)) { + if (i == edge_index) { + // Skip self-selection + continue; + } + + if (edges[i].first == shape_index) { + // Skip edges of the same operand + continue; + } + + const TopoDS_Edge& e0 = TopoDS::Edge(exp.Current()); + const TopoDS_Edge& e1 = edges[i].second; + + double u11, u12, u21, u22, U1, U2; + + GeomAPI_ExtremaCurveCurve ecc( + BRep_Tool::Curve(e0, u11, u12), + BRep_Tool::Curve(e1, u21, u22) + ); + + // @todo: extend this to work in case of multiple extrema and curved segments. + const bool unbounded_intersects = (!ecc.Extrema().IsParallel() && ecc.NbExtrema() == 1 && ecc.Distance(1) < eps); + if (unbounded_intersects) { + ecc.Parameters(1, U1, U2); + + if (u11 > u12) { + std::swap(u11, u12); + } + if (u21 > u22) { + std::swap(u21, u22); + } + + /// @todo: tfk: probably need different thresholds on non-linear curves + u11 -= eps; + u12 += eps; + u21 -= eps; + u22 += eps; + + if (u11 < U1 && U1 < u12 && u21 < U2 && U2 < u22) { + // Edge curves belonging to different operands intersect, don't process + // using builder. + Logger::Notice("Intersecting boundaries"); + return false; + } + } + } + } + } + + // Only inner wires are considered that are directly contained in the outer wire + // Redundant subtractions are eliminated. + + std::vector redundant(wires.size(), false); + + std::vector wire_faces; + wire_faces.reserve(wires.size()); + + std::vector wire_clss; + wire_clss.reserve(wires.size()); + + std::vector> sass; + sass.reserve(wires.size()); + + for (auto& w : wires) { + wire_faces.push_back(BRepBuilderAPI_MakeFace(w).Face()); + wire_clss.emplace_back(wire_faces.back(), eps); + sass.push_back(std::make_unique(BRep_Tool::Surface(wire_faces.back()))); + } + + // First check for containment in outer wire + for (auto it = ++wires.begin(); it != wires.end(); ++it) { + // Considering a single vertex is sufficient because we have already + // guaranteed that the edges of different operands do not cross. + TopoDS_Iterator it_ed(*it); + auto& ed = it_ed.Value(); + + TopoDS_Iterator it_v(ed); + auto& v = TopoDS::Vertex(it_v.Value()); + + auto pnt = BRep_Tool::Pnt(v); + auto p2d = sass[0]->ValueOfUV(pnt, eps); + if (wire_clss[0].Perform(p2d) != TopAbs_IN) { + // A wire is not contained in the outer wire, it's a subtraction without + // any effect and marked as redundant. Feeding it to the builder algo + // will likely cause problems. + redundant[std::distance(wires.begin(), it)] = true; + Logger::Notice("Subtraction operand outside of outer bound"); + } + } + + // Now build a tree to find inner wires contained in other inner wires + // NB first wire is *not* in this tree + ifcopenshell::geometry::impl::tree wire_tree; + for (size_t wire_index = 1; wire_index < wires.size(); ++wire_index) { + wire_tree.add(wire_index, wires[wire_index]); + } + + for (size_t wire_index = 1; wire_index < wires.size(); ++wire_index) { + Bnd_Box b; + BRepBndLib::Add(wires[wire_index], b); + b.Enlarge(eps); + + // We're only selecting operands completely within b because we + // have already guaranteed they do not intersect. So they are + // either fully in or out. Selecting with complete_within=true + // will filter out some unnecessary cases. It also means we need + // that due this asymmetry we need to process all pairs of wire + // indices and not just the pairs where the first element is less + // than the second element. + for (auto& other_index : wire_tree.select_box(b, true)) { + // other_index is fully contained in wire_index + if (wire_index == other_index) { + continue; + } + + TopoDS_Iterator it_ed(wires[other_index]); + auto& ed = it_ed.Value(); + + TopoDS_Iterator it_v(ed); + auto& v = TopoDS::Vertex(it_v.Value()); + + auto pnt = BRep_Tool::Pnt(v); + auto p2d = sass[wire_index]->ValueOfUV(pnt, eps); + if (wire_clss[wire_index].Perform(p2d) == TopAbs_IN) { + // A wire is contained within another operand + redundant[other_index] = true; + Logger::Notice("Subtraction operand contained in other"); + } + } + } + + BRepBuilderAPI_MakeFace mf(wire_faces[0]); + for (size_t wire_index = 1; wire_index < wires.size(); ++wire_index) { + if (!redundant[wire_index]) { + mf.Add(TopoDS::Wire(wires[wire_index].Reversed())); + } + } + result = mf.Face(); + + return true; +} + +void ifcopenshell::geometry::util::points_on_planar_face_generator::reset() { + i = j = (int)inset_; +} + +bool ifcopenshell::geometry::util::points_on_planar_face_generator::operator()(gp_Pnt& p) { + while (j < N) { + double u = u0 + (u1 - u0) * i / N; + double v = v0 + (v1 - v0) * j / N; + + i++; + if (i == N) { + i = 0; + j++; + } + + // Specifically does not consider ON + if (cls_.Perform(gp_Pnt2d(u, v)) == TopAbs_IN) { + plane_->D0(u, v, p); + return true; + } + } + + return false; +} \ No newline at end of file diff --git a/src/ifcgeom/kernels/opencascade/boolean_utils.h b/src/ifcgeom/kernels/opencascade/boolean_utils.h new file mode 100644 index 0000000000..a1d03f23c8 --- /dev/null +++ b/src/ifcgeom/kernels/opencascade/boolean_utils.h @@ -0,0 +1,91 @@ +/******************************************************************************** + * * + * 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 . * + * * + ********************************************************************************/ + +#ifndef BOOLEAN_UTILS_H +#define BOOLEAN_UTILS_H + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace ifcopenshell { namespace geometry { + namespace util { + + void copy_operand(const TopTools_ListOfShape& l, TopTools_ListOfShape& r); + + TopoDS_Shape copy_operand(const TopoDS_Shape& s); + + double min_edge_length(const TopoDS_Shape& a); + + double min_vertex_edge_distance(const TopoDS_Shape& a, double min_search, double max_search); + + class points_on_planar_face_generator { + private: + const TopoDS_Face& f_; + Handle(Geom_Surface) plane_; + BRepTopAdaptor_FClass2d cls_; + double u0, u1, v0, v1; + int i, j; + bool inset_; + static const int N = 10; + + public: + points_on_planar_face_generator(const TopoDS_Face& f, bool inset = false) + : f_(f) + , plane_(BRep_Tool::Surface(f_)) + , cls_(f_, BRep_Tool::Tolerance(f_)) + , i((int)inset), j((int)inset) + , inset_(inset) + { + BRepTools::UVBounds(f_, u0, u1, v0, v1); + } + + void reset(); + + bool operator()(gp_Pnt& p); + }; + + bool faces_overlap(const TopoDS_Face& f, const TopoDS_Face& g); + + double min_face_face_distance(const TopoDS_Shape& a, double max_search); + + int bounding_box_overlap(double p, const TopoDS_Shape& a, const TopTools_ListOfShape& b, TopTools_ListOfShape& c); + + bool get_edge_axis(const TopoDS_Edge& e, gp_Ax1& ax); + + bool is_subset(const TopTools_IndexedMapOfShape& lhs, const TopTools_IndexedMapOfShape& rhs); + + bool is_extrusion(const gp_Vec& v, const TopoDS_Shape& s, TopoDS_Face& base, std::pair& interval); + + int eliminate_touching_operands(double prec, const TopoDS_Shape& a, const TopTools_ListOfShape& bs, TopTools_ListOfShape& c); + + TopoDS_Shape unify(const TopoDS_Shape& s, double tolerance); + + bool boolean_subtraction_2d_using_builder(const TopoDS_Shape& a_input, const TopTools_ListOfShape& b_input, TopoDS_Shape& result, double eps); + + } + +} } + +#endif \ No newline at end of file diff --git a/src/ifcgeom/kernels/opencascade/face_definition.cpp b/src/ifcgeom/kernels/opencascade/face_definition.cpp index 522549f506..4111c6c675 100644 --- a/src/ifcgeom/kernels/opencascade/face_definition.cpp +++ b/src/ifcgeom/kernels/opencascade/face_definition.cpp @@ -6,7 +6,7 @@ #include /* Returns whether wire conforms to a polyhedron, i.e. only edges with linear curves*/ -bool ifcopenshell::geometry::util::is_polyhedron(const TopoDS_Wire& wire) { +bool IfcGeom::util::is_polyhedron(const TopoDS_Wire & wire) { double a, b; TopLoc_Location l; @@ -20,15 +20,3 @@ bool ifcopenshell::geometry::util::is_polyhedron(const TopoDS_Wire& wire) { return true; } - -/* Returns whether wire conforms to a polyhedron, i.e. only edges with linear curves*/ -bool ifcopenshell::geometry::util::is_polyhedron(const taxonomy::loop* wire) { - for (auto& edge : wire->children_as()) { - if (edge->basis) { - if (edge->basis->kind() != taxonomy::LINE) { - return false; - } - } - } - return true; -} \ No newline at end of file diff --git a/src/ifcgeom/kernels/opencascade/face_definition.h b/src/ifcgeom/kernels/opencascade/face_definition.h index a75d012eda..6fe982055b 100644 --- a/src/ifcgeom/kernels/opencascade/face_definition.h +++ b/src/ifcgeom/kernels/opencascade/face_definition.h @@ -20,64 +20,59 @@ #ifndef FACE_DEFINITION_H #define FACE_DEFINITION_H -#include "../../taxonomy.h" - #include #include #include #include -namespace ifcopenshell { - namespace geometry { - namespace util { +namespace IfcGeom { + namespace util { - /* Returns whether wire conforms to a polyhedron, i.e. only edges with linear curves*/ - bool is_polyhedron(const TopoDS_Wire& wire); + /* Returns whether wire conforms to a polyhedron, i.e. only edges with linear curves*/ + bool is_polyhedron(const TopoDS_Wire& wire); - /* Returns whether wire conforms to a polyhedron, i.e. only edges with linear curves*/ - bool is_polyhedron(const taxonomy::loop* wire); + /* A temporary structure to store the intermediate data for the face conversion */ + class face_definition { + private: + Handle(Geom_Surface) surface_; + std::vector wires_; + bool all_outer_; + public: + face_definition() : surface_(), all_outer_(false) {} - /* A temporary structure to store the intermediate data for the face conversion */ - class face_definition { - private: - Handle(Geom_Surface) surface_; - std::vector wires_; - bool all_outer_; - public: - face_definition() : surface_(), all_outer_(false) {} + typedef std::vector::const_iterator wire_it; - typedef std::vector::const_iterator wire_it; + bool& all_outer() { + return all_outer_; + } - bool& all_outer() { - return all_outer_; - } + bool all_outer() const { + return all_outer_; + } - bool all_outer() const { - return all_outer_; - } + Handle(Geom_Surface)& surface() { + return surface_; + } - Handle(Geom_Surface)& surface() { - return surface_; - } + const Handle(Geom_Surface)& surface() const { + return surface_; + } - const Handle(Geom_Surface)& surface() const { - return surface_; - } + std::vector& wires() { + return wires_; + } - std::vector& wires() { - return wires_; - } + const TopoDS_Wire& outer_wire() const { + return wires_.front(); + } - const TopoDS_Wire& outer_wire() const { - return wires_.front(); - } + std::pair inner_wires() const { + return { wires_.begin() + 1, wires_.end() }; + } + }; - std::pair inner_wires() const { - return { wires_.begin() + 1, wires_.end() }; - } - }; - } } } + #endif diff --git a/src/ifcgeom/kernels/opencascade/faceset_helper.cpp b/src/ifcgeom/kernels/opencascade/faceset_helper.cpp new file mode 100644 index 0000000000..2f606badbe --- /dev/null +++ b/src/ifcgeom/kernels/opencascade/faceset_helper.cpp @@ -0,0 +1,280 @@ +#include "OpenCascadeKernel.h" + +#include "IfcGeomTree.h" +#include "wire_utils.h" + +using namespace ifcopenshell::geometry; +using namespace ifcopenshell::geometry::kernels; + +namespace { + void find_neighbours(ifcopenshell::geometry::impl::tree& tree, std::vector>& pnts, std::set& visited, int p, double eps) { + visited.insert(p); + + Bnd_Box b; + b.Set(*pnts[p].get()); + b.Enlarge(eps); + + std::vector js = tree.select_box(b, false); + for (int j : js) { + visited.insert(j); +#ifdef FACESET_HELPER_RECURSIVE + if (visited.find(j) == visited.end()) { + // @todo, making this recursive removes the dependence on the initial ordering, but will + // likely result in empty results when all vertices are within 1 eps from another point. + find_neighbours(tree, pnts, visited, j, eps); + } +#endif + } + } +} + +OpenCascadeKernel::faceset_helper::faceset_helper(OpenCascadeKernel* kernel, const taxonomy::shell* shell) + : kernel_(kernel) + , non_manifold_(false) +{ + // @todo use pointers? + std::vector points; + std::vector loops; + + for (auto& f : shell->children_as()) { + for (auto& l : f->children_as()) { + loops.push_back(l); + for (auto& e : l->children_as()) { + // @todo make sure only cartesian points are provided here + points.push_back(boost::get(e->start)); + } + } + } + + std::vector> pnts(points.size()); + std::vector vertices(pnts.size()); + + // @todo + impl::tree tree; + + BRep_Builder B; + + Bnd_Box box; + for (size_t i = 0; i < points.size(); ++i) { + gp_Pnt* p = new gp_Pnt(convert_xyz(points[i])); + pnts[i].reset(p); + B.MakeVertex(vertices[i], *p, Precision::Confusion()); + tree.add(i, vertices[i]); + box.Add(*p); + } + + // Use the bbox diagonal to influence local epsilon + // double bdiff = std::sqrt(box.SquareExtent()); + + // @todo the bounding box diagonal is not used (see above) + // because we're explicitly interested in the miminal + // dimension of the element to limit the tolerance (for sheet- + // like elements for example). But the way below is very + // dependent on orientation due to the usage of the + // axis-aligned bounding box. Use PCA to find three non-aligned + // set of dimensions and use the one with the smallest eigenvalue. + + // Find the minimal bounding box edge + double bmin[3], bmax[3]; + box.Get(bmin[0], bmin[1], bmin[2], bmax[0], bmax[1], bmax[2]); + double bdiff = std::numeric_limits::infinity(); + for (size_t i = 0; i < 3; ++i) { + const double d = bmax[i] - bmin[i]; + if (d > kernel->settings_.getValue(ConversionSettings::GV_PRECISION) * 10. && d < bdiff) { + bdiff = d; + } + } + + eps_ = kernel->settings_.getValue(ConversionSettings::GV_PRECISION) * 10. * (std::min)(1.0, bdiff); + + // @todo, there a tiny possibility that the duplicate faces are triggered + // for an internal boundary, that is also present as an external boundary. + // This will result in non-manifold configuration then, but this is deemed + // such as corner-case that it is not considered. + + size_t loops_removed, non_manifold, duplicate_faces; + + std::map, int> edge_use; + + for (int i = 0; i < 3; ++i) { + // Some times files, have large tolerance values specified collapsing too many vertices. + // This case we detect below and re-run the loop with smaller epsilon. Normally + // the body of this loop would only be executed once. + + loops_removed = 0; + non_manifold = 0; + duplicate_faces = 0; + + vertex_mapping_.clear(); + duplicates_.clear(); + + edge_use.clear(); + + if (eps_ < Precision::Confusion()) { + // occt uses some hard coded precision values, don't go smaller than that. + // @todo, can be reset though with BRepLib::Precision(double) + eps_ = Precision::Confusion(); + } + + for (int pnt_i = 0; pnt_i < (int)pnts.size(); ++pnt_i) { + if (pnts[pnt_i]) { + std::set vs; + find_neighbours(tree, pnts, vs, pnt_i, eps_); + + for (int v : vs) { + auto& pt = points[v]; + // NB: insert() ignores duplicate keys + vertex_mapping_.insert({ pt.instance->data().id() , i }); + } + } + } + + std::set> unique; + for (int pnt_i = 0; pnt_i < (int)pnts.size(); ++pnt_i) { + if (pnts[pnt_i]) { + unique.insert(std::make_tuple( + (*pnts[pnt_i]).X(), + (*pnts[pnt_i]).Y(), + (*pnts[pnt_i]).Z() + )); + } + } + + if (unique.size() != vertex_mapping_.size()) { + Logger::Notice("Collapsed vertices from " + std::to_string(pnts.size()) + " (" + std::to_string(unique.size()) + " unique) to " + std::to_string(vertex_mapping_.size())); + } + + typedef std::array edge_t; + typedef std::set edge_set_t; + std::set edge_sets; + + for (auto& loop : loops) { + std::vector > segments; + edge_set_t segment_set; + + loop_(loop, [&segments, &segment_set](int C, int D, bool) { + segment_set.insert(edge_t{ C,D }); + segments.push_back(std::make_pair(C, D)); + }); + + if (edge_sets.find(segment_set) != edge_sets.end()) { + duplicate_faces++; + // @todo does this work with tesselated face sets, will they have an associated instance? Guess not. + duplicates_.insert(loop->instance->data().id()); + continue; + } + edge_sets.insert(segment_set); + + if (segments.size() >= 3) { + for (auto& p : segments) { + edge_use[p] ++; + } + } else { + loops_removed += 1; + } + } + + if (edge_use.size() != 0) { + break; + } else { + eps_ /= 10.; + } + } + + for (auto& p : edge_use) { + int a, b; + std::tie(a, b) = p.first; + edges_[p.first] = BRepBuilderAPI_MakeEdge(vertices[a], vertices[b]); + + if (p.second != 2) { + non_manifold += 1; + } + } + + if (duplicates_.size() || loops_removed || (non_manifold && shell->closed.get_value_or(false))) { + Logger::Warning(boost::lexical_cast(duplicate_faces) + " duplicate faces removed, " + boost::lexical_cast(loops_removed) + " degenerate loops eliminated and " + boost::lexical_cast(non_manifold) + " non-manifold edges"); + } +} + +void OpenCascadeKernel::faceset_helper::loop_(const taxonomy::loop* ps, const std::function& callback) { + if (ps->children.size() < 3) { + return; + } + + auto a = boost::get(((taxonomy::edge*) ps->children.back())->start).instance; + auto A = a->data().id(); + for (auto& b : ps->children) { + auto B = boost::get(((taxonomy::edge*) b)->start).instance->data().id(); + auto C = vertex_mapping_[A], D = vertex_mapping_[B]; + bool fwd = C < D; + if (!fwd) { + std::swap(C, D); + } + if (C != D) { + callback(C, D, fwd); + A = B; + } + } +} + +bool OpenCascadeKernel::faceset_helper::edge(int A, int B, TopoDS_Edge& e) { + auto it = edges_.find({ A, B }); + if (it == edges_.end()) { + return false; + } + e = it->second; + return true; +} + +bool OpenCascadeKernel::faceset_helper::wire(const taxonomy::loop* loop, TopoDS_Wire& w) { + TopTools_ListOfShape ws; + if (!wires(loop, ws)) { + return false; + } + util::select_largest(ws, w); + return true; +} + +bool OpenCascadeKernel::faceset_helper::wires(const taxonomy::loop* loop, TopTools_ListOfShape& wires) { + if (duplicates_.find(loop->instance->data().id()) != duplicates_.end()) { + return false; + } + TopoDS_Wire wire; + BRep_Builder builder; + builder.MakeWire(wire); + int count = 0; + loop_(loop, [this, &builder, &wire, &count](int A, int B, bool fwd) { + TopoDS_Edge e; + if (edge(A, B, e)) { + if (!fwd) { + e.Reverse(); + } + builder.Add(wire, e); + count += 1; + } + }); + if (count >= 3) { + wire.Closed(true); + + TopTools_ListOfShape results; + /* todo kernel_->getValue(GV_NO_WIRE_INTERSECTION_CHECK) < 0. && */ + /* todo kernel_->get_wire_intersection_tolerance(wire) */ + if (util::wire_intersections(wire, results, kernel_->settings_.getValue(ConversionSettings::GV_PRECISION), kernel_->settings_.getValue(ConversionSettings::GV_PRECISION))) { + Logger::Warning("Self-intersections with " + boost::lexical_cast(results.Extent()) + " cycles detected"); + non_manifold_ = true; + wires = results; + } else { + wires.Append(wire); + } + + return true; + } else { + return false; + } +} + +OpenCascadeKernel::faceset_helper::~faceset_helper() { + // @todo this is super ugly, but how else can we be notified that the unique_ptr goes out of scope? + // Perhaps just supply a custom std::deleter? + kernel_->faceset_helper_ = nullptr; +} diff --git a/src/ifcgeom/kernels/opencascade/shell.cpp b/src/ifcgeom/kernels/opencascade/shell.cpp index 81457f3423..c12b5a5d1a 100644 --- a/src/ifcgeom/kernels/opencascade/shell.cpp +++ b/src/ifcgeom/kernels/opencascade/shell.cpp @@ -3,6 +3,87 @@ using namespace ifcopenshell::geometry; using namespace ifcopenshell::geometry::kernels; +bool OpenCascadeKernel::convert(const taxonomy::shell* l, TopoDS_Shape& shape) { + std::unique_ptr helper_scope; + helper_scope.reset(new faceset_helper(this, l)); + + faceset_helper_ = helper_scope.get(); + + auto faces = l->children_as(); + double minimal_face_area = precision_ * precision_ * 0.5; + + double min_face_area = faceset_helper_ + ? (faceset_helper_->epsilon() * faceset_helper_->epsilon() / 20.) + : minimal_face_area; + + TopTools_ListOfShape face_list; + for (auto& face : faces) { + bool success = false; + TopoDS_Face occ_face; + + try { + success = convert(face, occ_face); + } catch (const std::exception& e) { + Logger::Error(e); + } catch (const Standard_Failure& e) { + if (e.GetMessageString() && strlen(e.GetMessageString())) { + Logger::Error(e.GetMessageString()); + } else { + Logger::Error("Unknown error creating face"); + } + } catch (...) { + Logger::Error("Unknown error creating face"); + } + + if (!success) { + Logger::Message(Logger::LOG_WARNING, "Failed to convert face:", face->instance); + continue; + } + + if (occ_face.ShapeType() == TopAbs_COMPOUND) { + TopoDS_Iterator face_it(occ_face, false); + for (; face_it.More(); face_it.Next()) { + if (face_it.Value().ShapeType() == TopAbs_FACE) { + // This should really be the case. This is not asserted. + const TopoDS_Face& triangle = TopoDS::Face(face_it.Value()); + if (face_area(triangle) > min_face_area) { + face_list.Append(triangle); + } else { + Logger::Message(Logger::LOG_WARNING, "Degenerate face:", face->instance); + } + } + } + } else { + if (face_area(occ_face) > min_face_area) { + face_list.Append(occ_face); + } else { + Logger::Message(Logger::LOG_WARNING, "Degenerate face:", face->instance); + } + } + } + + if (face_list.Extent() == 0) { + return false; + } + + // @todo + /* face_list.Extent() > getValue(GV_MAX_FACES_TO_ORIENT) || */ + + if (!create_solid_from_faces(face_list, shape)) { + TopoDS_Compound compound; + BRep_Builder builder; + builder.MakeCompound(compound); + + TopTools_ListIteratorOfListOfShape face_iterator; + for (face_iterator.Initialize(face_list); face_iterator.More(); face_iterator.Next()) { + builder.Add(compound, face_iterator.Value()); + } + shape = compound; + } + + return true; +} + bool OpenCascadeKernel::convert_impl(const taxonomy::shell *shell, ifcopenshell::geometry::ConversionResults& results) { TopoDS_Shape shape; if (!convert(shell, shape)) { diff --git a/src/ifcgeom/kernels/opencascade/sweep_utils.cpp b/src/ifcgeom/kernels/opencascade/sweep_utils.cpp new file mode 100644 index 0000000000..8c166b7dbb --- /dev/null +++ b/src/ifcgeom/kernels/opencascade/sweep_utils.cpp @@ -0,0 +1,356 @@ +#include "sweep_utils.h" + +#include "../ifcparse/IfcLogger.h" +#include "../ifcgeom_schema_agnostic/Kernel.h" + +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +bool IfcGeom::util::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 IfcGeom::util::wire_to_ax(const TopoDS_Wire & wire, gp_Ax2 & directrix) { + gp_Pnt directrix_origin; + gp_Vec directrix_tangent; + + TopoDS_Edge edge; + + // Find first edge + TopoDS_Vertex v0, v1; + TopExp::Vertices(wire, v0, v1); + TopTools_IndexedDataMapOfShapeListOfShape map; + TopExp::MapShapesAndAncestors(wire, TopAbs_VERTEX, TopAbs_EDGE, map); + if (v0.IsSame(v1) && map.Contains(v0) && map.FindFromKey(v0).Extent() == 2) { + // Closed wire, with more than 1 edges + auto es = map.FindFromKey(v0); + auto e1 = TopoDS::Edge(es.First()); + auto e2 = TopoDS::Edge(es.Last()); + + double u0, u1; + + gp_Vec accum; + + Handle(Geom_Curve) crv = BRep_Tool::Curve(e1, u0, u1); + crv->D1(TopExp::FirstVertex(e1).IsSame(v0) ? u0 : u1, directrix_origin, directrix_tangent); + + accum += directrix_tangent; + + crv = BRep_Tool::Curve(e2, u0, u1); + crv->D1(TopExp::FirstVertex(e2).IsSame(v0) ? u0 : u1, directrix_origin, directrix_tangent); + + accum += directrix_tangent; + + directrix_tangent = accum; + + } else if (map.Contains(v0) && map.FindFromKey(v0).Extent() == 1) { + edge = TopoDS::Edge(map.FindFromKey(v0).First()); + + double u0, u1; + Handle(Geom_Curve) crv = BRep_Tool::Curve(edge, u0, u1); + crv->D1(u0, directrix_origin, directrix_tangent); + } else { + Logger::Error("Unable to locate first edge"); + return false; + } + + directrix = gp_Ax2(directrix_origin, directrix_tangent); + + return true; +} + +bool IfcGeom::util::is_single_linear_edge(const TopoDS_Wire & wire) { + TopExp_Explorer exp(wire, TopAbs_EDGE); + if (!exp.More()) { + return false; + } + TopoDS_Edge e = TopoDS::Edge(exp.Current()); + exp.Next(); + if (exp.More()) { + return false; + } + double u, v; + Handle_Geom_Curve crv = BRep_Tool::Curve(e, u, v); + return crv->DynamicType() == STANDARD_TYPE(Geom_Line); +} + +bool IfcGeom::util::is_single_circular_edge(const TopoDS_Wire & wire) { + TopExp_Explorer exp(wire, TopAbs_EDGE); + if (!exp.More()) { + return false; + } + TopoDS_Edge e = TopoDS::Edge(exp.Current()); + exp.Next(); + if (exp.More()) { + return false; + } + double u, v; + Handle_Geom_Curve crv = BRep_Tool::Curve(e, u, v); + return crv->DynamicType() == STANDARD_TYPE(Geom_Circle); +} + +void IfcGeom::util::process_sweep_as_extrusion(const TopoDS_Wire & wire, const TopoDS_Wire & section, TopoDS_Shape & result) { + TopExp_Explorer exp(wire, TopAbs_EDGE); + TopoDS_Edge e = TopoDS::Edge(exp.Current()); + double u, v; + Handle_Geom_Curve crv = BRep_Tool::Curve(e, u, v); + const auto& dir = Handle(Geom_Line)::DownCast(crv)->Position().Direction(); + // OCCT line is normalized so diff in parametric coords equals length + const double depth = std::abs(u - v); + // @todo we could be extruding the wire only when we know this is an intermediate edge. + TopoDS_Face face = BRepBuilderAPI_MakeFace(section).Face(); + result = BRepPrimAPI_MakePrism(face, depth*dir).Shape(); +} + +void IfcGeom::util::process_sweep_as_revolution(const TopoDS_Wire & wire, const TopoDS_Wire & section, TopoDS_Shape & result) { + TopExp_Explorer exp(wire, TopAbs_EDGE); + TopoDS_Edge e = TopoDS::Edge(exp.Current()); + double u, v; + Handle_Geom_Curve crv = BRep_Tool::Curve(e, u, v); + auto circ = Handle(Geom_Circle)::DownCast(crv); + // @todo we could be extruding the wire only when we know this is an intermediate edge. + const double depth = std::abs(u - v); + TopoDS_Face face = BRepBuilderAPI_MakeFace(section).Face(); + result = BRepPrimAPI_MakeRevol(face, circ->Axis(), depth).Shape(); +} + +void IfcGeom::util::process_sweep_as_pipe(const TopoDS_Wire & wire, const TopoDS_Wire & section, TopoDS_Shape & result, bool force_transformed) { + // This tolerance is fairly high due to the linear edge substitution for small (or large radii) conical curves. + const bool is_continuous = wire_is_c1_continuous(wire, 1.e-2); + BRepOffsetAPI_MakePipeShell builder(wire); + builder.Add(section); + builder.SetTransitionMode(is_continuous || force_transformed ? BRepBuilderAPI_Transformed : BRepBuilderAPI_RightCorner); + try { + builder.Build(); + } catch (Standard_Failure& e) { + // We fallback to BRepBuilderAPI_Transformed, but likely with visual artefacts. + if (!(is_continuous || force_transformed)) { + return process_sweep_as_pipe(wire, section, result, true); + } else { + throw e; + } + } + builder.MakeSolid(); + result = builder.Shape(); +} + +void IfcGeom::util::sort_edges(const TopoDS_Wire & wire, std::vector& sorted_edges) { + TopTools_IndexedDataMapOfShapeListOfShape map; + TopExp::MapShapesAndAncestors(wire, TopAbs_VERTEX, TopAbs_EDGE, map); + + for (int i = 1; i <= map.Extent(); ++i) { + if (map.FindFromIndex(i).Extent() > 2) { + Logger::Warning("Self-intersecting Directrix"); + } + } + + std::set seen; + + auto num_edges = IfcGeom::Kernel::count(wire, TopAbs_EDGE); + + TopoDS_Vertex v0, v1; + // @todo this creates the ancestor map twice + TopExp::Vertices(wire, v0, v1); + + bool ignore_first_equality_because_closed = v0.IsSame(v1); + + // @todo this probably still does not work on a closed wire consisting of one (circular) edge. + + while ((int)sorted_edges.size() < num_edges && + (!v0.IsSame(v1) || ignore_first_equality_because_closed)) { + ignore_first_equality_because_closed = false; + if (!map.Contains(v0)) { + throw std::runtime_error("Disconnected vertex"); + } + const TopTools_ListOfShape& es = map.FindFromKey(v0); + TopoDS_Vertex ve0, ve1; + TopTools_ListIteratorOfListOfShape it(es); + bool added = false; + for (; it.More(); it.Next()) { + const TopoDS_Edge& e = TopoDS::Edge(it.Value()); + TopExp::Vertices(e, ve0, ve1, true); + if (ve0.IsSame(v0) && seen.find(&*e.TShape()) == seen.end()) { + sorted_edges.push_back(e); + v0 = ve1; + added = true; + seen.insert(&*e.TShape()); + break; + } + } + if (!added) { + throw std::runtime_error("Disconnected edge"); + } + } +} + +// #939: a closed loop causes failed triangulation in 7.3 and artefacts +// in 7.4 so we break up a closed wire into two equal parts. +void IfcGeom::util::break_closed(const TopoDS_Wire & wire, std::vector& wires) { + std::vector sorted_edges; + sort_edges(wire, sorted_edges); + + if (sorted_edges.size() == 1) { + wires.push_back(wire); + return; + } + + BRep_Builder B; + + wires.emplace_back(); + B.MakeWire(wires.back()); + + for (size_t i = 0; i < sorted_edges.size(); ++i) { + if (i == sorted_edges.size() / 2) { + wires.emplace_back(); + B.MakeWire(wires.back()); + } + + const auto& e = sorted_edges[i]; + B.Add(wires.back(), e); + } +} + +void IfcGeom::util::segment_adjacent_non_linear(const TopoDS_Wire & wire, std::vector& wires) { + std::vector sorted_edges; + sort_edges(wire, sorted_edges); + + BRep_Builder B; + double u, v; + + wires.emplace_back(); + B.MakeWire(wires.back()); + + for (int i = 0; i < (int)sorted_edges.size() - 1; ++i) { + const auto& e = sorted_edges[i]; + Handle_Geom_Curve crv = BRep_Tool::Curve(e, u, v); + const bool is_linear = crv->DynamicType() == STANDARD_TYPE(Geom_Line); + + const auto& f = sorted_edges[i + 1]; + crv = BRep_Tool::Curve(f, u, v); + const bool next_is_linear = crv->DynamicType() == STANDARD_TYPE(Geom_Line); + + B.Add(wires.back(), e); + + if (!is_linear && !next_is_linear) { + wires.emplace_back(); + B.MakeWire(wires.back()); + } + } + + if (!sorted_edges.empty()) { + B.Add(wires.back(), sorted_edges.back()); + } +} + +// @todo make this generic for other sweeps not just swept disk +void IfcGeom::util::process_sweep(const TopoDS_Wire & wire, double radius, TopoDS_Shape & result) { + std::vector wires, wires_tmp; + segment_adjacent_non_linear(wire, wires_tmp); + for (auto& w : wires_tmp) { + break_closed(w, wires); + } + + TopoDS_Compound C; + BRep_Builder B; + if (wires.size() > 1) { + B.MakeCompound(C); + } + + for (auto& w : wires) { + TopoDS_Shape part; + + gp_Ax2 directrix; + if (!wire_to_ax(w, directrix)) { + continue; + } + Handle(Geom_Circle) circle = new Geom_Circle(directrix, radius); + TopoDS_Wire section = BRepBuilderAPI_MakeWire(BRepBuilderAPI_MakeEdge(circle)); + + if (is_single_circular_edge(w)) { + process_sweep_as_revolution(w, section, part); + } else if (is_single_linear_edge(w)) { + process_sweep_as_extrusion(w, section, part); + } else { + process_sweep_as_pipe(w, section, part); + } + if (wires.size() > 1) { + B.Add(C, part); + } else { + result = part; + } + } + + if (wires.size() > 1) { + result = C; + } + + /* + // Eliminate Swept Surfaces? + result = ShapeCustom::SweptToElementary(result); + + // Eliminate Trimmed Surfaces? + ShapeBuild_ReShape sbrs; + BRep_Builder b; + TopExp_Explorer exp(result, TopAbs_FACE); + for (; exp.More(); exp.Next()) { + const TopoDS_Face& f = TopoDS::Face(exp.Current()); + auto S = BRep_Tool::Surface(f); + if (S->IsKind(STANDARD_TYPE(Geom_RectangularTrimmedSurface))) { + auto RTS = Handle(Geom_RectangularTrimmedSurface)::DownCast(S); + auto B = RTS->BasisSurface(); + TopoDS_Shape newf = f.EmptyCopied(); + // @todo Is it ok to assume no location? + b.MakeFace(TopoDS::Face(newf), B, BRep_Tool::Tolerance(f)); + sbrs.Replace(f, newf); + } + } + result = sbrs.Apply(result); + */ +} diff --git a/src/ifcgeom/kernels/opencascade/sweep_utils.h b/src/ifcgeom/kernels/opencascade/sweep_utils.h new file mode 100644 index 0000000000..033891d9c9 --- /dev/null +++ b/src/ifcgeom/kernels/opencascade/sweep_utils.h @@ -0,0 +1,59 @@ +/******************************************************************************** + * * + * 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 . * + * * + ********************************************************************************/ + +#ifndef SWEEP_UTILS_H +#define SWEEP_UTILS_H + +#include +#include + +#include + +namespace IfcGeom { + namespace util { + + bool wire_is_c1_continuous(const TopoDS_Wire& w, double tol); + + bool wire_to_ax(const TopoDS_Wire& wire, gp_Ax2& directrix); + + bool is_single_linear_edge(const TopoDS_Wire& wire); + + bool is_single_circular_edge(const TopoDS_Wire& wire); + + void process_sweep_as_extrusion(const TopoDS_Wire& wire, const TopoDS_Wire& section, TopoDS_Shape& result); + + void process_sweep_as_revolution(const TopoDS_Wire& wire, const TopoDS_Wire& section, TopoDS_Shape& result); + + void process_sweep_as_pipe(const TopoDS_Wire& wire, const TopoDS_Wire& section, TopoDS_Shape& result, bool force_transformed = false); + + void sort_edges(const TopoDS_Wire& wire, std::vector& sorted_edges); + + + // #939: a closed loop causes failed triangulation in 7.3 and artefacts + // in 7.4 so we break up a closed wire into two equal parts. + void break_closed(const TopoDS_Wire& wire, std::vector& wires); + + void segment_adjacent_non_linear(const TopoDS_Wire& wire, std::vector& wires); + + // @todo make this generic for other sweeps not just swept disk + void process_sweep(const TopoDS_Wire& wire, double radius, TopoDS_Shape& result); + } +} + +#endif diff --git a/src/ifcgeom/kernels/opencascade/wire_builder.cpp b/src/ifcgeom/kernels/opencascade/wire_builder.cpp index cc36324ffe..f63f88f498 100644 --- a/src/ifcgeom/kernels/opencascade/wire_builder.cpp +++ b/src/ifcgeom/kernels/opencascade/wire_builder.cpp @@ -1,7 +1,7 @@ #include "wire_builder.h" -#include "../../../ifcparse/IfcLogger.h" -#include "../../exceptions.h" +#include "../ifcparse/IfcLogger.h" +#include "../ifcgeom_schema_agnostic/Kernel.h" #include #include @@ -65,7 +65,7 @@ TopoDS_Wire IfcGeom::util::adjust(const TopoDS_Wire & w, const TopoDS_Vertex & v GC_MakeCircle mc(p1, p2, p3); if (!mc.IsDone()) { - throw ifcopenshell::geometry::geometry_exception("Failed to adjust circle"); + throw IfcGeom::geometry_exception("Failed to adjust circle"); } TopoDS_Edge edge = BRepBuilderAPI_MakeEdge(mc.Value(), p1, p3).Edge(); @@ -73,7 +73,7 @@ TopoDS_Wire IfcGeom::util::adjust(const TopoDS_Wire & w, const TopoDS_Vertex & v builder.Add(edge); return builder.Wire(); } else { - throw ifcopenshell::geometry::geometry_exception("Unexpected wire to adjust"); + throw IfcGeom::geometry_exception("Unexpected wire to adjust"); } } diff --git a/src/ifcgeom/kernels/opencascade/wire_builder.h b/src/ifcgeom/kernels/opencascade/wire_builder.h index 34178b815d..7a75c32a7a 100644 --- a/src/ifcgeom/kernels/opencascade/wire_builder.h +++ b/src/ifcgeom/kernels/opencascade/wire_builder.h @@ -20,7 +20,7 @@ #ifndef WIRE_BUILDER_H #define WIRE_BUILDER_H -#include "../../../ifcparse/IfcBaseClass.h" +#include "../ifcparse/IfcBaseClass.h" #include diff --git a/src/ifcgeom/kernels/opencascade/wire_utils.cpp b/src/ifcgeom/kernels/opencascade/wire_utils.cpp new file mode 100644 index 0000000000..5c53233012 --- /dev/null +++ b/src/ifcgeom/kernels/opencascade/wire_utils.cpp @@ -0,0 +1,569 @@ +#include "wire_utils.h" + +#include "../ifcparse/IfcLogger.h" +#include "../ifcgeom_schema_agnostic/Kernel.h" +#include "../ifcgeom_schema_agnostic/IfcGeomTree.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +bool IfcGeom::util::approximate_plane_through_wire(const TopoDS_Wire& wire, gp_Pln& plane, double eps_) { + // Newell's Method is used for the normal calculation + // as a simple edge cross product can give opposite results + // for a concave face boundary. + // Reference: Graphics Gems III p. 231 + + const double eps2 = eps_ * eps_; + + double x = 0, y = 0, z = 0; + gp_Pnt current, previous, first; + gp_XYZ center; + int n = 0; + + BRepTools_WireExplorer exp(wire); + + for (;; exp.Next()) { + const bool has_more = exp.More() != 0; + if (has_more) { + const TopoDS_Vertex& v = exp.CurrentVertex(); + current = BRep_Tool::Pnt(v); + center += current.XYZ(); + } else { + current = first; + } + if (n) { + const double& xn = previous.X(); + const double& yn = previous.Y(); + const double& zn = previous.Z(); + const double& xn1 = current.X(); + const double& yn1 = current.Y(); + const double& zn1 = current.Z(); + x += (yn - yn1)*(zn + zn1); + y += (xn + xn1)*(zn - zn1); + z += (xn - xn1)*(yn + yn1); + } else { + first = current; + } + if (!has_more) { + break; + } + previous = current; + ++n; + } + + if (n < 3) { + return false; + } + + gp_Vec v(x, y, z); + if (v.SquareMagnitude() < eps_ * eps_) { + Logger::Warning("Degenerate face boundary in normal estimation"); + return false; + } + + plane = gp_Pln(center / n, v); + + exp.Init(wire); + for (; exp.More(); exp.Next()) { + const TopoDS_Vertex& v = exp.CurrentVertex(); + current = BRep_Tool::Pnt(v); + if (plane.SquareDistance(current) > eps2) { + return false; + } + } + + return true; +} + +bool IfcGeom::util::flatten_wire(TopoDS_Wire& wire, double eps) { + gp_Pln pln; + if (!approximate_plane_through_wire(wire, pln, eps)) { + return false; + } + TopoDS_Face face = BRepBuilderAPI_MakeFace(pln).Face(); + BRepAlgo_NormalProjection proj(face); + proj.Add(wire); + proj.Build(); + if (!proj.IsDone()) { + return false; + } + TopTools_ListOfShape list; + proj.BuildWire(list); + if (list.Extent() != 1) { + return false; + } + wire = TopoDS::Wire(list.First()); + return true; +} + +IfcGeom::util::triangulate_wire_result IfcGeom::util::triangulate_wire(const std::vector& wires, TopTools_ListOfShape& faces) { + // This is a bit of a precarious approach, but seems to work for the + // versions of OCCT tested for. OCCT has a Delaunay triangulation function + // BRepMesh_Delaun, but it is notoriously hard to interpret the results + // (due to the Bowyer-Watson super triangle perhaps?). Therefore + // alternatively we use the regular OCCT incremental mesher on a new face + // created from the UV coordinates of the original wire. Pray to our gods + // that the vertex coordinates are unaffected by the meshing algorithm and + // map them back to 3d coordinates when iterating over the mesh triangles. + + // In addition, to maintain a manifold shell, we need to make sure that + // every edge from the input wire is used exactly once in the list of + // resulting faces. And that other internal edges are used twice. + + typedef std::pair uv_node; + + gp_Pln pln; + if (!approximate_plane_through_wire(wires.front(), pln, std::numeric_limits::infinity())) { + return TRIANGULATE_WIRE_FAIL; + } + + const gp_XYZ& udir = pln.Position().XDirection().XYZ(); + const gp_XYZ& vdir = pln.Position().YDirection().XYZ(); + const gp_XYZ& pnt = pln.Position().Location().XYZ(); + + std::map mapping; + std::map, TopoDS_Edge> existing_edges, new_edges; + + std::unique_ptr mf; + + for (auto it = wires.begin(); it != wires.end(); ++it) { + const TopoDS_Wire& wire = *it; + BRepTools_WireExplorer exp(wire); + BRepBuilderAPI_MakePolygon mp; + + // Add UV coordinates to a newly created polygon + for (; exp.More(); exp.Next()) { + // Project onto plane + const TopoDS_Vertex& V = exp.CurrentVertex(); + gp_Pnt p = BRep_Tool::Pnt(V); + double u = (p.XYZ() - pnt).Dot(udir); + double v = (p.XYZ() - pnt).Dot(vdir); + mp.Add(gp_Pnt(u, v, 0.)); + + mapping.insert(std::make_pair(std::make_pair(u, v), V)); + + // Store existing edges in a map so that triangles can + // actually reference the preexisting edges. + const TopoDS_Edge& e = exp.Current(); + TopoDS_Vertex V0, V1; + TopExp::Vertices(e, V0, V1, true); + gp_Pnt p0 = BRep_Tool::Pnt(V0); + gp_Pnt p1 = BRep_Tool::Pnt(V1); + double u0 = (p0.XYZ() - pnt).Dot(udir); + double v0 = (p0.XYZ() - pnt).Dot(vdir); + double u1 = (p1.XYZ() - pnt).Dot(udir); + double v1 = (p1.XYZ() - pnt).Dot(vdir); + uv_node uv0 = std::make_pair(u0, v0); + uv_node uv1 = std::make_pair(u1, v1); + existing_edges.insert(std::make_pair(std::make_pair(uv0, uv1), e)); + existing_edges.insert(std::make_pair(std::make_pair(uv1, uv0), TopoDS::Edge(e.Reversed()))); + } + + // Not closed by default + mp.Close(); + + if (mf) { + if (it - 1 == wires.begin()) { + // @todo is this necessary? + TopoDS_Face f = mf->Face(); + mf->Init(f); + } + mf->Add(mp.Wire()); + } else { + mf.reset(new BRepBuilderAPI_MakeFace(mp.Wire())); + } + } + + const TopoDS_Face& face = mf->Face(); + + // Create a triangular mesh from the face + BRepMesh_IncrementalMesh(face, Precision::Confusion()); + + int n123[3]; + TopLoc_Location loc; + Handle_Poly_Triangulation tri = BRep_Tool::Triangulation(face, loc); + + if (!tri.IsNull()) { + + const Poly_Array1OfTriangle& triangles = tri->Triangles(); + for (int i = 1; i <= triangles.Length(); ++i) { + if (face.Orientation() == TopAbs_REVERSED) + triangles(i).Get(n123[2], n123[1], n123[0]); + else triangles(i).Get(n123[0], n123[1], n123[2]); + + // Create polygons from the mesh vertices + BRepBuilderAPI_MakeWire mp2; + for (int j = 0; j < 3; ++j) { + + uv_node uvnodes[2]; + TopoDS_Vertex vs[2]; + + for (int k = 0; k < 2; ++k) { + const gp_Pnt& uv = tri->Node(n123[(j + k) % 3]); + uvnodes[k] = std::make_pair(uv.X(), uv.Y()); + + auto it = mapping.find(uvnodes[k]); + if (it == mapping.end()) { + Logger::Error("Internal error: unable to unproject uv-mesh"); + return TRIANGULATE_WIRE_FAIL; + } + + vs[k] = it->second; + } + + auto it = existing_edges.find(std::make_pair(uvnodes[0], uvnodes[1])); + if (it != existing_edges.end()) { + // This is a boundary edge, reuse existing edge from wire + mp2.Add(it->second); + } else { + auto jt = new_edges.find(std::make_pair(uvnodes[0], uvnodes[1])); + if (jt != new_edges.end()) { + // We have already added the reverse as part of another + // triangle, reuse this edge. + mp2.Add(TopoDS::Edge(jt->second)); + } else { + // This is a new internal edge. Register the reverse + // for reuse later. We need to be sure to reuse vertices + // for the edge construction because otherwise the wire + // builder will use geometrical proximity for vertex + // connections in which case the edge will be copied + // and no longer partner with other edges from the shell. + TopoDS_Edge ne = BRepBuilderAPI_MakeEdge(vs[0], vs[1]); + mp2.Add(ne); + // Store the reverse to be picked up later. + new_edges.insert(std::make_pair(std::make_pair(uvnodes[1], uvnodes[0]), TopoDS::Edge(ne.Reversed()))); + } + } + } + + BRepBuilderAPI_MakeFace mft(mp2.Wire()); + if (mft.IsDone()) { + TopoDS_Face triangle_face = mft.Face(); + TopoDS_Iterator jt(triangle_face, false); + for (; jt.More(); jt.Next()) { + const TopoDS_Wire& w = TopoDS::Wire(jt.Value()); + if (w.Orientation() != wires.front().Orientation()) { + triangle_face.Reverse(); + } + } + faces.Append(triangle_face); + } else { + Logger::Error("Internal error: missing face"); + return TRIANGULATE_WIRE_FAIL; + } + } + } + + TopTools_IndexedDataMapOfShapeListOfShape mape, mapn; + for (auto& wire : wires) { + TopExp::MapShapesAndAncestors(wire, TopAbs_EDGE, TopAbs_WIRE, mape); + } + TopTools_ListIteratorOfListOfShape it(faces); + for (; it.More(); it.Next()) { + TopExp::MapShapesAndAncestors(it.Value(), TopAbs_EDGE, TopAbs_WIRE, mapn); + } + + // Validation + bool non_manifold = false; + + for (int i = 1; i <= mape.Extent(); ++i) { +#if OCC_VERSION_HEX >= 0x70000 + TopTools_ListOfShape val; + if (!mapn.FindFromKey(mape.FindKey(i), val)) { +#else + bool contains = false; + try { + TopTools_ListOfShape val = mapn.FindFromKey(mape.FindKey(i)); + contains = true; + } catch (Standard_NoSuchObject&) {} + if (!contains) { +#endif + // All existing edges need to exist in the new faces + Logger::Error("Internal error, missing edge from triangulation"); + non_manifold = true; + } + } + + for (int i = 1; i <= mapn.Extent(); ++i) { + const TopoDS_Shape& v = mapn.FindKey(i); + int n = mapn.FindFromIndex(i).Extent(); + // Existing edges are boundaries with use 1 + // New edges are internal with use 2 + if (n != (mape.Contains(v) ? 1 : 2)) { + Logger::Error("Internal error, non-manifold result from triangulation"); + non_manifold = true; + } + } + + return non_manifold ? TRIANGULATE_WIRE_NON_MANIFOLD : TRIANGULATE_WIRE_OK; +} + +namespace { + + /* + * A small helper utility to wrap around a numeric range + */ + class bounded_int { + private: + int i; + size_t n; + public: + bounded_int(int i, size_t n) : i(i), n(n) {} + + bounded_int& operator--() { + --i; + if (i == -1) { + i = (int)n - 1; + } + return *this; + } + + bounded_int& operator++() { + ++i; + if (i == (int)n) { + i = 0; + } + return *this; + } + + operator int() { return i; } + }; +} + +bool IfcGeom::util::wire_intersections(const TopoDS_Wire& wire, TopTools_ListOfShape& wires, double eps, double eps_real) { + if (!wire.Closed()) { + wires.Append(wire); + return false; + } + + int n = IfcGeom::Kernel::count(wire, TopAbs_EDGE); + if (n < 3) { + wires.Append(wire); + return false; + } + + // Note: initialize empty + Handle(ShapeExtend_WireData) wd = new ShapeExtend_WireData(); + + // ... to be sure to get consecutive edges + BRepTools_WireExplorer exp(wire); + IfcGeom::impl::tree tree; + + int edge_idx = 0; + for (; exp.More(); exp.Next()) { + wd->Add(exp.Current()); + if (n > 64) { + // tfk: indices in tree are 0-based vd 1-based in wiredata + tree.add(edge_idx++, exp.Current()); + } + } + + if (wd->NbEdges() != n) { + // If the number of edges differs, BRepTools_WireExplorer did not + // reach every edge, probably due to loops exactly at vertex locations. + // This is not supported by this algorithm which only elimates loops + // due to edge crossings. + + throw geometry_exception("Invalid loop"); + } + + bool intersected = false; + + // tfk: Extrema on infinite curves proved to be more robust. + // TopoDS_Face face = BRepBuilderAPI_MakeFace(wire, true).Face(); + // ShapeAnalysis_Wire saw(wd, face, getValue(GV_PRECISION)); + + // @todo: should this start from 0 in case of n > 64? + for (int i = 2; i < n; ++i) { + + std::vector js; + if (n > 64) { + Bnd_Box b; + BRepBndLib::Add(wd->Edge(i + 1), b); + b.Enlarge(eps); + js = tree.select_box(b, false); + } else { + boost::push_back(js, boost::irange(0, i - 1)); + } + + for (std::vector::const_iterator it = js.begin(); it != js.end(); ++it) { + int j = *it; + + if (n > 64) { + if (j > i) { + continue; + } + if ((std::max)(i, j) - (std::min)(i, j) <= 1) { + continue; + } + } + + // Only check non-consecutive edges + if (i == n - 1 && j == 0) continue; + + double u11, u12, u21, u22, U1, U2; + GeomAPI_ExtremaCurveCurve ecc( + BRep_Tool::Curve(wd->Edge(i + 1), u11, u12), + BRep_Tool::Curve(wd->Edge(j + 1), u21, u22) + ); + + // @todo: extend this to work in case of multiple extrema and curved segments. + const bool unbounded_intersects = (!ecc.Extrema().IsParallel() && ecc.NbExtrema() == 1 && ecc.Distance(1) < eps); + if (unbounded_intersects) { + ecc.Parameters(1, U1, U2); + + if (u11 > u12) { + std::swap(u11, u12); + } + if (u21 > u22) { + std::swap(u21, u22); + } + + /// @todo: tfk: probably need different thresholds on non-linear curves + u11 -= eps; + u12 += eps; + u21 -= eps; + u22 += eps; + + // tfk: code below is for ShapeAnalysis_Wire::CheckIntersectingEdges() + // IntRes2d_SequenceOfIntersectionPoint points2d; + // TColgp_SequenceOfPnt points3d; + // TColStd_SequenceOfReal errors; + // if (saw.CheckIntersectingEdges(i + 1, j + 1, points2d, points3d, errors)) { + + if (u11 < U1 && U1 < u12 && u21 < U2 && U2 < u22) { + + intersected = true; + + // Explore a forward and backward cycle from the intersection point + for (int fb = 0; fb <= 1; ++fb) { + const bool forward = fb == 0; + + BRepBuilderAPI_MakeWire mw; + bool first = true; + + for (bounded_int k(j, n);;) { + bool intersecting = k == j || k == i; + if (intersecting) { + TopoDS_Edge e = wd->Edge(k + 1); + + TopoDS_Vertex v1, v2; + TopExp::Vertices(e, v1, v2, true); + const TopoDS_Vertex* v = first == forward ? &v2 : &v1; + + // gp_Pnt p2 = points3d.Value(1); + + gp_Pnt p1 = BRep_Tool::Pnt(*v); + gp_Pnt pp1, pp2; + ecc.Points(1, pp1, pp2); + const gp_Pnt& p2 = k == i ? pp1 : pp2; + + // Substitute with a new edge from/to the intersection point + if (p1.Distance(p2) > eps_real * 2) { + double _, __; + Handle_Geom_Curve crv = BRep_Tool::Curve(e, _, __); + BRepBuilderAPI_MakeEdge me(crv, p1, p2); + TopoDS_Edge ed = me.Edge(); + mw.Add(ed); + } + + first = false; + } else { + // Re-use original edge + mw.Add(wd->Edge(k + 1)); + } + + if (k == i) { + break; + } + + if (forward) { + ++k; + } else { + --k; + } + } + + ShapeFix_Wire sfw; + sfw.Load(mw.Wire()); + sfw.Perform(); + + // Recursively process both cuts + + // @todo this is a change in behaviour with eps precomputed from the kernel + // instead of adaptively calculated for the successive iterations. + wire_intersections(sfw.Wire(), wires, eps, eps_real); + } + + return true; + } + + } + } + } + + // No intersections found, append original wire + if (!intersected) { + wires.Append(wire); + } + + return intersected; +} + +void IfcGeom::util::select_largest(const TopTools_ListOfShape& shapes, TopoDS_Shape& largest) { + double mass = 0.; + TopTools_ListIteratorOfListOfShape it(shapes); + for (; it.More(); it.Next()) { + /* + // tfk: bounding box is more efficient probably + const TopoDS_Wire& w = TopoDS::Wire(it.Value()); + TopoDS_Face face = BRepBuilderAPI_MakeFace(w).Face(); + const double m = face_area(face); + */ + + Bnd_Box bb; + BRepBndLib::AddClose(it.Value(), bb); + double xyz_min[3], xyz_max[3]; + bb.Get(xyz_min[0], xyz_min[1], xyz_min[2], xyz_max[0], xyz_max[1], xyz_max[2]); + + // @todo hard coded precision. + // @todo this is a really strange measure for wire size. Why not use newell's + // method to project to plane and then calculate size of the 2d bbox? + const double eps = 1.e-5; + + double m = 1.; + for (int i = 0; i < 3; ++i) { + if (Precision::IsNegativeInfinite(xyz_min[i])) { + xyz_min[i] = 0.; + } + if (Precision::IsInfinite(xyz_max[i])) { + xyz_max[i] = 0.; + } + m *= (xyz_max[i] + eps) - (xyz_min[i] - eps); + } + + if (m > mass) { + mass = m; + largest = it.Value(); + } + } +} \ No newline at end of file diff --git a/src/ifcgeom/kernels/opencascade/wire_utils.h b/src/ifcgeom/kernels/opencascade/wire_utils.h new file mode 100644 index 0000000000..5dba461573 --- /dev/null +++ b/src/ifcgeom/kernels/opencascade/wire_utils.h @@ -0,0 +1,28 @@ +#include +#include +#include + +#include + +namespace ifcopenshell { namespace geometry { + namespace util { + bool approximate_plane_through_wire(const TopoDS_Wire& wire, gp_Pln& plane, double eps); + + bool flatten_wire(TopoDS_Wire& wire, double eps); + + enum triangulate_wire_result { + TRIANGULATE_WIRE_FAIL, + TRIANGULATE_WIRE_OK, + TRIANGULATE_WIRE_NON_MANIFOLD, + }; + + /// Triangulate the set of wires. The firstmost wire is assumed to be the outer wire. + triangulate_wire_result triangulate_wire(const std::vector& wires, TopTools_ListOfShape& faces); + + // eps: tolerance added to wire intersection checks, can be zero + // eps_real: tolerance used to construct new edge geometry around intersection points, cannot be zero + bool wire_intersections(const TopoDS_Wire& wire, TopTools_ListOfShape& wires, double eps, double eps_real); + + void select_largest(const TopTools_ListOfShape& shapes, TopoDS_Shape& largest); + } +} } diff --git a/src/ifcgeom/schema_agnostic/Converter.cpp b/src/ifcgeom/schema_agnostic/Converter.cpp index d058e9b441..0bb5267c04 100644 --- a/src/ifcgeom/schema_agnostic/Converter.cpp +++ b/src/ifcgeom/schema_agnostic/Converter.cpp @@ -8,6 +8,7 @@ ifcopenshell::geometry::Converter::Converter(const std::string& geometry_library { kernel_ = kernels::construct(geometry_library, file); mapping_ = impl::mapping_implementations().construct(file, settings_); + } namespace { @@ -32,7 +33,7 @@ ifcopenshell::geometry::NativeElement* ifcopenshell::geometry::Converter::create auto product = (IfcUtil::IfcBaseEntity*) product_node->instance; const std::string product_type = product->declaration().name(); // @todo - element_settings s(settings_, 1.0 /*getValue(GV_LENGTH_UNIT) */, product_type); + element_settings s(settings_, settings.getValue(ConversionSettings::GV_LENGTH_UNIT), product_type); std::stringstream representation_id_builder; diff --git a/src/ifcgeom/schema_agnostic/Converter.h b/src/ifcgeom/schema_agnostic/Converter.h index 4b5f4d32b5..87fc19452d 100644 --- a/src/ifcgeom/schema_agnostic/Converter.h +++ b/src/ifcgeom/schema_agnostic/Converter.h @@ -5,6 +5,7 @@ #include "../../ifcgeom/settings.h" #include "../../ifcgeom/schema_agnostic/ConversionResult.h" #include "../../ifcgeom/abstract_mapping.h" +#include "../../ifcgeom/ConversionSettings.h" #include "../../ifcgeom/kernel_agnostic/AbstractKernel.h" #include @@ -24,37 +25,9 @@ namespace ifcopenshell { namespace geometry { std::map cache_; public: - kernels::AbstractKernel* kernel() { return kernel_; } + ConversionSettings settings; - // Tolerances and settings for various geometrical operations: - enum GeomValue { - // Specifies the deflection of the mesher - // Default: 0.001m / 1mm - GV_DEFLECTION_TOLERANCE, - // Specifies the minimal area of a face to be included in an IfcConnectedFaceset - // Read-only - GV_MINIMAL_FACE_AREA, - // Specifies the threshold distance under which cartesian points are deemed equal - // Read-only - GV_POINT_EQUALITY_TOLERANCE, - // Specifies maximum number of faces for a shell to be reoriented. - // Default: -1 - GV_MAX_FACES_TO_ORIENT, - // The length unit used the creation of TopoDS_Shapes, primarily affects the - // interpretation of IfcCartesianPoints and IfcVector magnitudes - // DefaultL 1.0 - GV_LENGTH_UNIT, - // The plane angle unit used for the creation of TopoDS_Shapes, primarily affects - // the interpretation of IfcParamaterValues of IfcTrimmedCurves - // Default: -1.0 (= not set, fist try degrees, then radians) - GV_PLANEANGLE_UNIT, - // The precision used in boolean operations, setting this value too low results - // in artefacts and potentially modelling failures - // Default: 0.00001 (obtained from IfcGeometricRepresentationContext if available) - GV_PRECISION, - // Whether to process shapes of type Face or higher (1) Wire or lower (-1) or all (0) - GV_DIMENSIONALITY - }; + kernels::AbstractKernel* kernel() { return kernel_; } Converter(const std::string& geometry_library, IfcParse::IfcFile* file, ifcopenshell::geometry::settings& settings); @@ -62,16 +35,6 @@ namespace ifcopenshell { namespace geometry { abstract_mapping* mapping() const { return mapping_; } - /* - virtual void setValue(GeomValue var, double value) { - implementation_->setValue(var, value); - } - - virtual double getValue(GeomValue var) const { - return implementation_->getValue(var); - } - */ - /* virtual NativeElement* convert( const IteratorSettings& settings, IfcUtil::IfcBaseClass* representation,