#include "ManifoldKernel.h" #include "../../../ifcparse/logger.h" #include #include #include #include #include #include #include #include #include #include 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()(key.x); h ^= std::hash()(key.y) + 0x9e3779b97f4a7c15ull + (h << 6) + (h >> 2); h ^= std::hash()(key.z) + 0x9e3779b97f4a7c15ull + (h << 6) + (h >> 2); return h; } }; struct MeshBuilder { double precision; double dilation = 0.; std::vector vertices; std::unordered_map vertex_map; std::vector tri_verts; std::vector 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; std::vector vertex_use_count(vertices.size(), 0); std::vector 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(); } } mesh.vertProperties.reserve(vertices.size() * 3); 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)); } 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; 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()(key.a); h ^= std::hash()(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()(key.a); h ^= std::hash()(key.b) + 0x9e3779b97f4a7c15ull + (h << 6) + (h >> 2); h ^= std::hash()(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::infinity(); double max_edge = 0.; double min_area = std::numeric_limits::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 explicit_point(const taxonomy::edge::ptr& edge) { if (edge->start.index() != 1) { return std::nullopt; } return std::get(edge->start)->ccomponents(); } 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(basis); if (!nested || nested == edge) { break; } basis = nested->basis; } return basis; } bool resolve_curve_parameter(const taxonomy::item::ptr& curve, const std::variant& trim, double& u) { if (auto value = std::get_if(&trim)) { u = *value; return std::isfinite(u); } if (auto point = std::get_if(&trim)) { if (auto line = taxonomy::dcast(curve)) { project_onto_curve(line, **point, u); return std::isfinite(u); } if (auto circle = taxonomy::dcast(curve)) { project_onto_curve(circle, **point, u); return std::isfinite(u); } if (auto ellipse = taxonomy::dcast(curve)) { project_onto_curve(ellipse, **point, u); return std::isfinite(u); } } return false; } bool basis_from_points(const std::vector& 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; } bool 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_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()) { return false; } for (const auto& edge : loop->children) { if (!extrusion_edge_supported(edge)) { return false; } } } return true; } bool append_extrusion_loop_points(const taxonomy::loop::ptr& loop, int circle_segments, double precision, std::vector& points); bool loop_polygon_from_points(const std::vector& 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& polygons, size_t& outer_index) { polygons.clear(); outer_index = 0; if (!extrusion_face_supported(face)) { return false; } std::vector basis_points; double basis_area = 0.; std::vector> loops; loops.reserve(face->children.size()); for (const auto& loop : face->children) { std::vector 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; } bool face_basis(const taxonomy::face::ptr& face, Eigen::Vector3d& origin, Eigen::Vector3d& x, Eigen::Vector3d& y) { double best_score = -1.; std::vector best_points; for (const auto& loop : face->children) { std::vector 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); } return basis_from_points(best_points, origin, x, y); } 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 edge_use_count; std::unordered_map 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 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 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 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; } void extend_points(std::vector& points, const std::vector& 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& points) { if (!edge) { return false; } const auto two_pi = 2. * std::acos(-1.); std::vector edge_points; if (!edge->basis) { if (edge->start.index() != 1 || edge->end.index() != 1) { return false; } edge_points.push_back(*std::get(edge->start)); edge_points.push_back(*std::get(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(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(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(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& 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& 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; } 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 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(); } bool shell_to_mesh(const taxonomy::shell::ptr& shell, double precision, Mesh& mesh, double dilation) { MeshBuilder builder(precision); builder.dilation = dilation; 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_from_mesh(const Mesh& mesh, bool require_manifold, manifold::Manifold::Error* status_ptr = nullptr) { auto solid = std::optional{}; 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) { return *solid; } return mesh; } std::optional part_from_shell(const taxonomy::shell::ptr& shell, double precision, double dilation, manifold::Manifold::Error* status_ptr = nullptr) { Mesh mesh; if (!shell_to_mesh(shell, precision, mesh, dilation)) { return std::nullopt; } return part_from_mesh(mesh, false, status_ptr); } Mesh transform_mesh(const Mesh& mesh, const taxonomy::matrix4::ptr& place); std::optional part_from_extrusion(const taxonomy::extrusion::ptr& extrusion, double precision, double dilation, int circle_segments); 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; } const auto& shell = solid->children.front(); if (!shell || shell->children.size() != 1) { return nullptr; } 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(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 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_from_polygon_extrusion(std::vector 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()) { return std::nullopt; } auto dir = direction; if (dir.norm() < 1.e-12) { return std::nullopt; } dir.normalize(); auto normal = plane_normal; if (normal.norm() < 1.e-12) { return std::nullopt; } normal.normalize(); auto outer_area = signed_area(polygons[outer_index]); if (std::abs(outer_area) <= precision * precision) { return std::nullopt; } if (outer_area < 0.) { reverse_loop(polygons[outer_index]); outer_area = -outer_area; } for (size_t i = 0; i < polygons.size(); ++i) { if (i != outer_index && signed_area(polygons[i]) * outer_area > 0.) { reverse_loop(polygons[i]); } } auto direction_sign = normal.dot(dir); if (std::abs(direction_sign) < 1.e-9) { return std::nullopt; } auto offset = dir * depth; MeshBuilder builder(precision); builder.dilation = dilation; std::vector> bottoms; std::vector> tops; std::unordered_map 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; } for (const auto& tri : triangles) { auto a = (uint64_t)tri[0]; auto b = (uint64_t)tri[1]; auto c = (uint64_t)tri[2]; if (direction_sign > 0.) { builder.add_triangle(c, b, a, 0); builder.add_triangle(top_by_bottom[a], top_by_bottom[b], top_by_bottom[c], 1); } else { builder.add_triangle(a, b, c, 0); builder.add_triangle(top_by_bottom[c], top_by_bottom[b], top_by_bottom[a], 1); } } 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; } } return part_from_mesh(builder.build(), true); } std::optional 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(face->basis); // @todo verify order const auto transform = matrix_or_identity(solid->matrix) * matrix_or_identity(plane->matrix); const auto extrusion_dir = matrix_or_identity(solid->matrix).col(2).head<3>().eval(); 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>(); auto project_along_global_z = [&](const Eigen::Vector3d& p) -> std::optional { 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))}); }; const auto inside_sign = face->orientation.value_or(false) ? +1. : -1.; double u_min = std::numeric_limits::infinity(); double u_max = -std::numeric_limits::infinity(); double v_min = std::numeric_limits::infinity(); double v_max = -std::numeric_limits::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); 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); // 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); } } 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; } } } return part_from_polygon_extrusion({std::move(polygon)}, 0, normal, extrusion_dir * -inside_sign, max_depth + margin, precision, dilation); } std::optional 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(extrusion->basis); if (!extrusion_face_supported(face)) { return std::nullopt; } Eigen::Vector3d origin; Eigen::Vector3d x; Eigen::Vector3d y; std::vector 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(extrusion->basis); if (!extrusion_face_supported(face)) { return false; } Eigen::Vector3d origin; Eigen::Vector3d x; Eigen::Vector3d y; std::vector 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(); } 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 result_to_manifold(const IfcGeom::ConversionResult& result) { auto moved = std::unique_ptr(result.apply_transform()); auto* shape = dynamic_cast(moved.get()); if (!shape) { return std::nullopt; } return shape->as_manifold(); } std::optional results_to_operand(const IfcGeom::ConversionResults& results) { std::vector 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); } std::optional 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; } std::optional boolean_result_from_operands(const std::vector& 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) { auto part = part_from_extrusion(extrusion, settings_.get().get(), dilation_hack, settings_.get().get()); if (!part) { logger::warning("Manifold kernel: failed to convert extrusion, requires planar bounds with line, circle or ellipse edges", extrusion->instance); 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; auto part = part_from_shell(shell, settings_.get().get(), dilation_hack, &status); 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 shells; for (const auto& shell : solid->children) { const auto precision = settings_.get().get(); manifold::Manifold::Error before_status = manifold::Manifold::Error::NoError; auto part = part_from_shell(shell, precision, dilation_hack, &before_status); 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(); 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, new ifcopenshell::geometry::ManifoldShape(result), solid->surface_style)); return true; } bool ManifoldKernel::convert_impl(const taxonomy::boolean_result::ptr br, IfcGeom::ConversionResults& results) { std::vector operands; taxonomy::style::ptr style; std::optional first_bbox; const auto precision = settings_.get().get(); bool first = true; for (const auto& child : br->children) { std::optional operand; auto solid = std::dynamic_pointer_cast(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); } } if (!operand) { return false; } 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; } operands.push_back(*operand); first = false; } dilation_hack = 0.; 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, new ifcopenshell::geometry::ManifoldShape(*result), br->surface_style ? br->surface_style : style)); return true; } bool ManifoldKernel::convert_openings(const express::Base&, const std::vector>& openings, const IfcGeom::ConversionResults& entity_shapes, const taxonomy::matrix4& entity_trsf, IfcGeom::ConversionResults& cut_shapes) { std::vector opening_operands; 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().get() * 10.; for (const auto& opening : openings) { const auto relative = taxonomy::make(entity_trsf.ccomponents().inverse() * opening.second.ccomponents()); 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(result.Shape()->moved(taxonomy::make(relative->ccomponents() * result.Placement()->ccomponents()))); auto* shape = dynamic_cast(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); } } dilation_hack = 0.; 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(), new ifcopenshell::geometry::ManifoldShape(result), entity_shape.StylePtr())); } return !cut_shapes.empty(); }