2026-03-31 15:32:36 +02:00
# include <map>
2023-11-02 09:04:34 +01:00
# include <TopoDS.hxx>
# include <TopExp.hxx>
# include <BRepGProp.hxx>
# include <GProp_GProps.hxx>
# include <Geom_SphericalSurface.hxx>
2024-09-18 19:25:37 +02:00
# include <Geom_Plane.hxx>
2024-10-17 14:35:17 +02:00
# include <BRepTools_WireExplorer.hxx>
2025-09-26 14:24:49 +02:00
# include <TopoDS_Compound.hxx>
# include <BRep_Builder.hxx>
2023-11-02 09:04:34 +01:00
2026-08-08 14:19:57 +02:00
# include "opencascade_conversion_result.h"
2023-03-21 20:15:01 +01:00
2026-03-31 15:32:36 +02:00
# include "../../../ifcparse/logger.h"
2026-08-08 14:19:57 +02:00
# include "../../../ifcgeom/representation.h"
2023-08-26 11:35:23 +02:00
# include "base_utils.h"
2023-11-02 09:04:34 +01:00
# include "boolean_utils.h"
2023-03-21 20:15:01 +01:00
2024-06-19 14:44:52 +02:00
# include <Standard_Version.hxx>
2024-09-18 19:25:37 +02:00
# include <iostream>
# include <vector>
# include <unordered_map>
# include <tuple>
# include <algorithm>
2024-06-19 14:44:52 +02:00
# if OCC_VERSION_HEX >= 0x70600
# include <TopTools_FormatVersion.hxx>
# endif
2026-08-08 07:42:45 +02:00
using ifcopenshell : : geom : : opaque_number ;
using ifcopenshell : : geom : : opaque_coordinate ;
using ifcopenshell : : geom : : conversion_result_shape ;
2023-03-21 20:15:01 +01:00
namespace {
// We bypass the conversion to gp_GTrsf, because it does not work
void taxonomy_transform ( const Eigen : : Matrix4d * m , gp_XYZ & xyz ) {
if ( m ) {
Eigen : : Vector4d v ( xyz . X ( ) , xyz . Y ( ) , xyz . Z ( ) , 1.0 ) ;
auto v2 = ( * m * v ) . eval ( ) ;
xyz . ChangeData ( ) [ 0 ] = v2 ( 0 ) ;
xyz . ChangeData ( ) [ 1 ] = v2 ( 1 ) ;
xyz . ChangeData ( ) [ 2 ] = v2 ( 2 ) ;
}
}
}
2026-08-08 07:42:45 +02:00
ifcopenshell : : geom : : open_cascade_shape : : open_cascade_shape ( const TopoDS_Shape & shape )
2026-04-15 18:07:28 +02:00
: shape_ ( shape ) { }
2026-08-08 07:42:45 +02:00
ifcopenshell : : geom : : open_cascade_shape : : open_cascade_shape ( TopoDS_Shape & & shape )
2026-04-15 18:07:28 +02:00
: shape_ ( std : : move ( shape ) ) { }
2026-08-08 07:42:45 +02:00
const TopoDS_Shape & ifcopenshell : : geom : : open_cascade_shape : : shape ( ) const {
2026-04-15 18:07:28 +02:00
return shape_ ;
}
2026-08-08 07:42:45 +02:00
ifcopenshell : : geom : : open_cascade_shape : : operator const TopoDS_Shape & ( ) {
2026-04-15 18:07:28 +02:00
return shape_ ;
}
2026-08-08 07:42:45 +02:00
std : : string_view ifcopenshell : : geom : : open_cascade_shape : : backend_id ( ) const {
2026-04-15 18:07:28 +02:00
return " opencascade " ;
}
2026-08-08 07:42:45 +02:00
ifcopenshell : : geom : : conversion_result_shape * ifcopenshell : : geom : : open_cascade_shape : : clone ( ) const {
return new open_cascade_shape ( shape_ ) ;
2026-04-15 18:07:28 +02:00
}
2026-08-08 16:09:30 +02:00
void ifcopenshell : : geom : : open_cascade_shape : : triangulate ( ifcopenshell : : geom : : settings settings , const ifcopenshell : : geom : : taxonomy : : matrix4 & place , ifcopenshell : : geom : : triangulation * t , int item_id , int surface_style_id , ifcopenshell : : logger & logger ) const {
2023-03-21 20:15:01 +01:00
2026-08-08 07:42:45 +02:00
// @todo remove duplication with open_cascade_kernel::convert(const taxonomy::matrix4::ptr matrix, gp_GTrsf& trsf);
2023-03-21 20:15:01 +01:00
// above can be static?
// A 3x3 matrix to rotate the vertex normals
2026-01-04 10:40:02 +01:00
std : : optional < gp_Mat > rotation_matrix ;
2026-08-19 18:16:59 +05:00
2023-03-21 20:15:01 +01:00
if ( place . components_ ) {
const auto & m = * place . components_ ;
rotation_matrix . emplace (
m ( 0 , 0 ) , m ( 0 , 1 ) , m ( 0 , 2 ) ,
m ( 1 , 0 ) , m ( 1 , 1 ) , m ( 1 , 2 ) ,
m ( 2 , 0 ) , m ( 2 , 1 ) , m ( 2 , 2 )
) ;
}
2026-08-19 18:16:59 +05:00
2023-08-06 14:27:31 +08:00
// When welding vertices, vertex coords will be shared among faces so we need to per-shape set
// to keep track of which edges were already emitted.
std : : set < std : : pair < int , int > > emitted_edges ;
2023-03-21 20:15:01 +01:00
2025-05-18 22:14:22 +02:00
// Do our own check if there are triangulations. Any will do. This is faster than the OCCT incremental check which compares the deflection tolerances and initialized a bunch of state
bool has_triangulation = false ;
{
TopExp_Explorer exp ;
for ( exp . Init ( shape_ , TopAbs_FACE ) ; exp . More ( ) ; exp . Next ( ) ) {
TopLoc_Location loc ;
const Handle ( Poly_Triangulation ) & tri =
BRep_Tool : : Triangulation ( TopoDS : : Face ( exp . Current ( ) ) , loc ) ;
if ( tri ) {
has_triangulation = true ;
break ;
}
}
}
if ( ! has_triangulation ) {
2026-08-08 16:09:30 +02:00
// triangulate the shape
2025-05-18 22:14:22 +02:00
try {
BRepMesh_IncrementalMesh ( shape_ , settings . get < settings : : MesherLinearDeflection > ( ) . get ( ) , false , settings . get < settings : : MesherAngularDeflection > ( ) . get ( ) ) ;
} catch ( . . . ) {
2026-08-08 13:58:39 +02:00
ifcopenshell : : logger : : root ( ) . message ( ifcopenshell : : logger : : LOG_ERROR , " GEO " , 183 , " Failed to triangulate shape " ) ;
2025-05-18 22:14:22 +02:00
return ;
}
2023-03-21 20:15:01 +01:00
}
// Iterates over the faces of the shape
int num_faces = 0 ;
TopExp_Explorer exp ;
for ( exp . Init ( shape_ , TopAbs_FACE ) ; exp . More ( ) ; exp . Next ( ) , + + num_faces ) {
TopoDS_Face face = TopoDS : : Face ( exp . Current ( ) ) ;
2024-09-18 19:25:37 +02:00
2026-08-19 18:16:59 +05:00
size_t num_bounds = 0 ;
2024-09-18 19:25:37 +02:00
for ( TopoDS_Iterator it ( face ) ; it . More ( ) ; it . Next ( ) , + + num_bounds ) { }
2026-08-19 18:16:59 +05:00
2024-09-18 19:25:37 +02:00
const bool is_planar = BRep_Tool : : Surface ( face ) & & BRep_Tool : : Surface ( face ) - > DynamicType ( ) = = STANDARD_TYPE ( Geom_Plane ) ;
const bool has_inner_bounds = num_bounds > 1 ;
const bool polyhedral_output_with_holes = settings . get < settings : : TriangulationType > ( ) . get ( ) = = settings : : POLYHEDRON_WITH_HOLES & & is_planar ;
const bool polyhedral_output_without_holes = settings . get < settings : : TriangulationType > ( ) . get ( ) = = settings : : POLYHEDRON_WITHOUT_HOLES & & is_planar & & ! has_inner_bounds ;
std : : vector < std : : tuple < int , int , int > > triangle_indices ;
2023-03-21 20:15:01 +01:00
TopLoc_Location loc ;
2026-06-28 13:54:58 +02:00
opencascade : : handle < Poly_Triangulation > tri = BRep_Tool : : Triangulation ( face , loc ) ;
2023-03-21 20:15:01 +01:00
if ( tri . IsNull ( ) ) {
2026-08-08 13:58:39 +02:00
ifcopenshell : : logger : : root ( ) . message ( ifcopenshell : : logger : : LOG_ERROR , " GEO " , 184 , " Triangulation missing for face " ) ;
2023-03-21 20:15:01 +01:00
} else {
// Keep track of the number of times an edge is used
// Manifold edges (i.e. edges used twice) are deemed invisible
std : : map < std : : pair < int , int > , int > edgecount ;
std : : vector < gp_XYZ > coords ;
BRepGProp_Face prop ( face ) ;
std : : map < int , int > dict ;
// Vertex normals are only calculated if vertices are not welded and calculation is not disable explicitly.
2023-11-15 10:32:20 +01:00
const bool calculate_normals = ! settings . get < settings : : WeldVertices > ( ) . get ( ) & &
! settings . get < settings : : DontEmitNormals > ( ) . get ( ) ;
2023-03-21 20:15:01 +01:00
2023-04-09 19:28:37 +00:00
for ( int i = 1 ; i < = tri - > NbNodes ( ) ; + + i ) {
coords . push_back ( tri - > Node ( i ) . Transformed ( loc ) . XYZ ( ) ) ;
2023-03-21 20:15:01 +01:00
taxonomy_transform ( place . components_ , * coords . rbegin ( ) ) ;
const gp_XYZ & last = * coords . rbegin ( ) ;
2023-06-19 13:47:45 +02:00
dict [ i ] = t - > addVertex ( item_id , surface_style_id , last . X ( ) , last . Y ( ) , last . Z ( ) ) ;
2023-03-21 20:15:01 +01:00
if ( calculate_normals ) {
2023-04-09 19:28:37 +00:00
const gp_Pnt2d & uv = tri - > UVNode ( i ) ;
2023-03-21 20:15:01 +01:00
gp_Pnt p ;
gp_Vec normal_direction ;
prop . Normal ( uv . X ( ) , uv . Y ( ) , p , normal_direction ) ;
gp_Vec normal ( 0. , 0. , 0. ) ;
if ( normal_direction . Magnitude ( ) > 1.e-9 ) {
if ( rotation_matrix ) {
normal = gp_Dir ( normal_direction . XYZ ( ) * * rotation_matrix ) ;
} else {
normal = normal_direction ;
}
} else {
2026-06-28 13:54:58 +02:00
opencascade : : handle < Geom_Surface > surf = BRep_Tool : : Surface ( face ) ;
2023-03-21 20:15:01 +01:00
// Special case the normal at the poles of a spherical surface
if ( surf - > DynamicType ( ) = = STANDARD_TYPE ( Geom_SphericalSurface ) ) {
if ( fabs ( fabs ( uv . Y ( ) ) - M_PI / 2. ) < 1.e-9 ) {
const bool is_top = uv . Y ( ) > 0 ;
const bool is_forward = face . Orientation ( ) = = TopAbs_FORWARD ;
const double z = ( is_top = = is_forward ) ? 1. : - 1. ;
if ( rotation_matrix ) {
normal = gp_Dir ( gp_XYZ ( 0 , 0 , z ) * * rotation_matrix ) ;
} else {
normal = gp_Dir ( gp_XYZ ( 0 , 0 , z ) ) ;
}
}
}
// TODO: Do the same for conical surfaces, but they are rare in IFC.
}
t - > addNormal ( normal . X ( ) , normal . Y ( ) , normal . Z ( ) ) ;
}
}
2026-06-24 00:40:32 +03:00
const NCollection_Array1 < Poly_Triangle > & triangles = tri - > Triangles ( ) ;
2023-03-21 20:15:01 +01:00
for ( int i = 1 ; i < = triangles . Length ( ) ; + + i ) {
int n1 , n2 , n3 ;
if ( face . Orientation ( ) = = TopAbs_REVERSED )
triangles ( i ) . Get ( n3 , n2 , n1 ) ;
else triangles ( i ) . Get ( n1 , n2 , n3 ) ;
2024-03-28 09:06:41 +01:00
if ( dict [ n1 ] = = dict [ n2 ] | | dict [ n2 ] = = dict [ n3 ] | | dict [ n3 ] = = dict [ n1 ] ) {
2026-07-09 13:30:48 +02:00
logger . warning ( " GEO " , 185 , " Mesher generated a degenerate triangle, ignoring " ) ;
2024-03-28 09:06:41 +01:00
continue ;
}
2023-03-21 20:15:01 +01:00
/* An alternative would be to calculate normals based
* on the coordinates of the mesh vertices */
/*
const gp_XYZ pt1 = coords[n1-1];
const gp_XYZ pt2 = coords[n2-1];
const gp_XYZ pt3 = coords[n3-1];
const gp_XYZ v1 = pt2-pt1;
const gp_XYZ v2 = pt3-pt2;
gp_Dir normal = gp_Dir(v1^v2);
_normals.push_back((float)normal.X());
_normals.push_back((float)normal.Y());
_normals.push_back((float)normal.Z());
*/
2024-09-18 19:25:37 +02:00
if ( polyhedral_output_without_holes | | polyhedral_output_with_holes ) {
triangle_indices . push_back ( { dict [ n1 ] , dict [ n2 ] , dict [ n3 ] } ) ;
} else {
if ( settings . get < settings : : TriangulationType > ( ) . get ( ) = = settings : : POLYHEDRON_WITHOUT_HOLES ) {
t - > addFace ( item_id , surface_style_id , std : : vector < int > { dict [ n1 ] , dict [ n2 ] , dict [ n3 ] } ) ;
} else if ( settings . get < settings : : TriangulationType > ( ) . get ( ) = = settings : : POLYHEDRON_WITH_HOLES ) {
t - > addFace ( item_id , surface_style_id , std : : vector < std : : vector < int > > { { dict [ n1 ] , dict [ n2 ] , dict [ n3 ] } } ) ;
} else {
t - > addFace ( item_id , surface_style_id , dict [ n1 ] , dict [ n2 ] , dict [ n3 ] ) ;
2023-03-21 20:15:01 +01:00
2024-09-25 15:12:56 +05:00
t - > registerEdgeCount ( dict [ n1 ] , dict [ n2 ] , edgecount ) ;
t - > registerEdgeCount ( dict [ n2 ] , dict [ n3 ] , edgecount ) ;
t - > registerEdgeCount ( dict [ n3 ] , dict [ n1 ] , edgecount ) ;
2024-09-18 19:25:37 +02:00
}
}
2023-03-21 20:15:01 +01:00
}
2023-08-06 14:27:31 +08:00
for ( auto & p : edgecount ) {
// @todo should be != 2?
if ( p . second = = 1 & & emitted_edges . find ( p . first ) = = emitted_edges . end ( ) ) {
2023-03-21 20:15:01 +01:00
// non manifold edge, face boundary
2024-09-25 15:15:47 +05:00
t - > registerEdge ( item_id , p . first . first , p . first . second ) ;
2023-08-06 14:27:31 +08:00
if ( settings . get < settings : : WeldVertices > ( ) . get ( ) ) {
// only relevant while welding, because otherwise vertices are not shared among distinct faces
emitted_edges . insert ( p . first ) ;
}
2023-03-21 20:15:01 +01:00
}
}
}
2024-09-18 19:25:37 +02:00
if ( polyhedral_output_without_holes | | polyhedral_output_with_holes ) {
2026-08-08 07:42:45 +02:00
auto loops = ifcopenshell : : geom : : util : : find_boundary_loops ( t - > verts ( ) , triangle_indices ) ;
2024-09-18 19:25:37 +02:00
if ( polyhedral_output_without_holes ) {
if ( ! loops . empty ( ) & & ! loops [ 0 ] . empty ( ) ) {
t - > addFace ( item_id , surface_style_id , loops [ 0 ] ) ;
}
} else {
if ( ! loops . empty ( ) ) {
t - > addFace ( item_id , surface_style_id , loops ) ;
}
}
}
2023-03-21 20:15:01 +01:00
}
2023-11-15 10:32:20 +01:00
if ( ! t - > normals ( ) . empty ( ) & & settings . get < settings : : GenerateUvs > ( ) . get ( ) ) {
2026-08-08 15:56:41 +02:00
t - > uvs_ref ( ) = ifcopenshell : : geom : : triangulation : : box_project_uvs ( t - > verts ( ) , t - > normals ( ) ) ;
2023-03-21 20:15:01 +01:00
}
if ( num_faces = = 0 ) {
// Edges are only emitted if there are no faces. A mixed representation of faces
// and loose edges is discouraged by the standard. An alternative would be to use
// TopExp_Explorer texp(s, TopAbs_EDGE, TopAbs_FACE) to find edges that do not
// belong to any face.
2024-10-17 14:35:17 +02:00
2026-06-24 18:58:26 +03:00
NCollection_List < TopoDS_Shape > edges ;
2024-10-17 14:35:17 +02:00
// First collect edges part of wire in order
for ( TopExp_Explorer texp ( shape_ , TopAbs_WIRE ) ; texp . More ( ) ; texp . Next ( ) ) {
BRepTools_WireExplorer wexp ( TopoDS : : Wire ( texp . Current ( ) ) ) ;
for ( ; wexp . More ( ) ; wexp . Next ( ) ) {
edges . Append ( wexp . Current ( ) ) ;
}
}
// Then collect edges not part of wire
for ( TopExp_Explorer texp ( shape_ , TopAbs_EDGE , TopAbs_WIRE ) ; texp . More ( ) ; texp . Next ( ) ) {
edges . Append ( texp . Current ( ) ) ;
}
2026-06-24 18:58:26 +03:00
for ( NCollection_List < TopoDS_Shape > : : Iterator texp ( edges ) ; texp . More ( ) ; texp . Next ( ) ) {
2024-10-17 14:35:17 +02:00
BRepAdaptor_Curve crv ( TopoDS : : Edge ( texp . Value ( ) ) ) ;
2023-11-15 10:32:20 +01:00
GCPnts_QuasiUniformDeflection tessellater ( crv , settings . get < settings : : MesherLinearDeflection > ( ) . get ( ) ) ;
2023-03-21 20:15:01 +01:00
int n = tessellater . NbPoints ( ) ;
int previous = - 1 ;
2024-10-17 14:35:17 +02:00
const bool reversed = texp . Value ( ) . Orientation ( ) = = TopAbs_REVERSED ;
bool first = true ;
2025-04-18 10:08:57 +02:00
gp_Pnt p0 , p1 ;
double u0 = std : : numeric_limits < double > : : quiet_NaN ( ) , u1 = std : : numeric_limits < double > : : quiet_NaN ( ) ;
if ( auto crv = BRep_Tool : : Curve ( TopoDS : : Edge ( texp . Value ( ) ) , u0 , u1 ) ) {
TopoDS_Vertex v0 , v1 ;
TopExp : : Vertices ( TopoDS : : Edge ( texp . Value ( ) ) , v0 , v1 , false ) ;
if ( ! v0 . IsNull ( ) & & ! v1 . IsNull ( ) ) {
p0 = BRep_Tool : : Pnt ( v0 ) ;
p1 = BRep_Tool : : Pnt ( v1 ) ;
} else {
u0 = u1 = std : : numeric_limits < double > : : quiet_NaN ( ) ;
}
}
2024-10-17 14:35:17 +02:00
for ( int i = ( reversed ? n : 1 ) ; reversed ? ( i > = 1 ) : ( i < = n ) ; i + = reversed ? - 1 : 1 ) {
2025-04-18 10:08:57 +02:00
gp_XYZ p ;
if ( std : : fabs ( tessellater . Parameter ( i ) - u0 ) < 1.e-7 ) {
// Use the exact points from the topology when parameter is close to the begin or end of the parametric range
// This guarantees points are properly welded, because the GCPnts_QuasiUniformDeflection could otherwise introduce
// minor differences between the approximated points from shared vertices.
// @todo Using GCPnts_QuasiUniformDeflection on linear edges is pure lazyness
p = p0 . XYZ ( ) ;
} else if ( std : : fabs ( tessellater . Parameter ( i ) - u1 ) < 1.e-7 ) {
p = p1 . XYZ ( ) ;
} else {
p = tessellater . Value ( i ) . XYZ ( ) ;
}
2026-08-19 18:16:59 +05:00
2023-08-09 14:55:23 +08:00
auto p_local = p ;
2023-03-21 20:15:01 +01:00
taxonomy_transform ( place . components_ , p ) ;
2023-06-22 14:09:59 +02:00
int current = t - > addVertex ( item_id , surface_style_id , p . X ( ) , p . Y ( ) , p . Z ( ) ) ;
2023-03-21 20:15:01 +01:00
std : : vector < std : : pair < int , int > > segments ;
2024-10-17 14:35:17 +02:00
if ( ! first ) {
2023-03-21 20:15:01 +01:00
segments . push_back ( std : : make_pair ( previous , current ) ) ;
}
2024-10-17 14:35:17 +02:00
first = false ;
2023-03-21 20:15:01 +01:00
2023-11-15 10:32:20 +01:00
if ( settings . get < settings : : EdgeArrows > ( ) . get ( ) ) {
2023-03-21 20:15:01 +01:00
// In case you want direction arrows on your edges
double u = tessellater . Parameter ( i ) ;
gp_XYZ p2 , p3 ;
gp_Pnt tmp ;
gp_Vec tmp2 ;
crv . D1 ( u , tmp , tmp2 ) ;
gp_Dir d1 , d2 , d3 , d4 ;
d1 = tmp2 ;
2024-10-17 14:35:17 +02:00
if ( reversed ) {
2023-03-21 20:15:01 +01:00
d1 = - d1 ;
}
if ( fabs ( d1 . Z ( ) ) < 0.5 ) {
d2 = d1 . Crossed ( gp : : DZ ( ) ) ;
} else {
d2 = d1 . Crossed ( gp : : DY ( ) ) ;
}
d3 = d1 . XYZ ( ) + d2 . XYZ ( ) ;
d4 = d1 . XYZ ( ) - d2 . XYZ ( ) ;
2023-08-09 14:55:23 +08:00
p2 = p_local - d3 . XYZ ( ) / 10. ;
p3 = p_local - d4 . XYZ ( ) / 10. ;
2023-03-21 20:15:01 +01:00
taxonomy_transform ( place . components_ , p2 ) ;
taxonomy_transform ( place . components_ , p3 ) ;
2023-06-19 13:47:45 +02:00
int left = t - > addVertex ( item_id , surface_style_id , p2 . X ( ) , p2 . Y ( ) , p2 . Z ( ) ) ;
int right = t - > addVertex ( item_id , surface_style_id , p3 . X ( ) , p3 . Y ( ) , p3 . Z ( ) ) ;
2023-03-21 20:15:01 +01:00
segments . push_back ( std : : make_pair ( left , current ) ) ;
segments . push_back ( std : : make_pair ( right , current ) ) ;
}
for ( auto & sgmt : segments ) {
2024-09-25 15:15:47 +05:00
t - > addEdge ( item_id , surface_style_id , sgmt . first , sgmt . second ) ;
2023-03-21 20:15:01 +01:00
}
previous = current ;
}
}
}
2025-05-18 22:14:22 +02:00
if ( ! settings . get < settings : : OcctNoCleanTriangulation > ( ) . get ( ) ) {
BRepTools : : Clean ( shape_ ) ;
}
2023-03-21 20:15:01 +01:00
}
2026-08-08 16:09:30 +02:00
void ifcopenshell : : geom : : open_cascade_shape : : serialize ( const ifcopenshell : : geom : : taxonomy : : matrix4 & place , std : : string & r ) const {
2026-08-08 07:42:45 +02:00
auto s = ifcopenshell : : geom : : util : : apply_transformation ( shape_ , place ) ;
2023-08-25 21:46:32 +02:00
std : : stringstream sstream ;
2024-06-19 14:44:52 +02:00
# if OCC_VERSION_HEX >= 0x70600
BRepTools : : Write ( s , sstream , false , false , TopTools_FormatVersion_VERSION_2 ) ;
# else
2023-08-26 11:35:23 +02:00
BRepTools : : Write ( s , sstream ) ;
2024-06-19 14:44:52 +02:00
# endif
2023-08-25 21:46:32 +02:00
r = sstream . str ( ) ;
}
2026-08-08 07:42:45 +02:00
int ifcopenshell : : geom : : open_cascade_shape : : surface_genus ( ) const {
return ifcopenshell : : geom : : util : : surface_genus ( shape_ ) ;
2023-03-21 20:15:01 +01:00
}
2026-08-08 07:42:45 +02:00
bool ifcopenshell : : geom : : open_cascade_shape : : is_manifold ( ) const {
return ifcopenshell : : geom : : util : : is_manifold ( shape_ ) ;
2023-11-02 09:04:34 +01:00
}
2026-08-08 07:42:45 +02:00
int ifcopenshell : : geom : : open_cascade_shape : : num_vertices ( ) const
2023-11-02 09:04:34 +01:00
{
2026-08-08 07:42:45 +02:00
return ifcopenshell : : geom : : util : : count ( shape_ , TopAbs_VERTEX ) ;
2023-11-02 09:04:34 +01:00
}
2026-08-08 07:42:45 +02:00
int ifcopenshell : : geom : : open_cascade_shape : : num_edges ( ) const
2023-11-02 09:04:34 +01:00
{
2026-08-08 07:42:45 +02:00
return ifcopenshell : : geom : : util : : count ( shape_ , TopAbs_EDGE ) ;
2023-11-02 09:04:34 +01:00
}
2026-08-08 07:42:45 +02:00
int ifcopenshell : : geom : : open_cascade_shape : : num_faces ( ) const
2023-11-02 09:04:34 +01:00
{
2026-08-08 07:42:45 +02:00
return ifcopenshell : : geom : : util : : count ( shape_ , TopAbs_FACE ) ;
2023-11-02 09:04:34 +01:00
}
2026-08-08 07:42:45 +02:00
opaque_number ifcopenshell : : geom : : open_cascade_shape : : open_cascade_shape : : length ( )
2023-11-02 09:04:34 +01:00
{
GProp_GProps prop ;
BRepGProp : : LinearProperties ( shape_ , prop ) ;
double l = prop . Mass ( ) ;
2026-08-08 07:42:45 +02:00
return opaque_number ( l ) ;
2023-11-02 09:04:34 +01:00
}
2026-08-08 07:42:45 +02:00
opaque_number ifcopenshell : : geom : : open_cascade_shape : : area ( )
2023-11-02 09:04:34 +01:00
{
GProp_GProps prop ;
BRepGProp : : SurfaceProperties ( shape_ , prop ) ;
double l = prop . Mass ( ) ;
2026-08-08 07:42:45 +02:00
return opaque_number ( l ) ;
2023-11-02 09:04:34 +01:00
}
2026-08-08 07:42:45 +02:00
opaque_number ifcopenshell : : geom : : open_cascade_shape : : volume ( )
2023-11-02 09:04:34 +01:00
{
GProp_GProps prop ;
BRepGProp : : VolumeProperties ( shape_ , prop ) ;
double l = prop . Mass ( ) ;
2026-08-08 07:42:45 +02:00
return opaque_number ( l ) ;
2023-11-02 09:04:34 +01:00
}
# include <Geom_Plane.hxx>
2026-08-08 07:42:45 +02:00
opaque_coordinate < 3 > ifcopenshell : : geom : : open_cascade_shape : : position ( )
2023-11-02 09:04:34 +01:00
{
if ( shape_ . ShapeType ( ) = = TopAbs_FACE ) {
auto surf = BRep_Tool : : Surface ( TopoDS : : Face ( shape_ ) ) ;
auto plane = Handle ( Geom_Plane ) : : DownCast ( surf ) ;
if ( plane ) {
auto loc = plane - > Location ( ) ;
2026-08-08 07:42:45 +02:00
return opaque_coordinate < 3 > (
opaque_number ( loc . X ( ) ) ,
opaque_number ( loc . Y ( ) ) ,
opaque_number ( loc . Z ( ) )
2023-11-02 09:04:34 +01:00
) ;
}
}
throw std : : runtime_error ( " Invalid shape type " ) ;
}
2026-08-08 07:42:45 +02:00
opaque_coordinate < 3 > ifcopenshell : : geom : : open_cascade_shape : : axis ( )
2023-11-02 09:04:34 +01:00
{
if ( shape_ . ShapeType ( ) = = TopAbs_FACE ) {
auto surf = BRep_Tool : : Surface ( TopoDS : : Face ( shape_ ) ) ;
auto plane = Handle ( Geom_Plane ) : : DownCast ( surf ) ;
if ( plane ) {
auto dir = plane - > Axis ( ) . Direction ( ) ;
2026-08-08 07:42:45 +02:00
return opaque_coordinate < 3 > (
opaque_number ( dir . X ( ) ) ,
opaque_number ( dir . Y ( ) ) ,
opaque_number ( dir . Z ( ) )
2023-11-02 09:04:34 +01:00
) ;
}
}
throw std : : runtime_error ( " Invalid shape type " ) ;
}
2026-08-08 07:42:45 +02:00
opaque_coordinate < 4 > ifcopenshell : : geom : : open_cascade_shape : : plane_equation ( )
2023-11-02 09:04:34 +01:00
{
if ( shape_ . ShapeType ( ) = = TopAbs_FACE ) {
auto surf = BRep_Tool : : Surface ( TopoDS : : Face ( shape_ ) ) ;
auto plane = Handle ( Geom_Plane ) : : DownCast ( surf ) ;
if ( plane ) {
double a , b , c , d ;
plane - > Pln ( ) . Coefficients ( a , b , c , d ) ;
2026-08-08 07:42:45 +02:00
return opaque_coordinate < 4 > (
opaque_number ( a ) ,
opaque_number ( b ) ,
opaque_number ( c ) ,
opaque_number ( d )
2023-11-02 09:04:34 +01:00
) ;
}
}
throw std : : runtime_error ( " Invalid shape type " ) ;
}
2026-08-08 07:42:45 +02:00
std : : vector < conversion_result_shape * > ifcopenshell : : geom : : open_cascade_shape : : convex_decomposition ( )
2023-11-02 09:04:34 +01:00
{
throw std : : runtime_error ( " Not implemented " ) ;
}
2026-08-08 07:42:45 +02:00
conversion_result_shape * ifcopenshell : : geom : : open_cascade_shape : : halfspaces ( )
2023-11-02 09:04:34 +01:00
{
throw std : : runtime_error ( " Not implemented " ) ;
}
2026-08-08 07:42:45 +02:00
conversion_result_shape * ifcopenshell : : geom : : open_cascade_shape : : solid ( )
2023-11-02 09:04:34 +01:00
{
2023-03-21 20:15:01 +01:00
throw std : : runtime_error ( " Not implemented " ) ;
2023-11-02 09:04:34 +01:00
}
2026-08-08 07:42:45 +02:00
conversion_result_shape * ifcopenshell : : geom : : open_cascade_shape : : box ( )
2023-11-02 09:04:34 +01:00
{
throw std : : runtime_error ( " Not implemented " ) ;
}
2026-08-08 07:42:45 +02:00
conversion_result_shape * ifcopenshell : : geom : : open_cascade_shape : : wrap_in_compound ( )
2025-10-01 15:01:50 +02:00
{
TopoDS_Compound compound ;
BRep_Builder builder ;
builder . MakeCompound ( compound ) ;
builder . Add ( compound , shape_ ) ;
2026-08-08 07:42:45 +02:00
return new open_cascade_shape ( std : : move ( compound ) ) ;
2025-10-01 15:01:50 +02:00
}
2026-08-08 07:42:45 +02:00
std : : vector < conversion_result_shape * > ifcopenshell : : geom : : open_cascade_shape : : vertices ( )
2023-12-13 16:43:33 +01:00
{
2026-06-24 18:58:26 +03:00
NCollection_IndexedMap < TopoDS_Shape , TopTools_ShapeMapHasher > map ;
2023-12-13 16:43:33 +01:00
TopExp : : MapShapes ( shape_ , TopAbs_VERTEX , map ) ;
2026-08-08 07:42:45 +02:00
std : : vector < conversion_result_shape * > vec ;
2023-12-13 16:43:33 +01:00
for ( int i = 1 ; i < = map . Extent ( ) ; + + i ) {
2026-08-08 07:42:45 +02:00
vec . push_back ( new open_cascade_shape ( map . FindKey ( i ) ) ) ;
2023-12-13 16:43:33 +01:00
}
return vec ;
}
2026-08-08 07:42:45 +02:00
std : : vector < conversion_result_shape * > ifcopenshell : : geom : : open_cascade_shape : : edges ( )
2023-11-02 09:04:34 +01:00
{
2026-06-24 18:58:26 +03:00
NCollection_IndexedMap < TopoDS_Shape , TopTools_ShapeMapHasher > map ;
2023-11-02 09:04:34 +01:00
TopExp : : MapShapes ( shape_ , TopAbs_EDGE , map ) ;
2026-08-08 07:42:45 +02:00
std : : vector < conversion_result_shape * > vec ;
2023-11-02 09:04:34 +01:00
for ( int i = 1 ; i < = map . Extent ( ) ; + + i ) {
2026-08-08 07:42:45 +02:00
vec . push_back ( new open_cascade_shape ( map . FindKey ( i ) ) ) ;
2023-11-02 09:04:34 +01:00
}
return vec ;
}
2026-08-08 07:42:45 +02:00
std : : vector < conversion_result_shape * > ifcopenshell : : geom : : open_cascade_shape : : facets ( )
2023-11-02 09:04:34 +01:00
{
2026-06-24 18:58:26 +03:00
NCollection_IndexedMap < TopoDS_Shape , TopTools_ShapeMapHasher > map ;
2023-11-02 09:04:34 +01:00
TopExp : : MapShapes ( shape_ , TopAbs_FACE , map ) ;
2026-08-08 07:42:45 +02:00
std : : vector < conversion_result_shape * > vec ;
2023-11-02 09:04:34 +01:00
for ( int i = 1 ; i < = map . Extent ( ) ; + + i ) {
2026-08-08 07:42:45 +02:00
vec . push_back ( new open_cascade_shape ( map . FindKey ( i ) ) ) ;
2023-11-02 09:04:34 +01:00
}
return vec ;
}
namespace {
2026-08-08 07:42:45 +02:00
conversion_result_shape * boolean_op ( BOPAlgo_Operation op , const TopoDS_Shape & shape_ , const TopoDS_Shape & other_shape ) {
ifcopenshell : : geom : : util : : boolean_settings st ;
2023-11-02 09:04:34 +01:00
st . attempt_2d = true ;
st . debug = false ;
st . precision = 1.e-5 ;
TopoDS_Shape result ;
2026-08-08 07:42:45 +02:00
if ( ifcopenshell : : geom : : util : : boolean_operation ( st , shape_ , other_shape , op , result ) ) {
return new ifcopenshell : : geom : : open_cascade_shape ( result ) ;
2023-11-02 09:04:34 +01:00
} else {
throw std : : runtime_error ( " Failed to process boolean operation " ) ;
}
}
}
2026-08-08 07:42:45 +02:00
conversion_result_shape * ifcopenshell : : geom : : open_cascade_shape : : add ( conversion_result_shape * other )
2023-11-02 09:04:34 +01:00
{
2026-08-08 07:42:45 +02:00
return boolean_op ( BOPAlgo_FUSE , shape_ , ( ( ifcopenshell : : geom : : open_cascade_shape * ) other ) - > shape_ ) ;
2023-11-02 09:04:34 +01:00
}
2026-08-08 07:42:45 +02:00
conversion_result_shape * ifcopenshell : : geom : : open_cascade_shape : : subtract ( conversion_result_shape * other )
2023-11-02 09:04:34 +01:00
{
2026-08-08 07:42:45 +02:00
return boolean_op ( BOPAlgo_CUT , shape_ , ( ( ifcopenshell : : geom : : open_cascade_shape * ) other ) - > shape_ ) ;
2023-11-02 09:04:34 +01:00
}
2026-08-08 07:42:45 +02:00
conversion_result_shape * ifcopenshell : : geom : : open_cascade_shape : : intersect ( conversion_result_shape * other )
2023-11-02 09:04:34 +01:00
{
2026-08-08 07:42:45 +02:00
return boolean_op ( BOPAlgo_COMMON , shape_ , ( ( ifcopenshell : : geom : : open_cascade_shape * ) other ) - > shape_ ) ;
2023-11-02 09:04:34 +01:00
}
2026-08-08 07:42:45 +02:00
conversion_result_shape * ifcopenshell : : geom : : open_cascade_shape : : concat ( conversion_result_shape * other )
2025-09-26 14:24:49 +02:00
{
TopoDS_Compound compound ;
BRep_Builder builder ;
2026-08-19 18:16:59 +05:00
2025-09-26 14:24:49 +02:00
auto & left = shape_ ;
2026-08-08 07:42:45 +02:00
auto & right = ( ( ifcopenshell : : geom : : open_cascade_shape * ) other ) - > shape_ ;
2025-09-26 14:24:49 +02:00
2025-10-01 15:01:50 +02:00
// This reads a bit strange, but we want to specifically avoid compounds of faces that are
// the result of shell instances that are not sewn into a shell (yet).
2026-08-08 07:42:45 +02:00
if ( left . ShapeType ( ) = = TopAbs_COMPOUND & & ! ifcopenshell : : geom : : util : : is_compound_of_faces ( left ) ) {
2025-09-26 14:24:49 +02:00
compound = TopoDS : : Compound ( left ) ;
} else {
builder . MakeCompound ( compound ) ;
builder . Add ( compound , left ) ;
}
2026-08-19 18:16:59 +05:00
2025-09-26 14:24:49 +02:00
builder . Add ( compound , right ) ;
2026-08-08 07:42:45 +02:00
return new open_cascade_shape ( std : : move ( compound ) ) ;
2025-09-26 14:24:49 +02:00
}
2026-08-08 07:42:45 +02:00
std : : pair < opaque_coordinate < 3 > , opaque_coordinate < 3 > > ifcopenshell : : geom : : open_cascade_shape : : bounding_box ( ) const
2023-11-02 09:04:34 +01:00
{
throw std : : runtime_error ( " Not implemented " ) ;
}
2026-08-08 07:42:45 +02:00
conversion_result_shape * ifcopenshell : : geom : : open_cascade_shape : : moved ( ifcopenshell : : geom : : taxonomy : : matrix4 : : ptr t ) const
2023-11-02 09:04:34 +01:00
{
2026-08-08 07:42:45 +02:00
return new open_cascade_shape ( ifcopenshell : : geom : : util : : apply_transformation ( shape_ , * t ) ) ;
2023-11-02 09:04:34 +01:00
}
2025-09-26 14:24:49 +02:00
namespace {
void accumulate ( const gp_Ax3 & ax , const gp_Dir & normal , double area , double & along_x , double & along_y , double & along_z ) {
along_x + = area * fabs ( ax . XDirection ( ) . Dot ( normal ) ) ;
along_y + = area * fabs ( ax . YDirection ( ) . Dot ( normal ) ) ;
along_z + = area * fabs ( ax . Direction ( ) . Dot ( normal ) ) ;
}
void surface_area_along_direction_ ( double tol , const TopoDS_Shape & s , const gp_Ax3 & ax , double & along_x , double & along_y , double & along_z ) {
along_x = along_y = along_z = 0. ;
bool meshed = false ;
TopExp_Explorer exp ( s , TopAbs_FACE ) ;
for ( ; exp . More ( ) ; exp . Next ( ) ) {
const TopoDS_Face & face = TopoDS : : Face ( exp . Current ( ) ) ;
Handle ( Geom_Surface ) surf = BRep_Tool : : Surface ( face ) ;
Handle ( Geom_Plane ) plane = Handle ( Geom_Plane ) : : DownCast ( surf ) ;
if ( surf - > DynamicType ( ) = = STANDARD_TYPE ( Geom_Plane ) ) {
GProp_GProps prop_area ;
BRepGProp : : SurfaceProperties ( face , prop_area ) ;
const double area = prop_area . Mass ( ) ;
accumulate ( ax , plane - > Position ( ) . Direction ( ) , area , along_x , along_y , along_z ) ;
} else {
if ( ! meshed ) {
try {
BRepMesh_IncrementalMesh ( s , tol ) ;
} catch ( . . . ) {
2026-08-08 13:58:39 +02:00
ifcopenshell : : logger : : root ( ) . message ( ifcopenshell : : logger : : LOG_ERROR , " GEO " , 186 , " Failed to triangulate shape " ) ;
2025-09-26 14:24:49 +02:00
return ;
}
meshed = true ;
}
TopLoc_Location loc ;
Handle ( Poly_Triangulation ) tri = BRep_Tool : : Triangulation ( face , loc ) ;
if ( ! tri . IsNull ( ) ) {
std : : vector < gp_XYZ > coords ;
coords . reserve ( tri - > NbNodes ( ) ) ;
for ( int i = 1 ; i < = tri - > NbNodes ( ) ; + + i ) {
coords . push_back ( tri - > Node ( i ) . Transformed ( loc ) . XYZ ( ) ) ;
}
2026-06-24 00:40:32 +03:00
const NCollection_Array1 < Poly_Triangle > & triangles = tri - > Triangles ( ) ;
2025-09-26 14:24:49 +02:00
for ( int i = 1 ; i < = triangles . Length ( ) ; + + i ) {
int n1 , n2 , n3 ;
if ( face . Orientation ( ) = = TopAbs_REVERSED ) {
triangles ( i ) . Get ( n3 , n2 , n1 ) ;
} else {
triangles ( i ) . Get ( n1 , n2 , n3 ) ;
}
const gp_XYZ & pt1 = coords [ n1 - 1 ] ;
const gp_XYZ & pt2 = coords [ n2 - 1 ] ;
const gp_XYZ & pt3 = coords [ n3 - 1 ] ;
const gp_Vec v1 = pt2 - pt1 ;
const gp_Vec v2 = pt3 - pt2 ;
const gp_Vec v3 = pt1 - pt3 ;
const gp_Vec normal_vector = v1 ^ v2 ;
if ( normal_vector . Magnitude ( ) > 1.e-7 ) {
gp_Dir normal = gp_Dir ( ) ;
double edge_lengths [ 3 ] = { v1 . Magnitude ( ) , v2 . Magnitude ( ) , v3 . Magnitude ( ) } ;
std : : sort ( & edge_lengths [ 0 ] , & edge_lengths [ 2 ] ) ;
const double & a = edge_lengths [ 0 ] ;
const double & b = edge_lengths [ 1 ] ;
const double & c = edge_lengths [ 2 ] ;
const double area = 0.25 * sqrt ( ( a + ( b + c ) ) * ( c - ( a - b ) ) * ( c + ( a - b ) ) * ( a + ( b - c ) ) ) ;
accumulate ( ax , normal , area , along_x , along_y , along_z ) ;
}
}
}
}
}
}
}
2026-08-08 07:42:45 +02:00
bool ifcopenshell : : geom : : open_cascade_shape : : surface_area_along_direction ( double tol , const ifcopenshell : : geom : : taxonomy : : matrix4 : : ptr & place , double & along_x , double & along_y , double & along_z ) const
2025-09-26 14:24:49 +02:00
{
gp_GTrsf trsf ;
if ( place - > components_ ) {
gp_Trsf tr ;
const auto & m = place - > ccomponents ( ) ;
tr . SetValues (
m ( 0 , 0 ) , m ( 0 , 1 ) , m ( 0 , 2 ) , m ( 0 , 3 ) ,
m ( 1 , 0 ) , m ( 1 , 1 ) , m ( 1 , 2 ) , m ( 1 , 3 ) ,
m ( 2 , 0 ) , m ( 2 , 1 ) , m ( 2 , 2 ) , m ( 2 , 3 )
) ;
trsf = tr ;
}
gp_Mat mat = trsf . Trsf ( ) . HVectorialPart ( ) ;
gp_Ax3 ax ( trsf . TranslationPart ( ) , mat . Column ( 3 ) , mat . Column ( 1 ) ) ;
surface_area_along_direction_ ( tol , shape_ , ax , along_x , along_y , along_z ) ;
return true ;
}
2026-08-08 07:42:45 +02:00
std : : size_t ifcopenshell : : geom : : open_cascade_shape : : map ( opaque_coordinate < 4 > & , opaque_coordinate < 4 > & ) {
2023-11-02 09:04:34 +01:00
throw std : : runtime_error ( " Not implemented " ) ;
}
2023-12-11 21:03:55 +01:00
2026-08-08 07:42:45 +02:00
std : : size_t ifcopenshell : : geom : : open_cascade_shape : : map ( const std : : vector < opaque_coordinate < 4 > > & , const std : : vector < opaque_coordinate < 4 > > & ) {
2023-12-11 21:03:55 +01:00
throw std : : runtime_error ( " Not implemented " ) ;
}