From 4ca64ac42e7b5165b3c50a5df165e6e8ca33e05e Mon Sep 17 00:00:00 2001 From: Thomas Krijnen Date: Fri, 5 Mar 2021 21:50:32 +0100 Subject: [PATCH] SVG: preliminary --draw-storey-heights --- src/ifcconvert/IfcConvert.cpp | 5 ++ src/serializers/SvgSerializer.cpp | 112 +++++++++++++++++++++++++++++- src/serializers/SvgSerializer.h | 7 ++ 3 files changed, 121 insertions(+), 3 deletions(-) diff --git a/src/ifcconvert/IfcConvert.cpp b/src/ifcconvert/IfcConvert.cpp index afc82617e6..9b8efa5b18 100644 --- a/src/ifcconvert/IfcConvert.cpp +++ b/src/ifcconvert/IfcConvert.cpp @@ -380,6 +380,8 @@ int main(int argc, char** argv) { "Creates SVG cross section drawings automatically based on model extents") ("auto-elevation", "Creates SVG elevation drawings automatically based on model extents") + ("draw-storey-heights", + "Draws a horizontal line at the height of building storeys in vertical drawings") ("svg-xmlns", "Stores name and guid in a separate namespace as opposed to data-name, data-guid") ("svg-poly", @@ -949,6 +951,9 @@ int main(int argc, char** argv) { if (vmap.count("print-space-areas") != 0) { static_cast(serializer.get())->setPrintSpaceAreas(true); } + if (vmap.count("draw-storey-heights") != 0) { + static_cast(serializer.get())->setDrawStoreyHeights(true); + } if (bounding_width.is_initialized() && bounding_height.is_initialized()) { static_cast(serializer.get())->setBoundingRectangle(bounding_width.get(), bounding_height.get()); } diff --git a/src/serializers/SvgSerializer.cpp b/src/serializers/SvgSerializer.cpp index c9f9f6fe94..8b9c9eaf44 100644 --- a/src/serializers/SvgSerializer.cpp +++ b/src/serializers/SvgSerializer.cpp @@ -78,6 +78,8 @@ #include "../ifcparse/IfcGlobalId.h" +#include + #include "SvgSerializer.h" const double PI2 = M_PI * 2.; @@ -486,6 +488,7 @@ void SvgSerializer::write(const IfcGeom::BRepElement* brep_obj) { auto p = storey_elevation_from_element(brep_obj); IfcUtil::IfcBaseEntity* storey = p ? p->first : nullptr; double elev = p ? p->second : std::numeric_limits::quiet_NaN(); + // @todo is it correct to call nameElement() here with a single storey (what if this element spans multiple?) geometry_data data{ compound_local, trsf, brep_obj->product(), storey, elev, brep_obj->name(), nameElement(storey, brep_obj) }; if (auto_section_ || auto_elevation_ || section_ref_ || elevation_ref_) { @@ -540,6 +543,10 @@ void SvgSerializer::write(const geometry_data& data) { // (When determinant < 0, copy is implied and the input is not mutated.) auto compound_unmirrored = make_transform_global.Shape(); + if (is_floor_plan_) { + BRepBndLib::Add(compound_unmirrored, bnd_); + } + // SVG has a coordinate system with the origin in the *upper*-left corner // therefore we mirror the shape along the XZ-plane. gp_Trsf trsf_mirror; @@ -863,9 +870,10 @@ void SvgSerializer::write(const geometry_data& data) { object_type.erase(std::remove_if(object_type.begin(), object_type.end(), [](char c) { return !std::isalnum(c); }), object_type.end()); } - if (data.product->declaration().is("IfcAnnotation") && // is an Annotation - (proj.Magnitude() > 1.e-5) && // when projected onto the view has a length - zmin >= range.first && zmin <= range.second) // the Z-coords are within the range of the building storey + if (data.product->declaration().is("IfcAnnotation") && // is an Annotation + (proj.Magnitude() > 1.e-5) && // when projected onto the view has a length + zmin >= range.first && zmin < (range.second - 1.e-5)) // the Z-coords are within the range of the building storey, + // this excludes the upper bound with a small tolerance { auto svg_name = data.svg_name; @@ -1016,6 +1024,63 @@ void SvgSerializer::write(const geometry_data& data) { } write(*po, wire); + + if (data.product->declaration().is("IfcBuildingStorey") && draw_storey_heights_ && wires->Length() == 1 && IfcGeom::Kernel::count(wire, TopAbs_EDGE) == 1) { + + std::string elev_str; + + const double lu = file->getUnit("LENGTHUNIT").second; + auto a = data.product->get("Elevation"); + if (!a->isNull()) { + double elev = *a; + elev *= lu; + if (almost(1.) == lu) { + // m + elev_str = boost::str(boost::format("%.3f") % elev); + } + else { + elev_str = boost::str(boost::format("%d") % ((int) elev)); + } + } + + std::vector labels{ data.ifc_name, elev_str }; + util::string_buffer path; + + TopExp_Explorer exp(wire, TopAbs_EDGE); + auto edge = TopoDS::Edge(exp.Current()); + TopoDS_Vertex v0, v1; + TopExp::Vertices(edge, v0, v1); + gp_Pnt p0 = BRep_Tool::Pnt(v0); + gp_Pnt p1 = BRep_Tool::Pnt(v1); + + if (p0.X() > p1.X()) { + std::swap(p0, p1); + } + + // dominant-baseline="central" is not well supported in IE. + // so we add a 0.35 offset to the dy of the tspans + path.add(" "); + for (auto lit = labels.begin(); lit != labels.end(); ++lit) { + const auto& l = *lit; + double dy = labels.begin() == lit + ? 0.35 - (labels.size() - 1.) / 2. + : 1.0; // <- dy is relative to the previous text element, so + // always 1 for successive spans. + path.add("(dy)); + path.add("em\">"); + path.add(l); + path.add(""); + } + path.add(""); + po->second.push_back(path); + } } } @@ -1383,11 +1448,13 @@ void SvgSerializer::finalize() { for (auto& sd : *deferred_section_data_) { bool use_hlr = true; + const gp_Pln* pln = nullptr; std::string drawing_name; if (sd.which() == 2) { const auto& section = boost::get(sd); use_hlr = section.with_projection; drawing_name = section.name; + pln = §ion.plane; } if (use_hlr) { @@ -1410,6 +1477,45 @@ void SvgSerializer::finalize() { draw_hlr(ax, { nullptr, drawing_name }); } + if (draw_storey_heights_ && pln && std::abs(pln->Position().Direction().Z()) < 1.e-5) { + auto storeys = this->file->instances_by_type("IfcBuildingStorey"); + if (storeys) { + const double lu = file->getUnit("LENGTHUNIT").second; + for (auto& s : *storeys) { + auto storey = (IfcUtil::IfcBaseEntity*) s; + auto a = storey->get("Elevation"); + if (!a->isNull()) { + double elev = *a; + elev *= lu; + auto svg_name = nameElement(storey); + auto po = &start_path(*pln, drawing_name, svg_name); + + gp_Pln elev_pln(gp_Ax3(gp_Pnt(0, 0, elev), gp::DZ(), pln->Position().XDirection())); + auto ref_y = pln->Position().YDirection().XYZ().Dot(pln->Position().Location().XYZ()); + + double x0, y0, z0, x1, y1, z1; + bnd_.Get(x0, y0, z0, x1, y1, z1); + + // negate Y, see auto_elevation + BRepBuilderAPI_MakeFace mf(elev_pln, x0 - 1., x1 + 1., -(y1 + 1.), -(y0 - 1.)); + gp_Trsf trsf; + TopoDS_Compound C; + BRep_Builder B; + B.MakeCompound(C); + B.Add(C, mf.Face()); + std::string name; + auto a2 = storey->get("Name"); + if (!a2->isNull()) { + name = *a2; + } + write(geometry_data{ + C,trsf,storey,storey,elev,name,nameElement(storey) + }); + } + } + } + } + auto m3 = resize(); auto k = std::make_pair(nullptr, drawing_name); diff --git a/src/serializers/SvgSerializer.h b/src/serializers/SvgSerializer.h index e806789840..14239900f1 100644 --- a/src/serializers/SvgSerializer.h +++ b/src/serializers/SvgSerializer.h @@ -32,6 +32,7 @@ #include #include #include +#include #include #include @@ -132,6 +133,7 @@ protected: boost::optional scale_, calculated_scale_, center_x_, center_y_; bool with_section_heights_from_storey_, rescale, print_space_names_, print_space_areas_; + bool draw_storey_heights_; bool draw_door_arcs_, is_floor_plan_; bool auto_section_, auto_elevation_; bool use_namespace_, use_hlr_poly_, always_project_; @@ -155,6 +157,10 @@ protected: std::string namespace_prefix_; + // Used for drawing the storey elevation heights + // @todo maybe better to rely on a screen-space bounding box + Bnd_Box bnd_; + void draw_hlr(const gp_Pln& pln, const drawing_key& drawing_name); public: @@ -204,6 +210,7 @@ public: void setSectionHeightsFromStoreys(double offset=1.); void setPrintSpaceNames(bool b) { print_space_names_ = b; } void setPrintSpaceAreas(bool b) { print_space_areas_ = b; } + void setDrawStoreyHeights(bool b) { draw_storey_heights_ = b; } void setDrawDoorArcs(bool b) { draw_door_arcs_ = b; } std::array, 3> resize();