diff --git a/src/ifcconvert/IfcConvert.cpp b/src/ifcconvert/IfcConvert.cpp index c95a838584..c0f32c8540 100644 --- a/src/ifcconvert/IfcConvert.cpp +++ b/src/ifcconvert/IfcConvert.cpp @@ -1030,6 +1030,7 @@ int main(int argc, char** argv) { #ifdef IFOPSH_WITH_OPENCASCADE if (output_extension == SVG) { + // @todo turn these all into proper settings if (vmap.count("section-height-from-storeys") != 0) { if (vmap.count("section-height")) { static_cast(serializer.get())->setSectionHeightsFromStoreys(section_height); diff --git a/src/ifcgeom/GeometrySerializer.h b/src/ifcgeom/GeometrySerializer.h index 75cf038e40..deef2c59c2 100644 --- a/src/ifcgeom/GeometrySerializer.h +++ b/src/ifcgeom/GeometrySerializer.h @@ -81,11 +81,17 @@ inline namespace settings { static constexpr const char* const name = "base-uri"; static constexpr const char* const description = "Base URI for products to be used in RDF-based serializations."; }; + + struct WktUseSection : public SettingBase { + static constexpr const char* const name = "wkt-use-section"; + static constexpr const char* const description = "Use a geometrical section rather than full polyhedral output and footprint in TTL WKT"; + static constexpr bool defaultvalue = false; + }; } class SerializerSettings : public SettingsContainer < // @todo should we use tuple_cat here to unify the settings into a single class? - std::tuple + std::tuple > {}; diff --git a/src/serializers/TtlWktSerializer.cpp b/src/serializers/TtlWktSerializer.cpp index e3a012a54f..ad3acc1c15 100644 --- a/src/serializers/TtlWktSerializer.cpp +++ b/src/serializers/TtlWktSerializer.cpp @@ -19,6 +19,19 @@ #include "TtlWktSerializer.h" +#ifdef IFOPSH_WITH_OPENCASCADE +#include "../ifcgeom/kernels/opencascade/OpenCascadeConversionResult.h" + +#include +#include +#include +#include +#include +#include +#include + +#endif + #include #include #include @@ -210,8 +223,15 @@ TtlWktSerializer::TtlWktSerializer(const stream_or_filename& filename, const ifc , filename_(filename) { const auto& tri_setting = geometry_settings.get().get(); - if (tri_setting != ifcopenshell::geometry::settings::POLYHEDRON_WITH_HOLES) { - throw std::runtime_error("The RDF Turtle WKT serializer needs POLYHEDRON_WITH_HOLES triangulation output"); + if (settings_.get().get()) { + const auto& it_output = geometry_settings.get().get(); + if (it_output != ifcopenshell::geometry::settings::NATIVE) { + throw std::runtime_error("The RDF Turtle WKT serializer needs native geometry when section mode is enabled"); + } + } else { + if (tri_setting != ifcopenshell::geometry::settings::POLYHEDRON_WITH_HOLES) { + throw std::runtime_error("The RDF Turtle WKT serializer needs POLYHEDRON_WITH_HOLES triangulation output"); + } } filename_.stream << std::setprecision(settings.get().get()); } @@ -237,32 +257,16 @@ void TtlWktSerializer::writeHeader() void TtlWktSerializer::write(const IfcGeom::TriangulationElement* o) { - auto ttl_object_id = [&](const char* const postfix = nullptr) { - using namespace ifcopenshell::geometry::settings; - auto oid = boost::replace_all_copy(object_id(o), "-", "_"); - if (oid.find('$') == std::string::npos) { - return "base:" + oid + (postfix ? postfix : (const char* const)""); - } else { - std::string base; - if (settings_.get().has()) { - base = settings_.get().get(); - } else { - base = "http://example.org/"; - } - return "<" + base + oid + (postfix ? postfix : (const char* const) "") + ">"; - } - }; - - filename_.stream << ttl_object_id() << " a geo:Feature ;\n"; + filename_.stream << ttl_object_id(o) << " a geo:Feature ;\n"; filename_.stream << " dcterms:identifier " << escape_for_turtle( IfcUtil::convert_utf8_to_utf32(o->guid())) << " ;\n"; filename_.stream << " rdfs:label " << escape_for_turtle( IfcUtil::convert_utf8_to_utf32(o->name()) ) << " ;\n"; - filename_.stream << " geo:hasGeometry " << ttl_object_id("_geometry") << " .\n\n"; + filename_.stream << " geo:hasGeometry " << ttl_object_id(o, "_geometry") << " .\n\n"; if (!o->geometry().polyhedral_faces_with_holes().empty()) { - filename_.stream << ttl_object_id("_geometry") << " a geo:Geometry ;\n"; + filename_.stream << ttl_object_id(o, "_geometry") << " a geo:Geometry ;\n"; filename_.stream << " geo:asWKT " << escape_for_turtle( IfcUtil::convert_utf8_to_utf32( capture_output( @@ -304,8 +308,8 @@ void TtlWktSerializer::write(const IfcGeom::TriangulationElement* o) } if (lowest_face) { - filename_.stream << ttl_object_id() << " geo:hasGeometry " << ttl_object_id("_footprint_geometry") << " .\n\n"; - filename_.stream << ttl_object_id("_footprint_geometry") << " a geo:Geometry ;\n"; + filename_.stream << ttl_object_id(o) << " geo:hasGeometry " << ttl_object_id(o, "_footprint_geometry") << " .\n\n"; + filename_.stream << ttl_object_id(o, "_footprint_geometry") << " a geo:Geometry ;\n"; filename_.stream << " geo:asWKT " << escape_for_turtle( IfcUtil::convert_utf8_to_utf32( capture_output( @@ -319,7 +323,7 @@ void TtlWktSerializer::write(const IfcGeom::TriangulationElement* o) ) << "^^geo:wktLiteral .\n\n"; } } else { - filename_.stream << ttl_object_id("_geometry") << " a geo:Geometry ;\n"; + filename_.stream << ttl_object_id(o, "_geometry") << " a geo:Geometry ;\n"; bool force_2d = true; double z_value; for (size_t i = 2; i < o->geometry().verts().size(); i += 3) { @@ -343,3 +347,118 @@ void TtlWktSerializer::write(const IfcGeom::TriangulationElement* o) ) << "^^geo:wktLiteral .\n\n"; } } + +void TtlWktSerializer::write(const IfcGeom::BRepElement* brep_obj) { +#ifdef IFOPSH_WITH_OPENCASCADE + filename_.stream << ttl_object_id(brep_obj) << " a geo:Feature ;\n"; + filename_.stream << " dcterms:identifier " << escape_for_turtle( + IfcUtil::convert_utf8_to_utf32(brep_obj->guid())) << " ;\n"; + filename_.stream << " rdfs:label " << escape_for_turtle( + IfcUtil::convert_utf8_to_utf32(brep_obj->name()) + ) << " .\n"; + + // @todo unify logic with SVG serializer + + auto itm = brep_obj->geometry().as_compound(); + TopoDS_Shape compound_local = ((ifcopenshell::geometry::OpenCascadeShape*)itm)->shape(); + delete itm; + + gp_Trsf trsf; + const auto& m = brep_obj->transformation().data()->ccomponents(); + trsf.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) + ); + + BRepBuilderAPI_Transform make_transform_global(compound_local, trsf, true); + make_transform_global.Build(); + auto compound = make_transform_global.Shape(); + + Bnd_Box bb; + try { + BRepBndLib::Add(compound, bb); + } catch (const Standard_Failure&) {} + + // Empty geometry + if (bb.IsVoid()) { + return; + } + + double x1, y1, zmin, x2, y2, zmax; + bb.Get(x1, y1, zmin, x2, y2, zmax); + + gp_Pln pln(gp_Pnt(0, 0, zmin + 1.), gp::DZ()); + + Handle(TopTools_HSequenceOfShape) wires = new TopTools_HSequenceOfShape(); + + size_t N = 0; + TopoDS_Iterator it(compound); + // Iterate over components of compound to have better chance of matching section edges to closed wires + for (; it.More(); it.Next()) { + Handle(TopTools_HSequenceOfShape) edges = new TopTools_HSequenceOfShape(); + TopoDS_Shape result = BRepAlgoAPI_Section(it.Value(), pln); + + { + TopExp_Explorer exp(result, TopAbs_EDGE); + for (; exp.More(); exp.Next()) { + edges->Append(exp.Current()); + } + } + + ShapeAnalysis_FreeBounds::ConnectEdgesToWires(edges, 1e-4, false, wires); + for (int i = 1; i <= wires->Length(); ++i) { + const TopoDS_Wire& wire = TopoDS::Wire(wires->Value(i)); + BRepTools_WireExplorer it(wire); + std::vector loop_coords; + for (; it.More(); it.Next()) { + const auto& v = it.CurrentVertex(); + auto pnt = BRep_Tool::Pnt(v); + loop_coords.push_back(pnt.X()); + loop_coords.push_back(pnt.Y()); + loop_coords.push_back(pnt.Z()); + } + std::vector loop_idxs(loop_coords.size() / 3); + for (int i = 0; i < loop_idxs.size(); ++i) { + loop_idxs[i] = i; + } + + std::string postfix = "_section_geometry_" + std::to_string(N++); + + filename_.stream << ttl_object_id(brep_obj) << " geo:hasGeometry " << ttl_object_id(brep_obj, postfix.c_str()) << " .\n\n"; + filename_.stream << ttl_object_id(brep_obj, postfix.c_str()) << " a geo:Geometry ;\n"; + filename_.stream << " geo:asWKT " << escape_for_turtle( + IfcUtil::convert_utf8_to_utf32( + capture_output( + emit_line_component, + loop_coords, + loop_idxs, + true, + POLYGON)) + ) << "^^geo:wktLiteral .\n\n"; + } + } +#endif +} + +std::string TtlWktSerializer::ttl_object_id(const IfcGeom::Element* o, const char* const postfix) +{ + using namespace ifcopenshell::geometry::settings; + auto oid = boost::replace_all_copy(object_id(o), "-", "_"); + if (oid.find('$') == std::string::npos) { + return "base:" + oid + (postfix ? postfix : (const char* const)""); + } else { + std::string base; + if (settings_.get().has()) { + base = settings_.get().get(); + } else { + base = "http://example.org/"; + } + return "<" + base + oid + (postfix ? postfix : (const char* const)"") + ">"; + } +} + +bool TtlWktSerializer::isTesselated() const { + using namespace ifcopenshell::geometry::settings; + return !settings_.get().get(); +} diff --git a/src/serializers/TtlWktSerializer.h b/src/serializers/TtlWktSerializer.h index 5e064b47ed..3b601e4a44 100644 --- a/src/serializers/TtlWktSerializer.h +++ b/src/serializers/TtlWktSerializer.h @@ -37,11 +37,12 @@ public: bool ready(); void writeHeader(); void write(const IfcGeom::TriangulationElement* o); - void write(const IfcGeom::BRepElement* /*o*/) {} + void write(const IfcGeom::BRepElement* /*o*/); void finalize() {} - bool isTesselated() const { return true; } + bool isTesselated() const; void setUnitNameAndMagnitude(const std::string& /*name*/, float /*magnitude*/) {} void setFile(IfcParse::IfcFile*) {} + std::string ttl_object_id(const IfcGeom::Element* o, const char* const postfix = nullptr); }; #endif