2026-04-07 15:47:58 +02:00
# include "ManifoldKernel.h"
# include "../../../ifcparse/logger.h"
# include <Eigen/Dense>
# include <manifold/polygon.h>
# include <algorithm>
# include <array>
# include <cmath>
# include <iomanip>
# include <limits>
# include <optional>
# include <sstream>
# include <unordered_map>
using namespace ifcopenshell : : geometry ;
using namespace ifcopenshell : : geometry : : kernels ;
namespace {
using Mesh = manifold : : MeshGL64 ;
using Part = ifcopenshell : : geometry : : ManifoldPart ;
std : : string manifold_error_string ( manifold : : Manifold : : Error error ) {
switch ( error ) {
case manifold : : Manifold : : Error : : NoError :
return " no error " ;
case manifold : : Manifold : : Error : : NonFiniteVertex :
return " non-finite vertex " ;
case manifold : : Manifold : : Error : : NotManifold :
return " not manifold " ;
case manifold : : Manifold : : Error : : VertexOutOfBounds :
return " vertex out of bounds " ;
case manifold : : Manifold : : Error : : PropertiesWrongLength :
return " properties wrong length " ;
case manifold : : Manifold : : Error : : MissingPositionProperties :
return " missing position properties " ;
case manifold : : Manifold : : Error : : MergeVectorsDifferentLengths :
return " merge vectors different lengths " ;
case manifold : : Manifold : : Error : : MergeIndexOutOfBounds :
return " merge index out of bounds " ;
case manifold : : Manifold : : Error : : TransformWrongLength :
return " transform wrong length " ;
case manifold : : Manifold : : Error : : RunIndexWrongLength :
return " run index wrong length " ;
case manifold : : Manifold : : Error : : FaceIDWrongLength :
return " face id wrong length " ;
case manifold : : Manifold : : Error : : InvalidConstruction :
return " invalid construction " ;
case manifold : : Manifold : : Error : : ResultTooLarge :
return " result too large " ;
}
return " unknown error " ;
}
struct VertexKey {
long long x ;
long long y ;
long long z ;
bool operator = = ( const VertexKey & other ) const {
return x = = other . x & & y = = other . y & & z = = other . z ;
}
} ;
struct VertexKeyHash {
size_t operator ( ) ( const VertexKey & key ) const {
auto h = std : : hash < long long > ( ) ( key . x ) ;
h ^ = std : : hash < long long > ( ) ( key . y ) + 0x9e3779b97f4a7c15ull + ( h < < 6 ) + ( h > > 2 ) ;
h ^ = std : : hash < long long > ( ) ( key . z ) + 0x9e3779b97f4a7c15ull + ( h < < 6 ) + ( h > > 2 ) ;
return h ;
}
} ;
struct MeshBuilder {
double precision ;
2026-04-10 14:46:46 +02:00
double dilation = 0. ;
2026-04-07 15:47:58 +02:00
std : : vector < Eigen : : Vector3d > vertices ;
std : : unordered_map < VertexKey , uint64_t , VertexKeyHash > vertex_map ;
std : : vector < uint64_t > tri_verts ;
std : : vector < uint64_t > face_ids ;
explicit MeshBuilder ( double p ) : precision ( p > 0. ? p : 1.e-9 ) { }
VertexKey key ( const Eigen : : Vector3d & p ) const {
return {
( long long ) std : : llround ( p ( 0 ) / precision ) ,
( long long ) std : : llround ( p ( 1 ) / precision ) ,
( long long ) std : : llround ( p ( 2 ) / precision )
} ;
}
uint64_t add_vertex ( const Eigen : : Vector3d & p ) {
auto entry = vertex_map . find ( key ( p ) ) ;
if ( entry ! = vertex_map . end ( ) ) {
return entry - > second ;
}
auto idx = ( uint64_t ) vertices . size ( ) ;
vertices . push_back ( p ) ;
vertex_map . insert ( { key ( p ) , idx } ) ;
return idx ;
}
void add_triangle ( uint64_t a , uint64_t b , uint64_t c , uint64_t face_id ) {
if ( a = = b | | b = = c | | c = = a ) {
return ;
}
tri_verts . push_back ( a ) ;
tri_verts . push_back ( b ) ;
tri_verts . push_back ( c ) ;
face_ids . push_back ( face_id ) ;
}
Mesh build ( ) const {
Mesh mesh ;
mesh . numProp = 3 ;
2026-04-10 14:46:46 +02:00
std : : vector < size_t > vertex_use_count ( vertices . size ( ) , 0 ) ;
std : : vector < Eigen : : Vector3d > vertex_normals ( vertices . size ( ) , Eigen : : Vector3d : : Zero ( ) ) ;
for ( size_t i = 0 ; i < tri_verts . size ( ) ; i + = 3 ) {
for ( size_t j = 0 ; j < 3 ; + + j ) {
vertex_use_count [ tri_verts [ i + j ] ] + + ;
// Calculate triangle normal
Eigen : : Vector3d normal = ( vertices [ tri_verts [ i + 1 ] ] - vertices [ tri_verts [ i ] ] ) . cross ( vertices [ tri_verts [ i + 2 ] ] - vertices [ tri_verts [ i ] ] ) / 2. ;
vertex_normals [ tri_verts [ i + j ] ] + = normal ;
}
}
for ( auto & v : vertex_normals ) {
if ( v . norm ( ) > 0 ) {
v . normalize ( ) ;
}
}
2026-04-07 15:47:58 +02:00
mesh . vertProperties . reserve ( vertices . size ( ) * 3 ) ;
2026-04-10 14:46:46 +02:00
for ( size_t i = 0 ; i < vertices . size ( ) ; + + i ) {
auto slightly_dilated = vertices [ i ] + vertex_normals [ i ] * dilation ;
mesh . vertProperties . push_back ( slightly_dilated ( 0 ) ) ;
mesh . vertProperties . push_back ( slightly_dilated ( 1 ) ) ;
mesh . vertProperties . push_back ( slightly_dilated ( 2 ) ) ;
2026-04-07 15:47:58 +02:00
}
2026-04-10 14:46:46 +02:00
2026-04-07 15:47:58 +02:00
mesh . triVerts = tri_verts ;
mesh . faceID = face_ids ;
mesh . tolerance = precision ;
return mesh ;
}
} ;
struct LoopPoint {
Eigen : : Vector3d xyz ;
manifold : : vec2 uv ;
} ;
using LoopPolygon = std : : vector < LoopPoint > ;
struct EdgeKey {
uint64_t a ;
uint64_t b ;
bool operator = = ( const EdgeKey & other ) const {
return a = = other . a & & b = = other . b ;
}
} ;
struct EdgeKeyHash {
size_t operator ( ) ( const EdgeKey & key ) const {
auto h = std : : hash < uint64_t > ( ) ( key . a ) ;
h ^ = std : : hash < uint64_t > ( ) ( key . b ) + 0x9e3779b97f4a7c15ull + ( h < < 6 ) + ( h > > 2 ) ;
return h ;
}
} ;
struct FaceKey {
uint64_t a ;
uint64_t b ;
uint64_t c ;
bool operator = = ( const FaceKey & other ) const {
return a = = other . a & & b = = other . b & & c = = other . c ;
}
} ;
struct FaceKeyHash {
size_t operator ( ) ( const FaceKey & key ) const {
auto h = std : : hash < uint64_t > ( ) ( key . a ) ;
h ^ = std : : hash < uint64_t > ( ) ( key . b ) + 0x9e3779b97f4a7c15ull + ( h < < 6 ) + ( h > > 2 ) ;
h ^ = std : : hash < uint64_t > ( ) ( key . c ) + 0x9e3779b97f4a7c15ull + ( h < < 6 ) + ( h > > 2 ) ;
return h ;
}
} ;
struct EdgeUseCount {
size_t forward = 0 ;
size_t reverse = 0 ;
} ;
struct MeshDiagnostics {
size_t vertices = 0 ;
size_t triangles = 0 ;
size_t unique_edges = 0 ;
size_t invalid_indices = 0 ;
size_t nonfinite_vertices = 0 ;
size_t degenerate_triangles = 0 ;
size_t zero_area_triangles = 0 ;
size_t duplicate_faces = 0 ;
size_t boundary_edges = 0 ;
size_t nonmanifold_edges = 0 ;
size_t orientation_conflicts = 0 ;
long long euler_characteristic = 0 ;
bool has_bounds = false ;
Eigen : : Vector3d bounds_min = Eigen : : Vector3d : : Zero ( ) ;
Eigen : : Vector3d bounds_max = Eigen : : Vector3d : : Zero ( ) ;
double min_edge = std : : numeric_limits < double > : : infinity ( ) ;
double max_edge = 0. ;
double min_area = std : : numeric_limits < double > : : infinity ( ) ;
double max_area = 0. ;
} ;
struct ShellDiagnostics {
size_t faces = 0 ;
size_t loops = 0 ;
size_t edges = 0 ;
size_t faces_with_inner_loops = 0 ;
size_t max_loops_per_face = 0 ;
size_t non_planar_faces = 0 ;
size_t non_polygonal_edges = 0 ;
size_t implicit_vertices = 0 ;
} ;
std : : optional < Eigen : : Vector3d > explicit_point ( const taxonomy : : edge : : ptr & edge ) {
if ( edge - > start . index ( ) ! = 1 ) {
return std : : nullopt ;
}
return std : : get < taxonomy : : point3 : : ptr > ( edge - > start ) - > ccomponents ( ) ;
}
2026-04-10 14:46:46 +02:00
void evaluate_curve ( const taxonomy : : line : : ptr & c , double u , taxonomy : : point3 & p ) {
Eigen : : Vector4d xy { 0 , 0 , u , 1. } ;
p . components ( ) = ( c - > matrix - > ccomponents ( ) * xy ) . head < 3 > ( ) ;
}
void evaluate_curve ( const taxonomy : : circle : : ptr & c , double u , taxonomy : : point3 & p ) {
Eigen : : Vector4d xy { c - > radius * std : : cos ( u ) , c - > radius * std : : sin ( u ) , 0 , 1. } ;
p . components ( ) = ( c - > matrix - > ccomponents ( ) * xy ) . head < 3 > ( ) ;
}
void evaluate_curve ( const taxonomy : : ellipse : : ptr & c , double u , taxonomy : : point3 & p ) {
Eigen : : Vector4d xy { c - > radius * std : : cos ( u ) , c - > radius2 * std : : sin ( u ) , 0 , 1. } ;
p . components ( ) = ( c - > matrix - > ccomponents ( ) * xy ) . head < 3 > ( ) ;
}
void project_onto_curve ( const taxonomy : : line : : ptr & c , const taxonomy : : point3 & p , double & u ) {
u = ( c - > matrix - > ccomponents ( ) . inverse ( ) * p . ccomponents ( ) . homogeneous ( ) ) ( 2 ) ;
}
void project_onto_curve ( const taxonomy : : circle : : ptr & c , const taxonomy : : point3 & p , double & u ) {
Eigen : : Vector2d xy = ( c - > matrix - > ccomponents ( ) . inverse ( ) * p . ccomponents ( ) . homogeneous ( ) ) . head < 2 > ( ) ;
u = std : : atan2 ( xy ( 1 ) , xy ( 0 ) ) ;
}
void project_onto_curve ( const taxonomy : : ellipse : : ptr & c , const taxonomy : : point3 & p , double & u ) {
Eigen : : Vector2d xy = ( c - > matrix - > ccomponents ( ) . inverse ( ) * p . ccomponents ( ) . homogeneous ( ) ) . head < 2 > ( ) ;
u = std : : atan2 ( xy ( 1 ) , xy ( 0 ) ) ;
}
taxonomy : : item : : ptr effective_curve_basis ( const taxonomy : : edge : : ptr & edge ) {
auto basis = edge ? edge - > basis : nullptr ;
while ( basis & & basis - > kind ( ) = = taxonomy : : EDGE ) {
auto nested = taxonomy : : dcast < taxonomy : : edge > ( basis ) ;
if ( ! nested | | nested = = edge ) {
break ;
}
basis = nested - > basis ;
}
return basis ;
}
bool resolve_curve_parameter ( const taxonomy : : item : : ptr & curve , const std : : variant < boost : : blank , taxonomy : : point3 : : ptr , double > & trim , double & u ) {
if ( auto value = std : : get_if < double > ( & trim ) ) {
u = * value ;
return std : : isfinite ( u ) ;
}
if ( auto point = std : : get_if < taxonomy : : point3 : : ptr > ( & trim ) ) {
if ( auto line = taxonomy : : dcast < taxonomy : : line > ( curve ) ) {
project_onto_curve ( line , * * point , u ) ;
return std : : isfinite ( u ) ;
}
if ( auto circle = taxonomy : : dcast < taxonomy : : circle > ( curve ) ) {
project_onto_curve ( circle , * * point , u ) ;
return std : : isfinite ( u ) ;
}
if ( auto ellipse = taxonomy : : dcast < taxonomy : : ellipse > ( curve ) ) {
project_onto_curve ( ellipse , * * point , u ) ;
return std : : isfinite ( u ) ;
}
}
return false ;
}
bool basis_from_points ( const std : : vector < Eigen : : Vector3d > & points , Eigen : : Vector3d & origin , Eigen : : Vector3d & x , Eigen : : Vector3d & y ) {
if ( points . size ( ) < 3 ) {
return false ;
}
Eigen : : Vector3d normal = Eigen : : Vector3d : : Zero ( ) ;
for ( size_t i = 0 ; i < points . size ( ) ; + + i ) {
const auto & a = points [ i ] ;
const auto & b = points [ ( i + 1 ) % points . size ( ) ] ;
normal ( 0 ) + = ( a ( 1 ) - b ( 1 ) ) * ( a ( 2 ) + b ( 2 ) ) ;
normal ( 1 ) + = ( a ( 2 ) - b ( 2 ) ) * ( a ( 0 ) + b ( 0 ) ) ;
normal ( 2 ) + = ( a ( 0 ) - b ( 0 ) ) * ( a ( 1 ) + b ( 1 ) ) ;
}
if ( normal . norm ( ) < 1.e-12 ) {
return false ;
}
origin = points . front ( ) ;
x = points [ 1 ] - points . front ( ) ;
x - = normal . normalized ( ) * x . dot ( normal . normalized ( ) ) ;
if ( x . norm ( ) < 1.e-12 ) {
return false ;
}
x . normalize ( ) ;
y = normal . normalized ( ) . cross ( x ) . normalized ( ) ;
return true ;
}
bool edge_supported ( const taxonomy : : edge : : ptr & edge ) {
return edge & & ( ! edge - > basis | | edge - > basis - > kind ( ) = = taxonomy : : LINE ) & & edge - > start . index ( ) = = 1 ;
}
bool extrusion_edge_supported ( const taxonomy : : edge : : ptr & edge ) {
if ( ! edge ) {
return false ;
}
if ( ! edge - > basis ) {
return edge - > start . index ( ) = = 1 & & edge - > end . index ( ) = = 1 ;
}
auto basis = effective_curve_basis ( edge ) ;
if ( ! basis ) {
return false ;
}
if ( basis - > kind ( ) ! = taxonomy : : LINE & & basis - > kind ( ) ! = taxonomy : : CIRCLE & & basis - > kind ( ) ! = taxonomy : : ELLIPSE ) {
return false ;
}
const bool full_curve = edge - > start . index ( ) = = 0 & & edge - > end . index ( ) = = 0 & & ( basis - > kind ( ) = = taxonomy : : CIRCLE | | basis - > kind ( ) = = taxonomy : : ELLIPSE ) ;
if ( full_curve ) {
return true ;
}
return edge - > start . index ( ) ! = 0 & & edge - > end . index ( ) ! = 0 ;
}
bool loop_supported ( const taxonomy : : loop : : ptr & loop ) {
if ( ! loop | | loop - > children . size ( ) < 3 | | ! loop - > is_polyhedron ( ) ) {
return false ;
}
for ( const auto & edge : loop - > children ) {
if ( ! edge_supported ( edge ) ) {
return false ;
}
}
return true ;
}
2026-04-07 15:47:58 +02:00
bool face_supported ( const taxonomy : : face : : ptr & face ) {
2026-04-10 14:46:46 +02:00
if ( ! face | | face - > children . empty ( ) ) {
return false ;
}
2026-04-07 15:47:58 +02:00
if ( face - > basis & & face - > basis - > kind ( ) ! = taxonomy : : PLANE ) {
return false ;
}
for ( const auto & loop : face - > children ) {
2026-04-10 14:46:46 +02:00
if ( ! loop_supported ( loop ) ) {
return false ;
}
}
return true ;
}
bool extrusion_face_supported ( const taxonomy : : face : : ptr & face ) {
if ( ! face | | face - > children . empty ( ) ) {
return false ;
}
if ( face - > basis & & face - > basis - > kind ( ) ! = taxonomy : : PLANE ) {
return false ;
}
for ( const auto & loop : face - > children ) {
if ( ! loop | | loop - > children . empty ( ) ) {
2026-04-07 15:47:58 +02:00
return false ;
}
for ( const auto & edge : loop - > children ) {
2026-04-10 14:46:46 +02:00
if ( ! extrusion_edge_supported ( edge ) ) {
2026-04-07 15:47:58 +02:00
return false ;
}
}
}
return true ;
}
2026-04-10 14:46:46 +02:00
bool append_extrusion_loop_points ( const taxonomy : : loop : : ptr & loop , int circle_segments , double precision , std : : vector < Eigen : : Vector3d > & points ) ;
bool loop_polygon_from_points ( const std : : vector < Eigen : : Vector3d > & points , const Eigen : : Vector3d & origin , const Eigen : : Vector3d & x , const Eigen : : Vector3d & y , double precision , LoopPolygon & polygon ) ;
double signed_area ( const LoopPolygon & polygon ) ;
bool extrusion_face_polygons ( const taxonomy : : face : : ptr & face , int circle_segments , double precision , Eigen : : Vector3d & origin , Eigen : : Vector3d & x , Eigen : : Vector3d & y , std : : vector < LoopPolygon > & polygons , size_t & outer_index ) {
polygons . clear ( ) ;
outer_index = 0 ;
if ( ! extrusion_face_supported ( face ) ) {
return false ;
}
std : : vector < Eigen : : Vector3d > basis_points ;
double basis_area = 0. ;
std : : vector < std : : vector < Eigen : : Vector3d > > loops ;
loops . reserve ( face - > children . size ( ) ) ;
for ( const auto & loop : face - > children ) {
std : : vector < Eigen : : Vector3d > points ;
if ( ! append_extrusion_loop_points ( loop , circle_segments , precision , points ) ) {
return false ;
}
Eigen : : Vector3d loop_origin ;
Eigen : : Vector3d loop_x ;
Eigen : : Vector3d loop_y ;
if ( ! basis_from_points ( points , loop_origin , loop_x , loop_y ) ) {
return false ;
}
LoopPolygon polygon ;
if ( ! loop_polygon_from_points ( points , loop_origin , loop_x , loop_y , precision , polygon ) ) {
return false ;
}
const auto area = std : : fabs ( signed_area ( polygon ) ) ;
if ( area > basis_area ) {
basis_area = area ;
basis_points = points ;
}
loops . push_back ( std : : move ( points ) ) ;
}
if ( ! basis_from_points ( basis_points , origin , x , y ) ) {
return false ;
}
double outer_area = 0. ;
polygons . reserve ( loops . size ( ) ) ;
for ( const auto & points : loops ) {
LoopPolygon polygon ;
if ( ! loop_polygon_from_points ( points , origin , x , y , precision , polygon ) ) {
return false ;
}
const auto area = signed_area ( polygon ) ;
if ( std : : fabs ( area ) > std : : fabs ( outer_area ) ) {
outer_area = area ;
outer_index = polygons . size ( ) ;
}
polygons . push_back ( std : : move ( polygon ) ) ;
}
if ( polygons . empty ( ) | | std : : fabs ( outer_area ) < = precision * precision ) {
return false ;
}
return true ;
}
bool shell_supported ( const taxonomy : : shell : : ptr & shell ) {
if ( ! shell | | shell - > children . empty ( ) ) {
return false ;
}
for ( const auto & face : shell - > children ) {
if ( ! face_supported ( face ) ) {
return false ;
}
}
return true ;
2026-04-07 15:47:58 +02:00
}
bool face_basis ( const taxonomy : : face : : ptr & face , Eigen : : Vector3d & origin , Eigen : : Vector3d & x , Eigen : : Vector3d & y ) {
double best_score = - 1. ;
std : : vector < Eigen : : Vector3d > best_points ;
for ( const auto & loop : face - > children ) {
std : : vector < Eigen : : Vector3d > points ;
points . reserve ( loop - > children . size ( ) ) ;
for ( const auto & edge : loop - > children ) {
auto point = explicit_point ( edge ) ;
if ( ! point ) {
return false ;
}
if ( ! points . empty ( ) ) {
const auto d = points . back ( ) - * point ;
if ( d . squaredNorm ( ) < = 1.e-24 ) {
continue ;
}
}
points . push_back ( * point ) ;
}
if ( points . size ( ) > 1 ) {
const auto d = points . front ( ) - points . back ( ) ;
if ( d . squaredNorm ( ) < = 1.e-24 ) {
points . pop_back ( ) ;
}
}
if ( points . size ( ) < 3 ) {
continue ;
}
Eigen : : Vector3d normal = Eigen : : Vector3d : : Zero ( ) ;
for ( size_t i = 0 ; i < points . size ( ) ; + + i ) {
const auto & a = points [ i ] ;
const auto & b = points [ ( i + 1 ) % points . size ( ) ] ;
normal ( 0 ) + = ( a ( 1 ) - b ( 1 ) ) * ( a ( 2 ) + b ( 2 ) ) ;
normal ( 1 ) + = ( a ( 2 ) - b ( 2 ) ) * ( a ( 0 ) + b ( 0 ) ) ;
normal ( 2 ) + = ( a ( 0 ) - b ( 0 ) ) * ( a ( 1 ) + b ( 1 ) ) ;
}
const auto score = normal . squaredNorm ( ) ;
if ( score < = 1.e-24 | | score < = best_score ) {
continue ;
}
best_score = score ;
best_points = std : : move ( points ) ;
}
2026-04-10 14:46:46 +02:00
return basis_from_points ( best_points , origin , x , y ) ;
2026-04-07 15:47:58 +02:00
}
double signed_area ( const manifold : : SimplePolygonIdx & polygon ) {
double area = 0. ;
for ( size_t i = 0 ; i < polygon . size ( ) ; + + i ) {
const auto & a = polygon [ i ] . pos ;
const auto & b = polygon [ ( i + 1 ) % polygon . size ( ) ] . pos ;
area + = a [ 0 ] * b [ 1 ] - b [ 0 ] * a [ 1 ] ;
}
return 0.5 * area ;
}
Eigen : : Vector3d mesh_vertex ( const Mesh & mesh , size_t index ) {
return Eigen : : Vector3d (
mesh . vertProperties [ index * mesh . numProp + 0 ] ,
mesh . vertProperties [ index * mesh . numProp + 1 ] ,
mesh . vertProperties [ index * mesh . numProp + 2 ] ) ;
}
std : : string format_number ( double value ) {
std : : ostringstream ss ;
ss < < std : : setprecision ( 6 ) < < value ;
return ss . str ( ) ;
}
std : : string format_vector ( const Eigen : : Vector3d & value ) {
return " ( " + format_number ( value ( 0 ) ) + " , " + format_number ( value ( 1 ) ) + " , " + format_number ( value ( 2 ) ) + " ) " ;
}
MeshDiagnostics diagnose_mesh ( const Mesh & mesh , double precision ) {
MeshDiagnostics diagnostics ;
diagnostics . vertices = mesh . NumVert ( ) ;
diagnostics . triangles = mesh . NumTri ( ) ;
std : : unordered_map < EdgeKey , EdgeUseCount , EdgeKeyHash > edge_use_count ;
std : : unordered_map < FaceKey , size_t , FaceKeyHash > face_use_count ;
for ( size_t i = 0 ; i < diagnostics . vertices ; + + i ) {
auto p = mesh_vertex ( mesh , i ) ;
if ( ! std : : isfinite ( p ( 0 ) ) | | ! std : : isfinite ( p ( 1 ) ) | | ! std : : isfinite ( p ( 2 ) ) ) {
diagnostics . nonfinite_vertices + + ;
continue ;
}
if ( ! diagnostics . has_bounds ) {
diagnostics . has_bounds = true ;
diagnostics . bounds_min = p ;
diagnostics . bounds_max = p ;
} else {
diagnostics . bounds_min = diagnostics . bounds_min . cwiseMin ( p ) ;
diagnostics . bounds_max = diagnostics . bounds_max . cwiseMax ( p ) ;
}
}
for ( size_t i = 0 ; i < diagnostics . triangles ; + + i ) {
auto a = mesh . triVerts [ i * 3 + 0 ] ;
auto b = mesh . triVerts [ i * 3 + 1 ] ;
auto c = mesh . triVerts [ i * 3 + 2 ] ;
if ( a > = diagnostics . vertices | | b > = diagnostics . vertices | | c > = diagnostics . vertices ) {
diagnostics . invalid_indices + + ;
continue ;
}
auto pa = mesh_vertex ( mesh , ( size_t ) a ) ;
auto pb = mesh_vertex ( mesh , ( size_t ) b ) ;
auto pc = mesh_vertex ( mesh , ( size_t ) c ) ;
const auto ab = ( pb - pa ) . norm ( ) ;
const auto bc = ( pc - pb ) . norm ( ) ;
const auto ca = ( pa - pc ) . norm ( ) ;
if ( std : : isfinite ( ab ) & & ab > 0. ) {
diagnostics . min_edge = std : : min ( diagnostics . min_edge , ab ) ;
diagnostics . max_edge = std : : max ( diagnostics . max_edge , ab ) ;
}
if ( std : : isfinite ( bc ) & & bc > 0. ) {
diagnostics . min_edge = std : : min ( diagnostics . min_edge , bc ) ;
diagnostics . max_edge = std : : max ( diagnostics . max_edge , bc ) ;
}
if ( std : : isfinite ( ca ) & & ca > 0. ) {
diagnostics . min_edge = std : : min ( diagnostics . min_edge , ca ) ;
diagnostics . max_edge = std : : max ( diagnostics . max_edge , ca ) ;
}
if ( a = = b | | b = = c | | c = = a ) {
diagnostics . degenerate_triangles + + ;
continue ;
}
const auto area = 0.5 * ( ( pb - pa ) . cross ( pc - pa ) ) . norm ( ) ;
if ( std : : isfinite ( area ) ) {
diagnostics . min_area = std : : min ( diagnostics . min_area , area ) ;
diagnostics . max_area = std : : max ( diagnostics . max_area , area ) ;
if ( area < = precision * precision ) {
diagnostics . zero_area_triangles + + ;
}
}
std : : array < uint64_t , 3 > face = { a , b , c } ;
std : : sort ( face . begin ( ) , face . end ( ) ) ;
const FaceKey face_key { face [ 0 ] , face [ 1 ] , face [ 2 ] } ;
auto face_it = face_use_count . find ( face_key ) ;
if ( face_it = = face_use_count . end ( ) ) {
face_use_count . insert ( { face_key , 1 } ) ;
} else {
face_it - > second + + ;
diagnostics . duplicate_faces + + ;
}
std : : array < EdgeKey , 3 > edges = {
EdgeKey { std : : min ( a , b ) , std : : max ( a , b ) } ,
EdgeKey { std : : min ( b , c ) , std : : max ( b , c ) } ,
EdgeKey { std : : min ( c , a ) , std : : max ( c , a ) }
} ;
std : : array < bool , 3 > forward = {
a < b ,
b < c ,
c < a
} ;
for ( const auto & edge : edges ) {
if ( edge . a = = edge . b ) {
continue ;
}
}
for ( size_t j = 0 ; j < edges . size ( ) ; + + j ) {
const auto & edge = edges [ j ] ;
auto & use = edge_use_count [ edge ] ;
if ( forward [ j ] ) {
use . forward + + ;
} else {
use . reverse + + ;
}
}
}
for ( const auto & entry : edge_use_count ) {
const auto total = entry . second . forward + entry . second . reverse ;
if ( total = = 1 ) {
diagnostics . boundary_edges + + ;
} else if ( total > 2 ) {
diagnostics . nonmanifold_edges + + ;
} else if ( entry . second . forward ! = 1 | | entry . second . reverse ! = 1 ) {
diagnostics . orientation_conflicts + + ;
}
}
diagnostics . unique_edges = edge_use_count . size ( ) ;
diagnostics . euler_characteristic = ( long long ) diagnostics . vertices - ( long long ) diagnostics . unique_edges + ( long long ) diagnostics . triangles ;
return diagnostics ;
}
ShellDiagnostics diagnose_shell ( const taxonomy : : shell : : ptr & shell ) {
ShellDiagnostics diagnostics ;
diagnostics . faces = shell - > children . size ( ) ;
for ( const auto & face : shell - > children ) {
diagnostics . max_loops_per_face = std : : max ( diagnostics . max_loops_per_face , face - > children . size ( ) ) ;
if ( face - > children . size ( ) > 1 ) {
diagnostics . faces_with_inner_loops + + ;
}
diagnostics . loops + = face - > children . size ( ) ;
if ( face - > basis & & face - > basis - > kind ( ) ! = taxonomy : : PLANE ) {
diagnostics . non_planar_faces + + ;
}
for ( const auto & loop : face - > children ) {
diagnostics . edges + = loop - > children . size ( ) ;
for ( const auto & edge : loop - > children ) {
if ( edge - > basis & & edge - > basis - > kind ( ) ! = taxonomy : : LINE ) {
diagnostics . non_polygonal_edges + + ;
}
if ( edge - > start . index ( ) ! = 1 ) {
diagnostics . implicit_vertices + + ;
}
}
}
}
return diagnostics ;
}
std : : string mesh_diagnostics_string ( const MeshDiagnostics & diagnostics ) {
std : : ostringstream ss ;
ss < < " verts= " < < diagnostics . vertices
< < " tris= " < < diagnostics . triangles
< < " unique_edges= " < < diagnostics . unique_edges
< < " euler= " < < diagnostics . euler_characteristic
< < " invalid_idx= " < < diagnostics . invalid_indices
< < " nonfinite_verts= " < < diagnostics . nonfinite_vertices
< < " degenerate_tris= " < < diagnostics . degenerate_triangles
< < " zero_area_tris= " < < diagnostics . zero_area_triangles
< < " duplicate_faces= " < < diagnostics . duplicate_faces
< < " boundary_edges= " < < diagnostics . boundary_edges
< < " nonmanifold_edges= " < < diagnostics . nonmanifold_edges
< < " orientation_conflicts= " < < diagnostics . orientation_conflicts ;
if ( diagnostics . has_bounds ) {
ss < < " bbox_min= " < < format_vector ( diagnostics . bounds_min )
< < " bbox_max= " < < format_vector ( diagnostics . bounds_max ) ;
}
if ( std : : isfinite ( diagnostics . min_edge ) ) {
ss < < " min_edge= " < < format_number ( diagnostics . min_edge ) ;
}
if ( diagnostics . max_edge > 0. ) {
ss < < " max_edge= " < < format_number ( diagnostics . max_edge ) ;
}
if ( std : : isfinite ( diagnostics . min_area ) ) {
ss < < " min_area= " < < format_number ( diagnostics . min_area ) ;
}
if ( diagnostics . max_area > 0. ) {
ss < < " max_area= " < < format_number ( diagnostics . max_area ) ;
}
return ss . str ( ) ;
}
std : : string shell_diagnostics_string ( const ShellDiagnostics & diagnostics ) {
std : : ostringstream ss ;
ss < < " faces= " < < diagnostics . faces
< < " loops= " < < diagnostics . loops
< < " edges= " < < diagnostics . edges
< < " faces_with_inner_loops= " < < diagnostics . faces_with_inner_loops
< < " max_loops_per_face= " < < diagnostics . max_loops_per_face
< < " non_planar_faces= " < < diagnostics . non_planar_faces
< < " non_polygonal_edges= " < < diagnostics . non_polygonal_edges
< < " implicit_vertices= " < < diagnostics . implicit_vertices ;
return ss . str ( ) ;
}
std : : string matrix_diagnostics_string ( const taxonomy : : matrix4 : : ptr & place ) {
const auto & m = place - > ccomponents ( ) ;
const auto linear = m . block < 3 , 3 > ( 0 , 0 ) ;
const auto c0 = linear . col ( 0 ) ;
const auto c1 = linear . col ( 1 ) ;
const auto c2 = linear . col ( 2 ) ;
std : : ostringstream ss ;
ss < < " det= " < < format_number ( linear . determinant ( ) )
< < " scale=( " < < format_number ( c0 . norm ( ) ) < < " , " < < format_number ( c1 . norm ( ) ) < < " , " < < format_number ( c2 . norm ( ) ) < < " ) "
< < " dot=( " < < format_number ( c0 . dot ( c1 ) ) < < " , " < < format_number ( c0 . dot ( c2 ) ) < < " , " < < format_number ( c1 . dot ( c2 ) ) < < " ) "
< < " translation= " < < format_vector ( m . col ( 3 ) . head < 3 > ( ) ) ;
return ss . str ( ) ;
}
std : : string solid_shell_failure_diagnosis ( const Part & part , const MeshDiagnostics & before , const MeshDiagnostics & after , manifold : : Manifold : : Error before_status , manifold : : Manifold : : Error after_status ) {
const bool before_problematic =
! part . solid | |
before_status ! = manifold : : Manifold : : Error : : NoError | |
before . invalid_indices ! = 0 | |
before . nonfinite_vertices ! = 0 | |
before . degenerate_triangles ! = 0 | |
before . zero_area_triangles ! = 0 | |
before . boundary_edges ! = 0 | |
before . nonmanifold_edges ! = 0 ;
const bool after_problematic =
after_status ! = manifold : : Manifold : : Error : : NoError | |
after . invalid_indices ! = 0 | |
after . nonfinite_vertices ! = 0 | |
after . degenerate_triangles ! = 0 | |
after . zero_area_triangles ! = 0 | |
after . boundary_edges ! = 0 | |
after . nonmanifold_edges ! = 0 ;
if ( before_problematic ) {
return " shell is already problematic before transform " ;
}
if ( after_problematic ) {
return " shell is valid before transform, failure is likely introduced by transform or precision collapse " ;
}
return " shell looks clean before and after mesh inspection, issue may be in manifold validation details " ;
}
void log_solid_shell_transform_failure ( const taxonomy : : shell : : ptr & shell , const Part & before_part , const Mesh & after_mesh , const taxonomy : : matrix4 : : ptr & place , double precision , manifold : : Manifold : : Error before_status , manifold : : Manifold : : Error after_status ) {
const auto shell_info = diagnose_shell ( shell ) ;
const auto before = diagnose_mesh ( before_part . mesh , precision ) ;
const auto after = diagnose_mesh ( after_mesh , precision ) ;
logger : : warning (
" Manifold kernel: solid shell manifold validation failed; before_transform= " +
std : : string ( before_part . solid ? " solid " : " mesh-only " ) +
" ( " + manifold_error_string ( before_status ) + " ), after_transform=( " + manifold_error_string ( after_status ) + " ) " ,
shell - > instance ) ;
logger : : warning ( " Manifold kernel: solid shell diagnosis: " + solid_shell_failure_diagnosis ( before_part , before , after , before_status , after_status ) , shell - > instance ) ;
logger : : warning ( " Manifold kernel: solid shell input: " + shell_diagnostics_string ( shell_info ) , shell - > instance ) ;
logger : : warning ( " Manifold kernel: solid shell mesh before transform: " + mesh_diagnostics_string ( before ) , shell - > instance ) ;
logger : : warning ( " Manifold kernel: solid shell transform: " + matrix_diagnostics_string ( place ) , shell - > instance ) ;
logger : : warning ( " Manifold kernel: solid shell mesh after transform: " + mesh_diagnostics_string ( after ) , shell - > instance ) ;
}
double signed_area ( const manifold : : SimplePolygon & polygon ) {
double area = 0. ;
for ( size_t i = 0 ; i < polygon . size ( ) ; + + i ) {
const auto & a = polygon [ i ] ;
const auto & b = polygon [ ( i + 1 ) % polygon . size ( ) ] ;
area + = a [ 0 ] * b [ 1 ] - b [ 0 ] * a [ 1 ] ;
}
return 0.5 * area ;
}
double signed_area ( const LoopPolygon & polygon ) {
double area = 0. ;
for ( size_t i = 0 ; i < polygon . size ( ) ; + + i ) {
const auto & a = polygon [ i ] . uv ;
const auto & b = polygon [ ( i + 1 ) % polygon . size ( ) ] . uv ;
area + = a [ 0 ] * b [ 1 ] - b [ 0 ] * a [ 1 ] ;
}
return 0.5 * area ;
}
2026-04-10 14:46:46 +02:00
void extend_points ( std : : vector < Eigen : : Vector3d > & points , const std : : vector < taxonomy : : point3 > & edge_points , double precision ) {
if ( edge_points . empty ( ) ) {
return ;
}
const auto merge_tolerance = std : : max ( precision , 1.e-5 ) ;
size_t offset = 0 ;
if ( ! points . empty ( ) & & ( points . back ( ) - edge_points . front ( ) . ccomponents ( ) ) . norm ( ) < merge_tolerance ) {
offset = 1 ;
}
for ( size_t i = offset ; i < edge_points . size ( ) ; + + i ) {
const auto point = edge_points [ i ] . ccomponents ( ) ;
if ( ! points . empty ( ) & & ( points . back ( ) - point ) . norm ( ) < merge_tolerance ) {
continue ;
}
points . push_back ( point ) ;
}
}
bool append_extrusion_edge_points ( const taxonomy : : edge : : ptr & edge , int circle_segments , double precision , std : : vector < Eigen : : Vector3d > & points ) {
if ( ! edge ) {
return false ;
}
const auto two_pi = 2. * std : : acos ( - 1. ) ;
std : : vector < taxonomy : : point3 > edge_points ;
if ( ! edge - > basis ) {
if ( edge - > start . index ( ) ! = 1 | | edge - > end . index ( ) ! = 1 ) {
return false ;
}
edge_points . push_back ( * std : : get < taxonomy : : point3 : : ptr > ( edge - > start ) ) ;
edge_points . push_back ( * std : : get < taxonomy : : point3 : : ptr > ( edge - > end ) ) ;
extend_points ( points , edge_points , precision ) ;
return true ;
}
auto basis = effective_curve_basis ( edge ) ;
if ( ! basis ) {
return false ;
}
double a ;
double b ;
const bool full_conic = edge - > start . index ( ) = = 0 & & edge - > end . index ( ) = = 0 & & ( basis - > kind ( ) = = taxonomy : : CIRCLE | | basis - > kind ( ) = = taxonomy : : ELLIPSE ) ;
if ( full_conic ) {
a = 0. ;
b = two_pi ;
} else {
if ( ! resolve_curve_parameter ( basis , edge - > start , a ) | | ! resolve_curve_parameter ( basis , edge - > end , b ) ) {
return false ;
}
}
const bool reverse = ! edge - > curve_sense . value_or ( true ) ;
if ( reverse ) {
std : : swap ( a , b ) ;
}
taxonomy : : point3 point ;
if ( auto line = taxonomy : : dcast < taxonomy : : line > ( basis ) ) {
evaluate_curve ( line , a , point ) ;
edge_points . push_back ( point ) ;
evaluate_curve ( line , b , point ) ;
edge_points . push_back ( point ) ;
} else if ( auto circle = taxonomy : : dcast < taxonomy : : circle > ( basis ) ) {
a = std : : fmod ( a , two_pi ) ;
b = std : : fmod ( b , two_pi ) ;
if ( b < = a ) {
b + = two_pi ;
}
const auto num_segments = std : : max ( 1 , ( int ) std : : ceil ( std : : fabs ( a - b ) / two_pi * circle_segments ) ) ;
const auto du = ( b - a ) / num_segments ;
evaluate_curve ( circle , a , point ) ;
edge_points . push_back ( point ) ;
for ( int i = 1 ; i < num_segments ; + + i ) {
evaluate_curve ( circle , a + du * i , point ) ;
edge_points . push_back ( point ) ;
}
evaluate_curve ( circle , b , point ) ;
edge_points . push_back ( point ) ;
} else if ( auto ellipse = taxonomy : : dcast < taxonomy : : ellipse > ( basis ) ) {
a = std : : fmod ( a , two_pi ) ;
b = std : : fmod ( b , two_pi ) ;
if ( b < = a ) {
b + = two_pi ;
}
const auto num_segments = std : : max ( 1 , ( int ) std : : ceil ( std : : fabs ( a - b ) / two_pi * circle_segments ) ) ;
const auto du = ( b - a ) / num_segments ;
evaluate_curve ( ellipse , a , point ) ;
edge_points . push_back ( point ) ;
for ( int i = 1 ; i < num_segments ; + + i ) {
evaluate_curve ( ellipse , a + du * i , point ) ;
edge_points . push_back ( point ) ;
}
evaluate_curve ( ellipse , b , point ) ;
edge_points . push_back ( point ) ;
} else {
return false ;
}
if ( reverse ) {
std : : reverse ( edge_points . begin ( ) , edge_points . end ( ) ) ;
}
extend_points ( points , edge_points , precision ) ;
return true ;
}
bool append_extrusion_loop_points ( const taxonomy : : loop : : ptr & loop , int circle_segments , double precision , std : : vector < Eigen : : Vector3d > & points ) {
points . clear ( ) ;
if ( ! loop | | loop - > children . empty ( ) ) {
return false ;
}
for ( const auto & edge : loop - > children ) {
if ( ! append_extrusion_edge_points ( edge , circle_segments , precision , points ) ) {
return false ;
}
}
if ( points . size ( ) > 1 ) {
const auto merge_tolerance = std : : max ( precision , 1.e-5 ) ;
if ( ( points . front ( ) - points . back ( ) ) . norm ( ) < merge_tolerance ) {
points . pop_back ( ) ;
}
}
return points . size ( ) > = 3 ;
}
bool loop_polygon_from_points ( const std : : vector < Eigen : : Vector3d > & points , const Eigen : : Vector3d & origin , const Eigen : : Vector3d & x , const Eigen : : Vector3d & y , double precision , LoopPolygon & polygon ) {
polygon . clear ( ) ;
polygon . reserve ( points . size ( ) ) ;
for ( const auto & point : points ) {
if ( ! polygon . empty ( ) ) {
const auto d = polygon . back ( ) . xyz - point ;
if ( d . squaredNorm ( ) < = precision * precision ) {
continue ;
}
}
auto v = point - origin ;
polygon . push_back ( { point , manifold : : vec2 ( v . dot ( x ) , v . dot ( y ) ) } ) ;
}
if ( polygon . size ( ) > 1 ) {
const auto d = polygon . front ( ) . xyz - polygon . back ( ) . xyz ;
if ( d . squaredNorm ( ) < = precision * precision ) {
polygon . pop_back ( ) ;
}
}
return polygon . size ( ) > = 3 ;
}
2026-04-07 15:47:58 +02:00
bool append_simple_loop ( const taxonomy : : loop : : ptr & loop , const Eigen : : Vector3d & origin , const Eigen : : Vector3d & x , const Eigen : : Vector3d & y , double precision , LoopPolygon & polygon ) {
polygon . clear ( ) ;
polygon . reserve ( loop - > children . size ( ) ) ;
for ( const auto & edge : loop - > children ) {
if ( edge - > basis & & edge - > basis - > kind ( ) ! = taxonomy : : LINE ) {
return false ;
}
auto point = explicit_point ( edge ) ;
if ( ! point ) {
return false ;
}
if ( ! polygon . empty ( ) ) {
const auto d = polygon . back ( ) . xyz - * point ;
if ( d . squaredNorm ( ) < = precision * precision ) {
continue ;
}
}
auto v = * point - origin ;
polygon . push_back ( { * point , manifold : : vec2 ( v . dot ( x ) , v . dot ( y ) ) } ) ;
}
if ( polygon . size ( ) > 1 ) {
const auto d = polygon . front ( ) . xyz - polygon . back ( ) . xyz ;
if ( d . squaredNorm ( ) < = precision * precision ) {
polygon . pop_back ( ) ;
}
}
if ( polygon . size ( ) < 3 ) {
return false ;
}
return true ;
}
void reverse_loop ( LoopPolygon & polygon ) {
std : : reverse ( polygon . begin ( ) , polygon . end ( ) ) ;
}
void append_loop ( const LoopPolygon & loop_polygon , MeshBuilder & builder , manifold : : PolygonsIdx & polygons ) {
manifold : : SimplePolygonIdx polygon ;
polygon . reserve ( loop_polygon . size ( ) ) ;
for ( const auto & point : loop_polygon ) {
manifold : : PolyVert poly_vert ;
poly_vert . pos = point . uv ;
poly_vert . idx = ( int ) builder . add_vertex ( point . xyz ) ;
polygon . push_back ( poly_vert ) ;
}
polygons . push_back ( std : : move ( polygon ) ) ;
}
bool append_face ( const taxonomy : : face : : ptr & face , MeshBuilder & builder , uint64_t face_id ) {
if ( ! face_supported ( face ) ) {
return false ;
}
Eigen : : Vector3d origin ;
Eigen : : Vector3d x ;
Eigen : : Vector3d y ;
if ( ! face_basis ( face , origin , x , y ) ) {
return false ;
}
std : : vector < LoopPolygon > loops ;
loops . reserve ( face - > children . size ( ) ) ;
size_t outer_index = 0 ;
double outer_area = 0. ;
manifold : : PolygonsIdx polygons ;
polygons . reserve ( face - > children . size ( ) ) ;
for ( const auto & loop : face - > children ) {
LoopPolygon loop_polygon ;
if ( ! append_simple_loop ( loop , origin , x , y , builder . precision , loop_polygon ) ) {
return false ;
}
const auto area = signed_area ( loop_polygon ) ;
if ( std : : abs ( area ) > std : : abs ( outer_area ) ) {
outer_area = area ;
outer_index = loops . size ( ) ;
}
loops . push_back ( std : : move ( loop_polygon ) ) ;
}
if ( loops . empty ( ) | | std : : abs ( outer_area ) < 1.e-12 ) {
return false ;
}
for ( size_t i = 0 ; i < loops . size ( ) ; + + i ) {
if ( i ! = outer_index & & signed_area ( loops [ i ] ) * outer_area > 0. ) {
reverse_loop ( loops [ i ] ) ;
}
append_loop ( loops [ i ] , builder , polygons ) ;
}
auto triangles = manifold : : TriangulateIdx ( polygons , builder . precision , true ) ;
for ( const auto & tri : triangles ) {
builder . add_triangle ( ( uint32_t ) tri [ 0 ] , ( uint32_t ) tri [ 1 ] , ( uint32_t ) tri [ 2 ] , face_id ) ;
}
return ! triangles . empty ( ) ;
}
2026-04-10 14:46:46 +02:00
bool shell_to_mesh ( const taxonomy : : shell : : ptr & shell , double precision , Mesh & mesh , double dilation ) {
2026-04-07 15:47:58 +02:00
MeshBuilder builder ( precision ) ;
2026-04-10 14:46:46 +02:00
builder . dilation = dilation ;
2026-04-07 15:47:58 +02:00
uint64_t face_id = 0 ;
bool any = false ;
for ( const auto & face : shell - > children ) {
if ( ! append_face ( face , builder , face_id + + ) ) {
return false ;
}
any = true ;
}
if ( ! any ) {
return false ;
}
mesh = builder . build ( ) ;
return mesh . NumTri ( ) > 0 ;
}
std : : optional < Part > part_from_mesh ( const Mesh & mesh , bool require_manifold , manifold : : Manifold : : Error * status_ptr = nullptr ) {
auto solid = std : : optional < manifold : : Manifold > { } ;
manifold : : Manifold candidate ( mesh ) ;
auto status = candidate . Status ( ) ;
if ( status_ptr ) {
* status_ptr = status ;
}
if ( status = = manifold : : Manifold : : Error : : NoError ) {
solid = candidate ;
}
if ( ! solid & & require_manifold ) {
return std : : nullopt ;
}
if ( solid ) {
2026-05-18 18:16:49 +02:00
return * solid ;
2026-04-07 15:47:58 +02:00
}
2026-05-18 18:16:49 +02:00
return mesh ;
2026-04-07 15:47:58 +02:00
}
2026-04-10 14:46:46 +02:00
std : : optional < Part > part_from_shell ( const taxonomy : : shell : : ptr & shell , double precision , double dilation , manifold : : Manifold : : Error * status_ptr = nullptr ) {
2026-04-07 15:47:58 +02:00
Mesh mesh ;
2026-04-10 14:46:46 +02:00
if ( ! shell_to_mesh ( shell , precision , mesh , dilation ) ) {
2026-04-07 15:47:58 +02:00
return std : : nullopt ;
}
return part_from_mesh ( mesh , false , status_ptr ) ;
}
Mesh transform_mesh ( const Mesh & mesh , const taxonomy : : matrix4 : : ptr & place ) ;
2026-04-10 14:46:46 +02:00
std : : optional < Part > part_from_extrusion ( const taxonomy : : extrusion : : ptr & extrusion , double precision , double dilation , int circle_segments ) ;
2026-04-07 15:47:58 +02:00
2026-04-10 14:46:46 +02:00
Eigen : : Matrix4d matrix_or_identity ( const taxonomy : : matrix4 : : ptr & matrix ) {
return matrix ? matrix - > ccomponents ( ) : Eigen : : Matrix4d : : Identity ( ) ;
}
Eigen : : Vector3d transform_point ( const Eigen : : Matrix4d & matrix , const Eigen : : Vector3d & point ) {
return ( matrix * point . homogeneous ( ) ) . head < 3 > ( ) ;
}
Eigen : : Vector3d transform_vector ( const Eigen : : Matrix4d & matrix , const Eigen : : Vector3d & vector ) {
return matrix . block < 3 , 3 > ( 0 , 0 ) * vector ;
}
taxonomy : : face : : ptr halfspace_face ( const taxonomy : : solid : : ptr & solid ) {
if ( ! solid | | ! solid - > instance . declaration ( ) . is ( " IfcHalfSpaceSolid " ) | | solid - > children . size ( ) ! = 1 ) {
return nullptr ;
2026-04-07 15:47:58 +02:00
}
2026-04-10 14:46:46 +02:00
const auto & shell = solid - > children . front ( ) ;
if ( ! shell | | shell - > children . size ( ) ! = 1 ) {
return nullptr ;
2026-04-07 15:47:58 +02:00
}
2026-04-10 14:46:46 +02:00
auto face = shell - > children . front ( ) ;
if ( ! face | | ! face - > basis | | face - > basis - > kind ( ) ! = taxonomy : : PLANE | | face - > children . size ( ) > 1 ) {
return nullptr ;
}
return face ;
}
bool explicit_loop_polygon ( const taxonomy : : loop : : ptr & loop , const Eigen : : Matrix4d & transform , const Eigen : : Vector3d & plane_origin , const Eigen : : Vector3d & x , const Eigen : : Vector3d & y , double precision , LoopPolygon & polygon ) {
polygon . clear ( ) ;
if ( ! loop | | loop - > children . size ( ) < 3 ) {
return false ;
}
polygon . reserve ( loop - > children . size ( ) ) ;
for ( const auto & edge : loop - > children ) {
if ( ! edge | | ( edge - > basis & & edge - > basis - > kind ( ) ! = taxonomy : : LINE ) | | edge - > start . index ( ) ! = 1 ) {
return false ;
}
auto point = transform_point ( transform , std : : get < taxonomy : : point3 : : ptr > ( edge - > start ) - > ccomponents ( ) ) ;
if ( ! polygon . empty ( ) & & ( polygon . back ( ) . xyz - point ) . squaredNorm ( ) < = precision * precision ) {
continue ;
}
auto delta = point - plane_origin ;
polygon . push_back ( { point , manifold : : vec2 ( delta . dot ( x ) , delta . dot ( y ) ) } ) ;
}
if ( polygon . size ( ) > 1 & & ( polygon . front ( ) . xyz - polygon . back ( ) . xyz ) . squaredNorm ( ) < = precision * precision ) {
polygon . pop_back ( ) ;
}
return polygon . size ( ) > = 3 ;
}
std : : array < Eigen : : Vector3d , 8 > box_corners ( const manifold : : Box & box ) {
return {
Eigen : : Vector3d ( box . min [ 0 ] , box . min [ 1 ] , box . min [ 2 ] ) ,
Eigen : : Vector3d ( box . min [ 0 ] , box . min [ 1 ] , box . max [ 2 ] ) ,
Eigen : : Vector3d ( box . min [ 0 ] , box . max [ 1 ] , box . min [ 2 ] ) ,
Eigen : : Vector3d ( box . min [ 0 ] , box . max [ 1 ] , box . max [ 2 ] ) ,
Eigen : : Vector3d ( box . max [ 0 ] , box . min [ 1 ] , box . min [ 2 ] ) ,
Eigen : : Vector3d ( box . max [ 0 ] , box . min [ 1 ] , box . max [ 2 ] ) ,
Eigen : : Vector3d ( box . max [ 0 ] , box . max [ 1 ] , box . min [ 2 ] ) ,
Eigen : : Vector3d ( box . max [ 0 ] , box . max [ 1 ] , box . max [ 2 ] )
} ;
}
struct HalfspaceBuildState {
bool unchanged = false ;
double depth = 0. ;
} ;
std : : optional < Part > part_from_polygon_extrusion ( std : : vector < LoopPolygon > polygons , size_t outer_index , const Eigen : : Vector3d & plane_normal , const Eigen : : Vector3d & direction , double depth , double precision , double dilation ) {
if ( depth < precision | | polygons . empty ( ) | | outer_index > = polygons . size ( ) ) {
2026-04-07 15:47:58 +02:00
return std : : nullopt ;
}
2026-04-10 14:46:46 +02:00
auto dir = direction ;
2026-04-07 15:47:58 +02:00
if ( dir . norm ( ) < 1.e-12 ) {
return std : : nullopt ;
}
dir . normalize ( ) ;
2026-04-10 14:46:46 +02:00
auto normal = plane_normal ;
2026-04-07 15:47:58 +02:00
if ( normal . norm ( ) < 1.e-12 ) {
return std : : nullopt ;
}
normal . normalize ( ) ;
2026-04-10 14:46:46 +02:00
auto outer_area = signed_area ( polygons [ outer_index ] ) ;
if ( std : : abs ( outer_area ) < = precision * precision ) {
2026-04-07 15:47:58 +02:00
return std : : nullopt ;
}
2026-04-10 14:46:46 +02:00
if ( outer_area < 0. ) {
reverse_loop ( polygons [ outer_index ] ) ;
outer_area = - outer_area ;
2026-04-07 15:47:58 +02:00
}
2026-04-10 14:46:46 +02:00
for ( size_t i = 0 ; i < polygons . size ( ) ; + + i ) {
if ( i ! = outer_index & & signed_area ( polygons [ i ] ) * outer_area > 0. ) {
reverse_loop ( polygons [ i ] ) ;
}
2026-04-07 15:47:58 +02:00
}
2026-04-10 14:46:46 +02:00
auto direction_sign = normal . dot ( dir ) ;
if ( std : : abs ( direction_sign ) < 1.e-9 ) {
2026-04-07 15:47:58 +02:00
return std : : nullopt ;
}
2026-04-10 14:46:46 +02:00
auto offset = dir * depth ;
2026-04-07 15:47:58 +02:00
MeshBuilder builder ( precision ) ;
2026-04-10 14:46:46 +02:00
builder . dilation = dilation ;
std : : vector < std : : vector < uint64_t > > bottoms ;
std : : vector < std : : vector < uint64_t > > tops ;
std : : unordered_map < uint64_t , uint64_t > top_by_bottom ;
manifold : : PolygonsIdx polygon_idx ;
polygon_idx . reserve ( polygons . size ( ) ) ;
bottoms . reserve ( polygons . size ( ) ) ;
tops . reserve ( polygons . size ( ) ) ;
for ( const auto & polygon : polygons ) {
manifold : : SimplePolygonIdx loop_idx ;
loop_idx . reserve ( polygon . size ( ) ) ;
bottoms . emplace_back ( ) ;
tops . emplace_back ( ) ;
bottoms . back ( ) . reserve ( polygon . size ( ) ) ;
tops . back ( ) . reserve ( polygon . size ( ) ) ;
for ( const auto & point : polygon ) {
auto bottom = builder . add_vertex ( point . xyz ) ;
auto top = builder . add_vertex ( point . xyz + offset ) ;
bottoms . back ( ) . push_back ( bottom ) ;
tops . back ( ) . push_back ( top ) ;
top_by_bottom . insert ( { bottom , top } ) ;
loop_idx . push_back ( { point . uv , ( int ) bottom } ) ;
}
polygon_idx . push_back ( std : : move ( loop_idx ) ) ;
}
auto triangles = manifold : : TriangulateIdx ( polygon_idx , precision , true ) ;
if ( triangles . empty ( ) ) {
return std : : nullopt ;
2026-04-07 15:47:58 +02:00
}
for ( const auto & tri : triangles ) {
2026-04-10 14:46:46 +02:00
auto a = ( uint64_t ) tri [ 0 ] ;
auto b = ( uint64_t ) tri [ 1 ] ;
auto c = ( uint64_t ) tri [ 2 ] ;
2026-04-07 15:47:58 +02:00
if ( direction_sign > 0. ) {
2026-04-10 14:46:46 +02:00
builder . add_triangle ( c , b , a , 0 ) ;
builder . add_triangle ( top_by_bottom [ a ] , top_by_bottom [ b ] , top_by_bottom [ c ] , 1 ) ;
2026-04-07 15:47:58 +02:00
} else {
2026-04-10 14:46:46 +02:00
builder . add_triangle ( a , b , c , 0 ) ;
builder . add_triangle ( top_by_bottom [ c ] , top_by_bottom [ b ] , top_by_bottom [ a ] , 1 ) ;
2026-04-07 15:47:58 +02:00
}
}
2026-04-10 14:46:46 +02:00
uint64_t face_id = 2 ;
for ( size_t k = 0 ; k < polygons . size ( ) ; + + k ) {
for ( size_t i = 0 ; i < polygons [ k ] . size ( ) ; + + i ) {
auto j = ( i + 1 ) % polygons [ k ] . size ( ) ;
if ( direction_sign > 0. ) {
builder . add_triangle ( bottoms [ k ] [ i ] , bottoms [ k ] [ j ] , tops [ k ] [ j ] , face_id ) ;
builder . add_triangle ( bottoms [ k ] [ i ] , tops [ k ] [ j ] , tops [ k ] [ i ] , face_id ) ;
} else {
builder . add_triangle ( bottoms [ k ] [ i ] , tops [ k ] [ j ] , bottoms [ k ] [ j ] , face_id ) ;
builder . add_triangle ( bottoms [ k ] [ i ] , tops [ k ] [ i ] , tops [ k ] [ j ] , face_id ) ;
}
+ + face_id ;
2026-04-07 15:47:58 +02:00
}
}
return part_from_mesh ( builder . build ( ) , true ) ;
}
2026-04-10 14:46:46 +02:00
std : : optional < Part > part_from_halfspace_solid ( HalfspaceBuildState & state , const taxonomy : : solid : : ptr & solid , const taxonomy : : face : : ptr & face , const manifold : : Box & reference_box , double precision , double dilation ) {
auto plane = taxonomy : : cast < taxonomy : : plane > ( face - > basis ) ;
2026-05-18 18:18:05 +02:00
// @todo verify order
2026-04-10 14:46:46 +02:00
const auto transform = matrix_or_identity ( solid - > matrix ) * matrix_or_identity ( plane - > matrix ) ;
2026-05-18 18:18:05 +02:00
const auto extrusion_dir = matrix_or_identity ( solid - > matrix ) . col ( 2 ) . head < 3 > ( ) . eval ( ) ;
2026-04-10 14:46:46 +02:00
Eigen : : Vector3d x = transform . col ( 0 ) . head < 3 > ( ) ;
Eigen : : Vector3d y = transform . col ( 1 ) . head < 3 > ( ) ;
Eigen : : Vector3d normal = transform . col ( 2 ) . head < 3 > ( ) ;
Eigen : : Vector3d origin = transform . col ( 3 ) . head < 3 > ( ) ;
2026-05-18 18:18:05 +02:00
auto project_along_global_z = [ & ] ( const Eigen : : Vector3d & p )
- > std : : optional < LoopPoint > {
Eigen : : Vector3d hit ;
if ( std : : abs ( normal . z ( ) ) > precision ) {
const double t = normal . dot ( origin - p ) / normal . z ( ) ;
hit = p + Eigen : : Vector3d ( 0. , 0. , t ) ;
} else {
return std : : nullopt ;
}
const auto delta = hit - origin ;
return std : : make_optional ( LoopPoint {
hit ,
manifold : : vec2 ( delta . dot ( x ) , delta . dot ( y ) ) } ) ;
} ;
2026-04-10 14:46:46 +02:00
const auto inside_sign = face - > orientation . value_or ( false ) ? + 1. : - 1. ;
double u_min = std : : numeric_limits < double > : : infinity ( ) ;
double u_max = - std : : numeric_limits < double > : : infinity ( ) ;
double v_min = std : : numeric_limits < double > : : infinity ( ) ;
double v_max = - std : : numeric_limits < double > : : infinity ( ) ;
double max_depth = 0. ;
for ( const auto & corner : box_corners ( reference_box ) ) {
const auto delta = corner - origin ;
const auto u = delta . dot ( x ) ;
const auto v = delta . dot ( y ) ;
2026-05-18 18:18:05 +02:00
2026-04-10 14:46:46 +02:00
u_min = std : : min ( u_min , u ) ;
u_max = std : : max ( u_max , u ) ;
v_min = std : : min ( v_min , v ) ;
v_max = std : : max ( v_max , v ) ;
2026-05-18 18:18:05 +02:00
// Keep in mind that extrusion direction is not necessarily parallel to plane normal, so we need to project corner onto plane along global z and measure distance along extrusion direction
if ( auto proj = project_along_global_z ( corner ) ) {
auto w = ( corner - proj - > xyz ) . dot ( extrusion_dir ) ;
max_depth = std : : max ( max_depth , inside_sign * w ) ;
}
}
2026-04-10 14:46:46 +02:00
state . depth = max_depth ;
if ( max_depth < = precision * 20. | | max_depth < = 0.00002 ) {
state . unchanged = true ;
return std : : nullopt ;
}
const auto diagonal = Eigen : : Vector3d ( reference_box . max [ 0 ] - reference_box . min [ 0 ] , reference_box . max [ 1 ] - reference_box . min [ 1 ] , reference_box . max [ 2 ] - reference_box . min [ 2 ] ) . norm ( ) ;
const auto margin = std : : max ( precision * 100. , diagonal * 1.e-6 ) ;
LoopPolygon polygon ;
if ( face - > children . empty ( ) ) {
polygon = {
{ origin + x * ( u_min - margin ) + y * ( v_min - margin ) , manifold : : vec2 ( u_min - margin , v_min - margin ) } ,
{ origin + x * ( u_max + margin ) + y * ( v_min - margin ) , manifold : : vec2 ( u_max + margin , v_min - margin ) } ,
{ origin + x * ( u_max + margin ) + y * ( v_max + margin ) , manifold : : vec2 ( u_max + margin , v_max + margin ) } ,
{ origin + x * ( u_min - margin ) + y * ( v_max + margin ) , manifold : : vec2 ( u_min - margin , v_max + margin ) }
} ;
} else {
auto & loop = face - > children . front ( ) ;
for ( auto & e : loop - > children ) {
if ( auto point = explicit_point ( e ) ) {
auto transformed = transform_point ( matrix_or_identity ( face - > matrix ) , * point ) ;
if ( auto ppoint = project_along_global_z ( transformed ) ) {
polygon . push_back ( * ppoint ) ;
} else {
return std : : nullopt ;
}
} else {
return std : : nullopt ;
}
}
}
2026-05-18 18:18:05 +02:00
return part_from_polygon_extrusion ( { std : : move ( polygon ) } , 0 , normal , extrusion_dir * - inside_sign , max_depth + margin , precision , dilation ) ;
2026-04-10 14:46:46 +02:00
}
std : : optional < Part > part_from_extrusion ( const taxonomy : : extrusion : : ptr & extrusion , double precision , double dilation , int circle_segments ) {
if ( extrusion - > depth < precision ) {
return std : : nullopt ;
}
auto face = std : : dynamic_pointer_cast < taxonomy : : face > ( extrusion - > basis ) ;
if ( ! extrusion_face_supported ( face ) ) {
return std : : nullopt ;
}
Eigen : : Vector3d origin ;
Eigen : : Vector3d x ;
Eigen : : Vector3d y ;
std : : vector < LoopPolygon > polygons ;
size_t outer_index = 0 ;
if ( ! extrusion_face_polygons ( face , circle_segments , precision , origin , x , y , polygons , outer_index ) ) {
return std : : nullopt ;
}
auto normal = x . cross ( y ) ;
if ( normal . norm ( ) < 1.e-12 ) {
return std : : nullopt ;
}
return part_from_polygon_extrusion ( std : : move ( polygons ) , outer_index , normal , extrusion - > direction - > ccomponents ( ) , extrusion - > depth , precision , dilation ) ;
}
bool extrusion_supported ( const taxonomy : : extrusion : : ptr & extrusion , double precision ) {
if ( ! extrusion | | extrusion - > depth < precision | | ! extrusion - > direction ) {
return false ;
}
auto face = std : : dynamic_pointer_cast < taxonomy : : face > ( extrusion - > basis ) ;
if ( ! extrusion_face_supported ( face ) ) {
return false ;
}
Eigen : : Vector3d origin ;
Eigen : : Vector3d x ;
Eigen : : Vector3d y ;
std : : vector < LoopPolygon > polygons ;
size_t outer_index = 0 ;
if ( ! extrusion_face_polygons ( face , settings : : CircleSegments : : defaultvalue , precision , origin , x , y , polygons , outer_index ) ) {
return false ;
}
auto dir = extrusion - > direction - > ccomponents ( ) ;
if ( dir . norm ( ) < 1.e-12 ) {
return false ;
}
dir . normalize ( ) ;
auto normal = x . cross ( y ) ;
if ( normal . norm ( ) < 1.e-12 ) {
return false ;
}
normal . normalize ( ) ;
if ( std : : abs ( normal . dot ( dir ) ) < 1.e-9 ) {
return false ;
}
return ! polygons . empty ( ) ;
}
2026-04-07 15:47:58 +02:00
Mesh transform_mesh ( const Mesh & mesh , const taxonomy : : matrix4 : : ptr & place ) {
const auto & m = place - > ccomponents ( ) ;
Mesh result = mesh ;
const bool flip = m . block < 3 , 3 > ( 0 , 0 ) . determinant ( ) < 0. ;
for ( size_t i = 0 ; i < mesh . NumVert ( ) ; + + i ) {
Eigen : : Vector4d v (
mesh . vertProperties [ i * mesh . numProp + 0 ] ,
mesh . vertProperties [ i * mesh . numProp + 1 ] ,
mesh . vertProperties [ i * mesh . numProp + 2 ] ,
1. ) ;
auto v2 = m * v ;
result . vertProperties [ i * result . numProp + 0 ] = v2 ( 0 ) ;
result . vertProperties [ i * result . numProp + 1 ] = v2 ( 1 ) ;
result . vertProperties [ i * result . numProp + 2 ] = v2 ( 2 ) ;
}
if ( flip ) {
for ( size_t i = 0 ; i < mesh . NumTri ( ) ; + + i ) {
std : : swap ( result . triVerts [ i * 3 + 1 ] , result . triVerts [ i * 3 + 2 ] ) ;
}
}
result . runTransform . clear ( ) ;
return result ;
}
taxonomy : : style : : ptr fallback_style ( const taxonomy : : geom_item : : ptr & item , const IfcGeom : : ConversionResults & results ) {
if ( item - > surface_style ) {
return item - > surface_style ;
}
for ( const auto & result : results ) {
if ( result . hasStyle ( ) ) {
return result . StylePtr ( ) ;
}
}
return nullptr ;
}
std : : optional < manifold : : Manifold > result_to_manifold ( const IfcGeom : : ConversionResult & result ) {
auto moved = std : : unique_ptr < IfcGeom : : ConversionResultShape > ( result . apply_transform ( ) ) ;
auto * shape = dynamic_cast < ifcopenshell : : geometry : : ManifoldShape * > ( moved . get ( ) ) ;
if ( ! shape ) {
return std : : nullopt ;
}
return shape - > as_manifold ( ) ;
}
std : : optional < manifold : : Manifold > results_to_operand ( const IfcGeom : : ConversionResults & results ) {
std : : vector < manifold : : Manifold > operands ;
for ( const auto & result : results ) {
auto operand = result_to_manifold ( result ) ;
if ( operand ) {
operands . push_back ( * operand ) ;
}
}
if ( operands . empty ( ) ) {
return std : : nullopt ;
}
if ( operands . size ( ) = = 1 ) {
return operands . front ( ) ;
}
return manifold : : Manifold : : BatchBoolean ( operands , manifold : : OpType : : Add ) ;
}
2026-04-10 14:46:46 +02:00
std : : optional < manifold : : Box > results_bbox ( const IfcGeom : : ConversionResults & results ) {
bool any = false ;
manifold : : Box bbox ;
for ( const auto & result : results ) {
auto operand = result_to_manifold ( result ) ;
if ( ! operand ) {
return std : : nullopt ;
}
auto part_box = operand - > BoundingBox ( ) ;
if ( ! part_box . IsFinite ( ) ) {
return std : : nullopt ;
}
if ( ! any ) {
bbox = part_box ;
any = true ;
} else {
bbox . Union ( part_box . min ) ;
bbox . Union ( part_box . max ) ;
}
}
if ( ! any ) {
return std : : nullopt ;
}
return bbox ;
}
2026-04-07 15:47:58 +02:00
std : : optional < manifold : : Manifold > boolean_result_from_operands ( const std : : vector < manifold : : Manifold > & operands , taxonomy : : boolean_result : : operation_t operation ) {
if ( operands . empty ( ) ) {
return std : : nullopt ;
}
if ( operands . size ( ) = = 1 ) {
return operands . front ( ) ;
}
switch ( operation ) {
case taxonomy : : boolean_result : : UNION :
return manifold : : Manifold : : BatchBoolean ( operands , manifold : : OpType : : Add ) ;
case taxonomy : : boolean_result : : INTERSECTION :
return manifold : : Manifold : : BatchBoolean ( operands , manifold : : OpType : : Intersect ) ;
case taxonomy : : boolean_result : : SUBTRACTION :
return manifold : : Manifold : : BatchBoolean ( operands , manifold : : OpType : : Subtract ) ;
}
return std : : nullopt ;
}
}
bool ManifoldKernel : : convert_impl ( const taxonomy : : extrusion : : ptr extrusion , IfcGeom : : ConversionResults & results ) {
2026-04-10 14:46:46 +02:00
auto part = part_from_extrusion ( extrusion , settings_ . get < settings : : Precision > ( ) . get ( ) , dilation_hack , settings_ . get < settings : : CircleSegments > ( ) . get ( ) ) ;
2026-04-07 15:47:58 +02:00
if ( ! part ) {
2026-04-10 14:46:46 +02:00
logger : : warning ( " Manifold kernel: failed to convert extrusion, requires planar bounds with line, circle or ellipse edges " , extrusion - > instance ) ;
2026-04-07 15:47:58 +02:00
return false ;
}
results . emplace_back ( IfcGeom : : ConversionResult (
extrusion - > instance . id ( ) ,
extrusion - > matrix ,
new ifcopenshell : : geometry : : ManifoldShape ( std : : move ( * part ) ) ,
extrusion - > surface_style ) ) ;
return true ;
}
bool ManifoldKernel : : convert_impl ( const taxonomy : : shell : : ptr shell , IfcGeom : : ConversionResults & results ) {
manifold : : Manifold : : Error status = manifold : : Manifold : : Error : : NoError ;
2026-04-10 14:46:46 +02:00
auto part = part_from_shell ( shell , settings_ . get < settings : : Precision > ( ) . get ( ) , dilation_hack , & status ) ;
2026-04-07 15:47:58 +02:00
if ( ! part ) {
logger : : warning ( " Manifold kernel: failed to convert shell, requires planar polygonal faces with explicit vertices " , shell - > instance ) ;
return false ;
}
if ( ! part - > solid ) {
logger : : notice ( " Manifold kernel: shell converted as mesh only ( " + manifold_error_string ( status ) + " ) " , shell - > instance ) ;
}
results . emplace_back ( IfcGeom : : ConversionResult (
shell - > instance . id ( ) ,
shell - > matrix ,
new ifcopenshell : : geometry : : ManifoldShape ( std : : move ( * part ) ) ,
shell - > surface_style ) ) ;
return true ;
}
bool ManifoldKernel : : convert_impl ( const taxonomy : : solid : : ptr solid , IfcGeom : : ConversionResults & results ) {
std : : vector < manifold : : Manifold > shells ;
for ( const auto & shell : solid - > children ) {
const auto precision = settings_ . get < settings : : Precision > ( ) . get ( ) ;
manifold : : Manifold : : Error before_status = manifold : : Manifold : : Error : : NoError ;
2026-04-10 14:46:46 +02:00
auto part = part_from_shell ( shell , precision , dilation_hack , & before_status ) ;
2026-04-07 15:47:58 +02:00
if ( ! part ) {
logger : : warning ( " Manifold kernel: failed to convert solid shell, requires planar polygonal faces with explicit vertices " , shell - > instance ) ;
return false ;
}
auto place = shell - > matrix ? shell - > matrix : taxonomy : : make < taxonomy : : matrix4 > ( ) ;
auto transformed_mesh = transform_mesh ( part - > mesh , place ) ;
manifold : : Manifold : : Error after_status = manifold : : Manifold : : Error : : NoError ;
auto transformed = part_from_mesh ( transformed_mesh , true , & after_status ) ;
if ( ! transformed | | ! transformed - > solid ) {
log_solid_shell_transform_failure ( shell , * part , transformed_mesh , place , precision , before_status , after_status ) ;
return false ;
}
shells . push_back ( * transformed - > solid ) ;
}
if ( shells . empty ( ) ) {
return false ;
}
auto result = shells . front ( ) ;
for ( size_t i = 1 ; i < shells . size ( ) ; + + i ) {
result - = shells [ i ] ;
}
results . emplace_back ( IfcGeom : : ConversionResult (
solid - > instance . id ( ) ,
solid - > matrix ,
2026-05-18 18:16:49 +02:00
new ifcopenshell : : geometry : : ManifoldShape ( result ) ,
2026-04-07 15:47:58 +02:00
solid - > surface_style ) ) ;
return true ;
}
bool ManifoldKernel : : convert_impl ( const taxonomy : : boolean_result : : ptr br , IfcGeom : : ConversionResults & results ) {
std : : vector < manifold : : Manifold > operands ;
taxonomy : : style : : ptr style ;
2026-04-10 14:46:46 +02:00
std : : optional < manifold : : Box > first_bbox ;
const auto precision = settings_ . get < settings : : Precision > ( ) . get ( ) ;
bool first = true ;
2026-04-07 15:47:58 +02:00
for ( const auto & child : br - > children ) {
2026-04-10 14:46:46 +02:00
std : : optional < manifold : : Manifold > operand ;
auto solid = std : : dynamic_pointer_cast < taxonomy : : solid > ( child ) ;
taxonomy : : face : : ptr face = solid ? halfspace_face ( solid ) : nullptr ;
// @todo reset upon exceptions
dilation_hack = first ? 0. : precision * 10. ;
if ( ! first & & br - > operation = = taxonomy : : boolean_result : : SUBTRACTION & & face ) {
if ( ! first_bbox ) {
logger : : warning ( " Manifold kernel: cannot fit halfspace operand without a valid first operand bounds " , child - > instance ) ;
return false ;
}
HalfspaceBuildState state ;
auto part = part_from_halfspace_solid ( state , solid , face , * first_bbox , precision , dilation_hack ) ;
if ( ! part ) {
if ( state . unchanged & & br - > operation = = taxonomy : : boolean_result : : SUBTRACTION ) {
logger : : warning ( " Manifold kernel: halfspace subtraction yields unchanged volume " , child - > instance ) ;
continue ;
}
logger : : warning ( " Manifold kernel: failed to fit halfspace boolean operand to first operand bounds " , child - > instance ) ;
return false ;
}
if ( ! part - > solid ) {
logger : : warning ( " Manifold kernel: fitted halfspace operand is not a valid manifold solid " , child - > instance ) ;
return false ;
}
operand = * part - > solid ;
if ( ! style & & child - > surface_style ) {
style = child - > surface_style ;
}
} else {
IfcGeom : : ConversionResults converted ;
if ( ! AbstractKernel : : convert ( child , converted ) ) {
logger : : warning ( " Manifold kernel: failed to convert boolean operand " , child - > instance ) ;
return false ;
}
operand = results_to_operand ( converted ) ;
if ( ! operand ) {
logger : : warning ( " Manifold kernel: boolean operand is not a valid manifold solid " , child - > instance ) ;
return false ;
}
if ( ! style ) {
style = fallback_style ( child , converted ) ;
}
2026-04-07 15:47:58 +02:00
}
if ( ! operand ) {
return false ;
}
2026-04-10 14:46:46 +02:00
if ( first ) {
auto bbox = operand - > BoundingBox ( ) ;
if ( ! bbox . IsFinite ( ) ) {
logger : : warning ( " Manifold kernel: first boolean operand has no valid bounds " , child - > instance ) ;
return false ;
}
first_bbox = bbox ;
2026-04-07 15:47:58 +02:00
}
2026-04-10 14:46:46 +02:00
operands . push_back ( * operand ) ;
first = false ;
2026-04-07 15:47:58 +02:00
}
2026-04-10 14:46:46 +02:00
dilation_hack = 0. ;
2026-04-07 15:47:58 +02:00
auto result = boolean_result_from_operands ( operands , br - > operation ) ;
if ( ! result | | result - > IsEmpty ( ) ) {
logger : : warning ( " Manifold kernel: boolean operation produced no result " , br - > instance ) ;
return false ;
}
results . emplace_back ( IfcGeom : : ConversionResult (
br - > instance . id ( ) ,
br - > matrix ,
2026-05-18 18:16:49 +02:00
new ifcopenshell : : geometry : : ManifoldShape ( * result ) ,
2026-04-07 15:47:58 +02:00
br - > surface_style ? br - > surface_style : style ) ) ;
return true ;
}
bool ManifoldKernel : : convert_openings ( const express : : Base & , const std : : vector < std : : pair < taxonomy : : ptr , taxonomy : : matrix4 > > & openings , const IfcGeom : : ConversionResults & entity_shapes , const taxonomy : : matrix4 & entity_trsf , IfcGeom : : ConversionResults & cut_shapes ) {
std : : vector < manifold : : Manifold > opening_operands ;
2026-04-10 14:46:46 +02:00
auto entity_bbox = results_bbox ( entity_shapes ) ;
if ( ! entity_bbox ) {
logger : : warning ( " Manifold kernel: host shape has no valid bounds for halfspace fitting " ) ;
return false ;
}
dilation_hack = settings_ . get < settings : : Precision > ( ) . get ( ) * 10. ;
2026-04-07 15:47:58 +02:00
for ( const auto & opening : openings ) {
2026-04-10 14:46:46 +02:00
const auto relative = taxonomy : : make < taxonomy : : matrix4 > ( entity_trsf . ccomponents ( ) . inverse ( ) * opening . second . ccomponents ( ) ) ;
2026-04-07 15:47:58 +02:00
IfcGeom : : ConversionResults converted ;
if ( ! AbstractKernel : : convert ( opening . first , converted ) ) {
logger : : warning ( " Manifold kernel: failed to convert opening operand " , opening . first - > instance ) ;
return false ;
}
for ( const auto & result : converted ) {
auto moved = std : : unique_ptr < IfcGeom : : ConversionResultShape > ( result . Shape ( ) - > moved ( taxonomy : : make < taxonomy : : matrix4 > ( relative - > ccomponents ( ) * result . Placement ( ) - > ccomponents ( ) ) ) ) ;
auto * shape = dynamic_cast < ifcopenshell : : geometry : : ManifoldShape * > ( moved . get ( ) ) ;
if ( ! shape ) {
logger : : warning ( " Manifold kernel: opening result is not a manifold shape " ) ;
return false ;
}
auto operand = shape - > as_manifold ( ) ;
if ( ! operand ) {
logger : : warning ( " Manifold kernel: opening result is not a valid manifold solid " , opening . first - > instance ) ;
return false ;
}
opening_operands . push_back ( * operand ) ;
}
}
2026-04-10 14:46:46 +02:00
dilation_hack = 0. ;
2026-04-07 15:47:58 +02:00
if ( opening_operands . empty ( ) ) {
return false ;
}
auto opening_union = manifold : : Manifold : : BatchBoolean ( opening_operands , manifold : : OpType : : Add ) ;
for ( const auto & entity_shape : entity_shapes ) {
auto operand = result_to_manifold ( entity_shape ) ;
if ( ! operand ) {
logger : : warning ( " Manifold kernel: host shape is not a valid manifold solid " ) ;
return false ;
}
auto result = * operand - opening_union ;
cut_shapes . emplace_back ( IfcGeom : : ConversionResult (
entity_shape . ItemId ( ) ,
2026-05-18 18:16:49 +02:00
new ifcopenshell : : geometry : : ManifoldShape ( result ) ,
2026-04-07 15:47:58 +02:00
entity_shape . StylePtr ( ) ) ) ;
}
return ! cut_shapes . empty ( ) ;
}