2023-03-21 20:15:01 +01:00
# include "OpenCascadeKernel.h"
2022-09-12 10:34:04 +02:00
2023-03-21 20:15:01 +01:00
# include "IfcGeomTree.h"
# include "wire_utils.h"
2022-09-12 10:34:04 +02:00
namespace {
void find_neighbours ( IfcGeom : : 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
}
}
}
2023-03-21 20:15:01 +01:00
IfcGeom : : OpenCascadeKernel : : faceset_helper : : faceset_helper (
OpenCascadeKernel * kernel ,
2023-11-15 10:32:20 +01:00
const ifcopenshell : : geometry : : taxonomy : : shell : : ptr shell
2022-09-12 10:34:04 +02:00
)
: kernel_ ( kernel )
, non_manifold_ ( false )
{
2023-03-21 20:15:01 +01:00
// @todo use pointers?
2023-11-15 10:32:20 +01:00
std : : vector < ifcopenshell : : geometry : : taxonomy : : point3 : : ptr > points ;
std : : vector < ifcopenshell : : geometry : : taxonomy : : loop : : ptr > loops ;
2024-07-17 03:12:35 +02:00
std : : set < uint32_t > point_identities_visited ;
2023-03-21 20:15:01 +01:00
2023-07-27 16:42:06 +08:00
for ( auto & f : shell - > children ) {
for ( auto & l : f - > children ) {
2023-03-21 20:15:01 +01:00
loops . push_back ( l ) ;
2023-07-27 16:42:06 +08:00
for ( auto & e : l - > children ) {
2025-04-30 20:43:13 +02:00
for ( size_t i = 0 ; i < 2 ; + + i ) {
// @todo make sure only cartesian points are provided here
auto & p = boost : : get < ifcopenshell : : geometry : : taxonomy : : point3 : : ptr > ( i = = 0 ? e - > start : e - > end ) ;
if ( point_identities_visited . find ( p - > identity ( ) ) = = point_identities_visited . end ( ) ) {
point_identities_visited . insert ( p - > identity ( ) ) ;
points . push_back ( p ) ;
}
2024-07-17 03:12:35 +02:00
}
2023-03-21 20:15:01 +01:00
}
}
}
std : : vector < std : : unique_ptr < gp_Pnt > > pnts ( points . size ( ) ) ;
2022-09-12 10:34:04 +02:00
std : : vector < TopoDS_Vertex > vertices ( pnts . size ( ) ) ;
IfcGeom : : impl : : tree < int > tree ;
BRep_Builder B ;
Bnd_Box box ;
for ( size_t i = 0 ; i < points . size ( ) ; + + i ) {
2023-07-27 16:42:06 +08:00
gp_Pnt * p = new gp_Pnt ( convert_xyz < gp_Pnt > ( * points [ i ] ) ) ;
2023-03-21 20:15:01 +01:00
pnts [ i ] . reset ( p ) ;
B . MakeVertex ( vertices [ i ] , * p , Precision : : Confusion ( ) ) ;
tree . add ( i , vertices [ i ] ) ;
box . Add ( * p ) ;
2022-09-12 10:34:04 +02:00
}
// Use the bbox diagonal to influence local epsilon
// double bdiff = std::sqrt(box.SquareExtent());
// @todo the bounding box diagonal is not used (see above)
2023-07-07 14:36:01 +00:00
// because we're explicitly interested in the minimal
2022-09-12 10:34:04 +02:00
// 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 ] ;
2023-11-15 10:32:20 +01:00
if ( d > kernel - > settings ( ) . get < ifcopenshell : : geometry : : settings : : Precision > ( ) . get ( ) * 10. & & d < bdiff ) {
2022-09-12 10:34:04 +02:00
bdiff = d ;
}
}
2023-11-15 10:32:20 +01:00
eps_ = kernel - > settings ( ) . get < ifcopenshell : : geometry : : settings : : Precision > ( ) . get ( ) * 10. * ( std : : min ) ( 1.0 , bdiff ) ;
2022-09-12 10:34:04 +02:00
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 ( ) ;
}
2024-09-01 15:12:43 +02:00
std : : vector < bool > retained ( pnts . size ( ) ) ;
2022-09-12 10:34:04 +02:00
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 ) {
2023-07-27 16:42:06 +08:00
auto & pt = * points [ v ] ;
2022-09-12 10:34:04 +02:00
// NB: insert() ignores duplicate keys
// v-1?
2023-03-21 20:15:01 +01:00
// @todo this reliable also in case of tesselations?
2024-09-01 15:12:43 +02:00
if ( vertex_mapping_ . insert ( { pt . identity ( ) , pnt_i } ) . second ) {
retained [ pnt_i ] = true ;
}
2022-09-12 10:34:04 +02:00
}
}
}
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 ( )
) ) ;
}
}
2024-09-01 15:12:43 +02:00
auto num_retained = std : : count ( retained . begin ( ) , retained . end ( ) , true ) ;
2024-09-09 09:58:02 +02:00
if ( unique . size ( ) ! = num_retained ) {
2026-06-10 18:30:54 +02:00
Logger : : Root ( ) . Notice ( " GEO " , 168 , " Collapsed vertices from " + std : : to_string ( pnts . size ( ) ) + " ( " + std : : to_string ( unique . size ( ) ) + " unique) to " + std : : to_string ( num_retained ) ) ;
2022-09-14 14:13:24 +02:00
}
2022-09-12 10:34:04 +02:00
typedef std : : array < int , 2 > edge_t ;
typedef std : : set < edge_t > edge_set_t ;
2026-06-04 21:38:11 +02:00
// When a single face fills an interior loop, their edge_sets (canonicalized edges) will be identical.
// We can differentiate in this scenario in two ways:
// - std::map<edge_t, bool> retain the edge order from the bool passed to the loop_() lambda
// - std::pair<bool, edge_set_t> with pair::first populated from external (FaceBound / OuterBound)
// The second has been found more reliable for typical models, because inner bound winding can be wrong.
// The can be made more resilient by first checking correct population of external and falling back to approach 1.
std : : set < std : : pair < bool , edge_set_t > > edge_sets ;
2022-09-12 10:34:04 +02:00
2023-03-21 20:15:01 +01:00
for ( auto & loop : loops ) {
2022-09-12 10:34:04 +02:00
std : : vector < std : : pair < int , int > > segments ;
edge_set_t segment_set ;
2023-03-21 20:15:01 +01:00
loop_ ( loop , [ & segments , & segment_set ] ( int C , int D , bool ) {
2022-09-12 10:34:04 +02:00
segment_set . insert ( edge_t { C , D } ) ;
segments . push_back ( std : : make_pair ( C , D ) ) ;
} ) ;
2026-06-04 21:38:11 +02:00
if ( edge_sets . find ( { loop - > external . get_value_or ( false ) , segment_set } ) ! = edge_sets . end ( ) ) {
2022-09-12 10:34:04 +02:00
duplicate_faces + + ;
2023-04-03 13:33:22 +02:00
duplicates_ . insert ( loop - > identity ( ) ) ;
2022-09-12 10:34:04 +02:00
continue ;
}
2026-06-04 21:38:11 +02:00
edge_sets . insert ( { loop - > external . get_value_or ( false ) , segment_set } ) ;
2022-09-12 10:34:04 +02:00
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 ;
}
}
2023-03-21 20:15:01 +01:00
if ( duplicates_ . size ( ) | | loops_removed | | ( non_manifold & & shell - > closed . get_value_or ( false ) ) ) {
2026-06-10 18:30:54 +02:00
Logger : : Root ( ) . Warning ( " GEO " , 169 , 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 " ) ;
2022-09-12 10:34:04 +02:00
}
}
2023-11-15 10:32:20 +01:00
void IfcGeom : : OpenCascadeKernel : : faceset_helper : : loop_ ( const ifcopenshell : : geometry : : taxonomy : : loop : : ptr ps , const std : : function < void ( int , int , bool ) > & callback ) {
2023-03-21 20:15:01 +01:00
if ( ps - > children . size ( ) < 3 ) {
2022-09-12 10:34:04 +02:00
return ;
}
2025-04-30 20:43:13 +02:00
for ( auto & edge : ps - > children ) {
auto A = boost : : get < ifcopenshell : : geometry : : taxonomy : : point3 : : ptr > ( edge - > start ) - > identity ( ) ;
auto B = boost : : get < ifcopenshell : : geometry : : taxonomy : : point3 : : ptr > ( edge - > end ) - > identity ( ) ;
2022-09-12 10:34:04 +02:00
auto C = vertex_mapping_ [ A ] , D = vertex_mapping_ [ B ] ;
bool fwd = C < D ;
if ( ! fwd ) {
std : : swap ( C , D ) ;
}
2025-04-30 20:43:13 +02:00
if ( ! edge - > orientation . get_value_or ( true ) ) {
fwd = ! fwd ;
}
2022-09-12 10:34:04 +02:00
if ( C ! = D ) {
callback ( C , D , fwd ) ;
}
}
}
2023-03-21 20:15:01 +01:00
bool IfcGeom : : OpenCascadeKernel : : faceset_helper : : edge ( int A , int B , TopoDS_Edge & e ) {
2022-09-12 10:34:04 +02:00
auto it = edges_ . find ( { A , B } ) ;
if ( it = = edges_ . end ( ) ) {
return false ;
}
e = it - > second ;
return true ;
}
2023-11-15 10:32:20 +01:00
bool IfcGeom : : OpenCascadeKernel : : faceset_helper : : wire ( const ifcopenshell : : geometry : : taxonomy : : loop : : ptr loop , TopoDS_Wire & w ) {
2022-10-13 09:22:28 +02:00
TopTools_ListOfShape ws ;
if ( ! wires ( loop , ws ) ) {
return false ;
}
util : : select_largest ( ws , w ) ;
return true ;
}
2023-11-15 10:32:20 +01:00
bool IfcGeom : : OpenCascadeKernel : : faceset_helper : : wires ( const ifcopenshell : : geometry : : taxonomy : : loop : : ptr loop , TopTools_ListOfShape & wires ) {
2023-04-03 13:33:22 +02:00
if ( duplicates_ . find ( loop - > identity ( ) ) ! = duplicates_ . end ( ) ) {
2022-09-12 10:34:04 +02:00
return false ;
}
2022-10-13 09:22:28 +02:00
TopoDS_Wire wire ;
2022-09-12 10:34:04 +02:00
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 ;
2023-11-15 10:32:20 +01:00
if ( ! kernel_ - > settings ( ) . get < ifcopenshell : : geometry : : settings : : NoWireIntersectionCheck > ( ) . get ( ) & & util : : wire_intersections ( wire , results , {
! kernel_ - > settings ( ) . get < ifcopenshell : : geometry : : settings : : NoWireIntersectionCheck > ( ) . get ( ) ,
! kernel_ - > settings ( ) . get < ifcopenshell : : geometry : : settings : : NoWireIntersectionTolerance > ( ) . get ( ) , 0. ,
kernel_ - > settings ( ) . get < ifcopenshell : : geometry : : settings : : Precision > ( ) . get ( ) } ) )
{
2026-06-10 18:30:54 +02:00
Logger : : Root ( ) . Warning ( " GEO " , 170 , " Self-intersections with " + boost : : lexical_cast < std : : string > ( results . Extent ( ) ) + " cycles detected " ) ;
2022-09-12 10:34:04 +02:00
non_manifold_ = true ;
2022-10-13 09:22:28 +02:00
wires = results ;
} else {
wires . Append ( wire ) ;
2022-09-12 10:34:04 +02:00
}
return true ;
} else {
return false ;
}
}
2023-03-21 20:15:01 +01:00
IfcGeom : : OpenCascadeKernel : : faceset_helper : : ~ faceset_helper ( ) {
2022-09-12 10:34:04 +02:00
// @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 ;
}