Add passthrough kernel

This commit is contained in:
Thomas Krijnen
2026-04-09 17:17:53 +02:00
parent becd38c77d
commit 1840e3d1a8
8 changed files with 940 additions and 1 deletions
+4
View File
@@ -232,6 +232,10 @@ if(BUILD_IFCGEOM AND WITH_MANIFOLD)
list(APPEND GEOMETRY_KERNELS manifold)
endif()
if(BUILD_IFCGEOM)
list(APPEND GEOMETRY_KERNELS passthrough)
endif()
if(GLTF_SUPPORT)
UNIFY_ENVVARS_AND_CACHE(JSON_INCLUDE_DIR)
if(NOT JSON_INCLUDE_DIR)
+1 -1
View File
@@ -295,7 +295,7 @@ int main(int argc, char** argv) {
po::options_description geom_options("Geometry options");
geom_options.add_options()
("kernel", po::value<std::string>(&geometry_kernel)->default_value(default_kernel),
"Geometry kernel to use (opencascade, cgal, cgal-simple, manifold, hybrid-cgal-simple-opencascade).")
"Geometry kernel to use (opencascade, cgal, cgal-simple, manifold, passthrough, hybrid-cgal-simple-opencascade, hybrid-passthrough-opencascade).")
("threads,j", po::value<int>(&num_threads)->default_value(1),
"Number of parallel processing threads for geometry interpretation.")
("center-model",
+14
View File
@@ -39,6 +39,7 @@
#ifdef IFOPSH_WITH_MANIFOLD
#include "../ifcgeom/kernels/manifold/ManifoldKernel.h"
#endif
#include "../ifcgeom/kernels/passthrough/PassthroughKernel.h"
namespace {
inline bool is_valid_for_kernel(const ifcopenshell::geometry::kernels::AbstractKernel* k, const IfcGeom::ConversionResult& shp) {
@@ -60,6 +61,9 @@ namespace {
return dynamic_cast<ifcopenshell::geometry::ManifoldShape*>(shp.Shape().get()) != nullptr;
}
#endif
if (k->geometry_library() == "passthrough") {
return dynamic_cast<ifcopenshell::geometry::PassthroughShape*>(shp.Shape().get()) != nullptr;
}
return false;
}
}
@@ -105,6 +109,9 @@ namespace ifcopenshell {
continue;
}
#endif
if (has_openings && k->geometry_library() == "passthrough") {
continue;
}
bool success = false;
try {
success = k->convert(item, rs);
@@ -200,6 +207,9 @@ namespace ifcopenshell {
return std::make_unique<ManifoldKernel>(conv_settings);
}
#endif
if (geometry_library_lower == "passthrough") {
return std::make_unique<PassthroughKernel>(conv_settings);
}
if (geometry_library_lower.rfind("hybrid-", 0) == 0) {
geometry_library_lower = geometry_library_lower.substr(strlen("hybrid"));
@@ -235,6 +245,10 @@ namespace ifcopenshell {
geometry_library_lower = geometry_library_lower.substr(strlen("manifold"));
}
#endif
if (geometry_library_lower.find("passthrough", 0) == 0) {
kernels.emplace_back(new PassthroughKernel(conv_settings));
geometry_library_lower = geometry_library_lower.substr(strlen("passthrough"));
}
if (kernels.size() != n + 1) {
throw ifcopenshell::exception("Invalid hybrid kernel " + geometry_library);
}
+2
View File
@@ -1,5 +1,7 @@
find_package(Eigen3 REQUIRED)
message(STATUS "GEOMETRY_KERNELS ${GEOMETRY_KERNELS}")
foreach(kernel ${GEOMETRY_KERNELS})
string(TOUPPER ${kernel} KERNEL_UPPER)
file(GLOB IFCGEOM_H_FILES ${kernel}/*.h)
@@ -0,0 +1,505 @@
#include "PassthroughConversionResult.h"
#include "../../../ifcgeom/IfcGeomRepresentation.h"
#include <Eigen/Dense>
#include <algorithm>
#include <array>
#include <cmath>
#include <limits>
#include <numeric>
#include <sstream>
#include <unordered_map>
using namespace ifcopenshell::geometry;
namespace {
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 EdgeKey {
int a;
int 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<int>()(key.a);
h ^= std::hash<int>()(key.b) + 0x9e3779b97f4a7c15ull + (h << 6) + (h >> 2);
return h;
}
};
struct Box {
bool valid = false;
Eigen::Vector3d min = Eigen::Vector3d::Zero();
Eigen::Vector3d max = Eigen::Vector3d::Zero();
};
struct MeshData {
std::vector<Eigen::Vector3d> vertices;
std::vector<std::array<int, 3>> triangles;
std::unordered_map<EdgeKey, int, EdgeKeyHash> edge_counts;
std::unordered_map<VertexKey, int, VertexKeyHash> vertex_map;
};
VertexKey make_vertex_key(const Eigen::Vector3d& p) {
constexpr double scale = 1.e9;
return {
(long long)std::llround(p(0) * scale),
(long long)std::llround(p(1) * scale),
(long long)std::llround(p(2) * scale)
};
}
EdgeKey make_edge_key(int a, int b) {
return a < b ? EdgeKey{ a, b } : EdgeKey{ b, a };
}
Eigen::Matrix4d item_matrix(const taxonomy::geom_item::ptr& item) {
if (item && item->matrix) {
return item->matrix->ccomponents();
}
return Eigen::Matrix4d::Identity();
}
Eigen::Vector3d transform_point(const Eigen::Matrix4d& m, const Eigen::Vector3d& p) {
Eigen::Vector4d v(p(0), p(1), p(2), 1.);
return (m * v).head<3>();
}
bool loop_points(const taxonomy::loop::ptr& loop, std::vector<Eigen::Vector3d>& points) {
points.clear();
if (!loop) {
return false;
}
points.reserve(loop->children.size());
for (const auto& edge : loop->children) {
if (edge->basis && edge->basis->kind() != taxonomy::LINE) {
return false;
}
if (edge->start.index() != 1 || edge->end.index() != 1) {
return false;
}
points.push_back(std::get<taxonomy::point3::ptr>(edge->start)->ccomponents());
}
return points.size() >= 3;
}
bool face_points(const taxonomy::face::ptr& face, std::vector<Eigen::Vector3d>& points) {
if (!face || face->children.size() != 1) {
return false;
}
const auto& loop = face->children.front();
if (!loop || loop->children.size() < 3 || loop->children.size() > 4) {
return false;
}
return loop_points(loop, points);
}
int add_vertex(MeshData& mesh, const Eigen::Vector3d& p) {
auto key = make_vertex_key(p);
auto it = mesh.vertex_map.find(key);
if (it != mesh.vertex_map.end()) {
return it->second;
}
auto idx = (int)mesh.vertices.size();
mesh.vertices.push_back(p);
mesh.vertex_map.insert({ key, idx });
return idx;
}
void add_face(MeshData& mesh, const std::vector<int>& indices) {
if (indices.size() == 3) {
mesh.triangles.push_back({ indices[0], indices[1], indices[2] });
} else if (indices.size() == 4) {
mesh.triangles.push_back({ indices[0], indices[1], indices[2] });
mesh.triangles.push_back({ indices[0], indices[2], indices[3] });
}
for (size_t i = 0; i < indices.size(); ++i) {
mesh.edge_counts[make_edge_key(indices[i], indices[(i + 1) % indices.size()])]++;
}
}
MeshData build_mesh(const std::vector<PassthroughPart>& parts, const taxonomy::matrix4* place = nullptr) {
MeshData mesh;
auto external = place ? place->ccomponents() : Eigen::Matrix4d::Identity();
std::vector<Eigen::Vector3d> points;
for (const auto& part : parts) {
if (!part.shell) {
continue;
}
auto part_matrix = part.matrix ? part.matrix->ccomponents() : Eigen::Matrix4d::Identity();
for (const auto& face : part.shell->children) {
if (!face_points(face, points)) {
continue;
}
auto total = external * part_matrix * item_matrix(face) * item_matrix(face->children.front());
std::vector<int> indices;
indices.reserve(points.size());
for (const auto& point : points) {
indices.push_back(add_vertex(mesh, transform_point(total, point)));
}
add_face(mesh, indices);
}
}
return mesh;
}
double triangle_area(const Eigen::Vector3d& a, const Eigen::Vector3d& b, const Eigen::Vector3d& c) {
return 0.5 * ((b - a).cross(c - a)).norm();
}
double mesh_area(const MeshData& mesh) {
double total = 0.;
for (const auto& tri : mesh.triangles) {
total += triangle_area(mesh.vertices[tri[0]], mesh.vertices[tri[1]], mesh.vertices[tri[2]]);
}
return total;
}
double mesh_volume(const MeshData& mesh) {
double total = 0.;
for (const auto& tri : mesh.triangles) {
const auto& a = mesh.vertices[tri[0]];
const auto& b = mesh.vertices[tri[1]];
const auto& c = mesh.vertices[tri[2]];
total += a.dot(b.cross(c));
}
return std::abs(total) / 6.;
}
double mesh_length(const MeshData& mesh) {
double total = 0.;
for (const auto& edge : mesh.edge_counts) {
total += (mesh.vertices[edge.first.a] - mesh.vertices[edge.first.b]).norm();
}
return total;
}
void update_box(Box& box, const MeshData& mesh) {
for (const auto& vertex : mesh.vertices) {
if (!box.valid) {
box.valid = true;
box.min = vertex;
box.max = vertex;
} else {
box.min = box.min.cwiseMin(vertex);
box.max = box.max.cwiseMax(vertex);
}
}
}
double box_volume(const Box& box) {
if (!box.valid) {
return 0.;
}
auto size = box.max - box.min;
return size(0) * size(1) * size(2);
}
taxonomy::face::ptr make_face(const std::vector<Eigen::Vector3d>& points) {
auto face = taxonomy::make<taxonomy::face>();
auto loop = taxonomy::make<taxonomy::loop>();
loop->external = true;
loop->closed = true;
std::vector<taxonomy::point3::ptr> vertices;
vertices.reserve(points.size());
for (const auto& point : points) {
vertices.push_back(taxonomy::make<taxonomy::point3>(point));
}
for (size_t i = 0; i < vertices.size(); ++i) {
loop->children.push_back(taxonomy::make<taxonomy::edge>(vertices[i], vertices[(i + 1) % vertices.size()]));
}
face->children.push_back(loop);
return face;
}
taxonomy::shell::ptr make_box_shell(const Eigen::Vector3d& min, const Eigen::Vector3d& max) {
auto shell = taxonomy::make<taxonomy::shell>();
shell->closed = true;
const Eigen::Vector3d p000(min(0), min(1), min(2));
const Eigen::Vector3d p100(max(0), min(1), min(2));
const Eigen::Vector3d p110(max(0), max(1), min(2));
const Eigen::Vector3d p010(min(0), max(1), min(2));
const Eigen::Vector3d p001(min(0), min(1), max(2));
const Eigen::Vector3d p101(max(0), min(1), max(2));
const Eigen::Vector3d p111(max(0), max(1), max(2));
const Eigen::Vector3d p011(min(0), max(1), max(2));
shell->children.push_back(make_face({ p000, p010, p110, p100 }));
shell->children.push_back(make_face({ p001, p101, p111, p011 }));
shell->children.push_back(make_face({ p000, p100, p101, p001 }));
shell->children.push_back(make_face({ p100, p110, p111, p101 }));
shell->children.push_back(make_face({ p110, p010, p011, p111 }));
shell->children.push_back(make_face({ p010, p000, p001, p011 }));
return shell;
}
PassthroughPart normalize_part(const PassthroughPart& part) {
return {
part.shell,
part.matrix ? taxonomy::make<taxonomy::matrix4>(part.matrix->ccomponents()) : taxonomy::make<taxonomy::matrix4>(),
part.manifold
};
}
std::vector<PassthroughPart> normalize_parts(const std::vector<PassthroughPart>& parts) {
std::vector<PassthroughPart> result;
result.reserve(parts.size());
for (const auto& part : parts) {
result.push_back(normalize_part(part));
}
return result;
}
}
ifcopenshell::geometry::PassthroughShape::PassthroughShape(const PassthroughPart& part)
: parts_{ normalize_part(part) } {}
ifcopenshell::geometry::PassthroughShape::PassthroughShape(PassthroughPart&& part)
: parts_{ normalize_part(part) } {}
ifcopenshell::geometry::PassthroughShape::PassthroughShape(const std::vector<PassthroughPart>& parts)
: parts_(normalize_parts(parts)) {}
ifcopenshell::geometry::PassthroughShape::PassthroughShape(std::vector<PassthroughPart>&& parts)
: parts_(normalize_parts(parts)) {}
void ifcopenshell::geometry::PassthroughShape::Triangulate(ifcopenshell::geometry::Settings, const ifcopenshell::geometry::taxonomy::matrix4& place, IfcGeom::Representation::Triangulation* t, int item_id, int surface_style_id) const {
auto mesh = build_mesh(parts_, &place);
std::vector<int> indices(mesh.vertices.size());
for (size_t i = 0; i < mesh.vertices.size(); ++i) {
indices[i] = t->addVertex(item_id, surface_style_id, mesh.vertices[i](0), mesh.vertices[i](1), mesh.vertices[i](2));
}
for (const auto& tri : mesh.triangles) {
t->addFace(item_id, surface_style_id, indices[tri[0]], indices[tri[1]], indices[tri[2]]);
}
for (const auto& edge : mesh.edge_counts) {
if (edge.second == 1) {
t->registerEdge(item_id, indices[edge.first.a], indices[edge.first.b]);
}
}
}
void ifcopenshell::geometry::PassthroughShape::Serialize(const ifcopenshell::geometry::taxonomy::matrix4& place, std::string& result) const {
auto mesh = build_mesh(parts_, &place);
std::stringstream stream;
for (const auto& vertex : mesh.vertices) {
stream << "v " << vertex(0) << " " << vertex(1) << " " << vertex(2) << "\n";
}
for (const auto& tri : mesh.triangles) {
stream << "f " << tri[0] + 1 << " " << tri[1] + 1 << " " << tri[2] + 1 << "\n";
}
result = stream.str();
}
int ifcopenshell::geometry::PassthroughShape::surface_genus() const {
return 0;
}
bool ifcopenshell::geometry::PassthroughShape::is_manifold() const {
return std::all_of(parts_.begin(), parts_.end(), [](const auto& part) { return part.manifold; });
}
int ifcopenshell::geometry::PassthroughShape::num_vertices() const {
return (int)build_mesh(parts_).vertices.size();
}
int ifcopenshell::geometry::PassthroughShape::num_edges() const {
return (int)build_mesh(parts_).edge_counts.size();
}
int ifcopenshell::geometry::PassthroughShape::num_faces() const {
return (int)build_mesh(parts_).triangles.size();
}
double ifcopenshell::geometry::PassthroughShape::bounding_box(void*& box_ptr) const {
auto* box = static_cast<Box*>(box_ptr);
if (!box) {
box = new Box();
box_ptr = box;
} else {
*box = Box();
}
update_box(*box, build_mesh(parts_));
return box_volume(*box);
}
std::pair<IfcGeom::OpaqueCoordinate<3>, IfcGeom::OpaqueCoordinate<3>> ifcopenshell::geometry::PassthroughShape::bounding_box() const {
void* box_ptr = nullptr;
bounding_box(box_ptr);
auto* box = static_cast<Box*>(box_ptr);
if (!box || !box->valid) {
delete box;
throw std::runtime_error("Invalid shape");
}
auto result = std::make_pair(
IfcGeom::OpaqueCoordinate<3>(
new IfcGeom::NumberNativeDouble(box->min(0)),
new IfcGeom::NumberNativeDouble(box->min(1)),
new IfcGeom::NumberNativeDouble(box->min(2))),
IfcGeom::OpaqueCoordinate<3>(
new IfcGeom::NumberNativeDouble(box->max(0)),
new IfcGeom::NumberNativeDouble(box->max(1)),
new IfcGeom::NumberNativeDouble(box->max(2))));
delete box;
return result;
}
void ifcopenshell::geometry::PassthroughShape::set_box(void* box_ptr) {
auto* box = static_cast<Box*>(box_ptr);
if (!box || !box->valid) {
throw std::runtime_error("Invalid shape");
}
parts_ = { { make_box_shell(box->min, box->max), taxonomy::make<taxonomy::matrix4>(), true } };
}
IfcGeom::OpaqueNumber* ifcopenshell::geometry::PassthroughShape::length() {
return new IfcGeom::NumberNativeDouble(mesh_length(build_mesh(parts_)));
}
IfcGeom::OpaqueNumber* ifcopenshell::geometry::PassthroughShape::area() {
return new IfcGeom::NumberNativeDouble(mesh_area(build_mesh(parts_)));
}
IfcGeom::OpaqueNumber* ifcopenshell::geometry::PassthroughShape::volume() {
return new IfcGeom::NumberNativeDouble(is_manifold() ? mesh_volume(build_mesh(parts_)) : 0.);
}
IfcGeom::OpaqueCoordinate<3> ifcopenshell::geometry::PassthroughShape::position() {
throw std::runtime_error("Invalid shape");
}
IfcGeom::OpaqueCoordinate<3> ifcopenshell::geometry::PassthroughShape::axis() {
throw std::runtime_error("Invalid shape");
}
IfcGeom::OpaqueCoordinate<4> ifcopenshell::geometry::PassthroughShape::plane_equation() {
throw std::runtime_error("Invalid shape");
}
std::vector<IfcGeom::ConversionResultShape*> ifcopenshell::geometry::PassthroughShape::convex_decomposition() {
throw std::runtime_error("Not implemented");
}
IfcGeom::ConversionResultShape* ifcopenshell::geometry::PassthroughShape::halfspaces() {
throw std::runtime_error("Not implemented");
}
IfcGeom::ConversionResultShape* ifcopenshell::geometry::PassthroughShape::box() {
void* box_ptr = nullptr;
bounding_box(box_ptr);
auto* box = static_cast<Box*>(box_ptr);
if (!box || !box->valid) {
delete box;
throw std::runtime_error("Invalid shape");
}
auto* result = new PassthroughShape(PassthroughPart{ make_box_shell(box->min, box->max), taxonomy::make<taxonomy::matrix4>(), true });
delete box;
return result;
}
IfcGeom::ConversionResultShape* ifcopenshell::geometry::PassthroughShape::solid() {
if (!is_manifold()) {
throw std::runtime_error("Invalid shape");
}
return new PassthroughShape(parts_);
}
IfcGeom::ConversionResultShape* ifcopenshell::geometry::PassthroughShape::wrap_in_compound() {
return new PassthroughShape(parts_);
}
std::vector<IfcGeom::ConversionResultShape*> ifcopenshell::geometry::PassthroughShape::vertices() {
throw std::runtime_error("Not implemented");
}
std::vector<IfcGeom::ConversionResultShape*> ifcopenshell::geometry::PassthroughShape::edges() {
throw std::runtime_error("Not implemented");
}
std::vector<IfcGeom::ConversionResultShape*> ifcopenshell::geometry::PassthroughShape::facets() {
throw std::runtime_error("Not implemented");
}
IfcGeom::ConversionResultShape* ifcopenshell::geometry::PassthroughShape::add(IfcGeom::ConversionResultShape*) {
throw std::runtime_error("Not implemented");
}
IfcGeom::ConversionResultShape* ifcopenshell::geometry::PassthroughShape::subtract(IfcGeom::ConversionResultShape*) {
throw std::runtime_error("Not implemented");
}
IfcGeom::ConversionResultShape* ifcopenshell::geometry::PassthroughShape::intersect(IfcGeom::ConversionResultShape*) {
throw std::runtime_error("Not implemented");
}
IfcGeom::ConversionResultShape* ifcopenshell::geometry::PassthroughShape::concat(IfcGeom::ConversionResultShape* other) {
auto* rhs = dynamic_cast<PassthroughShape*>(other);
if (!rhs) {
throw std::runtime_error("Invalid shape");
}
auto parts = parts_;
parts.insert(parts.end(), rhs->parts_.begin(), rhs->parts_.end());
return new PassthroughShape(std::move(parts));
}
void ifcopenshell::geometry::PassthroughShape::map(IfcGeom::OpaqueCoordinate<4>&, IfcGeom::OpaqueCoordinate<4>&) {
throw std::runtime_error("Not implemented");
}
void ifcopenshell::geometry::PassthroughShape::map(const std::vector<IfcGeom::OpaqueCoordinate<4>>&, const std::vector<IfcGeom::OpaqueCoordinate<4>>&) {
throw std::runtime_error("Not implemented");
}
IfcGeom::ConversionResultShape* ifcopenshell::geometry::PassthroughShape::moved(ifcopenshell::geometry::taxonomy::matrix4::ptr place) const {
std::vector<PassthroughPart> moved_parts;
moved_parts.reserve(parts_.size());
for (const auto& part : parts_) {
auto matrix = part.matrix ? taxonomy::make<taxonomy::matrix4>(place->ccomponents() * part.matrix->ccomponents()) : taxonomy::make<taxonomy::matrix4>(place->ccomponents());
moved_parts.push_back({ part.shell, matrix, part.manifold });
}
return new PassthroughShape(std::move(moved_parts));
}
bool ifcopenshell::geometry::PassthroughShape::surface_area_along_direction(double, const ifcopenshell::geometry::taxonomy::matrix4::ptr& place, double& along_x, double& along_y, double& along_z) const {
along_x = along_y = along_z = 0.;
auto mesh = build_mesh(parts_, place.get());
for (const auto& tri : mesh.triangles) {
const auto& a = mesh.vertices[tri[0]];
const auto& b = mesh.vertices[tri[1]];
const auto& c = mesh.vertices[tri[2]];
auto n = (b - a).cross(c - a);
auto norm = n.norm();
if (norm < 1.e-12) {
continue;
}
auto tri_area = 0.5 * norm;
n /= norm;
along_x += tri_area * std::abs(n(0));
along_y += tri_area * std::abs(n(1));
along_z += tri_area * std::abs(n(2));
}
return true;
}
@@ -0,0 +1,76 @@
#ifndef IFCGEOMPASSTHROUGHREPRESENTATION_H
#define IFCGEOMPASSTHROUGHREPRESENTATION_H
#include "../../../ifcgeom/ConversionResult.h"
#include "../../../ifcgeom/kernels/ifc_geomlibrary_api.h"
namespace ifcopenshell {
namespace geometry {
struct IFC_GEOMLIBRARY_API PassthroughPart {
taxonomy::shell::ptr shell;
taxonomy::matrix4::ptr matrix;
bool manifold;
};
class IFC_GEOMLIBRARY_API PassthroughShape : public IfcGeom::ConversionResultShape {
public:
PassthroughShape() = default;
explicit PassthroughShape(const PassthroughPart& part);
explicit PassthroughShape(PassthroughPart&& part);
explicit PassthroughShape(const std::vector<PassthroughPart>& parts);
explicit PassthroughShape(std::vector<PassthroughPart>&& parts);
const std::vector<PassthroughPart>& parts() const { return parts_; }
virtual void Triangulate(ifcopenshell::geometry::Settings settings, const ifcopenshell::geometry::taxonomy::matrix4& place, IfcGeom::Representation::Triangulation* t, int item_id, int surface_style_id) const;
virtual void Serialize(const ifcopenshell::geometry::taxonomy::matrix4& place, std::string&) const;
virtual int surface_genus() const;
virtual bool is_manifold() const;
virtual int num_vertices() const;
virtual int num_edges() const;
virtual int num_faces() const;
virtual double bounding_box(void*&) const;
virtual std::pair<IfcGeom::OpaqueCoordinate<3>, IfcGeom::OpaqueCoordinate<3>> bounding_box() const;
virtual void set_box(void* b);
virtual IfcGeom::OpaqueNumber* length();
virtual IfcGeom::OpaqueNumber* area();
virtual IfcGeom::OpaqueNumber* volume();
virtual IfcGeom::OpaqueCoordinate<3> position();
virtual IfcGeom::OpaqueCoordinate<3> axis();
virtual IfcGeom::OpaqueCoordinate<4> plane_equation();
virtual std::vector<IfcGeom::ConversionResultShape*> convex_decomposition();
virtual IfcGeom::ConversionResultShape* halfspaces();
virtual IfcGeom::ConversionResultShape* box();
virtual IfcGeom::ConversionResultShape* solid();
virtual IfcGeom::ConversionResultShape* wrap_in_compound();
virtual std::vector<IfcGeom::ConversionResultShape*> vertices();
virtual std::vector<IfcGeom::ConversionResultShape*> edges();
virtual std::vector<IfcGeom::ConversionResultShape*> facets();
virtual IfcGeom::ConversionResultShape* add(IfcGeom::ConversionResultShape*);
virtual IfcGeom::ConversionResultShape* subtract(IfcGeom::ConversionResultShape*);
virtual IfcGeom::ConversionResultShape* intersect(IfcGeom::ConversionResultShape*);
virtual IfcGeom::ConversionResultShape* concat(IfcGeom::ConversionResultShape*);
virtual void map(IfcGeom::OpaqueCoordinate<4>& from, IfcGeom::OpaqueCoordinate<4>& to);
virtual void map(const std::vector<IfcGeom::OpaqueCoordinate<4>>& from, const std::vector<IfcGeom::OpaqueCoordinate<4>>& to);
virtual IfcGeom::ConversionResultShape* moved(ifcopenshell::geometry::taxonomy::matrix4::ptr) const;
virtual bool surface_area_along_direction(double tol, const ifcopenshell::geometry::taxonomy::matrix4::ptr&, double& along_x, double& along_y, double& along_z) const;
private:
std::vector<PassthroughPart> parts_;
};
}
}
#endif
@@ -0,0 +1,303 @@
#include "PassthroughKernel.h"
#include <Eigen/Dense>
#include <algorithm>
#include <array>
#include <cmath>
#include <numeric>
#include <unordered_map>
using namespace ifcopenshell::geometry;
using namespace ifcopenshell::geometry::kernels;
namespace {
taxonomy::style::ptr fallback_style(const taxonomy::geom_item::ptr& item, const taxonomy::geom_item::ptr& fallback) {
if (item && item->surface_style) {
return item->surface_style;
}
if (fallback && fallback->surface_style) {
return fallback->surface_style;
}
return nullptr;
}
bool loop_points(const taxonomy::loop::ptr& loop, std::vector<Eigen::Vector3d>& points) {
points.clear();
if (!loop) {
return false;
}
points.reserve(loop->children.size());
for (const auto& edge : loop->children) {
if (edge->basis && edge->basis->kind() != taxonomy::LINE) {
return false;
}
if (edge->start.index() != 1 || edge->end.index() != 1) {
return false;
}
points.push_back(std::get<taxonomy::point3::ptr>(edge->start)->ccomponents());
}
return points.size() >= 3;
}
bool shell_supported(const taxonomy::shell::ptr& shell) {
if (!shell || shell->children.empty()) {
return false;
}
std::vector<Eigen::Vector3d> points;
for (const auto& face : shell->children) {
if (!face || face->children.size() != 1) {
return false;
}
const auto& loop = face->children.front();
if (!loop || loop->children.size() < 3 || loop->children.size() > 4) {
return false;
}
if (!loop_points(loop, points)) {
return false;
}
}
return true;
}
bool extrusion_supported_face(const taxonomy::face::ptr& face, std::vector<Eigen::Vector3d>& points) {
return face && face->children.size() == 1 && loop_points(face->children.front(), points);
}
bool polygon_basis(const std::vector<Eigen::Vector3d>& points, double precision, Eigen::Vector3d& origin, Eigen::Vector3d& x, Eigen::Vector3d& y, Eigen::Vector3d& normal, std::vector<Eigen::Vector2d>& projected) {
if (points.size() < 3) {
return false;
}
origin = points.front();
normal.setZero();
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() <= precision) {
return false;
}
normal.normalize();
x = Eigen::Vector3d::Zero();
for (size_t i = 1; i < points.size(); ++i) {
auto candidate = points[i] - origin;
auto planar = candidate - normal * normal.dot(candidate);
if (planar.norm() > precision) {
x = planar.normalized();
break;
}
}
if (x.squaredNorm() < 1.e-12) {
return false;
}
y = normal.cross(x).normalized();
projected.clear();
projected.reserve(points.size());
for (const auto& point : points) {
auto v = point - origin;
if (std::abs(normal.dot(v)) > precision) {
return false;
}
projected.push_back(Eigen::Vector2d(v.dot(x), v.dot(y)));
}
return true;
}
double signed_area(const std::vector<Eigen::Vector2d>& points) {
double area = 0.;
for (size_t i = 0; i < points.size(); ++i) {
const auto& a = points[i];
const auto& b = points[(i + 1) % points.size()];
area += a(0) * b(1) - a(1) * b(0);
}
return 0.5 * area;
}
double triangle_cross(const Eigen::Vector2d& a, const Eigen::Vector2d& b, const Eigen::Vector2d& c) {
return (b(0) - a(0)) * (c(1) - a(1)) - (b(1) - a(1)) * (c(0) - a(0));
}
bool point_in_triangle(const Eigen::Vector2d& p, const Eigen::Vector2d& a, const Eigen::Vector2d& b, const Eigen::Vector2d& c, double eps) {
auto c1 = triangle_cross(a, b, p);
auto c2 = triangle_cross(b, c, p);
auto c3 = triangle_cross(c, a, p);
auto has_neg = c1 < -eps || c2 < -eps || c3 < -eps;
auto has_pos = c1 > eps || c2 > eps || c3 > eps;
return !(has_neg && has_pos);
}
bool triangulate_polygon(const std::vector<Eigen::Vector2d>& polygon, double precision, std::vector<std::array<int, 3>>& triangles) {
triangles.clear();
if (polygon.size() < 3) {
return false;
}
std::vector<int> indices(polygon.size());
std::iota(indices.begin(), indices.end(), 0);
auto orientation = signed_area(polygon);
if (std::abs(orientation) <= precision * precision) {
return false;
}
auto is_convex = [&](int a, int b, int c) {
auto cross = triangle_cross(polygon[a], polygon[b], polygon[c]);
return orientation > 0. ? cross > precision : cross < -precision;
};
while (indices.size() > 3) {
bool clipped = false;
for (size_t i = 0; i < indices.size(); ++i) {
auto prev = indices[(i + indices.size() - 1) % indices.size()];
auto curr = indices[i];
auto next = indices[(i + 1) % indices.size()];
if (!is_convex(prev, curr, next)) {
continue;
}
bool contains = false;
for (auto idx : indices) {
if (idx == prev || idx == curr || idx == next) {
continue;
}
if (point_in_triangle(polygon[idx], polygon[prev], polygon[curr], polygon[next], precision)) {
contains = true;
break;
}
}
if (contains) {
continue;
}
triangles.push_back({ prev, curr, next });
indices.erase(indices.begin() + (ptrdiff_t)i);
clipped = true;
break;
}
if (!clipped) {
return false;
}
}
triangles.push_back({ indices[0], indices[1], indices[2] });
return true;
}
taxonomy::face::ptr make_face(const std::vector<Eigen::Vector3d>& points) {
auto face = taxonomy::make<taxonomy::face>();
auto loop = taxonomy::make<taxonomy::loop>();
loop->external = true;
loop->closed = true;
std::vector<taxonomy::point3::ptr> vertices;
vertices.reserve(points.size());
for (const auto& point : points) {
vertices.push_back(taxonomy::make<taxonomy::point3>(point));
}
for (size_t i = 0; i < vertices.size(); ++i) {
loop->children.push_back(taxonomy::make<taxonomy::edge>(vertices[i], vertices[(i + 1) % vertices.size()]));
}
face->children.push_back(loop);
return face;
}
taxonomy::shell::ptr shell_from_extrusion(const taxonomy::extrusion::ptr& extrusion, double precision) {
if (!extrusion || extrusion->depth <= precision) {
return nullptr;
}
auto face = taxonomy::dcast<taxonomy::face>(extrusion->basis);
std::vector<Eigen::Vector3d> base_points;
if (!extrusion_supported_face(face, base_points)) {
return nullptr;
}
Eigen::Vector3d origin;
Eigen::Vector3d x;
Eigen::Vector3d y;
Eigen::Vector3d normal;
std::vector<Eigen::Vector2d> projected;
if (!polygon_basis(base_points, precision, origin, x, y, normal, projected)) {
return nullptr;
}
auto direction = extrusion->direction ? extrusion->direction->ccomponents() : Eigen::Vector3d::Zero();
if (direction.norm() <= precision) {
return nullptr;
}
direction.normalize();
if (std::abs(normal.dot(direction)) <= precision) {
return nullptr;
}
std::vector<std::array<int, 3>> cap_triangles;
if (!triangulate_polygon(projected, precision, cap_triangles)) {
return nullptr;
}
auto offset = direction * extrusion->depth;
auto shell = taxonomy::make<taxonomy::shell>();
shell->instance = extrusion->instance;
shell->closed = true;
shell->surface_style = extrusion->surface_style;
auto aligned = normal.dot(direction) > 0.;
for (const auto& tri : cap_triangles) {
if (aligned) {
shell->children.push_back(make_face({ base_points[tri[2]], base_points[tri[1]], base_points[tri[0]] }));
shell->children.push_back(make_face({ base_points[tri[0]] + offset, base_points[tri[1]] + offset, base_points[tri[2]] + offset }));
} else {
shell->children.push_back(make_face({ base_points[tri[0]], base_points[tri[1]], base_points[tri[2]] }));
shell->children.push_back(make_face({ base_points[tri[2]] + offset, base_points[tri[1]] + offset, base_points[tri[0]] + offset }));
}
}
for (size_t i = 0; i < base_points.size(); ++i) {
auto j = (i + 1) % base_points.size();
if (aligned) {
shell->children.push_back(make_face({ base_points[i], base_points[j], base_points[j] + offset, base_points[i] + offset }));
} else {
shell->children.push_back(make_face({ base_points[i], base_points[i] + offset, base_points[j] + offset, base_points[j] }));
}
}
return shell;
}
}
bool PassthroughKernel::convert_impl(const taxonomy::shell::ptr shell, IfcGeom::ConversionResults& results) {
if (!shell_supported(shell)) {
return false;
}
results.emplace_back(IfcGeom::ConversionResult(
shell->instance.id(),
shell->matrix,
new ifcopenshell::geometry::PassthroughShape(PassthroughPart{ shell, taxonomy::make<taxonomy::matrix4>(), shell->closed.value_or(false) }),
shell->surface_style));
return true;
}
bool PassthroughKernel::convert_impl(const taxonomy::solid::ptr solid, IfcGeom::ConversionResults& results) {
if (!solid || solid->children.size() != 1) {
return false;
}
auto shell = solid->children.front();
if (!shell_supported(shell)) {
return false;
}
results.emplace_back(IfcGeom::ConversionResult(
solid->instance.id(),
solid->matrix,
new ifcopenshell::geometry::PassthroughShape(PassthroughPart{
shell,
shell->matrix ? taxonomy::make<taxonomy::matrix4>(shell->matrix->ccomponents()) : taxonomy::make<taxonomy::matrix4>(),
true
}),
fallback_style(solid, shell)));
return true;
}
bool PassthroughKernel::convert_impl(const taxonomy::extrusion::ptr extrusion, IfcGeom::ConversionResults& results) {
auto shell = shell_from_extrusion(extrusion, settings_.get<settings::Precision>().get());
if (!shell) {
return false;
}
results.emplace_back(IfcGeom::ConversionResult(
extrusion->instance.id(),
extrusion->matrix,
new ifcopenshell::geometry::PassthroughShape(PassthroughPart{ shell, taxonomy::make<taxonomy::matrix4>(), true }),
extrusion->surface_style));
return true;
}
bool PassthroughKernel::convert_openings(const express::Base&, const std::vector<std::pair<taxonomy::ptr, ifcopenshell::geometry::taxonomy::matrix4>>&,
const IfcGeom::ConversionResults&, const ifcopenshell::geometry::taxonomy::matrix4&, IfcGeom::ConversionResults&) {
return false;
}
@@ -0,0 +1,35 @@
#ifndef PASSTHROUGH_KERNEL_H
#define PASSTHROUGH_KERNEL_H
#include "../../../ifcgeom/AbstractKernel.h"
#include "../../../ifcgeom/kernels/ifc_geomlibrary_api.h"
#include "../../../ifcgeom/kernels/passthrough/PassthroughConversionResult.h"
namespace ifcopenshell {
namespace geometry {
namespace kernels {
class IFC_GEOMLIBRARY_API PassthroughKernel : public AbstractKernel {
public:
PassthroughKernel(const Settings& settings)
: AbstractKernel("passthrough", settings) {}
virtual AbstractKernel* clone() const {
return new PassthroughKernel(settings());
}
virtual bool supports_boolean_operations() const { return false; }
virtual bool convert_impl(const taxonomy::shell::ptr, IfcGeom::ConversionResults&);
virtual bool convert_impl(const taxonomy::solid::ptr, IfcGeom::ConversionResults&);
virtual bool convert_impl(const taxonomy::extrusion::ptr, IfcGeom::ConversionResults&);
virtual bool convert_openings(const express::Base&, const std::vector<std::pair<taxonomy::ptr, ifcopenshell::geometry::taxonomy::matrix4>>&,
const IfcGeom::ConversionResults&, const ifcopenshell::geometry::taxonomy::matrix4&, IfcGeom::ConversionResults&);
};
}
}
}
#endif