Skip to content

Commit bfe3601

Browse files
committed
wkt-use-section option for svg-like occt section cuts instead of polyhedral topology
1 parent cc6249d commit bfe3601

4 files changed

Lines changed: 154 additions & 27 deletions

File tree

src/ifcconvert/IfcConvert.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1030,6 +1030,7 @@ int main(int argc, char** argv) {
10301030

10311031
#ifdef IFOPSH_WITH_OPENCASCADE
10321032
if (output_extension == SVG) {
1033+
// @todo turn these all into proper settings
10331034
if (vmap.count("section-height-from-storeys") != 0) {
10341035
if (vmap.count("section-height")) {
10351036
static_cast<SvgSerializer*>(serializer.get())->setSectionHeightsFromStoreys(section_height);

src/ifcgeom/GeometrySerializer.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,11 +81,17 @@ inline namespace settings {
8181
static constexpr const char* const name = "base-uri";
8282
static constexpr const char* const description = "Base URI for products to be used in RDF-based serializations.";
8383
};
84+
85+
struct WktUseSection : public SettingBase<WktUseSection, bool> {
86+
static constexpr const char* const name = "wkt-use-section";
87+
static constexpr const char* const description = "Use a geometrical section rather than full polyhedral output and footprint in TTL WKT";
88+
static constexpr bool defaultvalue = false;
89+
};
8490
}
8591

8692
class SerializerSettings : public SettingsContainer <
8793
// @todo should we use tuple_cat here to unify the settings into a single class?
88-
std::tuple<UseElementNames, UseElementGuids, UseElementStepIds, UseElementTypes, UseYUp, WriteGltfEcef, FloatingPointDigits, BaseUri>
94+
std::tuple<UseElementNames, UseElementGuids, UseElementStepIds, UseElementTypes, UseYUp, WriteGltfEcef, FloatingPointDigits, BaseUri, WktUseSection>
8995
>
9096
{};
9197

src/serializers/TtlWktSerializer.cpp

Lines changed: 143 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,19 @@
1919

2020
#include "TtlWktSerializer.h"
2121

22+
#ifdef IFOPSH_WITH_OPENCASCADE
23+
#include "../ifcgeom/kernels/opencascade/OpenCascadeConversionResult.h"
24+
25+
#include <TopTools_HSequenceOfShape.hxx>
26+
#include <BRepBuilderAPI_Transform.hxx>
27+
#include <BRepBndLib.hxx>
28+
#include <BRepAlgoAPI_Section.hxx>
29+
#include <ShapeAnalysis_FreeBounds.hxx>
30+
#include <BRepTools_WireExplorer.hxx>
31+
#include <TopoDS.hxx>
32+
33+
#endif
34+
2235
#include <iomanip>
2336
#include <iostream>
2437
#include <vector>
@@ -210,8 +223,15 @@ TtlWktSerializer::TtlWktSerializer(const stream_or_filename& filename, const ifc
210223
, filename_(filename)
211224
{
212225
const auto& tri_setting = geometry_settings.get<ifcopenshell::geometry::settings::TriangulationType>().get();
213-
if (tri_setting != ifcopenshell::geometry::settings::POLYHEDRON_WITH_HOLES) {
214-
throw std::runtime_error("The RDF Turtle WKT serializer needs POLYHEDRON_WITH_HOLES triangulation output");
226+
if (settings_.get<ifcopenshell::geometry::settings::WktUseSection>().get()) {
227+
const auto& it_output = geometry_settings.get<ifcopenshell::geometry::settings::IteratorOutput>().get();
228+
if (it_output != ifcopenshell::geometry::settings::NATIVE) {
229+
throw std::runtime_error("The RDF Turtle WKT serializer needs native geometry when section mode is enabled");
230+
}
231+
} else {
232+
if (tri_setting != ifcopenshell::geometry::settings::POLYHEDRON_WITH_HOLES) {
233+
throw std::runtime_error("The RDF Turtle WKT serializer needs POLYHEDRON_WITH_HOLES triangulation output");
234+
}
215235
}
216236
filename_.stream << std::setprecision(settings.get<ifcopenshell::geometry::settings::FloatingPointDigits>().get());
217237
}
@@ -237,32 +257,16 @@ void TtlWktSerializer::writeHeader()
237257

238258
void TtlWktSerializer::write(const IfcGeom::TriangulationElement* o)
239259
{
240-
auto ttl_object_id = [&](const char* const postfix = nullptr) {
241-
using namespace ifcopenshell::geometry::settings;
242-
auto oid = boost::replace_all_copy(object_id(o), "-", "_");
243-
if (oid.find('$') == std::string::npos) {
244-
return "base:" + oid + (postfix ? postfix : (const char* const)"");
245-
} else {
246-
std::string base;
247-
if (settings_.get<BaseUri>().has()) {
248-
base = settings_.get<BaseUri>().get();
249-
} else {
250-
base = "http://example.org/";
251-
}
252-
return "<" + base + oid + (postfix ? postfix : (const char* const) "") + ">";
253-
}
254-
};
255-
256-
filename_.stream << ttl_object_id() << " a geo:Feature ;\n";
260+
filename_.stream << ttl_object_id(o) << " a geo:Feature ;\n";
257261
filename_.stream << " dcterms:identifier " << escape_for_turtle(
258262
IfcUtil::convert_utf8_to_utf32(o->guid())) << " ;\n";
259263
filename_.stream << " rdfs:label " << escape_for_turtle(
260264
IfcUtil::convert_utf8_to_utf32(o->name())
261265
) << " ;\n";
262-
filename_.stream << " geo:hasGeometry " << ttl_object_id("_geometry") << " .\n\n";
266+
filename_.stream << " geo:hasGeometry " << ttl_object_id(o, "_geometry") << " .\n\n";
263267

264268
if (!o->geometry().polyhedral_faces_with_holes().empty()) {
265-
filename_.stream << ttl_object_id("_geometry") << " a geo:Geometry ;\n";
269+
filename_.stream << ttl_object_id(o, "_geometry") << " a geo:Geometry ;\n";
266270
filename_.stream << " geo:asWKT " << escape_for_turtle(
267271
IfcUtil::convert_utf8_to_utf32(
268272
capture_output(
@@ -304,8 +308,8 @@ void TtlWktSerializer::write(const IfcGeom::TriangulationElement* o)
304308
}
305309

306310
if (lowest_face) {
307-
filename_.stream << ttl_object_id() << " geo:hasGeometry " << ttl_object_id("_footprint_geometry") << " .\n\n";
308-
filename_.stream << ttl_object_id("_footprint_geometry") << " a geo:Geometry ;\n";
311+
filename_.stream << ttl_object_id(o) << " geo:hasGeometry " << ttl_object_id(o, "_footprint_geometry") << " .\n\n";
312+
filename_.stream << ttl_object_id(o, "_footprint_geometry") << " a geo:Geometry ;\n";
309313
filename_.stream << " geo:asWKT " << escape_for_turtle(
310314
IfcUtil::convert_utf8_to_utf32(
311315
capture_output(
@@ -319,7 +323,7 @@ void TtlWktSerializer::write(const IfcGeom::TriangulationElement* o)
319323
) << "^^geo:wktLiteral .\n\n";
320324
}
321325
} else {
322-
filename_.stream << ttl_object_id("_geometry") << " a geo:Geometry ;\n";
326+
filename_.stream << ttl_object_id(o, "_geometry") << " a geo:Geometry ;\n";
323327
bool force_2d = true;
324328
double z_value;
325329
for (size_t i = 2; i < o->geometry().verts().size(); i += 3) {
@@ -343,3 +347,118 @@ void TtlWktSerializer::write(const IfcGeom::TriangulationElement* o)
343347
) << "^^geo:wktLiteral .\n\n";
344348
}
345349
}
350+
351+
void TtlWktSerializer::write(const IfcGeom::BRepElement* brep_obj) {
352+
#ifdef IFOPSH_WITH_OPENCASCADE
353+
filename_.stream << ttl_object_id(brep_obj) << " a geo:Feature ;\n";
354+
filename_.stream << " dcterms:identifier " << escape_for_turtle(
355+
IfcUtil::convert_utf8_to_utf32(brep_obj->guid())) << " ;\n";
356+
filename_.stream << " rdfs:label " << escape_for_turtle(
357+
IfcUtil::convert_utf8_to_utf32(brep_obj->name())
358+
) << " .\n";
359+
360+
// @todo unify logic with SVG serializer
361+
362+
auto itm = brep_obj->geometry().as_compound();
363+
TopoDS_Shape compound_local = ((ifcopenshell::geometry::OpenCascadeShape*)itm)->shape();
364+
delete itm;
365+
366+
gp_Trsf trsf;
367+
const auto& m = brep_obj->transformation().data()->ccomponents();
368+
trsf.SetValues(
369+
m(0, 0), m(0, 1), m(0, 2), m(0, 3),
370+
m(1, 0), m(1, 1), m(1, 2), m(1, 3),
371+
m(2, 0), m(2, 1), m(2, 2), m(2, 3)
372+
);
373+
374+
BRepBuilderAPI_Transform make_transform_global(compound_local, trsf, true);
375+
make_transform_global.Build();
376+
auto compound = make_transform_global.Shape();
377+
378+
Bnd_Box bb;
379+
try {
380+
BRepBndLib::Add(compound, bb);
381+
} catch (const Standard_Failure&) {}
382+
383+
// Empty geometry
384+
if (bb.IsVoid()) {
385+
return;
386+
}
387+
388+
double x1, y1, zmin, x2, y2, zmax;
389+
bb.Get(x1, y1, zmin, x2, y2, zmax);
390+
391+
gp_Pln pln(gp_Pnt(0, 0, zmin + 1.), gp::DZ());
392+
393+
Handle(TopTools_HSequenceOfShape) wires = new TopTools_HSequenceOfShape();
394+
395+
size_t N = 0;
396+
TopoDS_Iterator it(compound);
397+
// Iterate over components of compound to have better chance of matching section edges to closed wires
398+
for (; it.More(); it.Next()) {
399+
Handle(TopTools_HSequenceOfShape) edges = new TopTools_HSequenceOfShape();
400+
TopoDS_Shape result = BRepAlgoAPI_Section(it.Value(), pln);
401+
402+
{
403+
TopExp_Explorer exp(result, TopAbs_EDGE);
404+
for (; exp.More(); exp.Next()) {
405+
edges->Append(exp.Current());
406+
}
407+
}
408+
409+
ShapeAnalysis_FreeBounds::ConnectEdgesToWires(edges, 1e-4, false, wires);
410+
for (int i = 1; i <= wires->Length(); ++i) {
411+
const TopoDS_Wire& wire = TopoDS::Wire(wires->Value(i));
412+
BRepTools_WireExplorer it(wire);
413+
std::vector<double> loop_coords;
414+
for (; it.More(); it.Next()) {
415+
const auto& v = it.CurrentVertex();
416+
auto pnt = BRep_Tool::Pnt(v);
417+
loop_coords.push_back(pnt.X());
418+
loop_coords.push_back(pnt.Y());
419+
loop_coords.push_back(pnt.Z());
420+
}
421+
std::vector<int> loop_idxs(loop_coords.size() / 3);
422+
for (int i = 0; i < loop_idxs.size(); ++i) {
423+
loop_idxs[i] = i;
424+
}
425+
426+
std::string postfix = "_section_geometry_" + std::to_string(N++);
427+
428+
filename_.stream << ttl_object_id(brep_obj) << " geo:hasGeometry " << ttl_object_id(brep_obj, postfix.c_str()) << " .\n\n";
429+
filename_.stream << ttl_object_id(brep_obj, postfix.c_str()) << " a geo:Geometry ;\n";
430+
filename_.stream << " geo:asWKT " << escape_for_turtle(
431+
IfcUtil::convert_utf8_to_utf32(
432+
capture_output(
433+
emit_line_component,
434+
loop_coords,
435+
loop_idxs,
436+
true,
437+
POLYGON))
438+
) << "^^geo:wktLiteral .\n\n";
439+
}
440+
}
441+
#endif
442+
}
443+
444+
std::string TtlWktSerializer::ttl_object_id(const IfcGeom::Element* o, const char* const postfix)
445+
{
446+
using namespace ifcopenshell::geometry::settings;
447+
auto oid = boost::replace_all_copy(object_id(o), "-", "_");
448+
if (oid.find('$') == std::string::npos) {
449+
return "base:" + oid + (postfix ? postfix : (const char* const)"");
450+
} else {
451+
std::string base;
452+
if (settings_.get<BaseUri>().has()) {
453+
base = settings_.get<BaseUri>().get();
454+
} else {
455+
base = "http://example.org/";
456+
}
457+
return "<" + base + oid + (postfix ? postfix : (const char* const)"") + ">";
458+
}
459+
}
460+
461+
bool TtlWktSerializer::isTesselated() const {
462+
using namespace ifcopenshell::geometry::settings;
463+
return !settings_.get<WktUseSection>().get();
464+
}

src/serializers/TtlWktSerializer.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,12 @@ class SERIALIZERS_API TtlWktSerializer : public WriteOnlyGeometrySerializer {
3737
bool ready();
3838
void writeHeader();
3939
void write(const IfcGeom::TriangulationElement* o);
40-
void write(const IfcGeom::BRepElement* /*o*/) {}
40+
void write(const IfcGeom::BRepElement* /*o*/);
4141
void finalize() {}
42-
bool isTesselated() const { return true; }
42+
bool isTesselated() const;
4343
void setUnitNameAndMagnitude(const std::string& /*name*/, float /*magnitude*/) {}
4444
void setFile(IfcParse::IfcFile*) {}
45+
std::string ttl_object_id(const IfcGeom::Element* o, const char* const postfix = nullptr);
4546
};
4647

4748
#endif

0 commit comments

Comments
 (0)