diff --git a/src/ifcgeom/IfcGeom.h b/src/ifcgeom/IfcGeom.h index c18478cbc5..b89b8b9fce 100644 --- a/src/ifcgeom/IfcGeom.h +++ b/src/ifcgeom/IfcGeom.h @@ -80,6 +80,23 @@ if ( it != cache.T.end() ) { e = it->second; return true; } #include INCLUDE_PARENT_DIR(IfcSchema) namespace IfcGeom { + class IFC_PARSE_API geometry_exception : public std::exception { + protected: + std::string message; + public: + geometry_exception(const std::string& m) + : message(m) {} + virtual ~geometry_exception() throw () {} + virtual const char* what() const throw() { + return message.c_str(); + } + }; + + class IFC_PARSE_API too_many_faces_exception : public geometry_exception { + public: + too_many_faces_exception() + : geometry_exception("Too many faces for operation") {} + }; class IFC_GEOM_API MAKE_TYPE_NAME(Cache) { public: diff --git a/src/ifcgeom/IfcGeomFaces.cpp b/src/ifcgeom/IfcGeomFaces.cpp index 8f45340761..ec3dab3ad4 100644 --- a/src/ifcgeom/IfcGeomFaces.cpp +++ b/src/ifcgeom/IfcGeomFaces.cpp @@ -341,7 +341,9 @@ bool IfcGeom::Kernel::convert(const IfcSchema::IfcFace* l, TopoDS_Shape& face) { TopoDS_Iterator jt(face, false); for (; jt.More(); jt.Next()) { const TopoDS_Wire& w = TopoDS::Wire(jt.Value()); - if (wire_map.IsBound(w)) { + // tfk: @todo if wire_map contains w, I would assume wire_senses also contains w, + // this is not the case in github issue #405. + if (wire_map.IsBound(w) && wire_senses.IsBound(w)) { const TopTools_ListOfShape& shapes = wire_map.Find(w); TopTools_ListIteratorOfListOfShape it(shapes); for (; it.More(); it.Next()) { diff --git a/src/ifcgeom/IfcGeomFunctions.cpp b/src/ifcgeom/IfcGeomFunctions.cpp index 35437546cd..483c8cb7d8 100644 --- a/src/ifcgeom/IfcGeomFunctions.cpp +++ b/src/ifcgeom/IfcGeomFunctions.cpp @@ -44,6 +44,9 @@ #include #include +#include +#include + #include #include #include @@ -144,6 +147,7 @@ #include "../ifcparse/IfcSIPrefix.h" #include "../ifcparse/IfcFile.h" #include "../ifcgeom/IfcGeom.h" +#include "../ifcgeom/IfcGeomTree.h" #if OCC_VERSION_HEX < 0x60900 #ifdef _MSC_VER @@ -233,12 +237,23 @@ bool IfcGeom::Kernel::create_solid_from_compound(const TopoDS_Shape& compound, T bool IfcGeom::Kernel::create_solid_from_faces(const TopTools_ListOfShape& face_list, TopoDS_Shape& shape) { bool valid_shell = false; + + int max_faces = getValue(GV_MAX_FACES_TO_SEW); + if (max_faces == -1) { + max_faces = 1000; + } + + if (face_list.Extent() > max_faces) { + throw too_many_faces_exception(); + } + TopTools_ListIteratorOfListOfShape face_iterator; BRepOffsetAPI_Sewing builder; builder.SetTolerance(getValue(GV_PRECISION)); builder.SetMaxTolerance(getValue(GV_PRECISION)); builder.SetMinTolerance(getValue(GV_PRECISION)); + for (face_iterator.Initialize(face_list); face_iterator.More(); face_iterator.Next()) { builder.Add(face_iterator.Value()); } @@ -1368,6 +1383,7 @@ IfcGeom::BRepElement* IfcGeom::Kernel::create_brep_for_representation_and } IfcGeom::IfcRepresentationShapeItems opened_shapes; + bool caught_error = false; try { #if OCC_VERSION_HEX < 0x60900 const bool faster_booleans = settings.get(IteratorSettings::FASTER_BOOLEANS); @@ -1387,9 +1403,17 @@ IfcGeom::BRepElement* IfcGeom::Kernel::create_brep_for_representation_and } else { convert_openings(product,openings,shapes,trsf,opened_shapes); } + } catch (const std::exception& e) { + Logger::Message(Logger::LOG_ERROR, std::string("Error processing openings for: ") + e.what() + ":", product); + caught_error = true; } catch(...) { Logger::Message(Logger::LOG_ERROR,"Error processing openings for:",product); } + + if (caught_error && opened_shapes.size() < shapes.size()) { + opened_shapes = shapes; + } + if (settings.get(IteratorSettings::USE_WORLD_COORDS)) { for ( IfcGeom::IfcRepresentationShapeItems::iterator it = opened_shapes.begin(); it != opened_shapes.end(); ++ it ) { it->prepend(trsf); @@ -2852,10 +2876,7 @@ bool IfcGeom::Kernel::wire_intersections(const TopoDS_Wire& wire, TopTools_ListO } int n = count(wire, TopAbs_EDGE); - if (n < 3 || n > 128) { - if (n > 128) { - Logger::Notice("Too many segments for detection of self-intersections"); - } + if (n < 3) { wires.Append(wire); return false; } @@ -2865,8 +2886,24 @@ bool IfcGeom::Kernel::wire_intersections(const TopoDS_Wire& wire, TopTools_ListO // ... to be sure to get consecutive edges BRepTools_WireExplorer exp(wire); + IfcGeom::impl::tree tree; + + int edge_idx = 0; for (; exp.More(); exp.Next()) { wd->Add(exp.Current()); + if (n > 64) { + // tfk: indices in tree are 0-based vd 1-based in wiredata + tree.add(edge_idx++, exp.Current()); + } + } + + if (wd->NbEdges() != n) { + // If the number of edges differs, BRepTools_WireExplorer did not + // reach every edge, probably due to loops exactly at vertex locations. + // This is not supported by this algorithm which only elimates loops + // due to edge crossings. + + throw geometry_exception("Invalid loop"); } bool intersected = false; @@ -2874,13 +2911,37 @@ bool IfcGeom::Kernel::wire_intersections(const TopoDS_Wire& wire, TopTools_ListO // 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)); - - for (int i = 2; i < n; ++i) { - for (int j = 0; j < i - 1; ++j) { - if (i == n - 1 && j == 0) continue; + + const double eps = getValue(GV_PRECISION) * 10.; + for (int i = 2; i < n; ++i) { + + std::vector js; + if (n > 64) { + Bnd_Box b; + BRepBndLib::Add(wd->Edge(i + 1), b); + b.Enlarge(eps); + js = tree.select_box(b, false); + } else { + boost::push_back(js, boost::irange(0, i - 1)); + } + + for(std::vector::const_iterator it = js.begin(); it != js.end(); ++it) { + int j = *it; + + if (n > 64) { + if (j > i) { + continue; + } + if ((std::max)(i, j) - (std::min)(i, j) <= 1) { + continue; + } + } + + // Only check non-consecutive edges + if (i == n - 1 && j == 0) continue; + bool unbounded_intersects; - const double eps = getValue(GV_PRECISION) * 10.; double u11, u12, u21, u22, U1, U2; GeomAPI_ExtremaCurveCurve ecc( @@ -3041,6 +3102,10 @@ bool IfcGeom::Kernel::fit_halfspace(const TopoDS_Shape& a, const TopoDS_Shape& b Bnd_Box bb; BRepBndLib::Add(a, bb); + if (bb.IsVoid()) { + return false; + } + double xs[2], ys[2], zs[2]; bb.Get(xs[0], ys[0], zs[0], xs[1], ys[1], zs[1]); @@ -3226,7 +3291,7 @@ namespace { TopoDS_Vertex v1, v2; TopExp::Vertices(e, v1, v2); - if (v.IsEqual(v1) || v.IsEqual(v2)) { + if (v.IsSame(v1) || v.IsSame(v2)) { continue; } @@ -3248,6 +3313,30 @@ namespace { return M; } + bool is_manifold(const TopoDS_Shape& a) { + 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 is_manifold(const TopTools_ListOfShape& l) { + TopTools_ListOfShape r; + TopTools_ListIteratorOfListOfShape it(l); + for (; it.More(); it.Next()) { + if (!is_manifold(it.Value())) { + return false; + } + } + return true; + } + } bool IfcGeom::Kernel::boolean_operation(const TopoDS_Shape& a, const TopTools_ListOfShape& b, BOPAlgo_Operation op, TopoDS_Shape& result, double fuzziness) { @@ -3307,7 +3396,20 @@ bool IfcGeom::Kernel::boolean_operation(const TopoDS_Shape& a, const TopTools_Li success = BRepCheck_Analyzer(r).IsValid() != 0; if (success) { - result = r; + + success = !is_manifold(a) || is_manifold(r); + + if (success) { + + // when there are edges or vertex-edge distances close to the used fuzziness, the + // output is not trusted and the operation is attempted with a higher fuzziness. + double min_len_check = (std::min)(min_edge_length(r), min_vertex_edge_distance(r, getValue(GV_PRECISION))); + success = min_len_check > fuzziness * 10.; + + if (success) { + result = r; + } + } } } delete builder; diff --git a/src/ifcgeom/IfcGeomWires.cpp b/src/ifcgeom/IfcGeomWires.cpp index ddb2976f95..d80c239362 100644 --- a/src/ifcgeom/IfcGeomWires.cpp +++ b/src/ifcgeom/IfcGeomWires.cpp @@ -494,7 +494,7 @@ bool IfcGeom::Kernel::convert(const IfcSchema::IfcPolyline* l, TopoDS_Wire& resu } const double eps = getValue(GV_PRECISION) * 10; - const bool closed_by_proximity = polygon.Length() >= 2 && polygon.First().Distance(polygon.Last()) < eps; + 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()); @@ -502,6 +502,10 @@ bool IfcGeom::Kernel::convert(const IfcSchema::IfcPolyline* l, TopoDS_Wire& resu // 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) { diff --git a/src/ifcparse/IfcLogger.cpp b/src/ifcparse/IfcLogger.cpp index b2389b3de0..fbab840cfa 100644 --- a/src/ifcparse/IfcLogger.cpp +++ b/src/ifcparse/IfcLogger.cpp @@ -45,7 +45,11 @@ namespace { } os << message << std::endl; if (instance) { - os << instance->data().toString() << std::endl; + std::string instance_string = instance->data().toString(); + if (instance_string.size() > 259) { + instance_string = instance_string.substr(0, 256) + "..."; + } + os << instance_string << std::endl; } }