mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-10 09:48:32 +00:00
Add passthrough kernel
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user