2023-11-02 09:04:34 +01:00
# include <map>
# 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
# include "OpenCascadeConversionResult.h"
2023-03-21 20:15:01 +01:00
# include "../../../ifcparse/IfcLogger.h"
# include "../../../ifcgeom/IfcGeomRepresentation.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
2023-11-02 09:04:34 +01:00
using IfcGeom : : OpaqueNumber ;
using IfcGeom : : OpaqueCoordinate ;
using IfcGeom : : ConversionResultShape ;
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-06-10 18:30:54 +02:00
void ifcopenshell : : geometry : : OpenCascadeShape : : Triangulate ( ifcopenshell : : geometry : : Settings settings , const ifcopenshell : : geometry : : taxonomy : : matrix4 & place , IfcGeom : : Representation : : Triangulation * t , int item_id , int surface_style_id , Logger & logger ) const {
2023-03-21 20:15:01 +01:00
2023-07-27 16:42:06 +08:00
// @todo remove duplication with OpenCascadeKernel::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
boost : : optional < gp_Mat > rotation_matrix ;
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 )
) ;
}
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 ) {
// Triangulate the shape
try {
BRepMesh_IncrementalMesh ( shape_ , settings . get < settings : : MesherLinearDeflection > ( ) . get ( ) , false , settings . get < settings : : MesherAngularDeflection > ( ) . get ( ) ) ;
} catch ( . . . ) {
2026-06-10 18:30:54 +02:00
Logger : : Root ( ) . Message ( 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
size_t num_bounds = 0 ;
for ( TopoDS_Iterator it ( face ) ; it . More ( ) ; it . Next ( ) , + + num_bounds ) { }
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-24 00:40:32 +03:00
occ : : handle < Poly_Triangulation > tri = BRep_Tool : : Triangulation ( face , loc ) ;
2023-03-21 20:15:01 +01:00
if ( tri . IsNull ( ) ) {
2026-06-10 18:30:54 +02:00
Logger : : Root ( ) . Message ( 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-24 00:40:32 +03:00
occ : : 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-06-10 18:30:54 +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 ) {
2024-10-04 19:11:02 +02:00
auto loops = IfcGeom : : 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 ( ) ) {
2024-10-18 10:37:44 +02:00
t - > uvs_ref ( ) = IfcGeom : : Representation : : 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
TopTools_ListOfShape edges ;
// 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 ( ) ) ;
}
for ( TopTools_ListIteratorOfListOfShape texp ( edges ) ; texp . More ( ) ; texp . Next ( ) ) {
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 ( ) ;
}
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
}
2023-08-26 11:35:23 +02:00
void ifcopenshell : : geometry : : OpenCascadeShape : : Serialize ( const ifcopenshell : : geometry : : taxonomy : : matrix4 & place , std : : string & r ) const {
auto s = IfcGeom : : 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 ( ) ;
}
2023-03-21 20:15:01 +01:00
int ifcopenshell : : geometry : : OpenCascadeShape : : surface_genus ( ) const {
2023-11-02 09:04:34 +01:00
return IfcGeom : : util : : surface_genus ( shape_ ) ;
2023-03-21 20:15:01 +01:00
}
bool ifcopenshell : : geometry : : OpenCascadeShape : : is_manifold ( ) const {
2023-11-02 09:04:34 +01:00
return IfcGeom : : util : : is_manifold ( shape_ ) ;
}
int ifcopenshell : : geometry : : OpenCascadeShape : : num_vertices ( ) const
{
return IfcGeom : : util : : count ( shape_ , TopAbs_VERTEX ) ;
}
int ifcopenshell : : geometry : : OpenCascadeShape : : num_edges ( ) const
{
return IfcGeom : : util : : count ( shape_ , TopAbs_EDGE ) ;
}
int ifcopenshell : : geometry : : OpenCascadeShape : : num_faces ( ) const
{
return IfcGeom : : util : : count ( shape_ , TopAbs_FACE ) ;
}
2026-06-22 10:38:25 +02:00
OpaqueNumber ifcopenshell : : geometry : : OpenCascadeShape : : OpenCascadeShape : : length ( )
2023-11-02 09:04:34 +01:00
{
GProp_GProps prop ;
BRepGProp : : LinearProperties ( shape_ , prop ) ;
double l = prop . Mass ( ) ;
2026-06-23 20:25:57 +02:00
return OpaqueNumber ( l ) ;
2023-11-02 09:04:34 +01:00
}
2026-06-22 10:38:25 +02:00
OpaqueNumber ifcopenshell : : geometry : : OpenCascadeShape : : area ( )
2023-11-02 09:04:34 +01:00
{
GProp_GProps prop ;
BRepGProp : : SurfaceProperties ( shape_ , prop ) ;
double l = prop . Mass ( ) ;
2026-06-23 20:25:57 +02:00
return OpaqueNumber ( l ) ;
2023-11-02 09:04:34 +01:00
}
2026-06-22 10:38:25 +02:00
OpaqueNumber ifcopenshell : : geometry : : OpenCascadeShape : : volume ( )
2023-11-02 09:04:34 +01:00
{
GProp_GProps prop ;
BRepGProp : : VolumeProperties ( shape_ , prop ) ;
double l = prop . Mass ( ) ;
2026-06-23 20:25:57 +02:00
return OpaqueNumber ( l ) ;
2023-11-02 09:04:34 +01:00
}
# include <Geom_Plane.hxx>
OpaqueCoordinate < 3 > ifcopenshell : : geometry : : OpenCascadeShape : : position ( )
{
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 ( ) ;
return OpaqueCoordinate < 3 > (
2026-06-23 20:25:57 +02:00
OpaqueNumber ( loc . X ( ) ) ,
OpaqueNumber ( loc . Y ( ) ) ,
OpaqueNumber ( loc . Z ( ) )
2023-11-02 09:04:34 +01:00
) ;
}
}
throw std : : runtime_error ( " Invalid shape type " ) ;
}
OpaqueCoordinate < 3 > ifcopenshell : : geometry : : OpenCascadeShape : : axis ( )
{
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 ( ) ;
return OpaqueCoordinate < 3 > (
2026-06-23 20:25:57 +02:00
OpaqueNumber ( dir . X ( ) ) ,
OpaqueNumber ( dir . Y ( ) ) ,
OpaqueNumber ( dir . Z ( ) )
2023-11-02 09:04:34 +01:00
) ;
}
}
throw std : : runtime_error ( " Invalid shape type " ) ;
}
OpaqueCoordinate < 4 > ifcopenshell : : geometry : : OpenCascadeShape : : plane_equation ( )
{
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 ) ;
return OpaqueCoordinate < 4 > (
2026-06-23 20:25:57 +02:00
OpaqueNumber ( a ) ,
OpaqueNumber ( b ) ,
OpaqueNumber ( c ) ,
OpaqueNumber ( d )
2023-11-02 09:04:34 +01:00
) ;
}
}
throw std : : runtime_error ( " Invalid shape type " ) ;
}
std : : vector < ConversionResultShape * > ifcopenshell : : geometry : : OpenCascadeShape : : convex_decomposition ( )
{
throw std : : runtime_error ( " Not implemented " ) ;
}
ConversionResultShape * ifcopenshell : : geometry : : OpenCascadeShape : : halfspaces ( )
{
throw std : : runtime_error ( " Not implemented " ) ;
}
ConversionResultShape * ifcopenshell : : geometry : : OpenCascadeShape : : solid ( )
{
2023-03-21 20:15:01 +01:00
throw std : : runtime_error ( " Not implemented " ) ;
2023-11-02 09:04:34 +01:00
}
ConversionResultShape * ifcopenshell : : geometry : : OpenCascadeShape : : box ( )
{
throw std : : runtime_error ( " Not implemented " ) ;
}
2025-10-01 15:01:50 +02:00
ConversionResultShape * ifcopenshell : : geometry : : OpenCascadeShape : : wrap_in_compound ( )
{
TopoDS_Compound compound ;
BRep_Builder builder ;
builder . MakeCompound ( compound ) ;
builder . Add ( compound , shape_ ) ;
return new OpenCascadeShape ( std : : move ( compound ) ) ;
}
2023-12-13 16:43:33 +01:00
std : : vector < ConversionResultShape * > ifcopenshell : : geometry : : OpenCascadeShape : : vertices ( )
{
TopTools_IndexedMapOfShape map ;
TopExp : : MapShapes ( shape_ , TopAbs_VERTEX , map ) ;
std : : vector < ConversionResultShape * > vec ;
for ( int i = 1 ; i < = map . Extent ( ) ; + + i ) {
vec . push_back ( new OpenCascadeShape ( map . FindKey ( i ) ) ) ;
}
return vec ;
}
2023-11-02 09:04:34 +01:00
std : : vector < ConversionResultShape * > ifcopenshell : : geometry : : OpenCascadeShape : : edges ( )
{
TopTools_IndexedMapOfShape map ;
TopExp : : MapShapes ( shape_ , TopAbs_EDGE , map ) ;
std : : vector < ConversionResultShape * > vec ;
for ( int i = 1 ; i < = map . Extent ( ) ; + + i ) {
vec . push_back ( new OpenCascadeShape ( map . FindKey ( i ) ) ) ;
}
return vec ;
}
std : : vector < ConversionResultShape * > ifcopenshell : : geometry : : OpenCascadeShape : : facets ( )
{
TopTools_IndexedMapOfShape map ;
TopExp : : MapShapes ( shape_ , TopAbs_FACE , map ) ;
std : : vector < ConversionResultShape * > vec ;
for ( int i = 1 ; i < = map . Extent ( ) ; + + i ) {
vec . push_back ( new OpenCascadeShape ( map . FindKey ( i ) ) ) ;
}
return vec ;
}
namespace {
ConversionResultShape * boolean_op ( BOPAlgo_Operation op , const TopoDS_Shape & shape_ , const TopoDS_Shape & other_shape ) {
IfcGeom : : util : : boolean_settings st ;
st . attempt_2d = true ;
st . debug = false ;
st . precision = 1.e-5 ;
TopoDS_Shape result ;
if ( IfcGeom : : util : : boolean_operation ( st , shape_ , other_shape , op , result ) ) {
return new ifcopenshell : : geometry : : OpenCascadeShape ( result ) ;
} else {
throw std : : runtime_error ( " Failed to process boolean operation " ) ;
}
}
}
ConversionResultShape * ifcopenshell : : geometry : : OpenCascadeShape : : add ( ConversionResultShape * other )
{
return boolean_op ( BOPAlgo_FUSE , shape_ , ( ( ifcopenshell : : geometry : : OpenCascadeShape * ) other ) - > shape_ ) ;
}
ConversionResultShape * ifcopenshell : : geometry : : OpenCascadeShape : : subtract ( ConversionResultShape * other )
{
return boolean_op ( BOPAlgo_CUT , shape_ , ( ( ifcopenshell : : geometry : : OpenCascadeShape * ) other ) - > shape_ ) ;
}
ConversionResultShape * ifcopenshell : : geometry : : OpenCascadeShape : : intersect ( ConversionResultShape * other )
{
return boolean_op ( BOPAlgo_COMMON , shape_ , ( ( ifcopenshell : : geometry : : OpenCascadeShape * ) other ) - > shape_ ) ;
}
2025-09-26 14:24:49 +02:00
ConversionResultShape * ifcopenshell : : geometry : : OpenCascadeShape : : concat ( ConversionResultShape * other )
{
TopoDS_Compound compound ;
BRep_Builder builder ;
auto & left = shape_ ;
auto & right = ( ( ifcopenshell : : geometry : : OpenCascadeShape * ) other ) - > shape_ ;
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).
if ( left . ShapeType ( ) = = TopAbs_COMPOUND & & ! IfcGeom : : 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 ) ;
}
builder . Add ( compound , right ) ;
return new OpenCascadeShape ( std : : move ( compound ) ) ;
}
2023-11-02 09:04:34 +01:00
std : : pair < OpaqueCoordinate < 3 > , OpaqueCoordinate < 3 > > ifcopenshell : : geometry : : OpenCascadeShape : : bounding_box ( ) const
{
throw std : : runtime_error ( " Not implemented " ) ;
}
ConversionResultShape * ifcopenshell : : geometry : : OpenCascadeShape : : moved ( ifcopenshell : : geometry : : taxonomy : : matrix4 : : ptr t ) const
{
return new OpenCascadeShape ( IfcGeom : : util : : apply_transformation ( shape_ , * t ) ) ;
}
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-06-10 18:30:54 +02:00
Logger : : Root ( ) . Message ( 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 ) ;
}
}
}
}
}
}
}
bool ifcopenshell : : geometry : : OpenCascadeShape : : surface_area_along_direction ( double tol , const ifcopenshell : : geometry : : taxonomy : : matrix4 : : ptr & place , double & along_x , double & along_y , double & along_z ) const
{
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-06-20 13:30:17 +02:00
std : : size_t ifcopenshell : : geometry : : OpenCascadeShape : : map ( OpaqueCoordinate < 4 > & , OpaqueCoordinate < 4 > & ) {
2023-11-02 09:04:34 +01:00
throw std : : runtime_error ( " Not implemented " ) ;
}
2023-12-11 21:03:55 +01:00
2026-06-20 13:30:17 +02:00
std : : size_t ifcopenshell : : geometry : : OpenCascadeShape : : map ( const std : : vector < OpaqueCoordinate < 4 > > & , const std : : vector < OpaqueCoordinate < 4 > > & ) {
2023-12-11 21:03:55 +01:00
throw std : : runtime_error ( " Not implemented " ) ;
}