From b5b73fd079e4a6ca8d2977c9fa46d3fc066ea3de Mon Sep 17 00:00:00 2001 From: Thomas Krijnen Date: Fri, 11 Nov 2022 13:20:10 +0100 Subject: [PATCH] Work on modularizing files --- cmake/CMakeLists.txt | 2 +- src/ifcgeom/abstract_mapping.cpp | 29 +- src/ifcgeom/abstract_mapping.h | 3 +- src/ifcgeom/exceptions.h | 29 + src/ifcgeom/kernels/cgal/CgalKernel.cpp | 25 +- .../kernels/opencascade/IfcGeomShapes.cpp | 832 ------------ .../opencascade/OpenCascadeConversionResult.h | 4 +- .../kernels/opencascade/OpenCascadeKernel.h | 30 +- src/ifcgeom/kernels/opencascade/extrusion.cpp | 85 ++ src/ifcgeom/kernels/opencascade/face.cpp | 298 +++++ .../kernels/opencascade/face_definition.cpp | 34 + .../kernels/opencascade/face_definition.h | 83 ++ src/ifcgeom/kernels/opencascade/loop.cpp | 186 +++ src/ifcgeom/kernels/opencascade/matrix4.cpp | 44 + src/ifcgeom/kernels/opencascade/shell.cpp | 18 + .../kernels/opencascade/wire_builder.cpp | 226 ++++ .../kernels/opencascade/wire_builder.h | 105 ++ src/ifcgeom/profile_helper.cpp | 122 ++ src/ifcgeom/profile_helper.h | 24 + .../schema/IfcArbitraryClosedProfileDef.cpp | 26 + src/ifcgeom/schema/IfcAxis2Placement2D.cpp | 17 + src/ifcgeom/schema/IfcAxis2Placement3D.cpp | 37 + src/ifcgeom/schema/IfcBooleanResult.cpp | 59 + src/ifcgeom/schema/IfcCartesianPoint.cpp | 12 + .../IfcCartesianTransformationOperator2D.cpp | 42 + .../IfcCartesianTransformationOperator3D.cpp | 54 + src/ifcgeom/schema/IfcCircle.cpp | 10 + src/ifcgeom/schema/IfcCircleProfileDef.cpp | 48 + src/ifcgeom/schema/IfcCompositeCurve.cpp | 30 + src/ifcgeom/schema/IfcConnectedFaceSet.cpp | 12 + src/ifcgeom/schema/IfcDirection.cpp | 12 + src/ifcgeom/schema/IfcExtrudedAreaSolid.cpp | 12 + src/ifcgeom/schema/IfcFace.cpp | 31 + .../schema/IfcFaceBasedSurfaceModel.cpp | 7 + src/ifcgeom/schema/IfcGeometricSet.cpp | 7 + src/ifcgeom/schema/IfcHalfSpaceSolid.cpp | 17 + src/ifcgeom/schema/IfcIShapeProfileDef.cpp | 67 + src/ifcgeom/schema/IfcLocalPlacement.cpp | 37 + src/ifcgeom/schema/IfcManifoldSolidBrep.cpp | 8 + src/ifcgeom/schema/IfcMappedItem.cpp | 32 + src/ifcgeom/schema/IfcPolyLoop.cpp | 37 + .../schema/IfcPolygonalBoundedHalfSpace.cpp | 10 + src/ifcgeom/schema/IfcPolyline.cpp | 36 + src/ifcgeom/schema/IfcProduct.cpp | 63 + .../schema/IfcRectangleHollowProfileDef.cpp | 63 + src/ifcgeom/schema/IfcRectangleProfileDef.cpp | 39 + src/ifcgeom/schema/IfcRepresentation.cpp | 26 + .../schema/IfcShellBasedSurfaceModel.cpp | 7 + src/ifcgeom/schema/IfcTrimmedCurve.cpp | 112 ++ src/ifcgeom/schema/mapping.cpp | 1172 ----------------- src/ifcgeom/schema/mapping.h | 101 ++ src/ifcgeom/schema/mapping.i | 64 +- src/ifcgeom/schema_agnostic/Converter.cpp | 7 +- src/ifcgeom/schema_agnostic/Converter.h | 1 + src/ifcgeom/taxonomy.cpp | 14 +- src/ifcgeom/taxonomy.h | 87 +- src/serializers/XmlSerializer.cpp | 8 +- 57 files changed, 2507 insertions(+), 2096 deletions(-) create mode 100644 src/ifcgeom/exceptions.h create mode 100644 src/ifcgeom/kernels/opencascade/extrusion.cpp create mode 100644 src/ifcgeom/kernels/opencascade/face.cpp create mode 100644 src/ifcgeom/kernels/opencascade/face_definition.cpp create mode 100644 src/ifcgeom/kernels/opencascade/face_definition.h create mode 100644 src/ifcgeom/kernels/opencascade/loop.cpp create mode 100644 src/ifcgeom/kernels/opencascade/matrix4.cpp create mode 100644 src/ifcgeom/kernels/opencascade/shell.cpp create mode 100644 src/ifcgeom/kernels/opencascade/wire_builder.cpp create mode 100644 src/ifcgeom/kernels/opencascade/wire_builder.h create mode 100644 src/ifcgeom/profile_helper.cpp create mode 100644 src/ifcgeom/profile_helper.h create mode 100644 src/ifcgeom/schema/IfcArbitraryClosedProfileDef.cpp create mode 100644 src/ifcgeom/schema/IfcAxis2Placement2D.cpp create mode 100644 src/ifcgeom/schema/IfcAxis2Placement3D.cpp create mode 100644 src/ifcgeom/schema/IfcBooleanResult.cpp create mode 100644 src/ifcgeom/schema/IfcCartesianPoint.cpp create mode 100644 src/ifcgeom/schema/IfcCartesianTransformationOperator2D.cpp create mode 100644 src/ifcgeom/schema/IfcCartesianTransformationOperator3D.cpp create mode 100644 src/ifcgeom/schema/IfcCircle.cpp create mode 100644 src/ifcgeom/schema/IfcCircleProfileDef.cpp create mode 100644 src/ifcgeom/schema/IfcCompositeCurve.cpp create mode 100644 src/ifcgeom/schema/IfcConnectedFaceSet.cpp create mode 100644 src/ifcgeom/schema/IfcDirection.cpp create mode 100644 src/ifcgeom/schema/IfcExtrudedAreaSolid.cpp create mode 100644 src/ifcgeom/schema/IfcFace.cpp create mode 100644 src/ifcgeom/schema/IfcFaceBasedSurfaceModel.cpp create mode 100644 src/ifcgeom/schema/IfcGeometricSet.cpp create mode 100644 src/ifcgeom/schema/IfcHalfSpaceSolid.cpp create mode 100644 src/ifcgeom/schema/IfcIShapeProfileDef.cpp create mode 100644 src/ifcgeom/schema/IfcLocalPlacement.cpp create mode 100644 src/ifcgeom/schema/IfcManifoldSolidBrep.cpp create mode 100644 src/ifcgeom/schema/IfcMappedItem.cpp create mode 100644 src/ifcgeom/schema/IfcPolyLoop.cpp create mode 100644 src/ifcgeom/schema/IfcPolygonalBoundedHalfSpace.cpp create mode 100644 src/ifcgeom/schema/IfcPolyline.cpp create mode 100644 src/ifcgeom/schema/IfcProduct.cpp create mode 100644 src/ifcgeom/schema/IfcRectangleHollowProfileDef.cpp create mode 100644 src/ifcgeom/schema/IfcRectangleProfileDef.cpp create mode 100644 src/ifcgeom/schema/IfcRepresentation.cpp create mode 100644 src/ifcgeom/schema/IfcShellBasedSurfaceModel.cpp create mode 100644 src/ifcgeom/schema/IfcTrimmedCurve.cpp diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt index 3f3acbbf4c..27cf76db55 100644 --- a/cmake/CMakeLists.txt +++ b/cmake/CMakeLists.txt @@ -543,7 +543,7 @@ function(files_for_ifc_version IFC_VERSION RESULT_NAME) ) endfunction() -set(SCHEMA_VERSIONS "2x3" "4" "4x1" "4x2") +set(SCHEMA_VERSIONS "2x3" "4") # "4x1" "4x2") if(COMPILE_SCHEMA) # @todo, this appears to be untested at the moment diff --git a/src/ifcgeom/abstract_mapping.cpp b/src/ifcgeom/abstract_mapping.cpp index 215b9d5caf..aa30203edc 100644 --- a/src/ifcgeom/abstract_mapping.cpp +++ b/src/ifcgeom/abstract_mapping.cpp @@ -9,14 +9,14 @@ ifcopenshell::geometry::impl::MappingFactoryImplementation& ifcopenshell::geomet extern void init_MappingImplementation_Ifc2x3(ifcopenshell::geometry::impl::MappingFactoryImplementation*); extern void init_MappingImplementation_Ifc4(ifcopenshell::geometry::impl::MappingFactoryImplementation*); -extern void init_MappingImplementation_Ifc4x1(ifcopenshell::geometry::impl::MappingFactoryImplementation*); -extern void init_MappingImplementation_Ifc4x2(ifcopenshell::geometry::impl::MappingFactoryImplementation*); +// extern void init_MappingImplementation_Ifc4x1(ifcopenshell::geometry::impl::MappingFactoryImplementation*); +// extern void init_MappingImplementation_Ifc4x2(ifcopenshell::geometry::impl::MappingFactoryImplementation*); ifcopenshell::geometry::impl::MappingFactoryImplementation::MappingFactoryImplementation() { init_MappingImplementation_Ifc2x3(this); init_MappingImplementation_Ifc4(this); - init_MappingImplementation_Ifc4x1(this); - init_MappingImplementation_Ifc4x2(this); + // init_MappingImplementation_Ifc4x1(this); + // init_MappingImplementation_Ifc4x2(this); } void ifcopenshell::geometry::impl::MappingFactoryImplementation::bind(const std::string& schema_name, ifcopenshell::geometry::impl::mapping_fn fn) { @@ -33,3 +33,24 @@ ifcopenshell::geometry::abstract_mapping* ifcopenshell::geometry::impl::MappingF } return it->second(file, s); } + +void ifcopenshell::geometry::remove_duplicate_points_from_loop(std::vector& polygon, bool closed, double tol) { + for (;;) { + bool removed = false; + int n = polygon.size() - (closed ? 0 : 1); + for (int i = 1; i <= n; ++i) { + // wrap around to the first point in case of a closed loop + int j = (i % polygon.size()) + 1; + double dist = (polygon.at(i - 1).components() - polygon.at(j - 1).components()).squaredNorm(); + if (dist < tol) { + // do not remove the first or last point to + // maintain connectivity with other wires + if ((closed && j == 1) || (!closed && j == n)) polygon.erase(polygon.begin() + i - 1); + else polygon.erase(polygon.begin() + j - 1); + removed = true; + break; + } + } + if (!removed) break; + } +} \ No newline at end of file diff --git a/src/ifcgeom/abstract_mapping.h b/src/ifcgeom/abstract_mapping.h index 9a39fb55d0..3d7d10c140 100644 --- a/src/ifcgeom/abstract_mapping.h +++ b/src/ifcgeom/abstract_mapping.h @@ -47,7 +47,8 @@ namespace geometry { MappingFactoryImplementation& mapping_implementations(); } - + + void remove_duplicate_points_from_loop(std::vector& polygon, bool closed, double tol); } } diff --git a/src/ifcgeom/exceptions.h b/src/ifcgeom/exceptions.h new file mode 100644 index 0000000000..37e3e3b878 --- /dev/null +++ b/src/ifcgeom/exceptions.h @@ -0,0 +1,29 @@ +#pragma once + +#include +#include + +#include "schema_agnostic/ifc_geom_api.h" + +namespace ifcopenshell { + namespace geometry { + + class IFC_GEOM_API geometry_exception : public std::exception { + protected: + std::string message; + public: + geometry_exception(const std::string& m) + : message(m) {} + virtual ~geometry_exception() throw () {} + virtual const char* what() const throw() { + return message.c_str(); + } + }; + + class IFC_GEOM_API too_many_faces_exception : public geometry_exception { + public: + too_many_faces_exception() + : geometry_exception("Too many faces for operation") {} + }; + } +} diff --git a/src/ifcgeom/kernels/cgal/CgalKernel.cpp b/src/ifcgeom/kernels/cgal/CgalKernel.cpp index 73581f0082..53debb9ee0 100644 --- a/src/ifcgeom/kernels/cgal/CgalKernel.cpp +++ b/src/ifcgeom/kernels/cgal/CgalKernel.cpp @@ -137,29 +137,6 @@ CGAL::Nef_polyhedron_3 ifcopenshell::geometry::utils::create_nef_polyhe } } -namespace { - template - void visit(const taxonomy::collection* c, const Fn& fn) { - static_assert(std::is_same::value, "@todo Only implemented for point3"); - for (auto& i : c->children) { - if (dynamic_cast(i)) { - visit(dynamic_cast(i), fn); - } else if (i->kind() == taxonomy::POINT3) { - fn((const taxonomy::point3*) i); - } else if (i->kind() == taxonomy::EDGE) { - // @todo maybe make edge a collection then as well? - auto l = (const taxonomy::edge *) i; - if (l->start.which() == 0) { - fn(&boost::get(l->start)); - } - if (l->end.which() == 0) { - fn(&boost::get(l->end)); - } - } - } - } -} - bool CgalKernel::convert(const taxonomy::shell* l, cgal_shape_t& shape) { auto faces = l->children_as(); @@ -170,7 +147,7 @@ bool CgalKernel::convert(const taxonomy::shell* l, cgal_shape_t& shape) { Eigen::Vector3d(-inf, -inf, -inf) ); size_t num_points = 0; - visit(l, [&minmax, &num_points](const taxonomy::point3* p) { + visit_2(l, [&minmax, &num_points](const taxonomy::point3* p) { auto& c = p->ccomponents(); ++num_points; for (int i = 0; i < 3; ++i) { diff --git a/src/ifcgeom/kernels/opencascade/IfcGeomShapes.cpp b/src/ifcgeom/kernels/opencascade/IfcGeomShapes.cpp index 02aee40e49..379cd4e67c 100644 --- a/src/ifcgeom/kernels/opencascade/IfcGeomShapes.cpp +++ b/src/ifcgeom/kernels/opencascade/IfcGeomShapes.cpp @@ -111,398 +111,13 @@ using namespace ifcopenshell::geometry; using namespace ifcopenshell::geometry::kernels; -bool OpenCascadeKernel::convert(const taxonomy::extrusion* extrusion, TopoDS_Shape& shape) { - const double& height = extrusion->depth; - - if (height < precision_) { - Logger::Error("Non-positive extrusion height encountered for:", extrusion->instance); - return false; - } - - TopoDS_Shape face; - if (!convert(&extrusion->basis, face)) { - return false; - } - - /* - // @todo we need to decide whether the matrix is kept on the taxonomy node or - // move the TopoDS_Shape, but obviously not both. - gp_GTrsf gtrsf; - if (!convert(&extrusion->matrix, gtrsf)) { - Logger::Error("Unable to move extrusion"); - } - auto trsf = gtrsf.Trsf(); - */ - - const auto& fs = extrusion->direction.ccomponents(); - gp_Dir dir(fs(0), fs(1), fs(2)); - - shape.Nullify(); - - if (face.ShapeType() == TopAbs_COMPOUND) { - - // For compounds (most likely the result of a IfcCompositeProfileDef) - // create a compound solid shape. - - TopExp_Explorer exp(face, TopAbs_FACE); - - TopoDS_CompSolid compound; - BRep_Builder builder; - builder.MakeCompSolid(compound); - - int num_faces_extruded = 0; - for (; exp.More(); exp.Next(), ++num_faces_extruded) { - builder.Add(compound, BRepPrimAPI_MakePrism(exp.Current(), height*dir)); - } - - if (num_faces_extruded) { - shape = compound; - } - - } - - if (shape.IsNull()) { - shape = BRepPrimAPI_MakePrism(face, height*dir); - } - - /* - if (!shape.IsNull()) { - // IfcSweptAreaSolid.Position (trsf) is an IfcAxis2Placement3D - // and therefore has a unit scale factor - shape.Move(trsf); - } - */ - - return !shape.IsNull(); -} - -namespace { - /* Returns whether wire conforms to a polyhedron, i.e. only edges with linear curves*/ - bool is_polyhedron(const TopoDS_Wire& wire) { - double a, b; - TopLoc_Location l; - - TopoDS_Iterator it(wire, false, false); - for (; it.More(); it.Next()) { - auto crv = BRep_Tool::Curve(TopoDS::Edge(it.Value()), l, a, b); - if (!crv || crv->DynamicType() != STANDARD_TYPE(Geom_Line)) { - return false; - } - } - - return true; - } - - /* Returns whether wire conforms to a polyhedron, i.e. only edges with linear curves*/ - bool is_polyhedron(const taxonomy::loop* wire) { - for (auto& edge : wire->children_as()) { - if (edge->basis) { - if (edge->basis->kind() != taxonomy::LINE) { - return false; - } - } - } - return true; - } - - /* A temporary structure to store the intermediate data for the face conversion */ - class face_definition { - private: - Handle(Geom_Surface) surface_; - std::vector wires_; - bool all_outer_; - public: - face_definition() : surface_(), all_outer_(false) {} - - typedef std::vector::const_iterator wire_it; - - bool& all_outer() { - return all_outer_; - } - - bool all_outer() const { - return all_outer_; - } - - Handle(Geom_Surface)& surface() { - return surface_; - } - - const Handle(Geom_Surface)& surface() const { - return surface_; - } - - std::vector& wires() { - return wires_; - } - - const TopoDS_Wire& outer_wire() const { - return wires_.front(); - } - - std::pair inner_wires() const { - return { wires_.begin() + 1, wires_.end() }; - } - }; -} #include #include #include #include -bool OpenCascadeKernel::convert(const taxonomy::face* face, TopoDS_Shape& result) { - auto bounds = face->children_as(); - - face_definition fd; - const bool is_face_surface = false; /* todo */ - - /* - if (is_face_surface) { - IfcSchema::IfcFaceSurface* fs = (IfcSchema::IfcFaceSurface*) l; - fs->FaceSurface(); - // FIXME: Surfaces are interpreted as a TopoDS_Shape - TopoDS_Shape surface_shape; - if (!convert_shape(fs->FaceSurface(), surface_shape)) return false; - - // FIXME: Assert this obtaines the only face - TopExp_Explorer exp(surface_shape, TopAbs_FACE); - if (!exp.More()) return false; - - TopoDS_Face surface = TopoDS::Face(exp.Current()); - fd.surface() = BRep_Tool::Surface(surface); - } - */ - - const int num_bounds = bounds.size(); - int num_outer_bounds = 0; - - for (auto& bound: bounds) { - if (bound->external.get_value_or(false)) { - num_outer_bounds++; - } - } - - // The number of outer bounds should be one according to the schema. Also Open Cascade - // expects this, but it is not strictly checked. Regardless, if the number is greater, - // the face will still be processed as long as there are no holes. A compound of faces - // is returned in that case. - if (num_bounds > 1 && num_outer_bounds > 1 && num_bounds != num_outer_bounds) { - Logger::Message(Logger::LOG_ERROR, "Invalid configuration of boundaries for:", face->instance); - return false; - } - - if (num_outer_bounds > 1) { - Logger::Message(Logger::LOG_WARNING, "Multiple outer boundaries for:", face->instance); - fd.all_outer() = true; - } - - TopTools_DataMapOfShapeInteger wire_senses; - - for (int process_interior = 0; process_interior <= 1; ++process_interior) { - for (auto& bound : bounds) { - bool same_sense = true; /* todo bound->Orientation(); */ - - const bool is_interior = - !bound->external.get_value_or(false) && - (num_bounds > 1) && - (num_outer_bounds < num_bounds); - - // The exterior face boundary is processed first - if (is_interior == !process_interior) continue; - - TopoDS_Wire wire; - if (faceset_helper_ && is_polyhedron(bound)) { - if (!faceset_helper_->wire(bound, wire)) { - Logger::Message(Logger::LOG_WARNING, "Face boundary loop not included", bound->instance); - continue; - } - } else if (!convert(bound, wire)) { - Logger::Message(Logger::LOG_ERROR, "Failed to process face boundary loop", bound->instance); - return false; - } - - if (!same_sense) { - wire.Reverse(); - } - - wire_senses.Bind(wire.Oriented(TopAbs_FORWARD), same_sense ? TopAbs_FORWARD : TopAbs_REVERSED); - - fd.wires().emplace_back(wire); - } - } - - if (fd.wires().empty()) { - Logger::Warning("Face with no boundaries", face->instance); - return false; - } - - if (fd.surface().IsNull()) { - // Use the first wire to find a plane manually for polygonal wires - const TopoDS_Wire& wire = fd.wires().front(); - if (is_polyhedron(wire)) { - TopExp_Explorer exp(wire, TopAbs_EDGE); - int count = 0; - TopoDS_Edge edges[2]; - for (; exp.More(); exp.Next(), count++) { - if (count < 2) { - edges[count] = TopoDS::Edge(exp.Current()); - } - } - - if (count == 3) { - // Help Open Cascade by finding the plane more efficiently - double _, __; - Handle(Geom_Line) c1 = Handle(Geom_Line)::DownCast(BRep_Tool::Curve(edges[0], _, __)); - Handle(Geom_Line) c2 = Handle(Geom_Line)::DownCast(BRep_Tool::Curve(edges[1], _, __)); - - const gp_Vec ab = c1->Position().Direction(); - const gp_Vec ac = c2->Position().Direction(); - const gp_Vec cross = ab.Crossed(ac); - - if (cross.SquareMagnitude() > ALMOST_ZERO) { - const gp_Dir n = cross; - fd.surface() = new Geom_Plane(c1->Position().Location(), n); - } - } else { - gp_Pln pln; - if (approximate_plane_through_wire(wire, pln)) { - fd.surface() = new Geom_Plane(pln); - } - } - } - } - - if (fd.surface().IsNull()) { - // BRepLib_FindSurface is used in case no surface is found or provided - - const TopoDS_Wire& wire = fd.wires().front(); - - BRepLib_FindSurface fs(wire, precision_, true, true); - if (fs.Found()) { - fd.surface() = fs.Surface(); - ShapeFix_ShapeTolerance ftol; - ftol.SetTolerance(wire, fs.ToleranceReached(), TopAbs_WIRE); - } - } - - TopTools_ListOfShape face_list; - - if (fd.surface().IsNull()) { - // The set of wires is triangulated in case no surface can be found - Logger::Message(Logger::LOG_WARNING, "Triangulating face boundaries for face", face->instance); - - if (fd.all_outer()) { - for (const auto& w : fd.wires()) { - TopTools_ListOfShape fl; - triangulate_wire({ w }, fl); - face_list.Append(fl); - } - } else { - triangulate_wire(fd.wires(), face_list); - } - } else if (!fd.all_outer()) { - BRepBuilderAPI_MakeFace mf(fd.surface(), fd.outer_wire()); - - if (mf.IsDone()) { - // Is this necessary - TopoDS_Face f = mf.Face(); - mf.Init(f); - - for (auto it = fd.inner_wires().first; it != fd.inner_wires().second; ++it) { - mf.Add(*it); - } - - face_list.Append(mf.Face()); - } - } else { - for (const auto& w : fd.wires()) { - BRepBuilderAPI_MakeFace mf(fd.surface(), w); - if (mf.IsDone()) { - face_list.Append(mf.Face()); - } - } - } - - if (!fd.surface().IsNull()) { - // Some fixes for orientation and p-curves. If we have no surface, it - // means the face has been triangulated in which case none of these - // fixes are necessary. - - if (fd.surface()->DynamicType() != STANDARD_TYPE(Geom_Plane)) { - // In case of (non-planar) face surface, p-curves need to be computed. - // For planar faces, Open Cascade generates p-curves on the fly. - - for (TopTools_ListIteratorOfListOfShape it(face_list); it.More(); it.Next()) { - // Small chance there are multiple faces - const TopoDS_Face& occ_face = TopoDS::Face(it.Value()); - for (TopExp_Explorer exp2(occ_face, TopAbs_EDGE); exp2.More(); exp2.Next()) { - const TopoDS_Edge& edge = TopoDS::Edge(exp2.Current()); - ShapeFix_Edge fix_edge; - fix_edge.FixAddPCurve(edge, occ_face, false, precision_); - } - } - } - - for (TopTools_ListIteratorOfListOfShape it(face_list); it.More(); it.Next()) { - const TopoDS_Face& occ_face = TopoDS::Face(it.Value()); - - ShapeFix_Face sfs(TopoDS::Face(occ_face)); - TopTools_DataMapOfShapeListOfShape wire_map; - sfs.FixOrientation(wire_map); - - TopoDS_Iterator jt(occ_face, false); - for (; jt.More(); jt.Next()) { - const TopoDS_Wire& w = TopoDS::Wire(jt.Value()); - // tfk: @todo if wire_map contains w, I would assume wire_senses also contains w, - // this is not the case in github issue #405. - if (wire_map.IsBound(w) && wire_senses.IsBound(w)) { - const TopTools_ListOfShape& shapes = wire_map.Find(w); - TopTools_ListIteratorOfListOfShape kt(shapes); - for (; kt.More(); kt.Next()) { - // Apparently the wire got reversed, so register it with opposite orientation in the map - wire_senses.Bind(kt.Value(), wire_senses.Find(w) == TopAbs_FORWARD ? TopAbs_REVERSED : TopAbs_FORWARD); - } - } - } - - it.Value() = sfs.Face(); - } - - for (TopTools_ListIteratorOfListOfShape it(face_list); it.More(); it.Next()) { - TopoDS_Face& occ_face = TopoDS::Face(it.Value()); - - bool all_reversed = true; - TopoDS_Iterator jt(occ_face, false); - for (; jt.More(); jt.Next()) { - const TopoDS_Wire& w = TopoDS::Wire(jt.Value()); - if (!wire_senses.IsBound(w.Oriented(TopAbs_FORWARD)) || (w.Orientation() == wire_senses.Find(w.Oriented(TopAbs_FORWARD)))) { - all_reversed = false; - } - } - - if (all_reversed) { - occ_face.Reverse(); - } - } - } - - if (face_list.Extent() > 1) { - TopoDS_Compound compound; - BRep_Builder builder; - builder.MakeCompound(compound); - for (TopTools_ListIteratorOfListOfShape it(face_list); it.More(); it.Next()) { - TopoDS_Face& occ_face = TopoDS::Face(it.Value()); - builder.Add(compound, occ_face); - } - result = compound; - } else { - result = face_list.First(); - } - - return true; -} #include #include @@ -511,458 +126,11 @@ bool OpenCascadeKernel::convert(const taxonomy::face* face, TopoDS_Shape& result #include #include -namespace { - template - T convert_xyz(const U& u) { - const auto& vs = u.ccomponents(); - return T(vs(0), vs(1), vs(2)); - } - - // @todo eliminate - template - T convert_xyz2(const U& vs) { - return T(vs(0), vs(1), vs(2)); - } - - typedef boost::variant curve_creation_visitor_result_type; - curve_creation_visitor_result_type convert_curve(OpenCascadeKernel* kernel, const taxonomy::item* curve); - - struct curve_creation_visitor { - OpenCascadeKernel* kernel; - curve_creation_visitor_result_type result; - - curve_creation_visitor_result_type operator()(const taxonomy::bspline_curve&) { - throw std::runtime_error("Not implemented"); - } - - curve_creation_visitor_result_type operator()(const taxonomy::line& l) { - const auto& m = l.matrix.ccomponents(); - return result = Handle(Geom_Curve)(new Geom_Line(convert_xyz2(m.col(3)), convert_xyz2(m.col(0)))); - } - - curve_creation_visitor_result_type operator()(const taxonomy::circle& c) { - const auto& m = c.matrix.ccomponents(); - return result = Handle(Geom_Curve)(new Geom_Circle(gp_Ax2(convert_xyz2(m.col(3)), convert_xyz2(m.col(2)), convert_xyz2(m.col(0))), c.radius)); - } - - curve_creation_visitor_result_type operator()(const taxonomy::ellipse& e) { - const auto& m = e.matrix.ccomponents(); - return result = Handle(Geom_Curve)(new Geom_Ellipse(gp_Ax2(convert_xyz2(m.col(3)), convert_xyz2(m.col(2)), convert_xyz2(m.col(0))), e.radius, e.radius2)); - } - - curve_creation_visitor_result_type operator()(const taxonomy::loop& l) { - TopoDS_Wire wire; - kernel->convert(&l, wire); - return result = wire; - } - - curve_creation_visitor_result_type operator()(const taxonomy::edge& e) { - // @todo for polyloops/-lines we should probably construct edges based on correct oriented TopoDS_Vertex instead. - - if (e.start.which() != e.end.which()) { - throw std::runtime_error("Different trim types not supported"); - } - - TopoDS_Edge E; - if (e.basis) { - auto crv_or_wire = convert_curve(kernel, e.basis); - Handle(Geom_Curve) curve; - if (crv_or_wire.which() == 0) { - curve = boost::get(crv_or_wire); - } else { - // @todo - const double precision_ = 1.e-5; - Logger::Warning("Approximating BasisCurve due to possible discontinuities", e.instance); - BRepAdaptor_CompCurve cc(boost::get(crv_or_wire), true); - Handle(Adaptor3d_HCurve) hcc = Handle(Adaptor3d_HCurve)(new BRepAdaptor_HCompCurve(cc)); - // @todo, arbitrary numbers here, note they cannot be too high as contiguous memory is allocated based on them. - Approx_Curve3d approx(hcc, precision_, GeomAbs_C0, 10, 10); - curve = approx.Curve(); - } - - const bool reversed = !((taxonomy::geom_item*)e.basis)->orientation.get_value_or(true); - const bool is_conic = e.basis->kind() == taxonomy::ELLIPSE || e.basis->kind() == taxonomy::CIRCLE; - - // @todo, copy over logic from previous IfcTrimmedCurve handling - if (e.start.which() == 0) { - auto p1 = convert_xyz(boost::get(e.start)); - auto p2 = convert_xyz(boost::get(e.end)); - - if (reversed) { - std::swap(p1, p2); - } - - E = BRepBuilderAPI_MakeEdge(curve, p1, p2).Edge(); - } else { - auto v1 = boost::get(e.start); - auto v2 = boost::get(e.end); - - if (reversed) { - std::swap(v1, v2); - } - - if (is_conic && ALMOST_THE_SAME(fmod(v2 - v1, M_PI*2.), 0.)) { - E = BRepBuilderAPI_MakeEdge(curve).Edge(); - } else { - E = BRepBuilderAPI_MakeEdge(curve, v1, v2).Edge(); - } - } - - if (reversed) { - E.Reverse(); - } - } else { - if (e.start.which() != 0) { - throw std::runtime_error("Non-cartesian trim on edge without curve"); - } - auto p1 = convert_xyz(boost::get(e.start)); - auto p2 = convert_xyz(boost::get(e.end)); - - E = BRepBuilderAPI_MakeEdge(p1, p2).Edge(); - } - BRep_Builder B; - TopoDS_Wire W; - B.MakeWire(W); - B.Add(W, E); - return result = W; - } - }; - - curve_creation_visitor_result_type convert_curve(OpenCascadeKernel* kernel, const taxonomy::item* curve) { - curve_creation_visitor v{ kernel }; - if (dispatch_curve_creation::dispatch(curve, v)) { - return v.result; - } else { - throw std::runtime_error("No curve created"); - } - } -} - #include #include -namespace { - // Returns the other vertex of an edge - TopoDS_Vertex other(const TopoDS_Edge& e, const TopoDS_Vertex& v) { - TopoDS_Vertex a, b; - TopExp::Vertices(e, a, b); - return v.IsSame(b) ? a : b; - } - TopoDS_Edge first_edge(const TopoDS_Wire& w) { - TopoDS_Vertex v1, v2; - TopExp::Vertices(w, v1, v2); - TopTools_IndexedDataMapOfShapeListOfShape wm; - TopExp::MapShapesAndAncestors(w, TopAbs_VERTEX, TopAbs_EDGE, wm); - return TopoDS::Edge(wm.FindFromKey(v1).First()); - } - // Returns new wire with the edge replaced by a linear edge with the vertex v moved to p - TopoDS_Wire adjust(const TopoDS_Wire& w, const TopoDS_Vertex& v, const gp_Pnt& p) { - TopTools_IndexedDataMapOfShapeListOfShape map; - TopExp::MapShapesAndAncestors(w, TopAbs_VERTEX, TopAbs_EDGE, map); - - bool all_linear = true, single_circle = false, first = true; - - const TopTools_ListOfShape& edges = map.FindFromKey(v); - TopTools_ListIteratorOfListOfShape it(edges); - for (; it.More(); it.Next()) { - const TopoDS_Edge& e = TopoDS::Edge(it.Value()); - double _, __; - Handle(Geom_Curve) crv = BRep_Tool::Curve(e, _, __); - const bool is_line = crv->DynamicType() == STANDARD_TYPE(Geom_Line); - const bool is_circle = crv->DynamicType() == STANDARD_TYPE(Geom_Circle); - all_linear = all_linear && is_line; - single_circle = first && is_circle; - } - - if (all_linear) { - BRep_Builder b; - TopoDS_Vertex v2; - b.MakeVertex(v2, p, BRep_Tool::Tolerance(v)); - - ShapeBuild_ReShape reshape; - reshape.Replace(v.Oriented(TopAbs_FORWARD), v2); - - return TopoDS::Wire(reshape.Apply(w)); - } else if (single_circle) { - TopoDS_Vertex v1, v2; - TopExp::Vertices(w, v1, v2); - - gp_Pnt p1, p2, p3; - p1 = v.IsEqual(v1) ? p : BRep_Tool::Pnt(v1); - p3 = v.IsEqual(v2) ? p : BRep_Tool::Pnt(v2); - - double a, b; - Handle(Geom_Curve) crv = BRep_Tool::Curve(TopoDS::Edge(edges.First()), a, b); - crv->D0((a + b) / 2., p2); - - GC_MakeCircle mc(p1, p2, p3); - if (!mc.IsDone()) { - throw std::runtime_error("Failed to adjust circle"); - } - - TopoDS_Edge edge = BRepBuilderAPI_MakeEdge(mc.Value(), p1, p3).Edge(); - BRepBuilderAPI_MakeWire builder; - builder.Add(edge); - return builder.Wire(); - } else { - throw std::runtime_error("Unexpected wire to adjust"); - } - } - - // A wrapper around BRepBuilderAPI_MakeWire that makes sure segments are connected either by moving end points or by adding intermediate segments - class wire_builder { - private: - BRepBuilderAPI_MakeWire mw_; - double p_; - bool override_next_; - gp_Pnt next_override_; - const IfcUtil::IfcBaseClass* inst_; - - public: - wire_builder(double p, const IfcUtil::IfcBaseClass* inst = 0) : p_(p), override_next_(false), inst_(inst) {} - - void operator()(const TopoDS_Shape& a) { - const TopoDS_Wire& w = TopoDS::Wire(a); - if (override_next_) { - override_next_ = false; - TopoDS_Edge e = first_edge(w); - mw_.Add(adjust(w, TopExp::FirstVertex(e, true), next_override_)); - } else { - mw_.Add(w); - } - } - - void operator()(const TopoDS_Shape& a, const TopoDS_Shape& b, bool last) { - TopoDS_Wire w1 = TopoDS::Wire(a); - const TopoDS_Wire& w2 = TopoDS::Wire(b); - - if (override_next_) { - override_next_ = false; - TopoDS_Edge e = first_edge(w1); - w1 = adjust(w1, TopExp::FirstVertex(e, true), next_override_); - } - - TopoDS_Vertex w11, w12, w21, w22; - TopExp::Vertices(w1, w11, w12); - TopExp::Vertices(w2, w21, w22); - - gp_Pnt p1 = BRep_Tool::Pnt(w12); - gp_Pnt p2 = BRep_Tool::Pnt(w21); - - double dist = p1.Distance(p2); - - // Distance is within tolerance, this is fine - if (dist < p_) { - mw_.Add(w1); - goto check; - } - - // Distance is too large for attempting to move end points, add intermediate edge - if (dist > 1000. * p_) { - mw_.Add(w1); - mw_.Add(BRepBuilderAPI_MakeEdge(p1, p2)); - Logger::Warning("Added additional segment to close gap with length " + boost::lexical_cast(dist) + " to:", inst_); - goto check; - } - - { - TopTools_IndexedDataMapOfShapeListOfShape wmap1, wmap2; - - // Find edges connected to end- and begin vertex - TopExp::MapShapesAndAncestors(w1, TopAbs_VERTEX, TopAbs_EDGE, wmap1); - TopExp::MapShapesAndAncestors(w2, TopAbs_VERTEX, TopAbs_EDGE, wmap2); - - const TopTools_ListOfShape& last_edges = wmap1.FindFromKey(w12); - const TopTools_ListOfShape& first_edges = wmap2.FindFromKey(w21); - - double _, __; - if (last_edges.Extent() == 1 && first_edges.Extent() == 1) { - Handle(Geom_Curve) c1 = BRep_Tool::Curve(TopoDS::Edge(last_edges.First()), _, __); - Handle(Geom_Curve) c2 = BRep_Tool::Curve(TopoDS::Edge(first_edges.First()), _, __); - - const bool is_line1 = c1->DynamicType() == STANDARD_TYPE(Geom_Line); - const bool is_line2 = c2->DynamicType() == STANDARD_TYPE(Geom_Line); - - const bool is_circle1 = c1->DynamicType() == STANDARD_TYPE(Geom_Circle); - const bool is_circle2 = c2->DynamicType() == STANDARD_TYPE(Geom_Circle); - - // Preferably adjust the segment that is linear - if (is_line1 || (is_circle1 && !is_line2)) { - mw_.Add(adjust(w1, w12, p2)); - Logger::Notice("Adjusted edge end-point with distance " + boost::lexical_cast(dist) + " on:", inst_); - } else if ((is_line2 || is_circle2) && !last) { - mw_.Add(w1); - override_next_ = true; - next_override_ = p1; - Logger::Notice("Adjusted edge end-point with distance " + boost::lexical_cast(dist) + " on:", inst_); - } else { - // In all other cases an edge is added - mw_.Add(w1); - mw_.Add(BRepBuilderAPI_MakeEdge(p1, p2)); - Logger::Warning("Added additional segment to close gap with length " + boost::lexical_cast(dist) + " to:", inst_); - } - } else { - Logger::Error("Internal error, inconsistent wire segments", inst_); - mw_.Add(w1); - } - } - - check: - if (mw_.Error() == BRepBuilderAPI_NonManifoldWire) { - Logger::Error("Non-manifold curve segments:", inst_); - } else if (mw_.Error() == BRepBuilderAPI_DisconnectedWire) { - Logger::Error("Failed to join curve segments:", inst_); - } - } - - const TopoDS_Wire& wire() { return mw_.Wire(); } - }; - - template - void shape_pair_enumerate(TopTools_ListIteratorOfListOfShape& it, Fn& fn, bool closed) { - bool is_first = true; - TopoDS_Shape first, previous, current; - for (; it.More(); it.Next(), is_first = false) { - current = it.Value(); - if (is_first) { - first = current; - } else { - fn(previous, current, false); - } - previous = current; - } - if (closed) { - fn(current, first, true); - } else { - fn(current); - } - } -} - -bool OpenCascadeKernel::convert(const taxonomy::loop* loop, TopoDS_Wire& wire) { - auto segments = loop->children_as(); - - TopTools_ListOfShape converted_segments; - - for (auto& segment : segments) { - auto segment_wire = boost::get(convert_curve(this, segment)); - -#ifdef IFOPSH_DEBUG - std::ostringstream o; - segment->print(o); - TopoDS_Vertex v0, v1; - TopExp::Vertices(segment_wire, v0, v1); - gp_Pnt p0 = BRep_Tool::Pnt(v0); - gp_Pnt p1 = BRep_Tool::Pnt(v1); - o << "p0 " << p0.X() << " " << p0.Y() << " " << p0.Z() << std::endl; - o << "p1 " << p1.X() << " " << p1.Y() << " " << p1.Z() << std::endl; - auto o_str = o.str(); - std::wcout << o_str.c_str() << std::endl; -#endif - - if (!segment->orientation_2.get_value_or(true)) { - segment_wire.Reverse(); - } - - ShapeFix_ShapeTolerance FTol; - FTol.SetTolerance(segment_wire, precision_, TopAbs_WIRE); - - converted_segments.Append(segment_wire); - } - - if (converted_segments.Extent() == 0) { - Logger::Message(Logger::LOG_ERROR, "No segment succesfully converted:", loop->instance); - return false; - } - - BRepBuilderAPI_MakeWire w; - TopoDS_Vertex wire_first_vertex, wire_last_vertex, edge_first_vertex, edge_last_vertex; - - TopTools_ListIteratorOfListOfShape it(converted_segments); - - /* - @todo - IfcEntityList::ptr profile = l->data().getInverse(&IfcSchema::IfcProfileDef::Class(), -1); - const bool force_close = profile && profile->size() > 0; - */ - const bool force_close = false; - - wire_builder bld(precision_, loop->instance); - shape_pair_enumerate(it, bld, force_close); - wire = bld.wire(); - - return true; -} - -bool OpenCascadeKernel::convert_impl(const taxonomy::extrusion* extrusion, ifcopenshell::geometry::ConversionResults& results) { - TopoDS_Shape shape; - if (!convert(extrusion, shape)) { - return false; - } - results.emplace_back(ConversionResult( - extrusion->instance->data().id(), - extrusion->matrix, - new OpenCascadeShape(shape), - extrusion->surface_style - )); - return true; -} - -bool OpenCascadeKernel::convert_impl(const taxonomy::shell *shell, ifcopenshell::geometry::ConversionResults& results) { - TopoDS_Shape shape; - if (!convert(shell, shape)) { - return false; - } - results.emplace_back(ConversionResult( - shell->instance->data().id(), - shell->matrix, - new OpenCascadeShape(shape), - shell->surface_style - )); - return true; -} - -bool OpenCascadeKernel::convert(const taxonomy::matrix4* matrix, gp_GTrsf& trsf) { - // @todo check - const auto& m = matrix->ccomponents(); - gp_Mat mat( - m(0, 0), m(0, 1), m(0, 2), - m(1, 0), m(1, 1), m(1, 2), - m(2, 0), m(2, 1), m(2, 2) - ); - - if (matrix->instance && matrix->instance->declaration().name() == "IfcCartesianTransformationOperator3DnonUniform") { - std::wcout << "non uniform" << std::endl; - } - - // @nb SetVectorialPart() sets gp_GTrsf.scale to 0.0, causing an non-invertable - // matrix later on which cannot be in TopLoc_Location. - - std::array ms{ { - mat.Column(1).Modulus(), - mat.Column(2).Modulus(), - mat.Column(3).Modulus() - } }; - std::sort(ms.begin(), ms.end()); - - if (std::fabs(ms.front() - ms.back()) < 1.e-7) { - gp_Trsf tr; - tr.SetValues( - m(0, 0), m(0, 1), m(0, 2), m(0, 3), - m(1, 0), m(1, 1), m(1, 2), m(1, 3), - m(2, 0), m(2, 1), m(2, 2), m(2, 3) - ); - trsf = tr; - } else { - trsf.SetVectorialPart(mat); - trsf.SetTranslationPart(gp_XYZ(m(0, 3), m(1, 3), m(2, 3))); - trsf.SetForm(); - } - - return true; -} #include diff --git a/src/ifcgeom/kernels/opencascade/OpenCascadeConversionResult.h b/src/ifcgeom/kernels/opencascade/OpenCascadeConversionResult.h index 74189a4e8a..5a4ba5c020 100644 --- a/src/ifcgeom/kernels/opencascade/OpenCascadeConversionResult.h +++ b/src/ifcgeom/kernels/opencascade/OpenCascadeConversionResult.h @@ -59,7 +59,7 @@ namespace ifcopenshell { virtual bool is_manifold() const; - virtual double bounding_box(void*& b) const { + virtual double bounding_box(void*&) const { throw std::runtime_error("Not implemented"); } @@ -67,7 +67,7 @@ namespace ifcopenshell { throw std::runtime_error("Not implemented"); } - virtual void set_box(void* b) { + virtual void set_box(void*) { throw std::runtime_error("Not implemented"); } diff --git a/src/ifcgeom/kernels/opencascade/OpenCascadeKernel.h b/src/ifcgeom/kernels/opencascade/OpenCascadeKernel.h index be38bd19d8..f02ec73b4f 100644 --- a/src/ifcgeom/kernels/opencascade/OpenCascadeKernel.h +++ b/src/ifcgeom/kernels/opencascade/OpenCascadeKernel.h @@ -74,24 +74,6 @@ namespace ifcopenshell { namespace geometry { namespace kernels { - class IFC_GEOM_API geometry_exception : public std::exception { - protected: - std::string message; - public: - geometry_exception(const std::string& m) - : message(m) {} - virtual ~geometry_exception() throw () {} - virtual const char* what() const throw() { - return message.c_str(); - } - }; - - class IFC_GEOM_API too_many_faces_exception : public geometry_exception { - public: - too_many_faces_exception() - : geometry_exception("Too many faces for operation") {} - }; - /* class IFC_GEOM_API POSTFIX_SCHEMA(Cache) { public: @@ -262,6 +244,18 @@ namespace kernels { IfcUtil::IfcBaseClass* POSTFIX_SCHEMA(serialise_)(const TopoDS_Shape& shape, bool advanced); */ + template + T convert_xyz(const U& u) { + const auto& vs = u.ccomponents(); + return T(vs(0), vs(1), vs(2)); + } + + // @todo eliminate + template + T convert_xyz2(const U& vs) { + return T(vs(0), vs(1), vs(2)); + } + } } } diff --git a/src/ifcgeom/kernels/opencascade/extrusion.cpp b/src/ifcgeom/kernels/opencascade/extrusion.cpp new file mode 100644 index 0000000000..99d8ce440f --- /dev/null +++ b/src/ifcgeom/kernels/opencascade/extrusion.cpp @@ -0,0 +1,85 @@ +#include "OpenCascadeKernel.h" + +#include + +using namespace ifcopenshell::geometry; +using namespace ifcopenshell::geometry::kernels; + +bool OpenCascadeKernel::convert(const taxonomy::extrusion* extrusion, TopoDS_Shape& shape) { + const double& height = extrusion->depth; + + if (height < precision_) { + Logger::Error("Non-positive extrusion height encountered for:", extrusion->instance); + return false; + } + + TopoDS_Shape face; + if (!convert(&extrusion->basis, face)) { + return false; + } + + /* + // @todo we need to decide whether the matrix is kept on the taxonomy node or + // move the TopoDS_Shape, but obviously not both. + gp_GTrsf gtrsf; + if (!convert(&extrusion->matrix, gtrsf)) { + Logger::Error("Unable to move extrusion"); + } + auto trsf = gtrsf.Trsf(); + */ + + const auto& fs = extrusion->direction.ccomponents(); + gp_Dir dir(fs(0), fs(1), fs(2)); + + shape.Nullify(); + + if (face.ShapeType() == TopAbs_COMPOUND) { + + // For compounds (most likely the result of a IfcCompositeProfileDef) + // create a compound solid shape. + + TopExp_Explorer exp(face, TopAbs_FACE); + + TopoDS_CompSolid compound; + BRep_Builder builder; + builder.MakeCompSolid(compound); + + int num_faces_extruded = 0; + for (; exp.More(); exp.Next(), ++num_faces_extruded) { + builder.Add(compound, BRepPrimAPI_MakePrism(exp.Current(), height*dir)); + } + + if (num_faces_extruded) { + shape = compound; + } + + } + + if (shape.IsNull()) { + shape = BRepPrimAPI_MakePrism(face, height*dir); + } + + /* + if (!shape.IsNull()) { + // IfcSweptAreaSolid.Position (trsf) is an IfcAxis2Placement3D + // and therefore has a unit scale factor + shape.Move(trsf); + } + */ + + return !shape.IsNull(); +} + +bool OpenCascadeKernel::convert_impl(const taxonomy::extrusion* extrusion, ifcopenshell::geometry::ConversionResults& results) { + TopoDS_Shape shape; + if (!convert(extrusion, shape)) { + return false; + } + results.emplace_back(ConversionResult( + extrusion->instance->data().id(), + extrusion->matrix, + new OpenCascadeShape(shape), + extrusion->surface_style + )); + return true; +} diff --git a/src/ifcgeom/kernels/opencascade/face.cpp b/src/ifcgeom/kernels/opencascade/face.cpp new file mode 100644 index 0000000000..1aec3281b3 --- /dev/null +++ b/src/ifcgeom/kernels/opencascade/face.cpp @@ -0,0 +1,298 @@ +/******************************************************************************** + * * + * This file is part of IfcOpenShell. * + * * + * IfcOpenShell is free software: you can redistribute it and/or modify * + * it under the terms of the Lesser GNU General Public License as published by * + * the Free Software Foundation, either version 3.0 of the License, or * + * (at your option) any later version. * + * * + * IfcOpenShell is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * Lesser GNU General Public License for more details. * + * * + * You should have received a copy of the Lesser GNU General Public License * + * along with this program. If not, see . * + * * + ********************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "OpenCascadeKernel.h" +#include "face_definition.h" + +using namespace ifcopenshell::geometry; +using namespace ifcopenshell::geometry::util; +using namespace ifcopenshell::geometry::kernels; + +bool OpenCascadeKernel::convert(const taxonomy::face* face, TopoDS_Shape& result) { + auto bounds = face->children_as(); + + face_definition fd; + + const bool is_face_surface = false; /* todo */ + + /* + if (is_face_surface) { + IfcSchema::IfcFaceSurface* fs = (IfcSchema::IfcFaceSurface*) l; + fs->FaceSurface(); + // FIXME: Surfaces are interpreted as a TopoDS_Shape + TopoDS_Shape surface_shape; + if (!convert_shape(fs->FaceSurface(), surface_shape)) return false; + + // FIXME: Assert this obtaines the only face + TopExp_Explorer exp(surface_shape, TopAbs_FACE); + if (!exp.More()) return false; + + TopoDS_Face surface = TopoDS::Face(exp.Current()); + fd.surface() = BRep_Tool::Surface(surface); + } + */ + + const int num_bounds = bounds.size(); + int num_outer_bounds = 0; + + for (auto& bound : bounds) { + if (bound->external.get_value_or(false)) { + num_outer_bounds++; + } + } + + // The number of outer bounds should be one according to the schema. Also Open Cascade + // expects this, but it is not strictly checked. Regardless, if the number is greater, + // the face will still be processed as long as there are no holes. A compound of faces + // is returned in that case. + if (num_bounds > 1 && num_outer_bounds > 1 && num_bounds != num_outer_bounds) { + Logger::Message(Logger::LOG_ERROR, "Invalid configuration of boundaries for:", face->instance); + return false; + } + + if (num_outer_bounds > 1) { + Logger::Message(Logger::LOG_WARNING, "Multiple outer boundaries for:", face->instance); + fd.all_outer() = true; + } + + TopTools_DataMapOfShapeInteger wire_senses; + + for (int process_interior = 0; process_interior <= 1; ++process_interior) { + for (auto& bound : bounds) { + bool same_sense = true; /* todo bound->Orientation(); */ + + const bool is_interior = + !bound->external.get_value_or(false) && + (num_bounds > 1) && + (num_outer_bounds < num_bounds); + + // The exterior face boundary is processed first + if (is_interior == !process_interior) continue; + + TopoDS_Wire wire; + if (faceset_helper_ && is_polyhedron(bound)) { + if (!faceset_helper_->wire(bound, wire)) { + Logger::Message(Logger::LOG_WARNING, "Face boundary loop not included", bound->instance); + continue; + } + } else if (!convert(bound, wire)) { + Logger::Message(Logger::LOG_ERROR, "Failed to process face boundary loop", bound->instance); + return false; + } + + if (!same_sense) { + wire.Reverse(); + } + + wire_senses.Bind(wire.Oriented(TopAbs_FORWARD), same_sense ? TopAbs_FORWARD : TopAbs_REVERSED); + + fd.wires().emplace_back(wire); + } + } + + if (fd.wires().empty()) { + Logger::Warning("Face with no boundaries", face->instance); + return false; + } + + if (fd.surface().IsNull()) { + // Use the first wire to find a plane manually for polygonal wires + const TopoDS_Wire& wire = fd.wires().front(); + if (is_polyhedron(wire)) { + TopExp_Explorer exp(wire, TopAbs_EDGE); + int count = 0; + TopoDS_Edge edges[2]; + for (; exp.More(); exp.Next(), count++) { + if (count < 2) { + edges[count] = TopoDS::Edge(exp.Current()); + } + } + + if (count == 3) { + // Help Open Cascade by finding the plane more efficiently + double _, __; + Handle(Geom_Line) c1 = Handle(Geom_Line)::DownCast(BRep_Tool::Curve(edges[0], _, __)); + Handle(Geom_Line) c2 = Handle(Geom_Line)::DownCast(BRep_Tool::Curve(edges[1], _, __)); + + const gp_Vec ab = c1->Position().Direction(); + const gp_Vec ac = c2->Position().Direction(); + const gp_Vec cross = ab.Crossed(ac); + + if (cross.SquareMagnitude() > ALMOST_ZERO) { + const gp_Dir n = cross; + fd.surface() = new Geom_Plane(c1->Position().Location(), n); + } + } else { + gp_Pln pln; + if (approximate_plane_through_wire(wire, pln)) { + fd.surface() = new Geom_Plane(pln); + } + } + } + } + + if (fd.surface().IsNull()) { + // BRepLib_FindSurface is used in case no surface is found or provided + + const TopoDS_Wire& wire = fd.wires().front(); + + BRepLib_FindSurface fs(wire, precision_, true, true); + if (fs.Found()) { + fd.surface() = fs.Surface(); + ShapeFix_ShapeTolerance ftol; + ftol.SetTolerance(wire, fs.ToleranceReached(), TopAbs_WIRE); + } + } + + TopTools_ListOfShape face_list; + + if (fd.surface().IsNull()) { + // The set of wires is triangulated in case no surface can be found + Logger::Message(Logger::LOG_WARNING, "Triangulating face boundaries for face", face->instance); + + if (fd.all_outer()) { + for (const auto& w : fd.wires()) { + TopTools_ListOfShape fl; + triangulate_wire({ w }, fl); + face_list.Append(fl); + } + } else { + triangulate_wire(fd.wires(), face_list); + } + } else if (!fd.all_outer()) { + BRepBuilderAPI_MakeFace mf(fd.surface(), fd.outer_wire()); + + if (mf.IsDone()) { + // Is this necessary + TopoDS_Face f = mf.Face(); + mf.Init(f); + + for (auto it = fd.inner_wires().first; it != fd.inner_wires().second; ++it) { + mf.Add(*it); + } + + face_list.Append(mf.Face()); + } + } else { + for (const auto& w : fd.wires()) { + BRepBuilderAPI_MakeFace mf(fd.surface(), w); + if (mf.IsDone()) { + face_list.Append(mf.Face()); + } + } + } + + if (!fd.surface().IsNull()) { + // Some fixes for orientation and p-curves. If we have no surface, it + // means the face has been triangulated in which case none of these + // fixes are necessary. + + if (fd.surface()->DynamicType() != STANDARD_TYPE(Geom_Plane)) { + // In case of (non-planar) face surface, p-curves need to be computed. + // For planar faces, Open Cascade generates p-curves on the fly. + + for (TopTools_ListIteratorOfListOfShape it(face_list); it.More(); it.Next()) { + // Small chance there are multiple faces + const TopoDS_Face& occ_face = TopoDS::Face(it.Value()); + for (TopExp_Explorer exp2(occ_face, TopAbs_EDGE); exp2.More(); exp2.Next()) { + const TopoDS_Edge& edge = TopoDS::Edge(exp2.Current()); + ShapeFix_Edge fix_edge; + fix_edge.FixAddPCurve(edge, occ_face, false, precision_); + } + } + } + + for (TopTools_ListIteratorOfListOfShape it(face_list); it.More(); it.Next()) { + const TopoDS_Face& occ_face = TopoDS::Face(it.Value()); + + ShapeFix_Face sfs(TopoDS::Face(occ_face)); + TopTools_DataMapOfShapeListOfShape wire_map; + sfs.FixOrientation(wire_map); + + TopoDS_Iterator jt(occ_face, false); + for (; jt.More(); jt.Next()) { + const TopoDS_Wire& w = TopoDS::Wire(jt.Value()); + // tfk: @todo if wire_map contains w, I would assume wire_senses also contains w, + // this is not the case in github issue #405. + if (wire_map.IsBound(w) && wire_senses.IsBound(w)) { + const TopTools_ListOfShape& shapes = wire_map.Find(w); + TopTools_ListIteratorOfListOfShape kt(shapes); + for (; kt.More(); kt.Next()) { + // Apparently the wire got reversed, so register it with opposite orientation in the map + wire_senses.Bind(kt.Value(), wire_senses.Find(w) == TopAbs_FORWARD ? TopAbs_REVERSED : TopAbs_FORWARD); + } + } + } + + it.Value() = sfs.Face(); + } + + for (TopTools_ListIteratorOfListOfShape it(face_list); it.More(); it.Next()) { + TopoDS_Face& occ_face = TopoDS::Face(it.Value()); + + bool all_reversed = true; + TopoDS_Iterator jt(occ_face, false); + for (; jt.More(); jt.Next()) { + const TopoDS_Wire& w = TopoDS::Wire(jt.Value()); + if (!wire_senses.IsBound(w.Oriented(TopAbs_FORWARD)) || (w.Orientation() == wire_senses.Find(w.Oriented(TopAbs_FORWARD)))) { + all_reversed = false; + } + } + + if (all_reversed) { + occ_face.Reverse(); + } + } + } + + if (face_list.Extent() > 1) { + TopoDS_Compound compound; + BRep_Builder builder; + builder.MakeCompound(compound); + for (TopTools_ListIteratorOfListOfShape it(face_list); it.More(); it.Next()) { + TopoDS_Face& occ_face = TopoDS::Face(it.Value()); + builder.Add(compound, occ_face); + } + result = compound; + } else { + result = face_list.First(); + } + + return true; +} \ No newline at end of file diff --git a/src/ifcgeom/kernels/opencascade/face_definition.cpp b/src/ifcgeom/kernels/opencascade/face_definition.cpp new file mode 100644 index 0000000000..522549f506 --- /dev/null +++ b/src/ifcgeom/kernels/opencascade/face_definition.cpp @@ -0,0 +1,34 @@ +#include "face_definition.h" + +#include +#include +#include +#include + +/* Returns whether wire conforms to a polyhedron, i.e. only edges with linear curves*/ +bool ifcopenshell::geometry::util::is_polyhedron(const TopoDS_Wire& wire) { + double a, b; + TopLoc_Location l; + + TopoDS_Iterator it(wire, false, false); + for (; it.More(); it.Next()) { + auto crv = BRep_Tool::Curve(TopoDS::Edge(it.Value()), l, a, b); + if (!crv || crv->DynamicType() != STANDARD_TYPE(Geom_Line)) { + return false; + } + } + + return true; +} + +/* Returns whether wire conforms to a polyhedron, i.e. only edges with linear curves*/ +bool ifcopenshell::geometry::util::is_polyhedron(const taxonomy::loop* wire) { + for (auto& edge : wire->children_as()) { + if (edge->basis) { + if (edge->basis->kind() != taxonomy::LINE) { + return false; + } + } + } + return true; +} \ No newline at end of file diff --git a/src/ifcgeom/kernels/opencascade/face_definition.h b/src/ifcgeom/kernels/opencascade/face_definition.h new file mode 100644 index 0000000000..a75d012eda --- /dev/null +++ b/src/ifcgeom/kernels/opencascade/face_definition.h @@ -0,0 +1,83 @@ +/******************************************************************************** + * * + * This file is part of IfcOpenShell. * + * * + * IfcOpenShell is free software: you can redistribute it and/or modify * + * it under the terms of the Lesser GNU General Public License as published by * + * the Free Software Foundation, either version 3.0 of the License, or * + * (at your option) any later version. * + * * + * IfcOpenShell is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * Lesser GNU General Public License for more details. * + * * + * You should have received a copy of the Lesser GNU General Public License * + * along with this program. If not, see . * + * * + ********************************************************************************/ + +#ifndef FACE_DEFINITION_H +#define FACE_DEFINITION_H + +#include "../../taxonomy.h" + +#include +#include + +#include +#include + +namespace ifcopenshell { + namespace geometry { + namespace util { + + /* Returns whether wire conforms to a polyhedron, i.e. only edges with linear curves*/ + bool is_polyhedron(const TopoDS_Wire& wire); + + /* Returns whether wire conforms to a polyhedron, i.e. only edges with linear curves*/ + bool is_polyhedron(const taxonomy::loop* wire); + + /* A temporary structure to store the intermediate data for the face conversion */ + class face_definition { + private: + Handle(Geom_Surface) surface_; + std::vector wires_; + bool all_outer_; + public: + face_definition() : surface_(), all_outer_(false) {} + + typedef std::vector::const_iterator wire_it; + + bool& all_outer() { + return all_outer_; + } + + bool all_outer() const { + return all_outer_; + } + + Handle(Geom_Surface)& surface() { + return surface_; + } + + const Handle(Geom_Surface)& surface() const { + return surface_; + } + + std::vector& wires() { + return wires_; + } + + const TopoDS_Wire& outer_wire() const { + return wires_.front(); + } + + std::pair inner_wires() const { + return { wires_.begin() + 1, wires_.end() }; + } + }; + } + } +} +#endif diff --git a/src/ifcgeom/kernels/opencascade/loop.cpp b/src/ifcgeom/kernels/opencascade/loop.cpp new file mode 100644 index 0000000000..76b7552aed --- /dev/null +++ b/src/ifcgeom/kernels/opencascade/loop.cpp @@ -0,0 +1,186 @@ +#include "OpenCascadeKernel.h" +#include "wire_builder.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace ifcopenshell::geometry; +using namespace ifcopenshell::geometry::kernels; + +using namespace IfcGeom::util; + +namespace { + typedef boost::variant curve_creation_visitor_result_type; + curve_creation_visitor_result_type convert_curve(OpenCascadeKernel* kernel, const taxonomy::item* curve); + + struct curve_creation_visitor { + OpenCascadeKernel* kernel; + curve_creation_visitor_result_type result; + + curve_creation_visitor_result_type operator()(const taxonomy::bspline_curve&) { + throw std::runtime_error("Not implemented"); + } + + curve_creation_visitor_result_type operator()(const taxonomy::line& l) { + const auto& m = l.matrix.ccomponents(); + return result = Handle(Geom_Curve)(new Geom_Line(convert_xyz2(m.col(3)), convert_xyz2(m.col(0)))); + } + + curve_creation_visitor_result_type operator()(const taxonomy::circle& c) { + const auto& m = c.matrix.ccomponents(); + return result = Handle(Geom_Curve)(new Geom_Circle(gp_Ax2(convert_xyz2(m.col(3)), convert_xyz2(m.col(2)), convert_xyz2(m.col(0))), c.radius)); + } + + curve_creation_visitor_result_type operator()(const taxonomy::ellipse& e) { + const auto& m = e.matrix.ccomponents(); + return result = Handle(Geom_Curve)(new Geom_Ellipse(gp_Ax2(convert_xyz2(m.col(3)), convert_xyz2(m.col(2)), convert_xyz2(m.col(0))), e.radius, e.radius2)); + } + + curve_creation_visitor_result_type operator()(const taxonomy::loop& l) { + TopoDS_Wire wire; + kernel->convert(&l, wire); + return result = wire; + } + + curve_creation_visitor_result_type operator()(const taxonomy::edge& e) { + // @todo for polyloops/-lines we should probably construct edges based on correct oriented TopoDS_Vertex instead. + + if (e.start.which() != e.end.which()) { + throw std::runtime_error("Different trim types not supported"); + } + + TopoDS_Edge E; + if (e.basis) { + auto crv_or_wire = convert_curve(kernel, e.basis); + Handle(Geom_Curve) curve; + if (crv_or_wire.which() == 0) { + curve = boost::get(crv_or_wire); + } else { + // @todo + const double precision_ = 1.e-5; + Logger::Warning("Approximating BasisCurve due to possible discontinuities", e.instance); + BRepAdaptor_CompCurve cc(boost::get(crv_or_wire), true); + Handle(Adaptor3d_HCurve) hcc = Handle(Adaptor3d_HCurve)(new BRepAdaptor_HCompCurve(cc)); + // @todo, arbitrary numbers here, note they cannot be too high as contiguous memory is allocated based on them. + Approx_Curve3d approx(hcc, precision_, GeomAbs_C0, 10, 10); + curve = approx.Curve(); + } + + const bool reversed = !((taxonomy::geom_item*)e.basis)->orientation.get_value_or(true); + const bool is_conic = e.basis->kind() == taxonomy::ELLIPSE || e.basis->kind() == taxonomy::CIRCLE; + + // @todo, copy over logic from previous IfcTrimmedCurve handling + if (e.start.which() == 0) { + auto p1 = convert_xyz(boost::get(e.start)); + auto p2 = convert_xyz(boost::get(e.end)); + + if (reversed) { + std::swap(p1, p2); + } + + E = BRepBuilderAPI_MakeEdge(curve, p1, p2).Edge(); + } else { + auto v1 = boost::get(e.start); + auto v2 = boost::get(e.end); + + if (reversed) { + std::swap(v1, v2); + } + + if (is_conic && ALMOST_THE_SAME(fmod(v2 - v1, M_PI*2.), 0.)) { + E = BRepBuilderAPI_MakeEdge(curve).Edge(); + } else { + E = BRepBuilderAPI_MakeEdge(curve, v1, v2).Edge(); + } + } + + if (reversed) { + E.Reverse(); + } + } else { + if (e.start.which() != 0) { + throw std::runtime_error("Non-cartesian trim on edge without curve"); + } + auto p1 = convert_xyz(boost::get(e.start)); + auto p2 = convert_xyz(boost::get(e.end)); + + E = BRepBuilderAPI_MakeEdge(p1, p2).Edge(); + } + BRep_Builder B; + TopoDS_Wire W; + B.MakeWire(W); + B.Add(W, E); + return result = W; + } + }; + + curve_creation_visitor_result_type convert_curve(OpenCascadeKernel* kernel, const taxonomy::item* curve) { + curve_creation_visitor v{ kernel }; + if (dispatch_curve_creation::dispatch(curve, v)) { + return v.result; + } else { + throw std::runtime_error("No curve created"); + } + } +} + +bool OpenCascadeKernel::convert(const taxonomy::loop* loop, TopoDS_Wire& wire) { + auto segments = loop->children_as(); + + TopTools_ListOfShape converted_segments; + + for (auto& segment : segments) { + auto segment_wire = boost::get(convert_curve(this, segment)); + +#ifdef IFOPSH_DEBUG + std::ostringstream o; + segment->print(o); + TopoDS_Vertex v0, v1; + TopExp::Vertices(segment_wire, v0, v1); + gp_Pnt p0 = BRep_Tool::Pnt(v0); + gp_Pnt p1 = BRep_Tool::Pnt(v1); + o << "p0 " << p0.X() << " " << p0.Y() << " " << p0.Z() << std::endl; + o << "p1 " << p1.X() << " " << p1.Y() << " " << p1.Z() << std::endl; + auto o_str = o.str(); + std::wcout << o_str.c_str() << std::endl; +#endif + + if (!segment->orientation_2.get_value_or(true)) { + segment_wire.Reverse(); + } + + ShapeFix_ShapeTolerance FTol; + FTol.SetTolerance(segment_wire, precision_, TopAbs_WIRE); + + converted_segments.Append(segment_wire); + } + + if (converted_segments.Extent() == 0) { + Logger::Message(Logger::LOG_ERROR, "No segment succesfully converted:", loop->instance); + return false; + } + + BRepBuilderAPI_MakeWire w; + TopoDS_Vertex wire_first_vertex, wire_last_vertex, edge_first_vertex, edge_last_vertex; + + TopTools_ListIteratorOfListOfShape it(converted_segments); + + /* + @todo + IfcEntityList::ptr profile = l->data().getInverse(&IfcSchema::IfcProfileDef::Class(), -1); + const bool force_close = profile && profile->size() > 0; + */ + const bool force_close = false; + + wire_builder bld(precision_, loop->instance); + shape_pair_enumerate(it, bld, force_close); + wire = bld.wire(); + + return true; +} \ No newline at end of file diff --git a/src/ifcgeom/kernels/opencascade/matrix4.cpp b/src/ifcgeom/kernels/opencascade/matrix4.cpp new file mode 100644 index 0000000000..c86f2190e5 --- /dev/null +++ b/src/ifcgeom/kernels/opencascade/matrix4.cpp @@ -0,0 +1,44 @@ +#include "OpenCascadeKernel.h" + +using namespace ifcopenshell::geometry; +using namespace ifcopenshell::geometry::kernels; + +bool OpenCascadeKernel::convert(const taxonomy::matrix4* matrix, gp_GTrsf& trsf) { + // @todo check + const auto& m = matrix->ccomponents(); + gp_Mat mat( + m(0, 0), m(0, 1), m(0, 2), + m(1, 0), m(1, 1), m(1, 2), + m(2, 0), m(2, 1), m(2, 2) + ); + + if (matrix->instance && matrix->instance->declaration().name() == "IfcCartesianTransformationOperator3DnonUniform") { + std::wcout << "non uniform" << std::endl; + } + + // @nb SetVectorialPart() sets gp_GTrsf.scale to 0.0, causing an non-invertable + // matrix later on which cannot be in TopLoc_Location. + + std::array ms{ { + mat.Column(1).Modulus(), + mat.Column(2).Modulus(), + mat.Column(3).Modulus() + } }; + std::sort(ms.begin(), ms.end()); + + if (std::fabs(ms.front() - ms.back()) < 1.e-7) { + gp_Trsf tr; + tr.SetValues( + m(0, 0), m(0, 1), m(0, 2), m(0, 3), + m(1, 0), m(1, 1), m(1, 2), m(1, 3), + m(2, 0), m(2, 1), m(2, 2), m(2, 3) + ); + trsf = tr; + } else { + trsf.SetVectorialPart(mat); + trsf.SetTranslationPart(gp_XYZ(m(0, 3), m(1, 3), m(2, 3))); + trsf.SetForm(); + } + + return true; +} \ No newline at end of file diff --git a/src/ifcgeom/kernels/opencascade/shell.cpp b/src/ifcgeom/kernels/opencascade/shell.cpp new file mode 100644 index 0000000000..81457f3423 --- /dev/null +++ b/src/ifcgeom/kernels/opencascade/shell.cpp @@ -0,0 +1,18 @@ +#include "OpenCascadeKernel.h" + +using namespace ifcopenshell::geometry; +using namespace ifcopenshell::geometry::kernels; + +bool OpenCascadeKernel::convert_impl(const taxonomy::shell *shell, ifcopenshell::geometry::ConversionResults& results) { + TopoDS_Shape shape; + if (!convert(shell, shape)) { + return false; + } + results.emplace_back(ConversionResult( + shell->instance->data().id(), + shell->matrix, + new OpenCascadeShape(shape), + shell->surface_style + )); + return true; +} diff --git a/src/ifcgeom/kernels/opencascade/wire_builder.cpp b/src/ifcgeom/kernels/opencascade/wire_builder.cpp new file mode 100644 index 0000000000..cc36324ffe --- /dev/null +++ b/src/ifcgeom/kernels/opencascade/wire_builder.cpp @@ -0,0 +1,226 @@ +#include "wire_builder.h" + +#include "../../../ifcparse/IfcLogger.h" +#include "../../exceptions.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Returns the first edge of a wire +TopoDS_Edge IfcGeom::util::first_edge(const TopoDS_Wire & w) { + TopoDS_Vertex v1, v2; + TopExp::Vertices(w, v1, v2); + TopTools_IndexedDataMapOfShapeListOfShape wm; + TopExp::MapShapesAndAncestors(w, TopAbs_VERTEX, TopAbs_EDGE, wm); + return TopoDS::Edge(wm.FindFromKey(v1).First()); +} + +// Returns new wire with the edge replaced by a linear edge with the vertex v moved to p +TopoDS_Wire IfcGeom::util::adjust(const TopoDS_Wire & w, const TopoDS_Vertex & v, const gp_Pnt & p) { + TopTools_IndexedDataMapOfShapeListOfShape map; + TopExp::MapShapesAndAncestors(w, TopAbs_VERTEX, TopAbs_EDGE, map); + + bool all_linear = true, single_circle = false, first = true; + + const TopTools_ListOfShape& edges = map.FindFromKey(v); + TopTools_ListIteratorOfListOfShape it(edges); + for (; it.More(); it.Next()) { + const TopoDS_Edge& e = TopoDS::Edge(it.Value()); + double _, __; + Handle(Geom_Curve) crv = BRep_Tool::Curve(e, _, __); + const bool is_line = crv->DynamicType() == STANDARD_TYPE(Geom_Line); + const bool is_circle = crv->DynamicType() == STANDARD_TYPE(Geom_Circle); + all_linear = all_linear && is_line; + single_circle = first && is_circle; + } + + if (all_linear) { + BRep_Builder b; + TopoDS_Vertex v2; + b.MakeVertex(v2, p, BRep_Tool::Tolerance(v)); + + ShapeBuild_ReShape reshape; + reshape.Replace(v.Oriented(TopAbs_FORWARD), v2); + + return TopoDS::Wire(reshape.Apply(w)); + } else if (single_circle) { + TopoDS_Vertex v1, v2; + TopExp::Vertices(w, v1, v2); + + gp_Pnt p1, p2, p3; + p1 = v.IsEqual(v1) ? p : BRep_Tool::Pnt(v1); + p3 = v.IsEqual(v2) ? p : BRep_Tool::Pnt(v2); + + double a, b; + Handle(Geom_Curve) crv = BRep_Tool::Curve(TopoDS::Edge(edges.First()), a, b); + crv->D0((a + b) / 2., p2); + + GC_MakeCircle mc(p1, p2, p3); + if (!mc.IsDone()) { + throw ifcopenshell::geometry::geometry_exception("Failed to adjust circle"); + } + + TopoDS_Edge edge = BRepBuilderAPI_MakeEdge(mc.Value(), p1, p3).Edge(); + BRepBuilderAPI_MakeWire builder; + builder.Add(edge); + return builder.Wire(); + } else { + throw ifcopenshell::geometry::geometry_exception("Unexpected wire to adjust"); + } +} + +double IfcGeom::util::deflection_for_approximating_circle(double radius, double param) { + return -radius * std::cos(1. / 2. * param) * std::cos(param) - radius * std::sin(1. / 2. * param) * std::sin(param) + radius; +} + +bool IfcGeom::util::create_edge_over_curve_with_log_messages(const Handle_Geom_Curve & crv, const double eps, const gp_Pnt & p1, const gp_Pnt & p2, TopoDS_Edge & result) { + if (crv->IsClosed() && p1.Distance(p2) <= eps) { + BRepBuilderAPI_MakeEdge me(crv); + if (me.IsDone()) { + result = me.Edge(); + return true; + } else { + return false; + } + } + + BRep_Builder builder; + TopoDS_Vertex v1, v2; + /// @todo project first and emit warnings accordingly + builder.MakeVertex(v1, p1, eps); + builder.MakeVertex(v2, p2, eps); + + BRepBuilderAPI_MakeEdge me(crv, v1, v2); + if (!me.IsDone()) { + const double eps2 = eps * eps; + if (me.Error() == BRepBuilderAPI_PointProjectionFailed) { + GeomAdaptor_Curve GAC(crv); + const gp_Pnt* ps[2] = { &p1, &p2 }; + for (int i = 0; i < 2; ++i) { + Extrema_ExtPC extrema(*ps[i], GAC); + if (extrema.IsDone()) { + int n = extrema.NbExt(); + double dmin = std::numeric_limits::infinity(); + for (int j = 1; j <= n; j++) { + const double d = extrema.SquareDistance(j); + if (d < dmin) { + dmin = d; + } + } + if (dmin == std::numeric_limits::infinity()) { + Logger::Error("No extrema for point"); + } else if (dmin > eps2) { + Logger::Error("Distance of " + boost::lexical_cast(std::sqrt(dmin)) + " exceeds tolerance"); + } + } else { + Logger::Error("Failed to calculate extrema for point"); + } + } + } + return false; + } + result = me.Edge(); + return true; +} + +void IfcGeom::util::wire_builder::operator()(const TopoDS_Shape& a) { + const TopoDS_Wire& w = TopoDS::Wire(a); + if (override_next_) { + override_next_ = false; + TopoDS_Edge e = first_edge(w); + mw_.Add(adjust(w, TopExp::FirstVertex(e, true), next_override_)); + } else { + mw_.Add(w); + } +} + +void IfcGeom::util::wire_builder::operator()(const TopoDS_Shape& a, const TopoDS_Shape& b, bool last) { + TopoDS_Wire w1 = TopoDS::Wire(a); + const TopoDS_Wire& w2 = TopoDS::Wire(b); + + if (override_next_) { + override_next_ = false; + TopoDS_Edge e = first_edge(w1); + w1 = adjust(w1, TopExp::FirstVertex(e, true), next_override_); + } + + TopoDS_Vertex w11, w12, w21, w22; + TopExp::Vertices(w1, w11, w12); + TopExp::Vertices(w2, w21, w22); + + gp_Pnt p1 = BRep_Tool::Pnt(w12); + gp_Pnt p2 = BRep_Tool::Pnt(w21); + + double dist = p1.Distance(p2); + + // Distance is within tolerance, this is fine + if (dist < p_) { + mw_.Add(w1); + goto check; + } + + // Distance is too large for attempting to move end points, add intermediate edge + if (dist > 1000. * p_) { + mw_.Add(w1); + mw_.Add(BRepBuilderAPI_MakeEdge(p1, p2)); + Logger::Warning("Added additional segment to close gap with length " + boost::lexical_cast(dist) + " to:", inst_); + goto check; + } + + { + TopTools_IndexedDataMapOfShapeListOfShape wmap1, wmap2; + + // Find edges connected to end- and begin vertex + TopExp::MapShapesAndAncestors(w1, TopAbs_VERTEX, TopAbs_EDGE, wmap1); + TopExp::MapShapesAndAncestors(w2, TopAbs_VERTEX, TopAbs_EDGE, wmap2); + + const TopTools_ListOfShape& last_edges = wmap1.FindFromKey(w12); + const TopTools_ListOfShape& first_edges = wmap2.FindFromKey(w21); + + double _, __; + if (last_edges.Extent() == 1 && first_edges.Extent() == 1) { + Handle(Geom_Curve) c1 = BRep_Tool::Curve(TopoDS::Edge(last_edges.First()), _, __); + Handle(Geom_Curve) c2 = BRep_Tool::Curve(TopoDS::Edge(first_edges.First()), _, __); + + const bool is_line1 = c1->DynamicType() == STANDARD_TYPE(Geom_Line); + const bool is_line2 = c2->DynamicType() == STANDARD_TYPE(Geom_Line); + + const bool is_circle1 = c1->DynamicType() == STANDARD_TYPE(Geom_Circle); + const bool is_circle2 = c2->DynamicType() == STANDARD_TYPE(Geom_Circle); + + // Preferably adjust the segment that is linear + if (is_line1 || (is_circle1 && !is_line2)) { + mw_.Add(adjust(w1, w12, p2)); + Logger::Notice("Adjusted edge end-point with distance " + boost::lexical_cast(dist) + " on:", inst_); + } else if ((is_line2 || is_circle2) && !last) { + mw_.Add(w1); + override_next_ = true; + next_override_ = p1; + Logger::Notice("Adjusted edge end-point with distance " + boost::lexical_cast(dist) + " on:", inst_); + } else { + // In all other cases an edge is added + mw_.Add(w1); + mw_.Add(BRepBuilderAPI_MakeEdge(p1, p2)); + Logger::Warning("Added additional segment to close gap with length " + boost::lexical_cast(dist) + " to:", inst_); + } + } else { + Logger::Error("Internal error, inconsistent wire segments", inst_); + mw_.Add(w1); + } + } + +check: + if (mw_.Error() == BRepBuilderAPI_NonManifoldWire) { + Logger::Error("Non-manifold curve segments:", inst_); + } else if (mw_.Error() == BRepBuilderAPI_DisconnectedWire) { + Logger::Error("Failed to join curve segments:", inst_); + } +} \ No newline at end of file diff --git a/src/ifcgeom/kernels/opencascade/wire_builder.h b/src/ifcgeom/kernels/opencascade/wire_builder.h new file mode 100644 index 0000000000..34178b815d --- /dev/null +++ b/src/ifcgeom/kernels/opencascade/wire_builder.h @@ -0,0 +1,105 @@ +/******************************************************************************** + * * + * This file is part of IfcOpenShell. * + * * + * IfcOpenShell is free software: you can redistribute it and/or modify * + * it under the terms of the Lesser GNU General Public License as published by * + * the Free Software Foundation, either version 3.0 of the License, or * + * (at your option) any later version. * + * * + * IfcOpenShell is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * Lesser GNU General Public License for more details. * + * * + * You should have received a copy of the Lesser GNU General Public License * + * along with this program. If not, see . * + * * + ********************************************************************************/ + +#ifndef WIRE_BUILDER_H +#define WIRE_BUILDER_H + +#include "../../../ifcparse/IfcBaseClass.h" + +#include + +#include +#include +#include + +#include +#include + +namespace IfcGeom { + namespace util { + // Returns the first edge of a wire + TopoDS_Edge first_edge(const TopoDS_Wire& w); + + // Returns new wire with the edge replaced by a linear edge with the vertex v moved to p + TopoDS_Wire adjust(const TopoDS_Wire& w, const TopoDS_Vertex& v, const gp_Pnt& p); + + // A wrapper around BRepBuilderAPI_MakeWire that makes sure segments are connected either by moving end points or by adding intermediate segments + class wire_builder { + private: + BRepBuilderAPI_MakeWire mw_; + double p_; + bool override_next_; + gp_Pnt next_override_; + const IfcUtil::IfcBaseClass* inst_; + + public: + wire_builder(double p, const IfcUtil::IfcBaseClass* inst = 0) : p_(p), override_next_(false), inst_(inst) {} + + void operator()(const TopoDS_Shape& a); + + void operator()(const TopoDS_Shape& a, const TopoDS_Shape& b, bool last); + + const TopoDS_Wire& wire() { return mw_.Wire(); } + }; + + template + void shape_pair_enumerate(TopTools_ListIteratorOfListOfShape& it, Fn& fn, bool closed) { + bool is_first = true; + TopoDS_Shape first, previous, current; + for (; it.More(); it.Next(), is_first = false) { + current = it.Value(); + if (is_first) { + first = current; + } else { + fn(previous, current, false); + } + previous = current; + } + if (closed) { + fn(current, first, true); + } else { + fn(current); + } + } + + /* + Below is code to deduce the formula below in SageMath + + | R, b = var('R b') + | + | Bxy = R * cos(b), R * sin(b) + | Cxy = R * cos(b/2), R * sin(b/2) + | + | def dot(v, w): + | return v[0] * w[0] + v[1] * w[1] + | + | def norm(v): + | l = sqrt(v[0]^2 + v[1]^2) + | return v[0] / l, v[1] / l + | + | (R - R*dot(norm(Cxy), norm(Bxy))).full_simplify() + */ + + double deflection_for_approximating_circle(double radius, double param); + + bool create_edge_over_curve_with_log_messages(const Handle_Geom_Curve& crv, const double eps, const gp_Pnt& p1, const gp_Pnt& p2, TopoDS_Edge& result); + } +} + +#endif diff --git a/src/ifcgeom/profile_helper.cpp b/src/ifcgeom/profile_helper.cpp new file mode 100644 index 0000000000..79c702b3b1 --- /dev/null +++ b/src/ifcgeom/profile_helper.cpp @@ -0,0 +1,122 @@ +#include "profile_helper.h" + +using namespace ifcopenshell::geometry; + +taxonomy::loop* ifcopenshell::geometry::polygon_from_points(const std::vector& ps, bool external) { + auto loop = new taxonomy::loop(); + loop->external = external; + boost::optional previous; + for (auto& p : ps) { + if (previous) { + auto e = new taxonomy::edge; + e->start = *previous; + e->end = p; + loop->children.push_back(e); + } + previous = p; + } + return loop; +} + +taxonomy::loop* ifcopenshell::geometry::profile_helper(Eigen::Matrix4d& m4, const std::vector& points) { + + /* TopoDS_Vertex* vertices = new TopoDS_Vertex[numVerts]; + + for (int i = 0; i < numVerts; i++) { + gp_XY xy(verts[2 * i], verts[2 * i + 1]); + trsf.Transforms(xy); + vertices[i] = BRepBuilderAPI_MakeVertex(gp_Pnt(xy.X(), xy.Y(), 0.0f)); + } + + BRepBuilderAPI_MakeWire w; + for (int i = 0; i < numVerts; i++) + w.Add(BRepBuilderAPI_MakeEdge(vertices[i], vertices[(i + 1) % numVerts])); + + TopoDS_Face face; + convert_wire_to_face(w.Wire(), face); + + if (numFillets && *std::max_element(filletRadii, filletRadii + numFillets) > ALMOST_ZERO) { + BRepFilletAPI_MakeFillet2d fillet(face); + for (int i = 0; i < numFillets; i++) { + const double radius = filletRadii[i]; + if (radius <= ALMOST_ZERO) continue; + fillet.AddFillet(vertices[filletIndices[i]], radius); + } + fillet.Build(); + if (fillet.IsDone()) { + face = TopoDS::Face(fillet.Shape()); + } else { + Logger::Error("Failed to process profile fillets"); + } + } + */ + + const bool has_position = !m4.isIdentity(); + + // @todo precision + + std::vector ps; + ps.reserve(points.size() + 1); + std::transform(points.begin(), points.end(), std::back_inserter(ps), [&has_position, &m4](const profile_point& p) { + if (has_position) { + Eigen::Vector4d v(p.xy[0], p.xy[1], 0., 1.); + v = m4 * v; + return taxonomy::point3(v(0), v(1), 0.); + } else { + return taxonomy::point3(p.xy[0], p.xy[1], 0.); + } + }); + ps.push_back(ps.front()); + + auto loop = polygon_from_points(ps); + + std::vector pps(points.size()); + for (int b = 0; b < points.size(); ++b) { + int c = (b - 1) % points.size(); + pps[b] = { Eigen::Vector2d(points[b].xy[0], points[b].xy[1]), points[b].radius, (taxonomy::edge*) loop->children[c], (taxonomy::edge*) loop->children[b] }; + } + + size_t i = pps.size(); + while (i--) { + const auto& p = pps[i]; + if (p.radius && *p.radius > 0.) { + // Position is a IfcAxis2Placement2D, so should remain 2d points + auto p0 = boost::get(p.previous->start).components_->head<2>(); + auto p1a = boost::get(p.previous->end).components_->head<2>(); + auto p2 = boost::get(p.next->end).components_->head<2>(); + auto p1b = boost::get(p.next->start).components_->head<2>(); + + auto ba_ = p0 - p1a; + auto bc_ = p2 - p1b; + + auto ba = ba_.normalized(); + auto bc = bc_.normalized(); + + const double angle = std::acos(ba.dot(bc)); + const double inset = *p.radius / std::tan(angle / 2.); + + boost::get(p.previous->end).components_->head<2>() += ba * inset; + boost::get(p.next->start).components_->head<2>() += bc * inset; + + auto e = new taxonomy::edge; + e->start = p.previous->end; + e->end = p.next->start; + + auto ab = Eigen::Vector3d(-ba(1), +ba(0), 0.); + + double sign = ab.head<2>().dot(bc) > 0 ? 1. : -1.; + + auto O = boost::get(p.previous->end).ccomponents().head<3>() + ab * *p.radius * sign; + + auto c = new taxonomy::circle; + c->matrix.components_ = new Eigen::Matrix4d(Eigen::Affine3d(Eigen::Translation3d(O)).matrix()); + c->radius = *p.radius; + e->basis = c; + c->orientation.reset(sign == -1.); + + loop->children.insert(std::find(loop->children.begin(), loop->children.end(), p.next), e); + } + }; + + return loop; +} \ No newline at end of file diff --git a/src/ifcgeom/profile_helper.h b/src/ifcgeom/profile_helper.h new file mode 100644 index 0000000000..60b8d3c21c --- /dev/null +++ b/src/ifcgeom/profile_helper.h @@ -0,0 +1,24 @@ +#include "taxonomy.h" + +namespace ifcopenshell { + + namespace geometry { + + struct profile_point { + std::array xy; + boost::optional radius; + }; + + struct profile_point_with_edges { + Eigen::Vector2d xy; + boost::optional radius; + taxonomy::edge *previous, *next; + }; + + taxonomy::loop* polygon_from_points(const std::vector& ps, bool external = true); + + taxonomy::loop* profile_helper(Eigen::Matrix4d& m4, const std::vector& points); + + } + +} \ No newline at end of file diff --git a/src/ifcgeom/schema/IfcArbitraryClosedProfileDef.cpp b/src/ifcgeom/schema/IfcArbitraryClosedProfileDef.cpp new file mode 100644 index 0000000000..0b1bc79301 --- /dev/null +++ b/src/ifcgeom/schema/IfcArbitraryClosedProfileDef.cpp @@ -0,0 +1,26 @@ +#include "mapping.h" +#define mapping POSTFIX_SCHEMA(mapping) +using namespace ifcopenshell::geometry; + +taxonomy::item* mapping::map_impl(const IfcSchema::IfcArbitraryClosedProfileDef* inst) { + auto loop = map(inst->OuterCurve()); + if (loop) { + auto face = new taxonomy::face; + ((taxonomy::loop*)loop)->external = true; + face->children = { loop }; + if (inst->as()) { + auto with_voids = inst->as(); + auto voids = with_voids->InnerCurves(); + for (auto& v : *voids) { + auto inner_loop = map(v); + if (inner_loop) { + ((taxonomy::loop*)inner_loop)->external = false; + face->children.push_back(inner_loop); + } + } + } + return face; + } else { + return nullptr; + } +} diff --git a/src/ifcgeom/schema/IfcAxis2Placement2D.cpp b/src/ifcgeom/schema/IfcAxis2Placement2D.cpp new file mode 100644 index 0000000000..eaa96e395e --- /dev/null +++ b/src/ifcgeom/schema/IfcAxis2Placement2D.cpp @@ -0,0 +1,17 @@ +#include "mapping.h" +#define mapping POSTFIX_SCHEMA(mapping) +using namespace ifcopenshell::geometry; + +taxonomy::item* mapping::map_impl(const IfcSchema::IfcAxis2Placement2D* inst) { + Eigen::Vector3d P, axis(0, 0, 1), V(1, 0, 0); + { + taxonomy::point3 v = as(map(inst->Location())); + P = *v.components_; + } + const bool hasRef = inst->hasRefDirection(); + if (hasRef) { + taxonomy::direction3 v = as(map(inst->RefDirection())); + V = *v.components_; + } + return new taxonomy::matrix4(P, axis, V); +} diff --git a/src/ifcgeom/schema/IfcAxis2Placement3D.cpp b/src/ifcgeom/schema/IfcAxis2Placement3D.cpp new file mode 100644 index 0000000000..4ff3ea0e8c --- /dev/null +++ b/src/ifcgeom/schema/IfcAxis2Placement3D.cpp @@ -0,0 +1,37 @@ +#include "mapping.h" +#define mapping POSTFIX_SCHEMA(mapping) +using namespace ifcopenshell::geometry; + +taxonomy::item* mapping::map_impl(const IfcSchema::IfcAxis2Placement3D* inst) { + Eigen::Vector3d o, axis(0, 0, 1), refDirection, X(1, 0, 0); + { + taxonomy::point3 v = as(map(inst->Location())); + o = *v.components_; + } + const bool hasAxis = inst->hasAxis(); + const bool hasRef = inst->hasRefDirection(); + + if (hasAxis != hasRef) { + Logger::Warning("Axis and RefDirection should be specified together", inst); + } + + if (hasAxis) { + taxonomy::direction3 v = as(map(inst->Axis())); + axis = *v.components_; + } + + if (hasRef) { + taxonomy::direction3 v = as(map(inst->RefDirection())); + refDirection = *v.components_; + } else { + if (acos(axis.dot(X)) > 1.e-5) { + refDirection = { 1., 0., 0. }; + } else { + refDirection = { 0., 0., 1. }; + } + auto Xvec = axis.dot(refDirection) * axis; + auto Xaxis = refDirection - Xvec; + refDirection = Xaxis; + } + return new taxonomy::matrix4(o, axis, refDirection); +} diff --git a/src/ifcgeom/schema/IfcBooleanResult.cpp b/src/ifcgeom/schema/IfcBooleanResult.cpp new file mode 100644 index 0000000000..0549c03f0f --- /dev/null +++ b/src/ifcgeom/schema/IfcBooleanResult.cpp @@ -0,0 +1,59 @@ +#include "mapping.h" +#define mapping POSTFIX_SCHEMA(mapping) +using namespace ifcopenshell::geometry; + +namespace { + taxonomy::boolean_result::operation_t boolean_op_type(IfcSchema::IfcBooleanOperator::Value op) { + if (op == IfcSchema::IfcBooleanOperator::IfcBooleanOperator_DIFFERENCE) { + return taxonomy::boolean_result::SUBTRACTION; + } else if (op == IfcSchema::IfcBooleanOperator::IfcBooleanOperator_INTERSECTION) { + return taxonomy::boolean_result::INTERSECTION; + } else if (op == IfcSchema::IfcBooleanOperator::IfcBooleanOperator_UNION) { + return taxonomy::boolean_result::UNION; + } else { + throw taxonomy::topology_error("Unknown boolean operation"); + } + } + +} + +taxonomy::item* mapping::map_impl(const IfcSchema::IfcBooleanResult* inst) { + IfcSchema::IfcBooleanOperand* operand1 = inst->FirstOperand(); + IfcSchema::IfcBooleanOperand* operand2 = inst->SecondOperand(); + + std::vector operands = { operand2 }; + + auto op = boolean_op_type(inst->Operator()); + + bool process_as_list = true; + while (true) { + auto res1 = operand1->as(); + if (res1) { + if (boolean_op_type(res1->Operator()) == op) { + operand1 = res1->FirstOperand(); + operands.push_back(res1->SecondOperand()); + } else { + process_as_list = false; + break; + } + } else { + operands.push_back(operand1); + break; + } + } + + if (process_as_list) { + std::reverse(operands.begin(), operands.end()); + } else { + operand1 = inst->FirstOperand(); + operands.clear(); + operands.push_back(operand1); + operands.push_back(operand2); + } + + auto br = map_to_collection(this, &operands); + if (br) { + br->operation = op; + } + return br; +} diff --git a/src/ifcgeom/schema/IfcCartesianPoint.cpp b/src/ifcgeom/schema/IfcCartesianPoint.cpp new file mode 100644 index 0000000000..da95a37bdb --- /dev/null +++ b/src/ifcgeom/schema/IfcCartesianPoint.cpp @@ -0,0 +1,12 @@ +#include "mapping.h" +#define mapping POSTFIX_SCHEMA(mapping) +using namespace ifcopenshell::geometry; + +taxonomy::item* mapping::map_impl(const IfcSchema::IfcCartesianPoint* inst) { + auto coords = inst->Coordinates(); + return new taxonomy::point3( + coords.size() >= 1 ? coords[0] * length_unit_ : 0., + coords.size() >= 2 ? coords[1] * length_unit_ : 0., + coords.size() >= 3 ? coords[2] * length_unit_ : 0. + ); +} diff --git a/src/ifcgeom/schema/IfcCartesianTransformationOperator2D.cpp b/src/ifcgeom/schema/IfcCartesianTransformationOperator2D.cpp new file mode 100644 index 0000000000..4c134a5878 --- /dev/null +++ b/src/ifcgeom/schema/IfcCartesianTransformationOperator2D.cpp @@ -0,0 +1,42 @@ +#include "mapping.h" +#define mapping POSTFIX_SCHEMA(mapping) +using namespace ifcopenshell::geometry; + +taxonomy::item* mapping::map_impl(const IfcSchema::IfcCartesianTransformationOperator2D* inst) { + auto m = new taxonomy::matrix4; + + Eigen::Vector4d origin, axis1(1.0, 0.0, 0.0, 0.0), axis2(0.0, 1.0, 0.0, 0.0), axis3(0.0, 0.0, 1.0, 0.0); + + taxonomy::point3 O = as(map(inst->LocalOrigin())); + origin << *O.components_, 1.0; + + if (inst->hasAxis1()) { + taxonomy::direction3 ax1 = as(map(inst->Axis1())); + axis1 << *ax1.components_, 0.0; + } + if (inst->hasAxis2()) { + taxonomy::direction3 ax2 = as(map(inst->Axis1())); + axis2 << *ax2.components_, 0.0; + } + + double scale1, scale2; + scale1 = scale2 = 1.0; + + if (inst->hasScale()) { + scale1 = inst->Scale(); + } + if (inst->as()) { + auto nu = inst->as(); + scale2 = nu->hasScale2() ? nu->Scale2() : scale1; + } + + m->components() << + axis1 * scale1, + axis2 * scale2, + axis3, + origin; + + m->components().transposeInPlace(); + + return m; +} diff --git a/src/ifcgeom/schema/IfcCartesianTransformationOperator3D.cpp b/src/ifcgeom/schema/IfcCartesianTransformationOperator3D.cpp new file mode 100644 index 0000000000..5abc448b3e --- /dev/null +++ b/src/ifcgeom/schema/IfcCartesianTransformationOperator3D.cpp @@ -0,0 +1,54 @@ +#include "mapping.h" +#define mapping POSTFIX_SCHEMA(mapping) +using namespace ifcopenshell::geometry; + +taxonomy::item* mapping::map_impl(const IfcSchema::IfcCartesianTransformationOperator3D* inst) { + auto m = new taxonomy::matrix4; + + Eigen::Vector4d origin; + Eigen::Vector4d axis1(1., 0., 0., 0.); + Eigen::Vector4d axis2(0., 1., 0., 0.); + Eigen::Vector4d axis3(0., 0., 1., 0.); + + taxonomy::point3 O = as(map(inst->LocalOrigin())); + origin << *O.components_, 1.0; + + if (inst->hasAxis1()) { + taxonomy::direction3 ax1 = as(map(inst->Axis1())); + axis1 << *ax1.components_, 0.0; + } + if (inst->hasAxis2()) { + taxonomy::direction3 ax2 = as(map(inst->Axis2())); + axis2 << *ax2.components_, 0.0; + } + if (inst->hasAxis3()) { + taxonomy::direction3 ax3 = as(map(inst->Axis3())); + axis3 << *ax3.components_, 0.0; + } + + double scale1, scale2, scale3; + scale1 = scale2 = scale3 = 1.; + + if (inst->hasScale()) { + scale1 = inst->Scale(); + } + if (inst->as()) { + auto nu = inst->as(); + scale2 = nu->hasScale2() ? nu->Scale2() : scale1; + scale3 = nu->hasScale3() ? nu->Scale3() : scale1; + } + + Eigen::Matrix4d tmp; + tmp << + axis1 * scale1, + axis2 * scale2, + axis3 * scale3, + origin; + + m->components() = tmp.inverse(); + m->components().transposeInPlace(); + + // @todo tag identity? + + return m; +} diff --git a/src/ifcgeom/schema/IfcCircle.cpp b/src/ifcgeom/schema/IfcCircle.cpp new file mode 100644 index 0000000000..9d483bfa5c --- /dev/null +++ b/src/ifcgeom/schema/IfcCircle.cpp @@ -0,0 +1,10 @@ +#include "mapping.h" +#define mapping POSTFIX_SCHEMA(mapping) +using namespace ifcopenshell::geometry; + +taxonomy::item* mapping::map_impl(const IfcSchema::IfcCircle* inst) { + auto c = new taxonomy::circle; + c->matrix = as(map(inst->Position())); + c->radius = inst->Radius() * length_unit_; + return c; +} diff --git a/src/ifcgeom/schema/IfcCircleProfileDef.cpp b/src/ifcgeom/schema/IfcCircleProfileDef.cpp new file mode 100644 index 0000000000..7dbb89c8db --- /dev/null +++ b/src/ifcgeom/schema/IfcCircleProfileDef.cpp @@ -0,0 +1,48 @@ +#include "mapping.h" +#define mapping POSTFIX_SCHEMA(mapping) +using namespace ifcopenshell::geometry; + +#include + +taxonomy::item* mapping::map_impl(const IfcSchema::IfcCircleProfileDef* inst) { + std::vector radii = { inst->Radius() * length_unit_ }; + + if (inst->as()) { + double t = inst->as()->WallThickness() * length_unit_; + radii.push_back(radii.front() - t); + } + + auto f = new taxonomy::face; + + for (auto it = radii.begin(); it != radii.end(); ++it) { + const double r = *it; + const bool exterior = it == radii.begin(); + + auto c = new taxonomy::circle; + c->radius = r; + + bool has_position = true; +#ifdef SCHEMA_IfcParameterizedProfileDef_Position_IS_OPTIONAL + has_position = inst->hasPosition(); +#endif + if (has_position) { + taxonomy::matrix4 m = as(map(inst->Position())); + if (m.components_) { + c->matrix = *m.components_; + } + } + + auto e = new taxonomy::edge; + e->basis = c; + e->start = 0.; + e->end = 2 * boost::math::constants::pi(); + + auto l = new taxonomy::loop; + l->children = { e }; + l->external = exterior; + + f->children.push_back(l); + } + + return f; +} diff --git a/src/ifcgeom/schema/IfcCompositeCurve.cpp b/src/ifcgeom/schema/IfcCompositeCurve.cpp new file mode 100644 index 0000000000..cc8155f308 --- /dev/null +++ b/src/ifcgeom/schema/IfcCompositeCurve.cpp @@ -0,0 +1,30 @@ +#include "mapping.h" +#define mapping POSTFIX_SCHEMA(mapping) +using namespace ifcopenshell::geometry; + +taxonomy::item* mapping::map_impl(const IfcSchema::IfcCompositeCurve* inst) { + auto loop = new taxonomy::loop; + auto segments = inst->Segments(); + for (auto& segment : *segments) { + auto crv = map(segment->ParentCurve()); + if (crv) { + if (crv->kind() == taxonomy::EDGE) { + ((taxonomy::edge*)crv)->orientation_2.reset(segment->SameSense()); + loop->children.push_back(crv); + } else if (crv->kind() == taxonomy::LOOP) { + if (!segment->SameSense()) { + crv->reverse(); + } + auto curve_segments = ((taxonomy::loop*)crv)->children_as(); + for (auto& s : curve_segments) { + loop->children.push_back(s); + } + // @todo delete crv without children + } + } + } + IfcEntityList::ptr profile = inst->data().getInverse(&IfcSchema::IfcProfileDef::Class(), -1); + const bool force_close = profile && profile->size() > 0; + loop->closed = force_close; + return loop; +} \ No newline at end of file diff --git a/src/ifcgeom/schema/IfcConnectedFaceSet.cpp b/src/ifcgeom/schema/IfcConnectedFaceSet.cpp new file mode 100644 index 0000000000..15240ba04c --- /dev/null +++ b/src/ifcgeom/schema/IfcConnectedFaceSet.cpp @@ -0,0 +1,12 @@ +#include "mapping.h" +#define mapping POSTFIX_SCHEMA(mapping) +using namespace ifcopenshell::geometry; + +taxonomy::item* mapping::map_impl(const IfcSchema::IfcConnectedFaceSet* inst) { + auto shell = map_to_collection(this, inst->CfsFaces()); + if (shell == nullptr) { + return nullptr; + } + shell->closed = inst->declaration().is(IfcSchema::IfcClosedShell::Class()); + return shell; +} diff --git a/src/ifcgeom/schema/IfcDirection.cpp b/src/ifcgeom/schema/IfcDirection.cpp new file mode 100644 index 0000000000..c036927e52 --- /dev/null +++ b/src/ifcgeom/schema/IfcDirection.cpp @@ -0,0 +1,12 @@ +#include "mapping.h" +#define mapping POSTFIX_SCHEMA(mapping) +using namespace ifcopenshell::geometry; + +taxonomy::item* mapping::map_impl(const IfcSchema::IfcDirection* inst) { + auto coords = inst->DirectionRatios(); + return new taxonomy::direction3( + coords.size() >= 1 ? coords[0] : 0., + coords.size() >= 2 ? coords[1] : 0., + coords.size() >= 3 ? coords[2] : 0. + ); +} \ No newline at end of file diff --git a/src/ifcgeom/schema/IfcExtrudedAreaSolid.cpp b/src/ifcgeom/schema/IfcExtrudedAreaSolid.cpp new file mode 100644 index 0000000000..c474efc71f --- /dev/null +++ b/src/ifcgeom/schema/IfcExtrudedAreaSolid.cpp @@ -0,0 +1,12 @@ +#include "mapping.h" +#define mapping POSTFIX_SCHEMA(mapping) +using namespace ifcopenshell::geometry; + +taxonomy::item* mapping::map_impl(const IfcSchema::IfcExtrudedAreaSolid* inst) { + return new taxonomy::extrusion( + as(map(inst->Position())), + as(map(inst->SweptArea())), + as(map(inst->ExtrudedDirection())), + inst->Depth() * length_unit_ + ); +} diff --git a/src/ifcgeom/schema/IfcFace.cpp b/src/ifcgeom/schema/IfcFace.cpp new file mode 100644 index 0000000000..819a6bc4b8 --- /dev/null +++ b/src/ifcgeom/schema/IfcFace.cpp @@ -0,0 +1,31 @@ +#include "mapping.h" +#define mapping POSTFIX_SCHEMA(mapping) +using namespace ifcopenshell::geometry; + +taxonomy::item* mapping::map_impl(const IfcSchema::IfcFace* inst) { + taxonomy::face* face = new taxonomy::face; + auto bounds = inst->Bounds(); + for (auto& bound : *bounds) { + if (auto r = map(bound->Bound())) { + if (!bound->Orientation()) { + r->reverse(); + } + if (bound->declaration().is(IfcSchema::IfcFaceOuterBound::Class())) { + ((taxonomy::loop*)r)->external = true; + /* + // Make a copy in case we need immutability later for e.g. caching + auto s = r->clone(); + ((taxonomy::loop*)s)->external = true; + delete r; + r = s; + */ + } + face->children.push_back(r); + } + } + if (face->children.empty()) { + delete face; + return nullptr; + } + return face; +} diff --git a/src/ifcgeom/schema/IfcFaceBasedSurfaceModel.cpp b/src/ifcgeom/schema/IfcFaceBasedSurfaceModel.cpp new file mode 100644 index 0000000000..6152817ac8 --- /dev/null +++ b/src/ifcgeom/schema/IfcFaceBasedSurfaceModel.cpp @@ -0,0 +1,7 @@ +#include "mapping.h" +#define mapping POSTFIX_SCHEMA(mapping) +using namespace ifcopenshell::geometry; + +taxonomy::item* mapping::map_impl(const IfcSchema::IfcFaceBasedSurfaceModel* inst) { + return map_to_collection(this, inst->FbsmFaces()); +} \ No newline at end of file diff --git a/src/ifcgeom/schema/IfcGeometricSet.cpp b/src/ifcgeom/schema/IfcGeometricSet.cpp new file mode 100644 index 0000000000..c9d2794595 --- /dev/null +++ b/src/ifcgeom/schema/IfcGeometricSet.cpp @@ -0,0 +1,7 @@ +#include "mapping.h" +#define mapping POSTFIX_SCHEMA(mapping) +using namespace ifcopenshell::geometry; + +taxonomy::item* mapping::map_impl(const IfcSchema::IfcGeometricSet* inst) { + return map_to_collection(this, inst->Elements()); +} \ No newline at end of file diff --git a/src/ifcgeom/schema/IfcHalfSpaceSolid.cpp b/src/ifcgeom/schema/IfcHalfSpaceSolid.cpp new file mode 100644 index 0000000000..0b2c7c3a0f --- /dev/null +++ b/src/ifcgeom/schema/IfcHalfSpaceSolid.cpp @@ -0,0 +1,17 @@ +#include "mapping.h" +#define mapping POSTFIX_SCHEMA(mapping) +using namespace ifcopenshell::geometry; + +taxonomy::item* mapping::map_impl(const IfcSchema::IfcHalfSpaceSolid* inst) { + IfcSchema::IfcSurface* surface = inst->BaseSurface(); + if (!surface->declaration().is(IfcSchema::IfcPlane::Class())) { + Logger::Message(Logger::LOG_ERROR, "Unsupported BaseSurface:", surface); + return nullptr; + } + auto p = new taxonomy::plane; + p->matrix = as(map(((IfcSchema::IfcPlane*)surface)->Position())); + p->orientation.reset(!inst->AgreementFlag()); + auto f = new taxonomy::face; + f->basis = p; + return f; +} diff --git a/src/ifcgeom/schema/IfcIShapeProfileDef.cpp b/src/ifcgeom/schema/IfcIShapeProfileDef.cpp new file mode 100644 index 0000000000..4609b040b7 --- /dev/null +++ b/src/ifcgeom/schema/IfcIShapeProfileDef.cpp @@ -0,0 +1,67 @@ +#include "mapping.h" +#define mapping POSTFIX_SCHEMA(mapping) +using namespace ifcopenshell::geometry; + +#include "../profile_helper.h" + +taxonomy::item* mapping::map_impl(const IfcSchema::IfcIShapeProfileDef* inst) { + const double x1 = inst->OverallWidth() / 2.0f * length_unit_; + const double y = inst->OverallDepth() / 2.0f * length_unit_; + const double d1 = inst->WebThickness() / 2.0f * length_unit_; + const double dy1 = inst->FlangeThickness() * length_unit_; + + bool doFillet1 = inst->hasFilletRadius(); + double f1 = 0.; + if (doFillet1) { + f1 = inst->FilletRadius() * length_unit_; + } + + bool doFillet2 = doFillet1; + double x2 = x1, dy2 = dy1, f2 = f1; + + if (inst->declaration().is(IfcSchema::IfcAsymmetricIShapeProfileDef::Class())) { + IfcSchema::IfcAsymmetricIShapeProfileDef* assym = (IfcSchema::IfcAsymmetricIShapeProfileDef*) inst; + x2 = assym->TopFlangeWidth() / 2. * length_unit_; + doFillet2 = assym->hasTopFlangeFilletRadius(); + if (doFillet2) { + f2 = assym->TopFlangeFilletRadius() * length_unit_; + } + if (assym->hasTopFlangeThickness()) { + dy2 = assym->TopFlangeThickness() * length_unit_; + } + } + + // @todo + const double precision_ = 1.e-5; + + if (x1 < precision_ || x2 < precision_ || y < precision_ || d1 < precision_ || dy1 < precision_ || dy2 < precision_) { + Logger::Message(Logger::LOG_NOTICE, "Skipping zero sized profile:", inst); + + return nullptr; + } + + Eigen::Matrix4d m4; + bool has_position = true; +#ifdef SCHEMA_IfcParameterizedProfileDef_Position_IS_OPTIONAL + has_position = inst->hasPosition(); +#endif + if (has_position) { + taxonomy::matrix4 m = as(map(inst->Position())); + m4 = m.ccomponents(); + } + + return profile_helper(m4, { + {{-x1,-y}}, + {{x1,-y}}, + {{x1,-y + dy1}}, + {{d1,-y + dy1}, f1}, + {{d1,y - dy2}, f2}, + {{x2,y - dy2}}, + {{x2,y}}, + {{-x2,y}}, + {{-x2,y - dy2}}, + {{-d1,y - dy2}, f2}, + {{-d1,-y + dy1}, f1}, + {{-x1,-y + dy1}} + }); +} diff --git a/src/ifcgeom/schema/IfcLocalPlacement.cpp b/src/ifcgeom/schema/IfcLocalPlacement.cpp new file mode 100644 index 0000000000..931a176866 --- /dev/null +++ b/src/ifcgeom/schema/IfcLocalPlacement.cpp @@ -0,0 +1,37 @@ +#include "mapping.h" +#define mapping POSTFIX_SCHEMA(mapping) +using namespace ifcopenshell::geometry; + +taxonomy::item* mapping::map_impl(const IfcSchema::IfcLocalPlacement* inst) { + IfcSchema::IfcLocalPlacement* current = (IfcSchema::IfcLocalPlacement*)inst; + auto m4 = new taxonomy::matrix4; + for (;;) { + IfcSchema::IfcAxis2Placement* relplacement = current->RelativePlacement(); + if (relplacement->declaration().is(IfcSchema::IfcAxis2Placement3D::Class())) { + taxonomy::matrix4 trsf2 = as(map(relplacement)); + // @todo check + m4->components() = trsf2.ccomponents() * m4->ccomponents(); + } + if (current->hasPlacementRelTo()) { + IfcSchema::IfcObjectPlacement* parent = current->PlacementRelTo(); + IfcSchema::IfcProduct::list::ptr parentPlaces = parent->PlacesObject(); + bool parentPlacesType = false; + for (IfcSchema::IfcProduct::list::it iter = parentPlaces->begin(); + iter != parentPlaces->end(); ++iter) { + if ((*iter)->declaration().is(*placement_rel_to_)) { + parentPlacesType = true; + } + } + if (parentPlacesType) { + break; + } else if (parent->declaration().is(IfcSchema::IfcLocalPlacement::Class())) { + current = (IfcSchema::IfcLocalPlacement*)current->PlacementRelTo(); + } else { + break; + } + } else { + break; + } + } + return m4; +} diff --git a/src/ifcgeom/schema/IfcManifoldSolidBrep.cpp b/src/ifcgeom/schema/IfcManifoldSolidBrep.cpp new file mode 100644 index 0000000000..7fe8d9d82d --- /dev/null +++ b/src/ifcgeom/schema/IfcManifoldSolidBrep.cpp @@ -0,0 +1,8 @@ +#include "mapping.h" +#define mapping POSTFIX_SCHEMA(mapping) +using namespace ifcopenshell::geometry; + +taxonomy::item* mapping::map_impl(const IfcSchema::IfcManifoldSolidBrep* inst) { + // @todo voids + return map(inst->Outer()); +} diff --git a/src/ifcgeom/schema/IfcMappedItem.cpp b/src/ifcgeom/schema/IfcMappedItem.cpp new file mode 100644 index 0000000000..a48a959902 --- /dev/null +++ b/src/ifcgeom/schema/IfcMappedItem.cpp @@ -0,0 +1,32 @@ +#include "mapping.h" +#define mapping POSTFIX_SCHEMA(mapping) +using namespace ifcopenshell::geometry; + +taxonomy::item* mapping::map_impl(const IfcSchema::IfcMappedItem* inst) { + IfcSchema::IfcCartesianTransformationOperator* transform = inst->MappingTarget(); + taxonomy::matrix4 gtrsf = as(map(transform)); + IfcSchema::IfcRepresentationMap* rmap = inst->MappingSource(); + IfcSchema::IfcAxis2Placement* placement = rmap->MappingOrigin(); + taxonomy::matrix4 trsf2 = as(map(placement)); + // Cannot be nullptr here + *gtrsf.components_ = *gtrsf.components_ * *trsf2.components_; + + // @todo immutable for caching? + // @todo allow for multiple levels of matrix? + auto shapes = map(rmap->MappedRepresentation()); + if (shapes == nullptr) { + return shapes; + } + + auto collection = new taxonomy::collection; + collection->children.push_back(shapes); + collection->matrix = *gtrsf.components_; + + if (shapes != nullptr) { + for (auto& c : ((taxonomy::collection*)shapes)->children) { + // @todo previously style was also copied. + } + } + + return collection; +} diff --git a/src/ifcgeom/schema/IfcPolyLoop.cpp b/src/ifcgeom/schema/IfcPolyLoop.cpp new file mode 100644 index 0000000000..3b615fec17 --- /dev/null +++ b/src/ifcgeom/schema/IfcPolyLoop.cpp @@ -0,0 +1,37 @@ +#include "mapping.h" +#define mapping POSTFIX_SCHEMA(mapping) +using namespace ifcopenshell::geometry; + +taxonomy::item* mapping::map_impl(const IfcSchema::IfcPolyLoop* inst) { + taxonomy::loop* loop = new taxonomy::loop; + + taxonomy::point3 first, previous; + bool is_first = true; + + auto points = inst->Polygon(); + for (auto& point : *points) { + auto p = as(map(point)); + if (is_first) { + previous = first = p; + is_first = false; + } else { + auto edge = new taxonomy::edge; + edge->start = previous; + edge->end = p; + loop->children.push_back(edge); + previous = p; + } + } + + auto edge = new taxonomy::edge; + edge->start = previous; + edge->end = first; + loop->children.push_back(edge); + + if (loop->children.size() < 3) { + Logger::Warning("Not enough edges for", inst); + delete loop; + return nullptr; + } + return loop; +} \ No newline at end of file diff --git a/src/ifcgeom/schema/IfcPolygonalBoundedHalfSpace.cpp b/src/ifcgeom/schema/IfcPolygonalBoundedHalfSpace.cpp new file mode 100644 index 0000000000..de21ca4d5b --- /dev/null +++ b/src/ifcgeom/schema/IfcPolygonalBoundedHalfSpace.cpp @@ -0,0 +1,10 @@ +#include "mapping.h" +#define mapping POSTFIX_SCHEMA(mapping) +using namespace ifcopenshell::geometry; + +taxonomy::item* mapping::map_impl(const IfcSchema::IfcPolygonalBoundedHalfSpace* inst) { + auto f = map_impl((IfcSchema::IfcHalfSpaceSolid*) inst); + ((taxonomy::face*)f)->children = { map(inst->PolygonalBoundary()) }; + ((taxonomy::face*)f)->matrix = as(map(inst->Position())); + return f; +} diff --git a/src/ifcgeom/schema/IfcPolyline.cpp b/src/ifcgeom/schema/IfcPolyline.cpp new file mode 100644 index 0000000000..c10658e5ae --- /dev/null +++ b/src/ifcgeom/schema/IfcPolyline.cpp @@ -0,0 +1,36 @@ +#include "mapping.h" +#define mapping POSTFIX_SCHEMA(mapping) +using namespace ifcopenshell::geometry; + +#include "../profile_helper.h" + +taxonomy::item* mapping::map_impl(const IfcSchema::IfcPolyline* inst) { + IfcSchema::IfcCartesianPoint::list::ptr points = inst->Points(); + + // @todo + const double precision_ = 1.e-5; + + // Parse and store the points in a sequence + std::vector polygon; + polygon.reserve(points->size()); + std::transform(points->begin(), points->end(), std::back_inserter(polygon), [this](const IfcSchema::IfcCartesianPoint* p) { + return as(map(p)); + }); + + const double eps = precision_ * 10; + const bool closed_by_proximity = polygon.size() >= 3 && (*polygon.front().components_ - *polygon.back().components_).norm() < eps; + + // @todo this removes the end point, since it's identical to the beginning. + if (closed_by_proximity) { + // polygon.resize(polygon.size() - 1); + } + + // Remove points that are too close to one another + // remove_duplicate_points_from_loop(polygon, closed_by_proximity, eps); + + if (polygon.size() < 2) { + return nullptr; + } + + return polygon_from_points(polygon); +} diff --git a/src/ifcgeom/schema/IfcProduct.cpp b/src/ifcgeom/schema/IfcProduct.cpp new file mode 100644 index 0000000000..540a2340f5 --- /dev/null +++ b/src/ifcgeom/schema/IfcProduct.cpp @@ -0,0 +1,63 @@ +#include "mapping.h" +#define mapping POSTFIX_SCHEMA(mapping) +using namespace ifcopenshell::geometry; + +taxonomy::item* mapping::map_impl(const IfcSchema::IfcProduct* inst) { + const bool use_body = !this->settings_.get(ifcopenshell::geometry::settings::INCLUDE_CURVES); + + auto openings = find_openings(inst); + // @todo const cast + auto reps = inst->data().file->traverse((IfcSchema::IfcProduct*) inst, 2)->as(); + IfcSchema::IfcRepresentation* body = nullptr; + for (auto& rep : *reps) { + if ((rep->RepresentationIdentifier() == "Body") == use_body) { + body = rep; + } + } + if (!body) { + return nullptr; + } + + auto c = new taxonomy::collection; + c->matrix = as(map(inst->ObjectPlacement())); + + const IfcSchema::IfcMaterial* single_material = get_single_material_association(inst); + if (single_material) { + auto material_style = map(single_material); + if (material_style) { + c->surface_style = (taxonomy::style*) material_style; + } + } + + if (openings->size() && !settings_.get(settings::DISABLE_OPENING_SUBTRACTIONS) && use_body) { + + Eigen::Matrix4d ci; + if (c->matrix.components_) { + ci = c->matrix.components_->inverse(); + } else { + ci.setIdentity(); + } + + IfcEntityList::ptr operands(new IfcEntityList); + operands->push(body); + operands->push(openings); + auto n = map_to_collection(this, operands); + std::for_each(n->children.begin() + 1, n->children.end(), [&ci](taxonomy::item* i) { + ((taxonomy::geom_item*)i)->matrix.components() = ci * ((taxonomy::geom_item*)i)->matrix.ccomponents(); + }); + n->operation = taxonomy::boolean_result::SUBTRACTION; + // @todo one indirection too many + n->instance = inst; + c->children = { n }; + } else { + auto child = map(body); + if (child) { + c->children = { child }; + } else { + delete c; + return nullptr; + } + } + + return c; +} diff --git a/src/ifcgeom/schema/IfcRectangleHollowProfileDef.cpp b/src/ifcgeom/schema/IfcRectangleHollowProfileDef.cpp new file mode 100644 index 0000000000..be8ba01086 --- /dev/null +++ b/src/ifcgeom/schema/IfcRectangleHollowProfileDef.cpp @@ -0,0 +1,63 @@ +#include "mapping.h" +#define mapping POSTFIX_SCHEMA(mapping) +using namespace ifcopenshell::geometry; + +#include "../profile_helper.h" + +taxonomy::item* mapping::map_impl(const IfcSchema::IfcRectangleHollowProfileDef* inst) { + const double x = inst->XDim() / 2.0f * length_unit_; + const double y = inst->YDim() / 2.0f * length_unit_; + const double d = inst->WallThickness() * length_unit_; + + boost::optional radius1, radius2; + if (inst->hasOuterFilletRadius()) { + radius1 = inst->OuterFilletRadius() * length_unit_; + } + if (inst->hasInnerFilletRadius()) { + radius2 = inst->InnerFilletRadius() * length_unit_; + } + + // @todo + const double precision_ = 1.e-5; + + if (x < precision_ || y < precision_) { + Logger::Message(Logger::LOG_NOTICE, "Skipping zero sized profile:", inst); + return nullptr; + } + + Eigen::Matrix4d m4; + bool has_position = true; +#ifdef SCHEMA_IfcParameterizedProfileDef_Position_IS_OPTIONAL + has_position = inst->hasPosition(); +#endif + if (has_position) { + taxonomy::matrix4 m = as(map(inst->Position())); + m4 = m.ccomponents(); + } + + auto outer_loop = profile_helper(m4, { + {{-x, -y}, radius1}, + {{+x, -y}, radius1}, + {{+x, +y}, radius1}, + {{-x, +y}, radius1}, + }); + outer_loop->external = true; + + auto inner_loop = profile_helper(m4, { + {{-x + d, -y + d}, radius2}, + {{+x - d, -y + d}, radius2}, + {{+x - d, +y - d}, radius2}, + {{-x + d, +y - d}, radius2}, + }); + inner_loop->reverse(); + inner_loop->external = false; + + auto face = new taxonomy::face; + face->children = { outer_loop, inner_loop }; + + // @todo is this necessary; + std::swap(outer_loop->matrix, face->matrix); + inner_loop->matrix = outer_loop->matrix; + + return face; +} \ No newline at end of file diff --git a/src/ifcgeom/schema/IfcRectangleProfileDef.cpp b/src/ifcgeom/schema/IfcRectangleProfileDef.cpp new file mode 100644 index 0000000000..025d7576d8 --- /dev/null +++ b/src/ifcgeom/schema/IfcRectangleProfileDef.cpp @@ -0,0 +1,39 @@ +#include "mapping.h" +#define mapping POSTFIX_SCHEMA(mapping) +using namespace ifcopenshell::geometry; + +#include "../profile_helper.h" + +taxonomy::item* mapping::map_impl(const IfcSchema::IfcRectangleProfileDef* inst) { + const double x = inst->XDim() / 2.0f * length_unit_; + const double y = inst->YDim() / 2.0f * length_unit_; + boost::optional radius; + if (inst->as()) { + radius = inst->as()->RoundingRadius() * length_unit_; + } + + // @todo + const double precision_ = 1.e-5; + + if (x < precision_ || y < precision_) { + Logger::Message(Logger::LOG_NOTICE, "Skipping zero sized profile:", inst); + return nullptr; + } + + Eigen::Matrix4d m4; + bool has_position = true; +#ifdef SCHEMA_IfcParameterizedProfileDef_Position_IS_OPTIONAL + has_position = inst->hasPosition(); +#endif + if (has_position) { + taxonomy::matrix4 m = as(map(inst->Position())); + m4 = m.ccomponents(); + } + + return profile_helper(m4, { + {{-x, -y}, radius}, + {{+x, -y}, radius}, + {{+x, +y}, radius}, + {{-x, +y}, radius}, + }); +} diff --git a/src/ifcgeom/schema/IfcRepresentation.cpp b/src/ifcgeom/schema/IfcRepresentation.cpp new file mode 100644 index 0000000000..aeed1c8073 --- /dev/null +++ b/src/ifcgeom/schema/IfcRepresentation.cpp @@ -0,0 +1,26 @@ +#include "mapping.h" +#define mapping POSTFIX_SCHEMA(mapping) +using namespace ifcopenshell::geometry; + +taxonomy::item* mapping::map_impl(const IfcSchema::IfcRepresentation* inst) { + const bool use_body = !this->settings_.get(ifcopenshell::geometry::settings::INCLUDE_CURVES); + + auto items = map_to_collection(this, inst->Items()); + if (items == nullptr) { + return nullptr; + } + + /* + // Don't blindly flatten, as we're culling away IfcMappedItem transformations + + auto flat = flatten(items); + if (flat == nullptr) { + return nullptr; + } + */ + + return filter_in_place(items, [&use_body](taxonomy::item* i) { + // @todo just filter loops for now. + return (i->kind() != taxonomy::LOOP) == use_body; + }); +} diff --git a/src/ifcgeom/schema/IfcShellBasedSurfaceModel.cpp b/src/ifcgeom/schema/IfcShellBasedSurfaceModel.cpp new file mode 100644 index 0000000000..a6088f0842 --- /dev/null +++ b/src/ifcgeom/schema/IfcShellBasedSurfaceModel.cpp @@ -0,0 +1,7 @@ +#include "mapping.h" +#define mapping POSTFIX_SCHEMA(mapping) +using namespace ifcopenshell::geometry; + +taxonomy::item* mapping::map_impl(const IfcSchema::IfcShellBasedSurfaceModel* inst) { + return map_to_collection(this, inst->SbsmBoundary()); +} \ No newline at end of file diff --git a/src/ifcgeom/schema/IfcTrimmedCurve.cpp b/src/ifcgeom/schema/IfcTrimmedCurve.cpp new file mode 100644 index 0000000000..67ff5264b2 --- /dev/null +++ b/src/ifcgeom/schema/IfcTrimmedCurve.cpp @@ -0,0 +1,112 @@ +#include "mapping.h" +#define mapping POSTFIX_SCHEMA(mapping) +using namespace ifcopenshell::geometry; + +#include + +taxonomy::item* mapping::map_impl(const IfcSchema::IfcTrimmedCurve* inst) { + IfcSchema::IfcCurve* basis_curve = inst->BasisCurve(); + bool isConic = basis_curve->declaration().is(IfcSchema::IfcConic::Class()); + double parameterFactor = isConic ? angle_unit_ : length_unit_; + + auto tc = new taxonomy::edge; + tc->basis = map(inst->BasisCurve()); + + bool trim_cartesian = inst->MasterRepresentation() != IfcSchema::IfcTrimmingPreference::IfcTrimmingPreference_PARAMETER; + IfcEntityList::ptr trims1 = inst->Trim1(); + IfcEntityList::ptr trims2 = inst->Trim2(); + + // reversed orientation handling happens in geometry kernel + unsigned sense_agreement = 0; // inst->SenseAgreement() ? 0 : 1; + double flts[2]; + taxonomy::point3 pnts[2]; + bool has_flts[2] = { false,false }; + bool has_pnts[2] = { false,false }; + + tc->orientation = inst->SenseAgreement(); + + for (IfcEntityList::it it = trims1->begin(); it != trims1->end(); it++) { + IfcUtil::IfcBaseClass* i = *it; + if (i->declaration().is(IfcSchema::IfcCartesianPoint::Class())) { + pnts[sense_agreement] = as(map(i)); + has_pnts[sense_agreement] = true; + } else if (i->declaration().is(IfcSchema::IfcParameterValue::Class())) { + const double value = *((IfcSchema::IfcParameterValue*)i); + flts[sense_agreement] = value * parameterFactor; + has_flts[sense_agreement] = true; + } + } + + for (IfcEntityList::it it = trims2->begin(); it != trims2->end(); it++) { + IfcUtil::IfcBaseClass* i = *it; + if (i->declaration().is(IfcSchema::IfcCartesianPoint::Class())) { + pnts[1 - sense_agreement] = as(map(i)); + has_pnts[1 - sense_agreement] = true; + } else if (i->declaration().is(IfcSchema::IfcParameterValue::Class())) { + const double value = *((IfcSchema::IfcParameterValue*)i); + flts[1 - sense_agreement] = value * parameterFactor; + has_flts[1 - sense_agreement] = true; + } + } + + // @todo + const double precision_ = 1.e-5; + + trim_cartesian &= has_pnts[0] && has_pnts[1]; + if (trim_cartesian) { + if ((*pnts[0].components_ - *pnts[1].components_).norm() < (2 * precision_)) { + Logger::Message(Logger::LOG_WARNING, "Skipping segment with length below tolerance level:", inst); + return nullptr; + } + tc->start = pnts[0]; + tc->end = pnts[1]; + } else if (has_flts[0] && has_flts[1]) { + // The Geom_Line is constructed from a gp_Pnt and gp_Dir, whereas the IfcLine + // is defined by an IfcCartesianPoint and an IfcVector with Magnitude. Because + // the vector is normalised when passed to Geom_Line constructor the magnitude + // needs to be factored in with the IfcParameterValue here. + if (basis_curve->declaration().is(IfcSchema::IfcLine::Class())) { + IfcSchema::IfcLine* line = static_cast(basis_curve); + const double magnitude = line->Dir()->Magnitude(); + flts[0] *= magnitude; flts[1] *= magnitude; + } + if (basis_curve->declaration().is(IfcSchema::IfcEllipse::Class())) { + IfcSchema::IfcEllipse* ellipse = static_cast(basis_curve); + double x = ellipse->SemiAxis1() * length_unit_; + double y = ellipse->SemiAxis2() * length_unit_; + // @todo the need for this rotation is OCCT-specific + const bool rotated = y > x; + if (rotated) { + flts[0] -= boost::math::constants::pi() / 2.; + flts[1] -= boost::math::constants::pi() / 2.; + } + } + tc->start = flts[0]; + tc->end = flts[1]; + } + + /* + // @todo + if (isConic) { + // Tiny circle segnments can cause issues later on, for example + // when the comp curve is used as the sweeping directrix. + double a, b; + Handle(Geom_Curve) crv = BRep_Tool::Curve(e, a, b); + double radius = -1.; + if (crv->DynamicType() == STANDARD_TYPE(Geom_Circle)) { + radius = Handle(Geom_Circle)::DownCast(crv)->Radius(); + } else if (crv->DynamicType() == STANDARD_TYPE(Geom_Ellipse)) { + // The formula above is for circles, but probably good enough + radius = Handle(Geom_Ellipse)::DownCast(crv)->MajorRadius(); + } + if (radius > 0. && deflection_for_approximating_circle(radius, b - a) < getValue(GV_PRECISION)) { + TopoDS_Vertex v0, v1; + TopExp::Vertices(e, v0, v1); + e = TopoDS::Edge(BRepBuilderAPI_MakeEdge(v0, v1).Edge().Oriented(e.Orientation())); + Logger::Warning("Subsituted edge with linear approximation", l); + } + } + */ + + return tc; +} diff --git a/src/ifcgeom/schema/mapping.cpp b/src/ifcgeom/schema/mapping.cpp index 82ba02ba8b..290068c912 100644 --- a/src/ifcgeom/schema/mapping.cpp +++ b/src/ifcgeom/schema/mapping.cpp @@ -45,542 +45,6 @@ void MAKE_INIT_FN(MappingImplementation)(ifcopenshell::geometry::impl::MappingFa #define mapping POSTFIX_SCHEMA(mapping) -namespace { - // Hacks around not wanting to use if constexpr - template - class loop_to_face_upgrade { - public: - loop_to_face_upgrade(taxonomy::item*) {} - - operator bool() const { - return false; - } - - operator taxonomy::face() const { - throw taxonomy::topology_error(); - } - - operator T() const { - throw taxonomy::topology_error(); - } - }; - - template <> - class loop_to_face_upgrade { - private: - boost::optional face_; - public: - loop_to_face_upgrade(taxonomy::item* item) { - taxonomy::loop* loop = dynamic_cast(item); - if (loop) { - loop->external = true; - - face_.emplace(); - face_->instance = loop->instance; - face_->matrix = loop->matrix; - face_->children = { loop->clone() }; - } - } - - operator bool() const { - return face_.is_initialized(); - } - - operator taxonomy::face() const { - return *face_; - } - }; - - // A RAII-based mechanism to cast the conversion results - // from map() into the right type expected by the higher - // level typology items. An exception is thrown if the - // types do not match or the result was nullptr. A copy - // will be assigned to the higher level topology member - // and the original pointer will be deleted. - - // This class is also able to uplift some topology items - // to higher level types, such as a loop to a face, which - // is why the cast operator does not return a reference. - template - class as { - private: - taxonomy::item* item_; - - public: - as(taxonomy::item* item) : item_(item) {} - operator T() const { - if (!item_) { - throw taxonomy::topology_error("item was nullptr"); - } - T* t = dynamic_cast(item_); - if (t) { - return T(*t); - } else { - { - loop_to_face_upgrade upgrade(item_); - if (upgrade) { - return upgrade; - } - } - throw taxonomy::topology_error("item does not match type"); - } - } - ~as() { - delete item_; - } - }; - - template - U* map_to_collection(mapping* m, const T& ts) { - auto c = new U; - if (ts->size()) { - for (auto it = ts->begin(); it != ts->end(); ++it) { - if (auto r = m->map(*it)) { - c->children.push_back(r); - } - } - } - if (c->children.empty()) { - delete c; - return nullptr; - } - return c; - } -}; - -taxonomy::item* mapping::map_impl(const IfcSchema::IfcExtrudedAreaSolid* inst) { - return new taxonomy::extrusion( - as(map(inst->Position())), - as(map(inst->SweptArea())), - as(map(inst->ExtrudedDirection())), - inst->Depth() * length_unit_ - ); -} - -namespace { - template - void visit(taxonomy::collection* deep, Fn fn) { - for (auto& c : deep->children) { - if (c->kind() == taxonomy::COLLECTION) { - visit((taxonomy::collection*)c, fn); - } else { - fn(c); - } - } - } - - taxonomy::collection* flatten(taxonomy::collection* deep) { - auto flat = new taxonomy::collection; - visit(deep, [&flat](taxonomy::item* i) { - flat->children.push_back(i); - }); - return flat; - } - - template - bool apply_predicate_to_collection(taxonomy::item* i, Fn fn) { - if (i->kind() == taxonomy::COLLECTION) { - auto c = (taxonomy::collection*) i; - for (auto& child : c->children) { - if (apply_predicate_to_collection(child, fn)) { - return true; - } - } - } else { - return fn(i); - } - } - - // @nb traverses nested collections - template - taxonomy::collection* filter(taxonomy::collection* collection, Fn fn) { - auto filtered = new taxonomy::collection; - for (auto& child : collection->children) { - if (apply_predicate_to_collection(child, fn)) { - filtered->children.push_back(child->clone()); - } - } - if (filtered->children.empty()) { - delete filtered; - return nullptr; - } - return filtered; - } - - // @nb traverses nested collections - template - taxonomy::collection* filter_in_place(taxonomy::collection* collection, Fn fn) { - for (auto it = --collection->children.end(); it >= collection->children.begin(); --it) { - if (!apply_predicate_to_collection(*it, fn)) { - delete *it; - collection->children.erase(it); - } - } - return collection; - } -} - -taxonomy::item* mapping::map_impl(const IfcSchema::IfcRepresentation* inst) { - const bool use_body = !this->settings_.get(ifcopenshell::geometry::settings::INCLUDE_CURVES); - - auto items = map_to_collection(this, inst->Items()); - if (items == nullptr) { - return nullptr; - } - - /* - // Don't blindly flatten, as we're culling away IfcMappedItem transformations - - auto flat = flatten(items); - if (flat == nullptr) { - return nullptr; - } - */ - - return filter_in_place(items, [&use_body](taxonomy::item* i) { - // @todo just filter loops for now. - return (i->kind() != taxonomy::LOOP) == use_body; - }); -} - -taxonomy::item* mapping::map_impl(const IfcSchema::IfcFaceBasedSurfaceModel* inst) { - return map_to_collection(this, inst->FbsmFaces()); -} - -taxonomy::item* mapping::map_impl(const IfcSchema::IfcShellBasedSurfaceModel* inst) { - return map_to_collection(this, inst->SbsmBoundary()); -} - -taxonomy::item* mapping::map_impl(const IfcSchema::IfcManifoldSolidBrep* inst) { - // @todo voids - return map(inst->Outer()); -} - -taxonomy::item* mapping::map_impl(const IfcSchema::IfcGeometricSet* inst) { - return map_to_collection(this, inst->Elements()); -} - -taxonomy::item* mapping::map_impl(const IfcSchema::IfcConnectedFaceSet* inst) { - auto shell = map_to_collection(this, inst->CfsFaces()); - if (shell == nullptr) { - return nullptr; - } - shell->closed = inst->declaration().is(IfcSchema::IfcClosedShell::Class()); - return shell; -} - -taxonomy::item* mapping::map_impl(const IfcSchema::IfcFace* inst) { - taxonomy::face* face = new taxonomy::face; - auto bounds = inst->Bounds(); - for (auto& bound : *bounds) { - if (auto r = map(bound->Bound())) { - if (!bound->Orientation()) { - r->reverse(); - } - if (bound->declaration().is(IfcSchema::IfcFaceOuterBound::Class())) { - ((taxonomy::loop*)r)->external = true; - /* - // Make a copy in case we need immutability later for e.g. caching - auto s = r->clone(); - ((taxonomy::loop*)s)->external = true; - delete r; - r = s; - */ - } - face->children.push_back(r); - } - } - if (face->children.empty()) { - delete face; - return nullptr; - } - return face; -} - -taxonomy::item* mapping::map_impl(const IfcSchema::IfcPolyLoop* inst) { - taxonomy::loop* loop = new taxonomy::loop; - - taxonomy::point3 first, previous; - bool is_first = true; - - auto points = inst->Polygon(); - for (auto& point : *points) { - auto p = as(map(point)); - if (is_first) { - previous = first = p; - is_first = false; - } else { - auto edge = new taxonomy::edge; - edge->start = previous; - edge->end = p; - loop->children.push_back(edge); - previous = p; - } - } - - auto edge = new taxonomy::edge; - edge->start = previous; - edge->end = first; - loop->children.push_back(edge); - - if (loop->children.size() < 3) { - Logger::Warning("Not enough edges for", inst); - delete loop; - return nullptr; - } - return loop; -} - -taxonomy::item* mapping::map_impl(const IfcSchema::IfcCartesianPoint* inst) { - auto coords = inst->Coordinates(); - return new taxonomy::point3( - coords.size() >= 1 ? coords[0] * length_unit_ : 0., - coords.size() >= 2 ? coords[1] * length_unit_ : 0., - coords.size() >= 3 ? coords[2] * length_unit_ : 0. - ); -} - -taxonomy::item* mapping::map_impl(const IfcSchema::IfcDirection* inst) { - auto coords = inst->DirectionRatios(); - return new taxonomy::direction3( - coords.size() >= 1 ? coords[0] : 0., - coords.size() >= 2 ? coords[1] : 0., - coords.size() >= 3 ? coords[2] : 0. - ); -} - -taxonomy::item* mapping::map_impl(const IfcSchema::IfcProduct* inst) { - const bool use_body = !this->settings_.get(ifcopenshell::geometry::settings::INCLUDE_CURVES); - - auto openings = find_openings(inst); - // @todo const cast - auto reps = inst->data().file->traverse((IfcSchema::IfcProduct*) inst, 2)->as(); - IfcSchema::IfcRepresentation* body = nullptr; - for (auto& rep : *reps) { - if ((rep->RepresentationIdentifier() == "Body") == use_body) { - body = rep; - } - } - if (!body) { - return nullptr; - } - - auto c = new taxonomy::collection; - c->matrix = as(map(inst->ObjectPlacement())); - - const IfcSchema::IfcMaterial* single_material = get_single_material_association(inst); - if (single_material) { - auto material_style = map(single_material); - if (material_style) { - c->surface_style = (taxonomy::style*) material_style; - } - } - - if (openings->size() && !settings_.get(settings::DISABLE_OPENING_SUBTRACTIONS) && use_body) { - - Eigen::Matrix4d ci; - if (c->matrix.components_) { - ci = c->matrix.components_->inverse(); - } else { - ci.setIdentity(); - } - - IfcEntityList::ptr operands(new IfcEntityList); - operands->push(body); - operands->push(openings); - auto n = map_to_collection(this, operands); - std::for_each(n->children.begin() + 1, n->children.end(), [&ci](taxonomy::item* i) { - ((taxonomy::geom_item*)i)->matrix.components() = ci * ((taxonomy::geom_item*)i)->matrix.ccomponents(); - }); - n->operation = taxonomy::boolean_result::SUBTRACTION; - // @todo one indirection too many - n->instance = inst; - c->children = { n }; - } else { - auto child = map(body); - if (child) { - c->children = { child }; - } else { - delete c; - return nullptr; - } - } - - return c; -} - -taxonomy::item* mapping::map_impl(const IfcSchema::IfcAxis2Placement3D* inst) { - Eigen::Vector3d o, axis(0, 0, 1), refDirection, X(1, 0, 0); - { - taxonomy::point3 v = as(map(inst->Location())); - o = *v.components_; - } - const bool hasAxis = inst->hasAxis(); - const bool hasRef = inst->hasRefDirection(); - - if (hasAxis != hasRef) { - Logger::Warning("Axis and RefDirection should be specified together", inst); - } - - if (hasAxis) { - taxonomy::direction3 v = as(map(inst->Axis())); - axis = *v.components_; - } - - if (hasRef) { - taxonomy::direction3 v = as(map(inst->RefDirection())); - refDirection = *v.components_; - } else { - if (acos(axis.dot(X)) > 1.e-5) { - refDirection = { 1., 0., 0. }; - } else { - refDirection = { 0., 0., 1. }; - } - auto Xvec = axis.dot(refDirection) * axis; - auto Xaxis = refDirection - Xvec; - refDirection = Xaxis; - } - return new taxonomy::matrix4(o, axis, refDirection); -} - -taxonomy::item* mapping::map_impl(const IfcSchema::IfcAxis2Placement2D* inst) { - Eigen::Vector3d P, axis(0, 0, 1), V(1, 0, 0); - { - taxonomy::point3 v = as(map(inst->Location())); - P = *v.components_; - } - const bool hasRef = inst->hasRefDirection(); - if (hasRef) { - taxonomy::direction3 v = as(map(inst->RefDirection())); - V = *v.components_; - } - return new taxonomy::matrix4(P, axis, V); -} - -taxonomy::item* mapping::map_impl(const IfcSchema::IfcCartesianTransformationOperator2D* inst) { - auto m = new taxonomy::matrix4; - - Eigen::Vector4d origin, axis1(1.0, 0.0, 0.0, 0.0), axis2(0.0, 1.0, 0.0, 0.0), axis3(0.0, 0.0, 1.0, 0.0); - - taxonomy::point3 O = as(map(inst->LocalOrigin())); - origin << *O.components_, 1.0; - - if (inst->hasAxis1()) { - taxonomy::direction3 ax1 = as(map(inst->Axis1())); - axis1 << *ax1.components_, 0.0; - } - if (inst->hasAxis2()) { - taxonomy::direction3 ax2 = as(map(inst->Axis1())); - axis2 << *ax2.components_, 0.0; - } - - double scale1, scale2; - scale1 = scale2 = 1.0; - - if (inst->hasScale()) { - scale1 = inst->Scale(); - } - if (inst->as()) { - auto nu = inst->as(); - scale2 = nu->hasScale2() ? nu->Scale2() : scale1; - } - - m->components() << - axis1 * scale1, - axis2 * scale2, - axis3, - origin; - - m->components().transposeInPlace(); - - return m; -} - -taxonomy::item* mapping::map_impl(const IfcSchema::IfcCartesianTransformationOperator3D* inst) { - auto m = new taxonomy::matrix4; - - Eigen::Vector4d origin; - Eigen::Vector4d axis1(1., 0., 0., 0.); - Eigen::Vector4d axis2(0., 1., 0., 0.); - Eigen::Vector4d axis3(0., 0., 1., 0.); - - taxonomy::point3 O = as(map(inst->LocalOrigin())); - origin << *O.components_, 1.0; - - if (inst->hasAxis1()) { - taxonomy::direction3 ax1 = as(map(inst->Axis1())); - axis1 << *ax1.components_, 0.0; - } - if (inst->hasAxis2()) { - taxonomy::direction3 ax2 = as(map(inst->Axis2())); - axis2 << *ax2.components_, 0.0; - } - if (inst->hasAxis3()) { - taxonomy::direction3 ax3 = as(map(inst->Axis3())); - axis3 << *ax3.components_, 0.0; - } - - double scale1, scale2, scale3; - scale1 = scale2 = scale3 = 1.; - - if (inst->hasScale()) { - scale1 = inst->Scale(); - } - if (inst->as()) { - auto nu = inst->as(); - scale2 = nu->hasScale2() ? nu->Scale2() : scale1; - scale3 = nu->hasScale3() ? nu->Scale3() : scale1; - } - - Eigen::Matrix4d tmp; - tmp << - axis1 * scale1, - axis2 * scale2, - axis3 * scale3, - origin; - - m->components() = tmp.inverse(); - m->components().transposeInPlace(); - - // @todo tag identity? - - return m; -} - -taxonomy::item* mapping::map_impl(const IfcSchema::IfcLocalPlacement* inst) { - IfcSchema::IfcLocalPlacement* current = (IfcSchema::IfcLocalPlacement*)inst; - auto m4 = new taxonomy::matrix4; - for (;;) { - IfcSchema::IfcAxis2Placement* relplacement = current->RelativePlacement(); - if (relplacement->declaration().is(IfcSchema::IfcAxis2Placement3D::Class())) { - taxonomy::matrix4 trsf2 = as(map(relplacement)); - // @todo check - m4->components() = trsf2.ccomponents() * m4->ccomponents(); - } - if (current->hasPlacementRelTo()) { - IfcSchema::IfcObjectPlacement* parent = current->PlacementRelTo(); - IfcSchema::IfcProduct::list::ptr parentPlaces = parent->PlacesObject(); - bool parentPlacesType = false; - for (IfcSchema::IfcProduct::list::it iter = parentPlaces->begin(); - iter != parentPlaces->end(); ++iter) { - if ((*iter)->declaration().is(*placement_rel_to_)) { - parentPlacesType = true; - } - } - if (parentPlacesType) { - break; - } else if (parent->declaration().is(IfcSchema::IfcLocalPlacement::Class())) { - current = (IfcSchema::IfcLocalPlacement*)current->PlacementRelTo(); - } else { - break; - } - } else { - break; - } - } - return m4; -} - IfcSchema::IfcProduct::list::ptr mapping::products_represented_by(const IfcSchema::IfcRepresentation* representation, bool only_direct) { IfcSchema::IfcProduct::list::ptr products(new IfcSchema::IfcProduct::list); @@ -1216,639 +680,3 @@ void mapping::initialize_units_() { Logger::Warning("No plane angle unit encountered"); } } - -namespace { - struct profile_point { - std::array xy; - boost::optional radius; - }; - - struct profile_point_with_edges { - Eigen::Vector2d xy; - boost::optional radius; - taxonomy::edge *previous, *next; - }; - - taxonomy::loop* polygon_from_points(const std::vector& ps, bool external = true) { - auto loop = new taxonomy::loop(); - loop->external = external; - boost::optional previous; - for (auto& p : ps) { - if (previous) { - auto e = new taxonomy::edge; - e->start = *previous; - e->end = p; - loop->children.push_back(e); - } - previous = p; - } - return loop; - } - - taxonomy::loop* profile_helper(mapping* self, const IfcSchema::IfcParameterizedProfileDef* inst, const std::vector& points) { - - /* TopoDS_Vertex* vertices = new TopoDS_Vertex[numVerts]; - - for (int i = 0; i < numVerts; i++) { - gp_XY xy(verts[2 * i], verts[2 * i + 1]); - trsf.Transforms(xy); - vertices[i] = BRepBuilderAPI_MakeVertex(gp_Pnt(xy.X(), xy.Y(), 0.0f)); - } - - BRepBuilderAPI_MakeWire w; - for (int i = 0; i < numVerts; i++) - w.Add(BRepBuilderAPI_MakeEdge(vertices[i], vertices[(i + 1) % numVerts])); - - TopoDS_Face face; - convert_wire_to_face(w.Wire(), face); - - if (numFillets && *std::max_element(filletRadii, filletRadii + numFillets) > ALMOST_ZERO) { - BRepFilletAPI_MakeFillet2d fillet(face); - for (int i = 0; i < numFillets; i++) { - const double radius = filletRadii[i]; - if (radius <= ALMOST_ZERO) continue; - fillet.AddFillet(vertices[filletIndices[i]], radius); - } - fillet.Build(); - if (fillet.IsDone()) { - face = TopoDS::Face(fillet.Shape()); - } else { - Logger::Error("Failed to process profile fillets"); - } - } - */ - - Eigen::Matrix4d m4; - - bool has_position = true; -#ifdef SCHEMA_IfcParameterizedProfileDef_Position_IS_OPTIONAL - has_position = inst->hasPosition(); -#endif - if (has_position) { - taxonomy::matrix4 m = as(self->map(inst->Position())); - m4 = m.ccomponents(); - } - - // @todo precision - if (m4.isIdentity()) { - has_position = false; - } - - std::vector ps; - ps.reserve(points.size() + 1); - std::transform(points.begin(), points.end(), std::back_inserter(ps), [&has_position, &m4](const profile_point& p) { - if (has_position) { - Eigen::Vector4d v(p.xy[0], p.xy[1], 0., 1.); - v = m4 * v; - return taxonomy::point3(v(0), v(1), 0.); - } else { - return taxonomy::point3(p.xy[0], p.xy[1], 0.); - } - }); - ps.push_back(ps.front()); - - auto loop = polygon_from_points(ps); - - std::vector pps(points.size()); - for (int b = 0; b < points.size(); ++b) { - int c = (b - 1) % points.size(); - pps[b] = { Eigen::Vector2d(points[b].xy[0], points[b].xy[1]), points[b].radius, (taxonomy::edge*) loop->children[c], (taxonomy::edge*) loop->children[b]}; - } - - size_t i = pps.size(); - while (i--) { - const auto& p = pps[i]; - if (p.radius && *p.radius > 0.) { - // Position is a IfcAxis2Placement2D, so should remain 2d points - auto p0 = boost::get(p.previous->start).components_->head<2>(); - auto p1a = boost::get(p.previous->end).components_->head<2>(); - auto p2 = boost::get(p.next->end).components_->head<2>(); - auto p1b = boost::get(p.next->start).components_->head<2>(); - - auto ba_ = p0 - p1a; - auto bc_ = p2 - p1b; - - auto ba = ba_.normalized(); - auto bc = bc_.normalized(); - - const double angle = std::acos(ba.dot(bc)); - const double inset = *p.radius / std::tan(angle / 2.); - - boost::get(p.previous->end).components_->head<2>() += ba * inset; - boost::get(p.next->start).components_->head<2>() += bc * inset; - - auto e = new taxonomy::edge; - e->start = p.previous->end; - e->end = p.next->start; - - auto ab = Eigen::Vector3d(-ba(1), +ba(0), 0.); - - double sign = ab.head<2>().dot(bc) > 0 ? 1. : -1.; - - auto O = boost::get(p.previous->end).ccomponents().head<3>() + ab * *p.radius * sign; - - auto c = new taxonomy::circle; - c->matrix.components_ = new Eigen::Matrix4d(Eigen::Affine3d(Eigen::Translation3d(O)).matrix()); - c->radius = *p.radius; - e->basis = c; - c->orientation.reset(sign == -1.); - - loop->children.insert(std::find(loop->children.begin(), loop->children.end(), p.next), e); - } - }; - - return loop; - } -} - -taxonomy::item* mapping::map_impl(const IfcSchema::IfcRectangleProfileDef* inst) { - const double x = inst->XDim() / 2.0f * length_unit_; - const double y = inst->YDim() / 2.0f * length_unit_; - boost::optional radius; - if (inst->as()) { - radius = inst->as()->RoundingRadius() * length_unit_; - } - - // @todo - const double precision_ = 1.e-5; - - if (x < precision_ || y < precision_) { - Logger::Message(Logger::LOG_NOTICE, "Skipping zero sized profile:", inst); - return nullptr; - } - - return profile_helper(this, inst, { - {{-x, -y}, radius}, - {{+x, -y}, radius}, - {{+x, +y}, radius}, - {{-x, +y}, radius}, - }); -} - -taxonomy::item* mapping::map_impl(const IfcSchema::IfcRectangleHollowProfileDef* inst) { - const double x = inst->XDim() / 2.0f * length_unit_; - const double y = inst->YDim() / 2.0f * length_unit_; - const double d = inst->WallThickness() * length_unit_; - - boost::optional radius1, radius2; - if (inst->hasOuterFilletRadius()) { - radius1 = inst->OuterFilletRadius() * length_unit_; - } - if (inst->hasInnerFilletRadius()) { - radius2 = inst->InnerFilletRadius() * length_unit_; - } - - // @todo - const double precision_ = 1.e-5; - - if (x < precision_ || y < precision_) { - Logger::Message(Logger::LOG_NOTICE, "Skipping zero sized profile:", inst); - return nullptr; - } - - auto outer_loop = profile_helper(this, inst, { - {{-x, -y}, radius1}, - {{+x, -y}, radius1}, - {{+x, +y}, radius1}, - {{-x, +y}, radius1}, - }); - outer_loop->external = true; - - auto inner_loop = profile_helper(this, inst, { - {{-x + d, -y + d}, radius2}, - {{+x - d, -y + d}, radius2}, - {{+x - d, +y - d}, radius2}, - {{-x + d, +y - d}, radius2}, - }); - inner_loop->reverse(); - inner_loop->external = false; - - auto face = new taxonomy::face; - face->children = { outer_loop, inner_loop }; - - // @todo is this necessary; - std::swap(outer_loop->matrix, face->matrix); - inner_loop->matrix = outer_loop->matrix; - - return face; -} - -taxonomy::item* mapping::map_impl(const IfcSchema::IfcCircleProfileDef* inst) { - std::vector radii = { inst->Radius() * length_unit_ }; - - if (inst->as()) { - double t = inst->as()->WallThickness() * length_unit_; - radii.push_back(radii.front() - t); - } - - auto f = new taxonomy::face; - - for (auto it = radii.begin(); it != radii.end(); ++it) { - const double r = *it; - const bool exterior = it == radii.begin(); - - auto c = new taxonomy::circle; - c->radius = r; - - bool has_position = true; -#ifdef SCHEMA_IfcParameterizedProfileDef_Position_IS_OPTIONAL - has_position = inst->hasPosition(); -#endif - if (has_position) { - taxonomy::matrix4 m = as(map(inst->Position())); - if (m.components_) { - c->matrix = *m.components_; - } - } - - auto e = new taxonomy::edge; - e->basis = c; - e->start = 0.; - e->end = 2 * M_PI; - - auto l = new taxonomy::loop; - l->children = { e }; - l->external = exterior; - - f->children.push_back(l); - } - - return f; -} - -taxonomy::item* mapping::map_impl(const IfcSchema::IfcIShapeProfileDef* inst) { - const double x1 = inst->OverallWidth() / 2.0f * length_unit_; - const double y = inst->OverallDepth() / 2.0f * length_unit_; - const double d1 = inst->WebThickness() / 2.0f * length_unit_; - const double dy1 = inst->FlangeThickness() * length_unit_; - - bool doFillet1 = inst->hasFilletRadius(); - double f1 = 0.; - if (doFillet1) { - f1 = inst->FilletRadius() * length_unit_; - } - - bool doFillet2 = doFillet1; - double x2 = x1, dy2 = dy1, f2 = f1; - - if (inst->declaration().is(IfcSchema::IfcAsymmetricIShapeProfileDef::Class())) { - IfcSchema::IfcAsymmetricIShapeProfileDef* assym = (IfcSchema::IfcAsymmetricIShapeProfileDef*) inst; - x2 = assym->TopFlangeWidth() / 2. * length_unit_; - doFillet2 = assym->hasTopFlangeFilletRadius(); - if (doFillet2) { - f2 = assym->TopFlangeFilletRadius() * length_unit_; - } - if (assym->hasTopFlangeThickness()) { - dy2 = assym->TopFlangeThickness() * length_unit_; - } - } - - // @todo - const double precision_ = 1.e-5; - - if (x1 < precision_ || x2 < precision_ || y < precision_ || d1 < precision_ || dy1 < precision_ || dy2 < precision_) { - Logger::Message(Logger::LOG_NOTICE, "Skipping zero sized profile:", inst); - - return nullptr; - } - - return profile_helper(this, inst, { - {{-x1,-y}}, - {{x1,-y}}, - {{x1,-y + dy1}}, - {{d1,-y + dy1}, f1}, - {{d1,y - dy2}, f2}, - {{x2,y - dy2}}, - {{x2,y}}, - {{-x2,y}}, - {{-x2,y - dy2}}, - {{-d1,y - dy2}, f2}, - {{-d1,-y + dy1}, f1}, - {{-x1,-y + dy1}} - }); -} - -taxonomy::item* mapping::map_impl(const IfcSchema::IfcArbitraryClosedProfileDef* inst) { - auto loop = map(inst->OuterCurve()); - if (loop) { - auto face = new taxonomy::face; - ((taxonomy::loop*)loop)->external = true; - face->children = { loop }; - if (inst->as()) { - auto with_voids = inst->as(); - auto voids = with_voids->InnerCurves(); - for (auto& v : *voids) { - auto inner_loop = map(v); - if (inner_loop) { - ((taxonomy::loop*)inner_loop)->external = false; - face->children.push_back(inner_loop); - } - } - } - return face; - } else { - return nullptr; - } -} - -namespace { - void remove_duplicate_points_from_loop(std::vector& polygon, bool closed, double tol) { - for (;;) { - bool removed = false; - int n = polygon.size() - (closed ? 0 : 1); - for (int i = 1; i <= n; ++i) { - // wrap around to the first point in case of a closed loop - int j = (i % polygon.size()) + 1; - double dist = (polygon.at(i - 1).components() - polygon.at(j - 1).components()).squaredNorm(); - if (dist < tol) { - // do not remove the first or last point to - // maintain connectivity with other wires - if ((closed && j == 1) || (!closed && j == n)) polygon.erase(polygon.begin() + i - 1); - else polygon.erase(polygon.begin() + j - 1); - removed = true; - break; - } - } - if (!removed) break; - } - } -} - -taxonomy::item* mapping::map_impl(const IfcSchema::IfcPolyline* inst) { - IfcSchema::IfcCartesianPoint::list::ptr points = inst->Points(); - - // @todo - const double precision_ = 1.e-5; - - // Parse and store the points in a sequence - std::vector polygon; - polygon.reserve(points->size()); - std::transform(points->begin(), points->end(), std::back_inserter(polygon), [this](const IfcSchema::IfcCartesianPoint* p) { - return as(map(p)); - }); - - const double eps = precision_ * 10; - const bool closed_by_proximity = polygon.size() >= 3 && (*polygon.front().components_ - *polygon.back().components_).norm() < eps; - - // @todo this removes the end point, since it's identical to the beginning. - if (closed_by_proximity) { - // polygon.resize(polygon.size() - 1); - } - - // Remove points that are too close to one another - // remove_duplicate_points_from_loop(polygon, closed_by_proximity, eps); - - if (polygon.size() < 2) { - return nullptr; - } - - return polygon_from_points(polygon); -} - -taxonomy::item* mapping::map_impl(const IfcSchema::IfcMappedItem* inst) { - IfcSchema::IfcCartesianTransformationOperator* transform = inst->MappingTarget(); - taxonomy::matrix4 gtrsf = as(map(transform)); - IfcSchema::IfcRepresentationMap* rmap = inst->MappingSource(); - IfcSchema::IfcAxis2Placement* placement = rmap->MappingOrigin(); - taxonomy::matrix4 trsf2 = as(map(placement)); - // Cannot be nullptr here - *gtrsf.components_ = *gtrsf.components_ * *trsf2.components_; - - // @todo immutable for caching? - // @todo allow for multiple levels of matrix? - auto shapes = map(rmap->MappedRepresentation()); - if (shapes == nullptr) { - return shapes; - } - - auto collection = new taxonomy::collection; - collection->children.push_back(shapes); - collection->matrix = *gtrsf.components_; - - if (shapes != nullptr) { - for (auto& c : ((taxonomy::collection*)shapes)->children) { - // @todo previously style was also copied. - } - } - - return collection; -} - -taxonomy::item* mapping::map_impl(const IfcSchema::IfcCompositeCurve* inst) { - auto loop = new taxonomy::loop; - auto segments = inst->Segments(); - for (auto& segment : *segments) { - auto crv = map(segment->ParentCurve()); - if (crv) { - if (crv->kind() == taxonomy::EDGE) { - ((taxonomy::edge*)crv)->orientation_2.reset(segment->SameSense()); - loop->children.push_back(crv); - } else if (crv->kind() == taxonomy::LOOP) { - if (!segment->SameSense()) { - crv->reverse(); - } - auto curve_segments = ((taxonomy::loop*)crv)->children_as(); - for (auto& s : curve_segments) { - loop->children.push_back(s); - } - // @todo delete crv without children - } - } - } - IfcEntityList::ptr profile = inst->data().getInverse(&IfcSchema::IfcProfileDef::Class(), -1); - const bool force_close = profile && profile->size() > 0; - loop->closed = force_close; - return loop; -} - -taxonomy::item* mapping::map_impl(const IfcSchema::IfcTrimmedCurve* inst) { - IfcSchema::IfcCurve* basis_curve = inst->BasisCurve(); - bool isConic = basis_curve->declaration().is(IfcSchema::IfcConic::Class()); - double parameterFactor = isConic ? angle_unit_ : length_unit_; - - auto tc = new taxonomy::edge; - tc->basis = map(inst->BasisCurve()); - - bool trim_cartesian = inst->MasterRepresentation() != IfcSchema::IfcTrimmingPreference::IfcTrimmingPreference_PARAMETER; - IfcEntityList::ptr trims1 = inst->Trim1(); - IfcEntityList::ptr trims2 = inst->Trim2(); - - // reversed orientation handling happens in geometry kernel - unsigned sense_agreement = 0; // inst->SenseAgreement() ? 0 : 1; - double flts[2]; - taxonomy::point3 pnts[2]; - bool has_flts[2] = { false,false }; - bool has_pnts[2] = { false,false }; - - tc->orientation = inst->SenseAgreement(); - - for (IfcEntityList::it it = trims1->begin(); it != trims1->end(); it++) { - IfcUtil::IfcBaseClass* i = *it; - if (i->declaration().is(IfcSchema::IfcCartesianPoint::Class())) { - pnts[sense_agreement] = as(map(i)); - has_pnts[sense_agreement] = true; - } else if (i->declaration().is(IfcSchema::IfcParameterValue::Class())) { - const double value = *((IfcSchema::IfcParameterValue*)i); - flts[sense_agreement] = value * parameterFactor; - has_flts[sense_agreement] = true; - } - } - - for (IfcEntityList::it it = trims2->begin(); it != trims2->end(); it++) { - IfcUtil::IfcBaseClass* i = *it; - if (i->declaration().is(IfcSchema::IfcCartesianPoint::Class())) { - pnts[1 - sense_agreement] = as(map(i)); - has_pnts[1 - sense_agreement] = true; - } else if (i->declaration().is(IfcSchema::IfcParameterValue::Class())) { - const double value = *((IfcSchema::IfcParameterValue*)i); - flts[1 - sense_agreement] = value * parameterFactor; - has_flts[1 - sense_agreement] = true; - } - } - - // @todo - const double precision_ = 1.e-5; - - trim_cartesian &= has_pnts[0] && has_pnts[1]; - if (trim_cartesian) { - if ((*pnts[0].components_ - *pnts[1].components_).norm() < (2 * precision_)) { - Logger::Message(Logger::LOG_WARNING, "Skipping segment with length below tolerance level:", inst); - return nullptr; - } - tc->start = pnts[0]; - tc->end = pnts[1]; - } else if (has_flts[0] && has_flts[1]) { - // The Geom_Line is constructed from a gp_Pnt and gp_Dir, whereas the IfcLine - // is defined by an IfcCartesianPoint and an IfcVector with Magnitude. Because - // the vector is normalised when passed to Geom_Line constructor the magnitude - // needs to be factored in with the IfcParameterValue here. - if (basis_curve->declaration().is(IfcSchema::IfcLine::Class())) { - IfcSchema::IfcLine* line = static_cast(basis_curve); - const double magnitude = line->Dir()->Magnitude(); - flts[0] *= magnitude; flts[1] *= magnitude; - } - if (basis_curve->declaration().is(IfcSchema::IfcEllipse::Class())) { - IfcSchema::IfcEllipse* ellipse = static_cast(basis_curve); - double x = ellipse->SemiAxis1() * length_unit_; - double y = ellipse->SemiAxis2() * length_unit_; - // @todo the need for this rotation is OCCT-specific - const bool rotated = y > x; - if (rotated) { - flts[0] -= M_PI / 2.; - flts[1] -= M_PI / 2.; - } - } - tc->start = flts[0]; - tc->end = flts[1]; - } - - /* - // @todo - if (isConic) { - // Tiny circle segnments can cause issues later on, for example - // when the comp curve is used as the sweeping directrix. - double a, b; - Handle(Geom_Curve) crv = BRep_Tool::Curve(e, a, b); - double radius = -1.; - if (crv->DynamicType() == STANDARD_TYPE(Geom_Circle)) { - radius = Handle(Geom_Circle)::DownCast(crv)->Radius(); - } else if (crv->DynamicType() == STANDARD_TYPE(Geom_Ellipse)) { - // The formula above is for circles, but probably good enough - radius = Handle(Geom_Ellipse)::DownCast(crv)->MajorRadius(); - } - if (radius > 0. && deflection_for_approximating_circle(radius, b - a) < getValue(GV_PRECISION)) { - TopoDS_Vertex v0, v1; - TopExp::Vertices(e, v0, v1); - e = TopoDS::Edge(BRepBuilderAPI_MakeEdge(v0, v1).Edge().Oriented(e.Orientation())); - Logger::Warning("Subsituted edge with linear approximation", l); - } - } - */ - - return tc; -} - -taxonomy::item* mapping::map_impl(const IfcSchema::IfcCircle* inst) { - auto c = new taxonomy::circle; - c->matrix = as(map(inst->Position())); - c->radius = inst->Radius() * length_unit_; - return c; -} - -namespace { - taxonomy::boolean_result::operation_t boolean_op_type(IfcSchema::IfcBooleanOperator::Value op) { - if (op == IfcSchema::IfcBooleanOperator::IfcBooleanOperator_DIFFERENCE) { - return taxonomy::boolean_result::SUBTRACTION; - } else if (op == IfcSchema::IfcBooleanOperator::IfcBooleanOperator_INTERSECTION) { - return taxonomy::boolean_result::INTERSECTION; - } else if (op == IfcSchema::IfcBooleanOperator::IfcBooleanOperator_UNION) { - return taxonomy::boolean_result::UNION; - } else { - throw taxonomy::topology_error("Unknown boolean operation"); - } - } - -} - -taxonomy::item* mapping::map_impl(const IfcSchema::IfcBooleanResult* inst) { - IfcSchema::IfcBooleanOperand* operand1 = inst->FirstOperand(); - IfcSchema::IfcBooleanOperand* operand2 = inst->SecondOperand(); - - std::vector operands = { operand2 }; - - auto op = boolean_op_type(inst->Operator()); - - bool process_as_list = true; - while (true) { - auto res1 = operand1->as(); - if (res1) { - if (boolean_op_type(res1->Operator()) == op) { - operand1 = res1->FirstOperand(); - operands.push_back(res1->SecondOperand()); - } else { - process_as_list = false; - break; - } - } else { - operands.push_back(operand1); - break; - } - } - - if (process_as_list) { - std::reverse(operands.begin(), operands.end()); - } else { - operand1 = inst->FirstOperand(); - operands.clear(); - operands.push_back(operand1); - operands.push_back(operand2); - } - - auto br = map_to_collection(this, &operands); - if (br) { - br->operation = op; - } - return br; -} - -taxonomy::item* mapping::map_impl(const IfcSchema::IfcPolygonalBoundedHalfSpace* inst) { - auto f = map_impl((IfcSchema::IfcHalfSpaceSolid*) inst); - ((taxonomy::face*)f)->children = { map(inst->PolygonalBoundary()) }; - ((taxonomy::face*)f)->matrix = as(map(inst->Position())); - return f; -} - - -taxonomy::item* mapping::map_impl(const IfcSchema::IfcHalfSpaceSolid* inst) { - IfcSchema::IfcSurface* surface = inst->BaseSurface(); - if (!surface->declaration().is(IfcSchema::IfcPlane::Class())) { - Logger::Message(Logger::LOG_ERROR, "Unsupported BaseSurface:", surface); - return nullptr; - } - auto p = new taxonomy::plane; - p->matrix = as(map(((IfcSchema::IfcPlane*)surface)->Position())); - p->orientation.reset(!inst->AgreementFlag()); - auto f = new taxonomy::face; - f->basis = p; - return f; -} diff --git a/src/ifcgeom/schema/mapping.h b/src/ifcgeom/schema/mapping.h index 232ee8e848..2c5feb39a1 100644 --- a/src/ifcgeom/schema/mapping.h +++ b/src/ifcgeom/schema/mapping.h @@ -38,6 +38,107 @@ namespace geometry { #include "bind_convert_decl.i" }; + + // Hacks around not wanting to use if constexpr + template + class loop_to_face_upgrade { + public: + loop_to_face_upgrade(taxonomy::item*) {} + + operator bool() const { + return false; + } + + operator taxonomy::face() const { + throw taxonomy::topology_error(); + } + + operator T() const { + throw taxonomy::topology_error(); + } + }; + + template <> + class loop_to_face_upgrade { + private: + boost::optional face_; + public: + loop_to_face_upgrade(taxonomy::item* item) { + taxonomy::loop* loop = dynamic_cast(item); + if (loop) { + loop->external = true; + + face_.emplace(); + face_->instance = loop->instance; + face_->matrix = loop->matrix; + face_->children = { loop->clone() }; + } + } + + operator bool() const { + return face_.is_initialized(); + } + + operator taxonomy::face() const { + return *face_; + } + }; + + // A RAII-based mechanism to cast the conversion results + // from map() into the right type expected by the higher + // level typology items. An exception is thrown if the + // types do not match or the result was nullptr. A copy + // will be assigned to the higher level topology member + // and the original pointer will be deleted. + + // This class is also able to uplift some topology items + // to higher level types, such as a loop to a face, which + // is why the cast operator does not return a reference. + template + class as { + private: + taxonomy::item* item_; + + public: + as(taxonomy::item* item) : item_(item) {} + operator T() const { + if (!item_) { + throw taxonomy::topology_error("item was nullptr"); + } + T* t = dynamic_cast(item_); + if (t) { + return T(*t); + } else { + { + loop_to_face_upgrade upgrade(item_); + if (upgrade) { + return upgrade; + } + } + throw taxonomy::topology_error("item does not match type"); + } + } + ~as() { + delete item_; + } + }; + + template + U* map_to_collection(POSTFIX_SCHEMA(mapping)* m, const T& ts) { + auto c = new U; + if (ts->size()) { + for (auto it = ts->begin(); it != ts->end(); ++it) { + if (auto r = m->map(*it)) { + c->children.push_back(r); + } + } + } + if (c->children.empty()) { + delete c; + return nullptr; + } + return c; + } } diff --git a/src/ifcgeom/schema/mapping.i b/src/ifcgeom/schema/mapping.i index 382ee65f3b..109bf0a7bc 100644 --- a/src/ifcgeom/schema/mapping.i +++ b/src/ifcgeom/schema/mapping.i @@ -24,18 +24,20 @@ * * ********************************************************************************/ +// @todo bring back return value for reduced casting, casting of vector is expensive? + BIND(IfcProduct); -BIND(IfcShellBasedSurfaceModel); -BIND(IfcFaceBasedSurfaceModel); -BIND(IfcRepresentation); -BIND(IfcMappedItem); +BIND(IfcShellBasedSurfaceModel); // -> collection +BIND(IfcFaceBasedSurfaceModel); // -> collection +BIND(IfcRepresentation); // -> collection +BIND(IfcMappedItem); // -> collection // IfcFacetedBrep included // IfcAdvancedBrep included // IfcFacetedBrepWithVoids included // IfcAdvancedBrepWithVoids included -BIND(IfcManifoldSolidBrep); -BIND(IfcGeometricSet); +BIND(IfcManifoldSolidBrep); // -> shell +BIND(IfcGeometricSet); // -> collection #ifdef SCHEMA_HAS_IfcCylindricalSurface // BIND(IfcCylindricalSurface); @@ -53,12 +55,12 @@ BIND(IfcGeometricSet); #ifdef SCHEMA_HAS_IfcExtrudedAreaSolidTapered // BIND(IfcExtrudedAreaSolidTapered); #endif -BIND(IfcExtrudedAreaSolid); +BIND(IfcExtrudedAreaSolid); // -> extrusion // BIND(IfcRevolvedAreaSolid); -BIND(IfcConnectedFaceSet); -BIND(IfcBooleanResult); -BIND(IfcPolygonalBoundedHalfSpace); -BIND(IfcHalfSpaceSolid); +BIND(IfcConnectedFaceSet); // -> shell +BIND(IfcBooleanResult); // -> boolean_result +BIND(IfcPolygonalBoundedHalfSpace); // -> face +BIND(IfcHalfSpaceSolid); // -> face // BIND(IfcSurfaceOfLinearExtrusion); // BIND(IfcSurfaceOfRevolution); // BIND(IfcBlock); @@ -73,43 +75,43 @@ BIND(IfcHalfSpaceSolid); // BIND(IfcSweptDiskSolid); // IfcArbitraryProfileDefWithVoids included -BIND(IfcArbitraryClosedProfileDef); -BIND(IfcRectangleHollowProfileDef); +BIND(IfcArbitraryClosedProfileDef); // -> face +BIND(IfcRectangleHollowProfileDef); // -> face // IfcRoundedRectangleProfileDef included -BIND(IfcRectangleProfileDef); +BIND(IfcRectangleProfileDef); // -> face // BIND(IfcTrapeziumProfileDef) // BIND(IfcCShapeProfileDef); // IfcAsymmetricIShapeProfileDef included -BIND(IfcIShapeProfileDef); +BIND(IfcIShapeProfileDef); // -> face // BIND(IfcLShapeProfileDef); // BIND(IfcTShapeProfileDef); // BIND(IfcUShapeProfileDef); // BIND(IfcZShapeProfileDef); // IfcCircleHollowProfileDef included -BIND(IfcCircleProfileDef); +BIND(IfcCircleProfileDef); // -> face // BIND(IfcEllipseProfileDef); // BIND(IfcCenterLineProfileDef); // BIND(IfcCompositeProfileDef); // BIND(IfcDerivedProfileDef); // IfcFaceSurface included // IfcAdvancedFace included in case of IFC4 -BIND(IfcFace); +BIND(IfcFace); // -> face // BIND(IfcEdgeCurve); // BIND(IfcSubedge); // BIND(IfcOrientedEdge); // BIND(IfcEdge); // BIND(IfcEdgeLoop); -BIND(IfcPolyline); -BIND(IfcPolyLoop); -BIND(IfcCompositeCurve); -BIND(IfcTrimmedCurve); +BIND(IfcPolyline); // -> loop +BIND(IfcPolyLoop); // -> loop +BIND(IfcCompositeCurve); // -> loop +BIND(IfcTrimmedCurve); // -> edge // BIND(IfcArbitraryOpenProfileDef); #ifdef SCHEMA_HAS_IfcIndexedPolyCurve // BIND(IfcIndexedPolyCurve) #endif -BIND(IfcCircle); +BIND(IfcCircle); // -> circle // BIND(IfcEllipse); // BIND(IfcLine); #ifdef SCHEMA_HAS_IfcBSplineCurveWithKnots @@ -117,19 +119,19 @@ BIND(IfcCircle); // BIND(IfcBSplineCurveWithKnots); #endif -BIND(IfcCartesianPoint); -BIND(IfcDirection); -BIND(IfcAxis2Placement2D); -BIND(IfcAxis2Placement3D); +BIND(IfcCartesianPoint); // -> point3 +BIND(IfcDirection); // -> direction3 +BIND(IfcAxis2Placement2D); // -> matrix4 +BIND(IfcAxis2Placement3D); // -> matrix4 // BIND(IfcAxis1Placement); // IfcCartesianTransformationOperator2DnonUniform included -BIND(IfcCartesianTransformationOperator2D); +BIND(IfcCartesianTransformationOperator2D); // -> matrix4 // IfcCartesianTransformationOperator3DnonUniform included -BIND(IfcCartesianTransformationOperator3D); -BIND(IfcLocalPlacement); +BIND(IfcCartesianTransformationOperator3D); // -> matrix4 +BIND(IfcLocalPlacement); // -> matrix4 // BIND(IfcVector); // BIND(IfcPlane); // BIND(IfcColourRgb); -BIND(IfcMaterial); -BIND(IfcStyledItem); +BIND(IfcMaterial); // -> style +BIND(IfcStyledItem); // -> style diff --git a/src/ifcgeom/schema_agnostic/Converter.cpp b/src/ifcgeom/schema_agnostic/Converter.cpp index 965496aeec..d058e9b441 100644 --- a/src/ifcgeom/schema_agnostic/Converter.cpp +++ b/src/ifcgeom/schema_agnostic/Converter.cpp @@ -3,7 +3,8 @@ #include "../../ifcgeom/schema_agnostic/IfcGeomElement.h" ifcopenshell::geometry::Converter::Converter(const std::string& geometry_library, IfcParse::IfcFile* file, settings& s) - : settings_(s) + : geometry_library_(boost::to_lower_copy(geometry_library)) + , settings_(s) { kernel_ = kernels::construct(geometry_library, file); mapping_ = impl::mapping_implementations().construct(file, settings_); @@ -65,7 +66,9 @@ ifcopenshell::geometry::NativeElement* ifcopenshell::geometry::Converter::create total_geom_time += (geom_end - geom_start) / (double)CLOCKS_PER_SEC; double d; - substitute_with_box_based_on_density(shapes, d); + if (geometry_library_ == "cgal") { + substitute_with_box_based_on_density(shapes, d); + } shape = brep_ptr(new ifcopenshell::geometry::Representation::BRep(s, representation_id_builder.str(), shapes)); } catch (...) { diff --git a/src/ifcgeom/schema_agnostic/Converter.h b/src/ifcgeom/schema_agnostic/Converter.h index 5a1118f3d3..4b5f4d32b5 100644 --- a/src/ifcgeom/schema_agnostic/Converter.h +++ b/src/ifcgeom/schema_agnostic/Converter.h @@ -17,6 +17,7 @@ namespace ifcopenshell { namespace geometry { public: typedef boost::shared_ptr brep_ptr; private: + std::string geometry_library_; abstract_mapping* mapping_; kernels::AbstractKernel* kernel_; ifcopenshell::geometry::settings settings_; diff --git a/src/ifcgeom/taxonomy.cpp b/src/ifcgeom/taxonomy.cpp index 2f6fc21101..56bc1c0224 100644 --- a/src/ifcgeom/taxonomy.cpp +++ b/src/ifcgeom/taxonomy.cpp @@ -114,6 +114,10 @@ namespace { return *it == -1; } + bool compare(const node&, const node&) { + throw std::runtime_error("not implemented"); + } + bool compare(const style& a, const style& b) { const int order[5] = { less_to_order(a.name, b.name), @@ -246,4 +250,12 @@ namespace { return a.children.size() < b.children.size(); } } -} \ No newline at end of file +} + +ifcopenshell::geometry::taxonomy::collection * ifcopenshell::geometry::flatten(const taxonomy::collection * deep) { + auto flat = new taxonomy::collection; + visit(deep, [&flat](taxonomy::item* i) { + flat->children.push_back(i); + }); + return flat; +} diff --git a/src/ifcgeom/taxonomy.h b/src/ifcgeom/taxonomy.h index c0d5aaa894..59d7b2fc9a 100644 --- a/src/ifcgeom/taxonomy.h +++ b/src/ifcgeom/taxonomy.h @@ -12,6 +12,8 @@ #include #include +// @todo don't do std::less but use hashing and cache hash values. + namespace ifcopenshell { namespace geometry { @@ -341,6 +343,7 @@ struct edge : public trimmed_curve { virtual kinds kind() const { return EDGE; } }; +// template struct collection : public geom_item { std::vector children; @@ -388,7 +391,7 @@ struct collection : public geom_item { } }; -struct shell : public collection { +struct shell : public collection /**/ { boost::optional closed; virtual item* clone() const { return new shell(*this); } @@ -406,14 +409,14 @@ struct plane : public surface { } }; -struct face : public collection { +struct face : public collection /**/ { item* basis; virtual item* clone() const { return new face(*this); } virtual kinds kind() const { return FACE; } }; -struct loop : public collection { +struct loop : public collection /**/ { boost::optional external, closed; virtual item* clone() const { return new loop(*this); } @@ -443,11 +446,13 @@ struct extrusion : public sweep { } }; -struct node : public collection { +struct node : public item { std::map representations; virtual item* clone() const { return new node(*this); } virtual kinds kind() const { return NODE; } + + void print(std::ostream&, int = 0) const {} }; struct boolean_result : public collection { @@ -482,7 +487,81 @@ struct curves { } + template + void visit(const taxonomy::collection* deep, Fn fn) { + for (auto& c : deep->children) { + if (c->kind() == taxonomy::COLLECTION) { + visit((taxonomy::collection*)c, fn); + } else { + fn(c); + } + } + } + template + void visit_2(const taxonomy::collection* c, const Fn& fn) { + static_assert(std::is_same::value, "@todo Only implemented for point3"); + for (auto& i : c->children) { + if (dynamic_cast(i)) { + visit_2(dynamic_cast(i), fn); + } else if (i->kind() == taxonomy::POINT3) { + fn((const taxonomy::point3*) i); + } else if (i->kind() == taxonomy::EDGE) { + // @todo maybe make edge a collection then as well? + auto l = (const taxonomy::edge *) i; + if (l->start.which() == 0) { + fn(&boost::get(l->start)); + } + if (l->end.which() == 0) { + fn(&boost::get(l->end)); + } + } + } + } + + taxonomy::collection* flatten(const taxonomy::collection* deep); + + template + bool apply_predicate_to_collection(taxonomy::item* i, Fn fn) { + if (i->kind() == taxonomy::COLLECTION) { + auto c = (taxonomy::collection*) i; + for (auto& child : c->children) { + if (apply_predicate_to_collection(child, fn)) { + return true; + } + } + } else { + return fn(i); + } + } + + // @nb traverses nested collections + template + taxonomy::collection* filter(taxonomy::collection* collection, Fn fn) { + auto filtered = new taxonomy::collection; + for (auto& child : collection->children) { + if (apply_predicate_to_collection(child, fn)) { + filtered->children.push_back(child->clone()); + } + } + if (filtered->children.empty()) { + delete filtered; + return nullptr; + } + return filtered; + } + + // @nb traverses nested collections + template + taxonomy::collection* filter_in_place(taxonomy::collection* collection, Fn fn) { + for (auto it = --collection->children.end(); it >= collection->children.begin(); --it) { + if (!apply_predicate_to_collection(*it, fn)) { + delete *it; + collection->children.erase(it); + } + } + return collection; + } } diff --git a/src/serializers/XmlSerializer.cpp b/src/serializers/XmlSerializer.cpp index 66ab4c879c..8516698d73 100644 --- a/src/serializers/XmlSerializer.cpp +++ b/src/serializers/XmlSerializer.cpp @@ -2,14 +2,14 @@ extern void init_XmlSerializer_Ifc2x3(XmlSerializerFactory::Factory*); extern void init_XmlSerializer_Ifc4(XmlSerializerFactory::Factory*); -extern void init_XmlSerializer_Ifc4x1(XmlSerializerFactory::Factory*); -extern void init_XmlSerializer_Ifc4x2(XmlSerializerFactory::Factory*); +// extern void init_XmlSerializer_Ifc4x1(XmlSerializerFactory::Factory*); +// extern void init_XmlSerializer_Ifc4x2(XmlSerializerFactory::Factory*); XmlSerializerFactory::Factory::Factory() { init_XmlSerializer_Ifc2x3(this); init_XmlSerializer_Ifc4(this); - init_XmlSerializer_Ifc4x1(this); - init_XmlSerializer_Ifc4x2(this); + // init_XmlSerializer_Ifc4x1(this); + // init_XmlSerializer_Ifc4x2(this); } void XmlSerializerFactory::Factory::bind(const std::string& schema_name, fn f) {