mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-12 18:43:26 +00:00
Refactoring
This commit is contained in:
@@ -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];
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
#ifndef CONVERSIONSETTINGS_H
|
||||
#define CONVERSIONSETTINGS_H
|
||||
|
||||
#include <array>
|
||||
|
||||
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<double, 8> 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
|
||||
@@ -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<float, float>* ifcopenshell::geometry::kernels::AbstractKernel::create_brep_for_representation_and_product<float, float>(
|
||||
// const IteratorSettings& settings, IfcSchema::IfcRepresentation* representation, IfcSchema::IfcProduct* product);
|
||||
//template IFC_GEOM_API ifcopenshell::geometry::kernels::NativeElement<float, double>* ifcopenshell::geometry::kernels::AbstractKernel::create_brep_for_representation_and_product<float, double>(
|
||||
// const IteratorSettings& settings, IfcSchema::IfcRepresentation* representation, IfcSchema::IfcProduct* product);
|
||||
//template IFC_GEOM_API ifcopenshell::geometry::kernels::NativeElement<double, double>* ifcopenshell::geometry::kernels::AbstractKernel::create_brep_for_representation_and_product<double, double>(
|
||||
// const IteratorSettings& settings, IfcSchema::IfcRepresentation* representation, IfcSchema::IfcProduct* product);
|
||||
//
|
||||
//template IFC_GEOM_API ifcopenshell::geometry::kernels::NativeElement<float, float>* ifcopenshell::geometry::kernels::AbstractKernel::create_brep_for_processed_representation<float, float>(
|
||||
// const IteratorSettings& settings, IfcSchema::IfcRepresentation* representation, IfcSchema::IfcProduct* product, ifcopenshell::geometry::kernels::NativeElement<float, float>* brep);
|
||||
//template IFC_GEOM_API ifcopenshell::geometry::kernels::NativeElement<float, double>* ifcopenshell::geometry::kernels::AbstractKernel::create_brep_for_processed_representation<float, double>(
|
||||
// const IteratorSettings& settings, IfcSchema::IfcRepresentation* representation, IfcSchema::IfcProduct* product, ifcopenshell::geometry::kernels::NativeElement<float, double>* brep);
|
||||
//template IFC_GEOM_API ifcopenshell::geometry::kernels::NativeElement<double, double>* ifcopenshell::geometry::kernels::AbstractKernel::create_brep_for_processed_representation<double, double>(
|
||||
// const IteratorSettings& settings, IfcSchema::IfcRepresentation* representation, IfcSchema::IfcProduct* product, ifcopenshell::geometry::kernels::NativeElement<double, double>* brep);
|
||||
@@ -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&);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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 <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
/********************************************************************************
|
||||
* *
|
||||
* Implementations of the various conversion functions defined in IfcRegister.h *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <gp_Pnt.hxx>
|
||||
#include <gp_Vec.hxx>
|
||||
#include <gp_Dir.hxx>
|
||||
#include <gp_Pnt2d.hxx>
|
||||
#include <gp_Vec2d.hxx>
|
||||
#include <gp_Dir2d.hxx>
|
||||
#include <gp_Mat.hxx>
|
||||
#include <gp_Mat2d.hxx>
|
||||
#include <gp_GTrsf.hxx>
|
||||
#include <gp_GTrsf2d.hxx>
|
||||
#include <gp_Trsf.hxx>
|
||||
#include <gp_Trsf2d.hxx>
|
||||
#include <gp_Ax3.hxx>
|
||||
#include <gp_Ax2d.hxx>
|
||||
#include <gp_Pln.hxx>
|
||||
#include <gp_Circ.hxx>
|
||||
|
||||
#include <TColgp_Array1OfPnt.hxx>
|
||||
#include <TColgp_Array1OfPnt2d.hxx>
|
||||
#include <TColStd_Array1OfReal.hxx>
|
||||
#include <TColStd_Array1OfInteger.hxx>
|
||||
#include <Geom_Line.hxx>
|
||||
#include <Geom_Circle.hxx>
|
||||
#include <Geom_Ellipse.hxx>
|
||||
#include <Geom_TrimmedCurve.hxx>
|
||||
|
||||
#include <BRepOffsetAPI_Sewing.hxx>
|
||||
#include <BRepBuilderAPI_MakeFace.hxx>
|
||||
#include <BRepBuilderAPI_MakeEdge.hxx>
|
||||
#include <BRepBuilderAPI_MakeWire.hxx>
|
||||
#include <BRepBuilderAPI_MakePolygon.hxx>
|
||||
#include <BRepBuilderAPI_MakeVertex.hxx>
|
||||
|
||||
#include <TopoDS.hxx>
|
||||
#include <TopoDS_Wire.hxx>
|
||||
#include <TopoDS_Face.hxx>
|
||||
#include <TopExp_Explorer.hxx>
|
||||
|
||||
#include <BRepPrimAPI_MakePrism.hxx>
|
||||
#include <BRepBuilderAPI_MakeShell.hxx>
|
||||
#include <BRepBuilderAPI_MakeSolid.hxx>
|
||||
#include <BRepPrimAPI_MakeHalfSpace.hxx>
|
||||
#include <BRepAlgoAPI_Cut.hxx>
|
||||
|
||||
#include <ShapeFix_Shape.hxx>
|
||||
#include <ShapeFix_ShapeTolerance.hxx>
|
||||
#include <ShapeFix_Solid.hxx>
|
||||
|
||||
#include <BRepFilletAPI_MakeFillet2d.hxx>
|
||||
|
||||
#include <TopLoc_Location.hxx>
|
||||
|
||||
#include "../../../ifcgeom/kernels/opencascade/IfcGeom.h"
|
||||
|
||||
#ifdef SCHEMA_HAS_IfcBSplineCurveWithKnots
|
||||
#include <Geom_BSplineCurve.hxx>
|
||||
#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<int> mults = l->KnotMultiplicities();
|
||||
const std::vector<double> 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<double> weights = rl->WeightsData();
|
||||
|
||||
i = 0;
|
||||
for (std::vector<double>::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<int>::const_iterator it = mults.begin(); it != mults.end(); ++it, ++i) {
|
||||
Mults(i) = *it;
|
||||
}
|
||||
|
||||
i = 0;
|
||||
for (std::vector<double>::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
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -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 <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
/********************************************************************************
|
||||
* *
|
||||
* Implementations of the various conversion functions defined in IfcRegister.h *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
#include <gp_Pnt.hxx>
|
||||
#include <gp_Vec.hxx>
|
||||
#include <gp_Dir.hxx>
|
||||
#include <gp_Pnt2d.hxx>
|
||||
#include <gp_Vec2d.hxx>
|
||||
#include <gp_Dir2d.hxx>
|
||||
#include <gp_Mat.hxx>
|
||||
#include <gp_Mat2d.hxx>
|
||||
#include <gp_GTrsf.hxx>
|
||||
#include <gp_GTrsf2d.hxx>
|
||||
#include <gp_Trsf.hxx>
|
||||
#include <gp_Trsf2d.hxx>
|
||||
#include <gp_Ax3.hxx>
|
||||
#include <gp_Ax2d.hxx>
|
||||
#include <gp_Pln.hxx>
|
||||
#include <gp_Circ.hxx>
|
||||
|
||||
#include <TColgp_Array1OfPnt.hxx>
|
||||
#include <TColgp_Array1OfPnt2d.hxx>
|
||||
#include <TColStd_Array1OfReal.hxx>
|
||||
#include <TColStd_Array1OfInteger.hxx>
|
||||
#include <Geom_Line.hxx>
|
||||
#include <Geom_Circle.hxx>
|
||||
#include <Geom_Ellipse.hxx>
|
||||
#include <Geom_TrimmedCurve.hxx>
|
||||
|
||||
#include <BRepOffsetAPI_Sewing.hxx>
|
||||
#include <BRepBuilderAPI_MakeFace.hxx>
|
||||
#include <BRepBuilderAPI_MakeEdge.hxx>
|
||||
#include <BRepBuilderAPI_MakeWire.hxx>
|
||||
#include <BRepBuilderAPI_MakePolygon.hxx>
|
||||
#include <BRepBuilderAPI_MakeVertex.hxx>
|
||||
|
||||
#include <TopoDS.hxx>
|
||||
#include <TopoDS_Wire.hxx>
|
||||
#include <TopoDS_Face.hxx>
|
||||
#include <TopExp_Explorer.hxx>
|
||||
|
||||
#include <BRepPrimAPI_MakePrism.hxx>
|
||||
#include <BRepBuilderAPI_MakeShell.hxx>
|
||||
#include <BRepBuilderAPI_MakeSolid.hxx>
|
||||
#include <BRepPrimAPI_MakeHalfSpace.hxx>
|
||||
#include <BRepAlgoAPI_Cut.hxx>
|
||||
|
||||
#include <ShapeFix_Shape.hxx>
|
||||
#include <ShapeFix_ShapeTolerance.hxx>
|
||||
#include <ShapeFix_Solid.hxx>
|
||||
|
||||
#include <BRepFilletAPI_MakeFillet2d.hxx>
|
||||
|
||||
#include <TopLoc_Location.hxx>
|
||||
|
||||
#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 <typename T>
|
||||
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 <typename T> struct dimension_count {};
|
||||
template <> struct dimension_count <gp_Trsf2d > { static const int n = 2; };
|
||||
template <> struct dimension_count <gp_GTrsf2d> { 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 <typename T>
|
||||
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<T>::n + 2; ++i) {
|
||||
for (int j = 1; j < dimension_count<T>::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<double> 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<double> 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;
|
||||
}
|
||||
@@ -1,660 +0,0 @@
|
||||
#include <Geom_Line.hxx>
|
||||
#include <Geom_Circle.hxx>
|
||||
#include <Geom_Ellipse.hxx>
|
||||
#include <Geom_BSplineCurve.hxx>
|
||||
|
||||
#include <Geom_Plane.hxx>
|
||||
#include <Geom_BSplineSurface.hxx>
|
||||
#include <Geom_CylindricalSurface.hxx>
|
||||
|
||||
#include <BRepTools_WireExplorer.hxx>
|
||||
|
||||
#include <TColgp_Array2OfPnt.hxx>
|
||||
#include <TColStd_Array1OfReal.hxx>
|
||||
#include <TColStd_Array2OfReal.hxx>
|
||||
#include <TColStd_Array1OfInteger.hxx>
|
||||
|
||||
#include "IfcGeom.h"
|
||||
|
||||
template <typename T, typename U>
|
||||
int convert_to_ifc(const T& t, U*& u, bool /*advanced*/) {
|
||||
std::vector<double> 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 <typename T, typename U>
|
||||
void opencascade_array_to_vector(T& t, std::vector<U>& u) {
|
||||
u.reserve(t.Length());
|
||||
for (int i = t.Lower(); i <= t.Upper(); ++i) {
|
||||
u.push_back(t.Value(i));
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T, typename U>
|
||||
void opencascade_array_to_vector2(T& t, std::vector< std::vector<U> >& u) {
|
||||
u.reserve(t.RowLength());
|
||||
for (int j = t.LowerRow(); j <= t.UpperRow(); ++j) {
|
||||
std::vector<U> 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<int> mults;
|
||||
std::vector<double> knots;
|
||||
std::vector<double> 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<double>::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<IfcSchema::IfcCartesianPoint> 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<IfcSchema::IfcCartesianPoint*> 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<int> umults;
|
||||
std::vector<int> vmults;
|
||||
std::vector<double> uknots;
|
||||
std::vector<double> vknots;
|
||||
std::vector< std::vector<double> > 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<double> >::const_iterator it = weights.begin(); it != weights.end(); ++it) {
|
||||
for (std::vector<double>::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<IfcSchema::IfcOrientedEdge>());
|
||||
}
|
||||
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 <typename U>
|
||||
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<IfcSchema::IfcRepresentationItem>());
|
||||
} 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::IfcRepresentationItem>());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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<IfcSchema::IfcCartesianPoint*> vertices;
|
||||
for (int i = 1; i <= nodes.Length(); ++i) {
|
||||
gp_Pnt pnt = nodes(i).Transformed(loc);
|
||||
std::vector<double> 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;
|
||||
}
|
||||
@@ -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 <Geom_Plane.hxx>
|
||||
#include <BRepLib_FindSurface.hxx>
|
||||
#include <ShapeFix_Edge.hxx>
|
||||
|
||||
#include <BRepBuilderAPI_GTransform.hxx>
|
||||
|
||||
|
||||
#include <Geom_Curve.hxx>
|
||||
@@ -134,354 +135,6 @@ using namespace ifcopenshell::geometry::kernels;
|
||||
|
||||
#include <BRepTools_WireExplorer.hxx>
|
||||
|
||||
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<TopoDS_Wire>& 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<double, double> uv_node;
|
||||
|
||||
gp_Pln pln;
|
||||
if (!approximate_plane_through_wire(wires.front(), pln, std::numeric_limits<double>::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<uv_node, TopoDS_Vertex> mapping;
|
||||
std::map<std::pair<uv_node, uv_node>, TopoDS_Edge> existing_edges, new_edges;
|
||||
|
||||
std::unique_ptr<BRepBuilderAPI_MakeFace> 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<faceset_helper> helper_scope;
|
||||
helper_scope.reset(new faceset_helper(this, l));
|
||||
|
||||
auto faces = l->children_as<taxonomy::face>();
|
||||
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 <BRepGProp.hxx>
|
||||
#include <GProp_GProps.hxx>
|
||||
@@ -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<int>& tree, std::vector<std::unique_ptr<gp_Pnt>>& pnts, std::set<int>& visited, int p, double eps) {
|
||||
visited.insert(p);
|
||||
|
||||
Bnd_Box b;
|
||||
b.Set(*pnts[p].get());
|
||||
b.Enlarge(eps);
|
||||
|
||||
std::vector<int> 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<taxonomy::point3> points;
|
||||
std::vector<taxonomy::loop*> loops;
|
||||
|
||||
for (auto& f : shell->children_as<taxonomy::face>()) {
|
||||
for (auto& l : f->children_as<taxonomy::loop>()) {
|
||||
loops.push_back(l);
|
||||
for (auto& e : l->children_as<taxonomy::edge>()) {
|
||||
// @todo make sure only cartesian points are provided here
|
||||
points.push_back(boost::get<taxonomy::point3>(e->start));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<gp_Pnt>> pnts(points.size());
|
||||
std::vector<TopoDS_Vertex> vertices(pnts.size());
|
||||
|
||||
// @todo
|
||||
impl::tree<int> tree;
|
||||
|
||||
BRep_Builder B;
|
||||
|
||||
Bnd_Box box;
|
||||
for (size_t i = 0; i < points.size(); ++i) {
|
||||
gp_Pnt* p = new gp_Pnt(convert_xyz<gp_Pnt>(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<double>::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<std::pair<int, int>, 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<int> 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<int, 2> edge_t;
|
||||
typedef std::set<edge_t> edge_set_t;
|
||||
std::set<edge_set_t> edge_sets;
|
||||
|
||||
for (auto& loop : loops) {
|
||||
std::vector<std::pair<int, int> > 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<std::string>(duplicate_faces) + " duplicate faces removed, " + boost::lexical_cast<std::string>(loops_removed) + " loops removed and " + boost::lexical_cast<std::string>(non_manifold) + " non-manifold edges for:", shell->instance);
|
||||
}
|
||||
}
|
||||
|
||||
#include <ShapeUpgrade_UnifySameDomain.hxx>
|
||||
#include <Extrema_ExtPC.hxx>
|
||||
#include <BRepTopAdaptor_FClass2d.hxx>
|
||||
|
||||
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<double>::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<double>::infinity();
|
||||
|
||||
TopTools_IndexedMapOfShape vertices, edges;
|
||||
|
||||
TopExp::MapShapes(a, TopAbs_VERTEX, vertices);
|
||||
TopExp::MapShapes(a, TopAbs_EDGE, edges);
|
||||
|
||||
impl::tree<int> 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<int> edge_idxs = tree.select_box(b, false);
|
||||
std::vector<int>::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<double>::infinity();
|
||||
|
||||
TopTools_IndexedMapOfShape faces;
|
||||
|
||||
TopExp::MapShapes(a, TopAbs_FACE, faces);
|
||||
|
||||
ifcopenshell::geometry::impl::tree<int> 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<int> face_idxs = tree.select_box(b, false);
|
||||
std::vector<int>::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 <BRepBuilderAPI_GTransform.hxx>
|
||||
|
||||
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");
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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 <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
/********************************************************************************
|
||||
* *
|
||||
* Implementations of the various conversion functions defined in IfcRegister.h *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
#define _USE_MATH_DEFINES
|
||||
#include <cmath>
|
||||
|
||||
#include <gp_Pnt.hxx>
|
||||
#include <gp_Vec.hxx>
|
||||
#include <gp_Dir.hxx>
|
||||
#include <gp_Pnt2d.hxx>
|
||||
#include <gp_Vec2d.hxx>
|
||||
#include <gp_Dir2d.hxx>
|
||||
#include <gp_Mat.hxx>
|
||||
#include <gp_Mat2d.hxx>
|
||||
#include <gp_GTrsf.hxx>
|
||||
#include <gp_GTrsf2d.hxx>
|
||||
#include <gp_Trsf.hxx>
|
||||
#include <gp_Trsf2d.hxx>
|
||||
#include <gp_Ax3.hxx>
|
||||
#include <gp_Ax2d.hxx>
|
||||
#include <gp_Pln.hxx>
|
||||
#include <gp_Circ.hxx>
|
||||
|
||||
#include <GC_MakeCircle.hxx>
|
||||
|
||||
#include <TColgp_Array1OfPnt.hxx>
|
||||
#include <TColgp_Array1OfPnt2d.hxx>
|
||||
#include <TColStd_Array1OfReal.hxx>
|
||||
#include <TColStd_Array1OfInteger.hxx>
|
||||
|
||||
#include <Geom_Line.hxx>
|
||||
#include <Geom_Circle.hxx>
|
||||
#include <Geom_Ellipse.hxx>
|
||||
#include <Geom_TrimmedCurve.hxx>
|
||||
|
||||
#include <BRepBuilderAPI_MakeVertex.hxx>
|
||||
#include <BRepBuilderAPI_MakeFace.hxx>
|
||||
#include <BRepBuilderAPI_MakeEdge.hxx>
|
||||
#include <BRepBuilderAPI_MakeWire.hxx>
|
||||
#include <BRepBuilderAPI_MakeShell.hxx>
|
||||
#include <BRepBuilderAPI_MakeSolid.hxx>
|
||||
#include <BRepBuilderAPI_MakePolygon.hxx>
|
||||
#include <BRepBuilderAPI_MakeVertex.hxx>
|
||||
|
||||
#include <TopoDS.hxx>
|
||||
#include <TopoDS_Wire.hxx>
|
||||
#include <TopoDS_Face.hxx>
|
||||
|
||||
#include <TopExp.hxx>
|
||||
#include <TopExp_Explorer.hxx>
|
||||
#include <TopLoc_Location.hxx>
|
||||
#include <TopTools_ListOfShape.hxx>
|
||||
|
||||
#include <BRepAlgoAPI_Cut.hxx>
|
||||
#include <BRepOffsetAPI_Sewing.hxx>
|
||||
#include <BRepPrimAPI_MakePrism.hxx>
|
||||
#include <BRepPrimAPI_MakeHalfSpace.hxx>
|
||||
#include <BRepFilletAPI_MakeFillet2d.hxx>
|
||||
|
||||
#include <BRep_Tool.hxx>
|
||||
|
||||
#include <ShapeFix_Shape.hxx>
|
||||
#include <ShapeFix_ShapeTolerance.hxx>
|
||||
#include <ShapeFix_Solid.hxx>
|
||||
|
||||
#include <Geom_BSplineCurve.hxx>
|
||||
#include <BRepTools_WireExplorer.hxx>
|
||||
#include <ShapeBuild_ReShape.hxx>
|
||||
#include <TopTools_ListOfShape.hxx>
|
||||
#include <TopTools_ListIteratorOfListOfShape.hxx>
|
||||
|
||||
#include <BRepAdaptor_CompCurve.hxx>
|
||||
#include <BRepAdaptor_HCompCurve.hxx>
|
||||
#include <Approx_Curve3d.hxx>
|
||||
|
||||
#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<std::string>(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<std::string>(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<std::string>(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<std::string>(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 <typename Fn>
|
||||
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<IfcSchema::IfcLine*>(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<IfcSchema::IfcEllipse*>(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<std::string>(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<double> > coordinates;
|
||||
if (point_list->as<IfcSchema::IfcCartesianPointList2D>()) {
|
||||
coordinates = point_list->as<IfcSchema::IfcCartesianPointList2D>()->CoordList();
|
||||
} else if (point_list->as<IfcSchema::IfcCartesianPointList3D>()) {
|
||||
coordinates = point_list->as<IfcSchema::IfcCartesianPointList3D>()->CoordList();
|
||||
}
|
||||
|
||||
std::vector<gp_Pnt> points;
|
||||
points.reserve(coordinates.size());
|
||||
for (std::vector< std::vector<double> >::const_iterator it = coordinates.begin(); it != coordinates.end(); ++it) {
|
||||
const std::vector<double>& 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<int> indices = *line;
|
||||
gp_Pnt previous;
|
||||
for (std::vector<int>::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<std::string>(*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<int> 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<std::string>(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<gp_Pnt>::const_iterator previous = points.begin();
|
||||
for (std::vector<gp_Pnt>::const_iterator current = previous+1; current < points.end(); ++current){
|
||||
w.Add(BRepBuilderAPI_MakeEdge(*previous, *current));
|
||||
previous = current;
|
||||
}
|
||||
}
|
||||
|
||||
result = w.Wire();
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -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 <typename Fn>
|
||||
void loop_(const taxonomy::loop* ps, const Fn& callback) {
|
||||
if (ps->children.size() < 3) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto a = boost::get<taxonomy::point3>(((taxonomy::edge*) ps->children.back())->start).instance;
|
||||
auto A = a->data().id();
|
||||
for (auto& b : ps->children) {
|
||||
auto B = boost::get<taxonomy::point3>(((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<void(int, int, bool)>& 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<std::string>(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<TopoDS_Wire>& 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);
|
||||
|
||||
@@ -0,0 +1,804 @@
|
||||
#include "boolean_utils.h"
|
||||
|
||||
#include "IfcGeomTree.h"
|
||||
|
||||
#include <BRepBuilderAPI_Copy.hxx>
|
||||
#include <TopExp_Explorer.hxx>
|
||||
#include <GProp_GProps.hxx>
|
||||
#include <BRepGProp.hxx>
|
||||
#include <TopExp.hxx>
|
||||
#include <TopoDS.hxx>
|
||||
#include <Bnd_Box.hxx>
|
||||
#include <Extrema_ExtPC.hxx>
|
||||
#include <Geom_Plane.hxx>
|
||||
#include <Geom_BSplineCurve.hxx>
|
||||
#include <ShapeUpgrade_UnifySameDomain.hxx>
|
||||
#include <GeomAPI_ExtremaCurveCurve.hxx>
|
||||
#include <ShapeAnalysis_Surface.hxx>
|
||||
#include <BRepBuilderAPI_MakeFace.hxx>
|
||||
#include <Standard_Version.hxx>
|
||||
#include <BRepAdaptor_Curve.hxx>
|
||||
#include <BRepExtrema_DistShapeShape.hxx>
|
||||
#include <BRepBndLib.hxx>
|
||||
|
||||
#include <vector>
|
||||
|
||||
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<double>::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<double>::infinity();
|
||||
|
||||
TopTools_IndexedMapOfShape vertices, edges;
|
||||
|
||||
TopExp::MapShapes(a, TopAbs_VERTEX, vertices);
|
||||
TopExp::MapShapes(a, TopAbs_EDGE, edges);
|
||||
|
||||
impl::tree<int> 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<int> edge_idxs = tree.select_box(b, false);
|
||||
std::vector<int>::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<double>::infinity();
|
||||
|
||||
TopTools_IndexedMapOfShape faces;
|
||||
|
||||
TopExp::MapShapes(a, TopAbs_FACE, faces);
|
||||
|
||||
impl::tree<int> 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<int> face_idxs = tree.select_box(b, false);
|
||||
std::vector<int>::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<double, double>& 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<int> 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<int> 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<int, int> edge_index_to_shape_index;
|
||||
|
||||
std::vector<TopoDS_Shape> shapes;
|
||||
std::vector<std::pair<size_t, TopoDS_Edge>> edges;
|
||||
// First is the outer wire
|
||||
std::vector<TopoDS_Wire> 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<bool> redundant(wires.size(), false);
|
||||
|
||||
std::vector<TopoDS_Face> wire_faces;
|
||||
wire_faces.reserve(wires.size());
|
||||
|
||||
std::vector<BRepTopAdaptor_FClass2d> wire_clss;
|
||||
wire_clss.reserve(wires.size());
|
||||
|
||||
std::vector<std::unique_ptr<ShapeAnalysis_Surface>> 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<ShapeAnalysis_Surface>(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<int> 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;
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
#ifndef BOOLEAN_UTILS_H
|
||||
#define BOOLEAN_UTILS_H
|
||||
|
||||
#include <TopoDS_Shape.hxx>
|
||||
#include <TopTools_ListOfShape.hxx>
|
||||
#include <Geom_Surface.hxx>
|
||||
#include <TopoDS_Face.hxx>
|
||||
#include <BRepTopAdaptor_FClass2d.hxx>
|
||||
#include <BRep_Tool.hxx>
|
||||
#include <BRepTools.hxx>
|
||||
#include <TopTools_IndexedMapOfShape.hxx>
|
||||
|
||||
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<double, double>& 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
|
||||
@@ -6,7 +6,7 @@
|
||||
#include <TopoDS_Iterator.hxx>
|
||||
|
||||
/* 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<taxonomy::edge>()) {
|
||||
if (edge->basis) {
|
||||
if (edge->basis->kind() != taxonomy::LINE) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -20,64 +20,59 @@
|
||||
#ifndef FACE_DEFINITION_H
|
||||
#define FACE_DEFINITION_H
|
||||
|
||||
#include "../../taxonomy.h"
|
||||
|
||||
#include <TopoDS_Wire.hxx>
|
||||
#include <Geom_Surface.hxx>
|
||||
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
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<TopoDS_Wire> 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<TopoDS_Wire> wires_;
|
||||
bool all_outer_;
|
||||
public:
|
||||
face_definition() : surface_(), all_outer_(false) {}
|
||||
typedef std::vector<TopoDS_Wire>::const_iterator wire_it;
|
||||
|
||||
typedef std::vector<TopoDS_Wire>::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<TopoDS_Wire>& wires() {
|
||||
return wires_;
|
||||
}
|
||||
|
||||
std::vector<TopoDS_Wire>& wires() {
|
||||
return wires_;
|
||||
}
|
||||
const TopoDS_Wire& outer_wire() const {
|
||||
return wires_.front();
|
||||
}
|
||||
|
||||
const TopoDS_Wire& outer_wire() const {
|
||||
return wires_.front();
|
||||
}
|
||||
std::pair<wire_it, wire_it> inner_wires() const {
|
||||
return { wires_.begin() + 1, wires_.end() };
|
||||
}
|
||||
};
|
||||
|
||||
std::pair<wire_it, wire_it> inner_wires() const {
|
||||
return { wires_.begin() + 1, wires_.end() };
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -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<int>& tree, std::vector<std::unique_ptr<gp_Pnt>>& pnts, std::set<int>& visited, int p, double eps) {
|
||||
visited.insert(p);
|
||||
|
||||
Bnd_Box b;
|
||||
b.Set(*pnts[p].get());
|
||||
b.Enlarge(eps);
|
||||
|
||||
std::vector<int> 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<taxonomy::point3> points;
|
||||
std::vector<taxonomy::loop*> loops;
|
||||
|
||||
for (auto& f : shell->children_as<taxonomy::face>()) {
|
||||
for (auto& l : f->children_as<taxonomy::loop>()) {
|
||||
loops.push_back(l);
|
||||
for (auto& e : l->children_as<taxonomy::edge>()) {
|
||||
// @todo make sure only cartesian points are provided here
|
||||
points.push_back(boost::get<taxonomy::point3>(e->start));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<gp_Pnt>> pnts(points.size());
|
||||
std::vector<TopoDS_Vertex> vertices(pnts.size());
|
||||
|
||||
// @todo
|
||||
impl::tree<int> tree;
|
||||
|
||||
BRep_Builder B;
|
||||
|
||||
Bnd_Box box;
|
||||
for (size_t i = 0; i < points.size(); ++i) {
|
||||
gp_Pnt* p = new gp_Pnt(convert_xyz<gp_Pnt>(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<double>::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<std::pair<int, int>, 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<int> 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<std::tuple<double, double, double>> 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<int, 2> edge_t;
|
||||
typedef std::set<edge_t> edge_set_t;
|
||||
std::set<edge_set_t> edge_sets;
|
||||
|
||||
for (auto& loop : loops) {
|
||||
std::vector<std::pair<int, int> > 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<std::string>(duplicate_faces) + " duplicate faces removed, " + boost::lexical_cast<std::string>(loops_removed) + " degenerate loops eliminated and " + boost::lexical_cast<std::string>(non_manifold) + " non-manifold edges");
|
||||
}
|
||||
}
|
||||
|
||||
void OpenCascadeKernel::faceset_helper::loop_(const taxonomy::loop* ps, const std::function<void(int, int, bool)>& callback) {
|
||||
if (ps->children.size() < 3) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto a = boost::get<taxonomy::point3>(((taxonomy::edge*) ps->children.back())->start).instance;
|
||||
auto A = a->data().id();
|
||||
for (auto& b : ps->children) {
|
||||
auto B = boost::get<taxonomy::point3>(((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<std::string>(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;
|
||||
}
|
||||
@@ -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<faceset_helper> helper_scope;
|
||||
helper_scope.reset(new faceset_helper(this, l));
|
||||
|
||||
faceset_helper_ = helper_scope.get();
|
||||
|
||||
auto faces = l->children_as<taxonomy::face>();
|
||||
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)) {
|
||||
|
||||
@@ -0,0 +1,356 @@
|
||||
#include "sweep_utils.h"
|
||||
|
||||
#include "../ifcparse/IfcLogger.h"
|
||||
#include "../ifcgeom_schema_agnostic/Kernel.h"
|
||||
|
||||
#include <gp_Ax2.hxx>
|
||||
#include <Geom_Line.hxx>
|
||||
#include <Geom_Circle.hxx>
|
||||
|
||||
#include <TopExp.hxx>
|
||||
#include <TopExp_Explorer.hxx>
|
||||
|
||||
#include <TopoDS.hxx>
|
||||
#include <TopoDS_Vertex.hxx>
|
||||
#include <TopoDS_Compound.hxx>
|
||||
|
||||
#include <BRep_Tool.hxx>
|
||||
#include <BRep_Builder.hxx>
|
||||
|
||||
#include <BRepPrimAPI_MakePrism.hxx>
|
||||
#include <BRepPrimAPI_MakeRevol.hxx>
|
||||
#include <BRepBuilderAPI_MakeEdge.hxx>
|
||||
#include <BRepBuilderAPI_MakeWire.hxx>
|
||||
#include <BRepBuilderAPI_MakeFace.hxx>
|
||||
#include <BRepOffsetAPI_MakePipeShell.hxx>
|
||||
|
||||
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<TopoDS_Edge>& 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<TopoDS_TShape*> 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<TopoDS_Wire>& wires) {
|
||||
std::vector<TopoDS_Edge> 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<TopoDS_Wire>& wires) {
|
||||
std::vector<TopoDS_Edge> 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<TopoDS_Wire> 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);
|
||||
*/
|
||||
}
|
||||
@@ -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 <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
********************************************************************************/
|
||||
|
||||
#ifndef SWEEP_UTILS_H
|
||||
#define SWEEP_UTILS_H
|
||||
|
||||
#include <TopoDS_Wire.hxx>
|
||||
#include <TopoDS_Edge.hxx>
|
||||
|
||||
#include <vector>
|
||||
|
||||
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<TopoDS_Edge>& 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<TopoDS_Wire>& wires);
|
||||
|
||||
void segment_adjacent_non_linear(const TopoDS_Wire& wire, std::vector<TopoDS_Wire>& 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
|
||||
@@ -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 <TopExp.hxx>
|
||||
#include <TopoDS.hxx>
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
#ifndef WIRE_BUILDER_H
|
||||
#define WIRE_BUILDER_H
|
||||
|
||||
#include "../../../ifcparse/IfcBaseClass.h"
|
||||
#include "../ifcparse/IfcBaseClass.h"
|
||||
|
||||
#include <Geom_Curve.hxx>
|
||||
|
||||
|
||||
@@ -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 <TopExp.hxx>
|
||||
#include <TopoDS.hxx>
|
||||
#include <TopoDS_Iterator.hxx>
|
||||
#include <ShapeFix_Wire.hxx>
|
||||
#include <BRep_Tool.hxx>
|
||||
#include <BRepTools_WireExplorer.hxx>
|
||||
#include <BRepBuilderAPI_MakeFace.hxx>
|
||||
#include <BRepAlgo_NormalProjection.hxx>
|
||||
#include <BRepMesh_IncrementalMesh.hxx>
|
||||
#include <BRepBuilderAPI_MakePolygon.hxx>
|
||||
#include <BRepBuilderAPI_MakeWire.hxx>
|
||||
#include <BRepBuilderAPI_MakeEdge.hxx>
|
||||
#include <TopTools_ListOfShape.hxx>
|
||||
#include <ShapeExtend_WireData.hxx>
|
||||
#include <Standard_Version.hxx>
|
||||
#include <GeomAPI_ExtremaCurveCurve.hxx>
|
||||
|
||||
#include <boost/range/irange.hpp>
|
||||
#include <boost/range/algorithm_ext/push_back.hpp>
|
||||
|
||||
#include <map>
|
||||
|
||||
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<TopoDS_Wire>& 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<double, double> uv_node;
|
||||
|
||||
gp_Pln pln;
|
||||
if (!approximate_plane_through_wire(wires.front(), pln, std::numeric_limits<double>::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<uv_node, TopoDS_Vertex> mapping;
|
||||
std::map<std::pair<uv_node, uv_node>, TopoDS_Edge> existing_edges, new_edges;
|
||||
|
||||
std::unique_ptr<BRepBuilderAPI_MakeFace> 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<int> 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<int> 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<int>::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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
#include <gp_Pln.hxx>
|
||||
#include <TopoDS_Wire.hxx>
|
||||
#include <TopTools_ListOfShape.hxx>
|
||||
|
||||
#include <vector>
|
||||
|
||||
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<TopoDS_Wire>& 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);
|
||||
}
|
||||
} }
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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 <boost/function.hpp>
|
||||
@@ -24,37 +25,9 @@ namespace ifcopenshell { namespace geometry {
|
||||
std::map<ifcopenshell::geometry::taxonomy::item*, brep_ptr, ifcopenshell::geometry::taxonomy::less_functor> 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<double, double>* convert(
|
||||
const IteratorSettings& settings, IfcUtil::IfcBaseClass* representation,
|
||||
|
||||
Reference in New Issue
Block a user