/********************************************************************************
* *
* Copyright 2015 IfcOpenShell and ROOT B.V. *
* *
* 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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "../ifcparse/IfcGlobalId.h"
#include
#include
#include "SvgSerializer.h"
const double PI2 = M_PI * 2.;
bool SvgSerializer::ready() {
return true;
}
void SvgSerializer::write(path_object& p, const TopoDS_Wire& wire, boost::optional> dash_array) {
/* ShapeFix_Wire fix;
Handle(ShapeExtend_WireData) data = new ShapeExtend_WireData;
for (TopExp_Explorer edges(result, TopAbs_EDGE); edges.More(); edges.Next()) {
data->Add(edges.Current());
}
fix.Load(data);
fix.FixReorder();
fix.FixConnected();
const TopoDS_Wire fixed_wire = fix.Wire(); */
bool first = true;
util::string_buffer path;
for (TopExp_Explorer edges(wire, TopAbs_EDGE); edges.More(); edges.Next()) {
const TopoDS_Edge& edge = TopoDS::Edge(edges.Current());
double u1, u2;
Handle(Geom_Curve) curve = BRep_Tool::Curve(edge, u1, u2);
Handle(Geom2d_Curve) curve2d;
if (curve.IsNull()) {
TopLoc_Location loc;
Handle_Geom_Surface surf;
BRep_Tool::CurveOnSurface(edge, curve2d, surf, loc, u1, u2);
if (curve2d.IsNull()) {
Logger::Error("Failed to obtain 2d and 3d curve from edge");
continue;
}
Handle(Standard_Type) sty = surf->DynamicType();
if (sty != STANDARD_TYPE(Geom_Plane)) {
Logger::Error("Non-planar p-curves are not supported by this serializer");
continue;
}
gp_Pln pln = Handle(Geom_Plane)::DownCast(surf)->Pln();
curve = GeomAPI::To3d(curve2d, pln);
}
Handle(Standard_Type) ty = curve->DynamicType();
bool conical = (ty == STANDARD_TYPE(Geom_Circle) || ty == STANDARD_TYPE(Geom_Ellipse));
// TODO: ALMOST_THE_SAME utilities in separate header
bool closed = fabs((u1 + PI2) - u2) < 1.e-9;
if (!polygonal_ && (conical && closed)) {
if (first) {
if (ty == STANDARD_TYPE(Geom_Circle)) {
Handle(Geom_Circle) circle = Handle(Geom_Circle)::DownCast(curve);
double r = circle->Radius();
gp_Circ c = circle->Circ();
gp_Pnt center = c.Location();
path.add(" Elips();
gp_Pnt center = e.Location();
// Write the ellipse with major radius along X axis:
path.add(" (id "
<< p.first << ")";
Logger::Warning(ss.str());
}
}
const bool reversed = edge.Orientation() == TopAbs_REVERSED;
gp_Pnt p1, p2;
curve->D0(u1, p1);
curve->D0(u2, p2);
if (reversed) {
std::swap(p1, p2);
}
if (first) {
path.add(" Position().Axis().Direction().Z() < 0;
double r1, r2;
bool larger_arc_segment = (fmod(u2 - u1 + PI2, PI2) > M_PI);
bool positive_direction = (u2 > u1);
if (mirrored != reversed) {
// In case the local coordinate system is mirrored
// the direction is reversed.
positive_direction = !positive_direction;
}
gp_Pnt center;
if (ty == STANDARD_TYPE(Geom_Circle)) {
Handle(Geom_Circle) circle = Handle(Geom_Circle)::DownCast(curve);
r1 = r2 = circle->Radius();
center = circle->Location();
} else {
Handle(Geom_Ellipse) ellipse = Handle(Geom_Ellipse)::DownCast(curve);
r1 = ellipse->MajorRadius();
r2 = ellipse->MinorRadius();
center = ellipse->Location();
}
// Make sure the arc segment is entirely inside bounding box:
growBoundingBox(center.X() - r1, center.Y() - r1);
growBoundingBox(center.X() + r1, center.Y() + r1);
// Calculate the angle between 2d vecs to have signed result
const gp_Dir& d = conic->Position().XDirection();
const gp_Dir2d d2(d.X(), d.Y());
const double ang = d2.Angle(gp::DX2d());
// Write radii
path.add(" A");
addSizeComponent(path.add(r1));
path.add(",");
addSizeComponent(path.add(r2));
// Write X-axis rotation
{ std::stringstream ss; ss << " " << ang << " ";
path.add(ss.str()); }
// Write large-arc-flag and sweep-flag
path.add(std::string(1, '0'+static_cast(larger_arc_segment)));
path.add(",");
path.add(std::string(1, '0'+static_cast(positive_direction)));
path.add(" ");
// Write arc end point
xcoords.push_back(path.add(p2.X()));
path.add(",");
ycoords.push_back(path.add(p2.Y()));
} else if (ty != STANDARD_TYPE(Geom_Line)) {
BRepAdaptor_Curve crv(edge);
GCPnts_QuasiUniformDeflection tessellater(crv, settings().deflection_tolerance());
// NB: Start at 2: 1-based and skip the first point, assume it coincides with p1.
for (int i = 2; i <= tessellater.NbPoints(); ++i) {
gp_Pnt pi = tessellater.Value(i);
path.add(" L");
xcoords.push_back(path.add(pi.X()));
path.add(",");
ycoords.push_back(path.add(pi.Y()));
growBoundingBox(pi.X(), pi.Y());
}
} else {
// Either a Geom_Line or something unimplemented,
// drawn as a straight line segment.
path.add(" L");
xcoords.push_back(path.add(p2.X()));
path.add(",");
ycoords.push_back(path.add(p2.Y()));
}
first = false;
}
path.add("\"");
if (dash_array) {
path.add(" stroke-dasharray=\"");
bool first = true;
for (auto& d : *dash_array) {
if (!first) {
path.add(" ");
}
first = false;
radii.push_back(path.add(d));
}
path.add("\"");
}
path.add("/>\n");
p.second.push_back(path);
}
SvgSerializer::path_object& SvgSerializer::start_path(const gp_Pln& pln, IfcUtil::IfcBaseEntity* storey, const std::string& id) {
auto key = std::make_pair(std::make_pair(storey, ""), path_object());
SvgSerializer::path_object& p = paths.insert(key)->second;
drawing_metadata[key.first].pln_3d = pln;
p.first = id;
return p;
}
SvgSerializer::path_object& SvgSerializer::start_path(const gp_Pln& pln, const std::string& drawing_name, const std::string& id) {
auto key = std::make_pair(std::make_pair(nullptr, drawing_name), path_object());
SvgSerializer::path_object& p = paths.insert(key)->second;
drawing_metadata[key.first].pln_3d = pln;
p.first = id;
return p;
}
namespace {
boost::optional> storey_elevation_from_element(const IfcGeom::BRepElement* o) {
for (const auto& p : o->parents()) {
if (p->type() == "IfcBuildingStorey") {
try {
const IfcGeom::ElementSettings& settings = o->geometry().settings();
double e = *p->product()->get("Elevation");
double storey_elevation = e * settings.unit_magnitude();
return std::make_pair(p->product(), storey_elevation);
} catch (...) {
continue;
}
break;
}
}
return boost::none;
}
typedef std::pair, std::array> box_t;
boost::optional edge_from_compound(TopoDS_Shape& compound) {
TopoDS_Iterator it(compound);
if (it.More()) {
TopoDS_Shape wire = it.Value();
it.Next();
if (!it.More() && wire.ShapeType() == TopAbs_WIRE) {
TopoDS_Iterator jt(wire);
if (jt.More()) {
TopoDS_Shape edge = jt.Value();
jt.Next();
if (!jt.More() && edge.ShapeType() == TopAbs_EDGE) {
return TopoDS::Edge(edge);
}
}
}
}
return boost::none;
}
class almost {
private:
double v_, eps_;
public:
almost(double v, double eps = 1.e-7)
: v_(v)
, eps_(eps)
{}
bool operator==(double other) const {
return std::fabs(other - v_) < eps_;
}
bool operator!=(double other) const {
return !(*this == other);
}
};
boost::optional box_from_compound(TopoDS_Shape& compound) {
TopExp_Explorer exp(compound, TopAbs_SHELL);
TopoDS_Shell shell;
if (exp.More()) {
shell = TopoDS::Shell(exp.Current());
exp.Next();
if (exp.More()) {
return boost::none;
}
}
else {
return boost::none;
}
if (IfcGeom::Kernel::count(shell, TopAbs_FACE) != 6) {
return boost::none;
}
TopoDS_Iterator it(shell);
for (; it.More(); it.Next()) {
const auto& face = TopoDS::Face(it.Value());
auto surf = BRep_Tool::Surface(face);
if (surf->DynamicType() != STANDARD_TYPE(Geom_Plane)) {
return boost::none;
}
auto pln = Handle(Geom_Plane)::DownCast(surf);
auto dz = std::abs(pln->Position().Direction().Z());
if (almost(0.) != dz && almost(1.) != dz) {
return boost::none;
}
auto dy = std::abs(pln->Position().Direction().Y());
if (almost(0.) != dy && almost(1.) != dy) {
return boost::none;
}
}
Bnd_Box b;
BRepBndLib::Add(compound, b, false);
double x0, y0, z0, x1, y1, z1;
b.Get(x0, y0, z0, x1, y1, z1);
return box_t{ {{x0, y0, z0}}, {{x1, y1, z1}} };
}
struct string_property {
std::string pset_name, prop_name, value;
};
template
void enumerate_string_properties(IfcUtil::IfcBaseEntity* product, It output_it) {
auto rels = product->get_inverse("IsDefinedBy");
for (auto& rel : *rels) {
if (rel->declaration().is("IfcRelDefinesByProperties")) {
auto pset = (IfcUtil::IfcBaseEntity*) (IfcUtil::IfcBaseClass*) *((IfcUtil::IfcBaseEntity*) rel)->get("RelatingPropertyDefinition");
std::string pset_name;
if (!pset->get("Name")->isNull()) {
pset_name = (std::string) *pset->get("Name");
}
IfcEntityList::ptr props = *pset->get("HasProperties");
for (auto& prop : *props) {
if (prop->declaration().is("IfcPropertySingleValue")) {
std::string name = *((IfcUtil::IfcBaseEntity*) prop)->get("Name");
IfcUtil::IfcBaseClass* v = *((IfcUtil::IfcBaseEntity*) prop)->get("NominalValue");
auto value = v->data().getArgument(0);
if (value->type() == IfcUtil::Argument_STRING) {
std::string v_str = *value;
*output_it++ = string_property{ pset_name, name, v_str };
}
}
}
}
}
}
}
namespace {
boost::optional get_curve_style_name(IfcUtil::IfcBaseEntity* item) {
auto refs = item->get_inverse("StyledByItem");
for (auto& ref : *refs) {
if (ref->declaration().is("IfcStyledItem")) {
IfcEntityList::ptr styles = *((IfcUtil::IfcBaseEntity*)ref)->get("Styles");
for (auto& s_ : *styles) {
auto s = (IfcUtil::IfcBaseEntity*) s_;
std::vector pss;
if (s->declaration().is("IfcPresentationStyleAssignment")) {
IfcEntityList::ptr pstyles = *s->get("Styles");
for (auto& ssss : *pstyles) {
pss.push_back((IfcUtil::IfcBaseEntity*) ssss);
}
} else {
pss.push_back(s);
}
for (auto& ps : pss) {
if (ps->declaration().is("IfcCurveStyle")) {
auto arg = ps->get("Name");
if (!arg->isNull()) {
return (std::string) *arg;
}
}
}
}
}
}
return boost::none;
}
}
void SvgSerializer::write(const IfcGeom::BRepElement* brep_obj) {
boost::optional object_type;
if (!brep_obj->product()->get("ObjectType")->isNull()) {
object_type = static_cast(*brep_obj->product()->get("ObjectType"));
}
std::vector>> dash_arrays;
TopoDS_Shape compound_local = brep_obj->geometry().as_compound();
for (auto& x : brep_obj->geometry()) {
dash_arrays.emplace_back();
auto item = (IfcUtil::IfcBaseEntity*) this->file->instance_by_id(x.ItemId());
auto curve_style_name = get_curve_style_name(item);
if (curve_style_name &&
(boost::starts_with(*curve_style_name, "LINE_") ||
boost::starts_with(*curve_style_name, "DASH_")))
{
std::vector tokens;
boost::split(tokens, *curve_style_name, boost::is_any_of("_"));
if (tokens.size() > 1) {
dash_arrays.back().emplace();
for (auto& tok : tokens) {
double d;
try {
d = boost::lexical_cast(tok);
} catch (boost::bad_lexical_cast&) {
continue;
}
dash_arrays.back()->push_back(d / 1000.);
}
}
}
}
const gp_Trsf& trsf = brep_obj->transformation().data();
const bool is_section = (section_ref_ && object_type && *section_ref_ == *object_type);
const bool is_elevation = (elevation_ref_ && object_type && *elevation_ref_ == *object_type);
if (is_section || is_elevation) {
BRepBuilderAPI_Transform make_transform_global(compound_local, trsf, true);
make_transform_global.Build();
// (When determinant < 0, copy is implied and the input is not mutated.)
auto compound_unmirrored = make_transform_global.Shape();
boost::optional scale;
boost::optional> size;
auto e = edge_from_compound(compound_unmirrored);
boost::optional pln;
if (e) {
TopoDS_Edge global_edge = TopoDS::Edge(e->Moved(trsf));
double u0, u1;
auto crv = BRep_Tool::Curve(global_edge, u0, u1);
if (crv->DynamicType() == STANDARD_TYPE(Geom_Line)) {
gp_Pnt P;
gp_Vec V;
crv->D1((u0 + u1) / 2., P, V);
auto N = V.Crossed(gp::DZ());
pln = gp_Pln(gp_Ax3(P, N, V));
}
}
else if (boost::optional b = box_from_compound(compound_local)) {
pln = gp_Pln().Transformed(trsf);
size = std::make_pair(
b->second[0] - b->first[0],
b->second[1] - b->first[1]
);
#if OCC_VERSION_HEX >= 0x70300
view_box_3d_.emplace();
BRepBndLib::AddOBB(compound_unmirrored, *view_box_3d_, false, false, false);
#endif
}
std::vector props;
enumerate_string_properties(brep_obj->product(), std::back_inserter(props));
std::map prop_map;
for (auto& p : props) {
prop_map[p.pset_name + "." + p.prop_name] = p.value;
}
auto pit = prop_map.find("EPset_Drawing.Scale");
if (pit != prop_map.end()) {
typedef boost::tokenizer> tokenizer;
tokenizer tok{ pit->second };
auto tokit = tok.begin();
std::string num, denum;
if (tokit != tok.end()) {
num = *tokit++;
}
tokit++;
if (tokit != tok.end()) {
denum = *tokit++;
}
if (num.size() && denum.size()) {
try {
scale = (float) boost::lexical_cast(num) / boost::lexical_cast(denum);
}
catch (boost::bad_lexical_cast&) {}
}
}
if (!emit_building_storeys_ && scale && size) {
scale_ = scale;
size_ = std::make_pair(
// The header writes values in mm
size->first * 1000 * *scale_,
size->second * 1000 * *scale_
);
}
if (pln) {
// Move pln to have projection of origin at plane center.
// This is necessary to have Poly and BRep HLR at the same position
// (Poly) is wrong otherwise.
Extrema_ExtPElS ext;
ext.Perform(gp::Origin(), *pln, 1.e-5);
auto P0 = pln->Location();
pln->SetLocation(ext.Point(1).Value());
if (!emit_building_storeys_ && scale && size) {
auto P1 = pln->Location();
gp_Vec v(P1.XYZ() - P0.XYZ());
gp_Trsf pi;
pi.SetTransformation(pln->Position());
pi.Invert();
v.Transform(pi);
offset_2d_ = std::make_pair(
(-size->first / 2. - v.X()) * 1000 * *scale_,
(-size->second / 2. + v.Y()) * 1000 * *scale_
);
}
if (!deferred_section_data_) {
deferred_section_data_.emplace();
}
std::string name = brep_obj->name();
if (name.empty()) {
name = boost::lexical_cast(brep_obj->id());
}
if (is_section) {
deferred_section_data_->push_back(vertical_section{ *pln , "Section " + name, false, scale, size });
}
if (is_elevation) {
deferred_section_data_->push_back(vertical_section{ *pln , "Elevation " + name, true, scale, size });
}
}
return;
}
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, dash_arrays, trsf, brep_obj->product(), storey, elev, brep_obj->name(), nameElement(storey, brep_obj) };
if (auto_section_ || auto_elevation_ || section_ref_ || elevation_ref_) {
element_buffer_.push_back(data);
}
if (emit_building_storeys_) {
write(data);
}
}
namespace {
class hlr_writer {
const TopoDS_Shape& shape_;
public:
typedef void result_type;
hlr_writer(const TopoDS_Shape& shape) : shape_(shape)
{}
void operator()(boost::blank&) const {
throw std::runtime_error("");
}
void operator()(Handle(HLRBRep_Algo)& algo) const {
algo->Add(shape_);
}
void operator()(Handle(HLRBRep_PolyAlgo)& algo) const {
BRepMesh_IncrementalMesh(shape_, 0.10);
algo->Load(shape_);
}
};
}
void SvgSerializer::write(const geometry_data& data) {
std::vector section_heights_storage;
const std::vector* section_heights_used = §ion_heights_storage;
if (section_data_) {
section_heights_used = section_data_.get_ptr();
} else {
if (data.storey) {
section_heights_storage.push_back(horizontal_plan{ data.storey, data.storey_elevation, +1. });
} else {
Logger::Warning("No global section height and unable to determine building storey for:", data.product);
return;
}
}
BRepBuilderAPI_Transform make_transform_global(data.compound_local, data.trsf, true);
make_transform_global.Build();
// (When determinant < 0, copy is implied and the input is not mutated.)
auto compound_unmirrored = make_transform_global.Shape();
#if OCC_VERSION_HEX >= 0x70300
if (view_box_3d_) {
Bnd_OBB obb;
BRepBndLib::AddOBB(compound_unmirrored, obb, false, false, false);
if (view_box_3d_->IsOut(obb)) {
Logger::Notice("Not including element due to viewBox", data.product);
return;
}
}
#endif
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;
trsf_mirror.SetMirror(gp_Ax2(gp::Origin(), gp::DY()));
BRepBuilderAPI_Transform make_transform_mirror(compound_unmirrored, trsf_mirror, true);
make_transform_mirror.Build();
// (When determinant < 0, copy is implied and the input is not mutated.)
auto compound = make_transform_mirror.Shape();
TopoDS_Wire annotation;
if (is_floor_plan_ && draw_door_arcs_ && data.product->declaration().is("IfcDoor")) {
boost::optional operation_type;
try {
IfcEntityList::ptr rels;
if (data.product->declaration().schema()->name() == "IFC2X3") {
rels = data.product->get_inverse("IsDefinedBy");
} else {
// Damn you, IFC
rels = data.product->get_inverse("IsTypedBy");
}
for (auto& rel : *rels) {
if (rel->declaration().name() == "IfcRelDefinesByType") {
IfcUtil::IfcBaseClass* ty = *((IfcUtil::IfcBaseEntity*)rel)->get("RelatingType");
const std::string& ty_entity_name = ty->declaration().name();
// Damn you, IFC
if (ty_entity_name == "IfcDoorStyle" || ty_entity_name == "IfcDoorType") {
operation_type = *((IfcUtil::IfcBaseEntity*)ty)->get("OperationType");
}
}
}
} catch (std::exception& e) {
Logger::Error(e);
}
if (operation_type && ((*operation_type == "SINGLE_SWING_LEFT") || (*operation_type == "SINGLE_SWING_RIGHT"))) {
const bool is_left = *operation_type == "SINGLE_SWING_LEFT";
Bnd_Box bb;
BRepBndLib::Add(data.compound_local, bb);
if (bb.IsVoid()) {
return;
}
double x1, y1, z1, x2, y2, z2;
bb.Get(x1, y1, z1, x2, y2, z2);
double width = x2 - x1;
double y12 = (y1 + y2) / 2.;
gp_Pnt center(is_left ? x1 : x2, y12, 0);
gp_Pnt p1(is_left ? x2 : x1, y12, 0);
gp_Pnt p2(is_left ? x1 : x2, y12 + width, 0);
if (!is_left) {
// circles are counter clockwise, so for swing right
// we need to reverse the points in order to get the
// shorter part of the circle arc.
std::swap(p1, p2);
}
BRepBuilderAPI_MakeEdge me(gp_Circ(gp_Ax2(center, gp::DZ()), width), p1, p2);
if (me.IsDone()) {
BRep_Builder B;
B.MakeWire(annotation);
auto edge = me.Edge();
make_transform_global.Perform(edge, true);
auto edge_global = make_transform_global.Shape();
make_transform_mirror.Perform(edge_global, true);
auto edge_global_mirrored = make_transform_mirror.Shape();
center.Transform(data.trsf);
p1.Transform(data.trsf);
p2.Transform(data.trsf);
center.Transform(trsf_mirror);
p1.Transform(trsf_mirror);
p2.Transform(trsf_mirror);
if (!is_left) {
// For the purpose of the SVG serializer we do not a topologically
// connected wire. So adding disconnected edges is fine.
B.Add(annotation, BRepBuilderAPI_MakeEdge(center, p1).Edge());
}
B.Add(annotation, edge_global_mirrored);
if (is_left) {
B.Add(annotation, BRepBuilderAPI_MakeEdge(p2, center).Edge());
}
}
}
}
bool emitted = false;
for (auto sit = section_heights_used->begin(); sit != section_heights_used->end(); ++sit) {
const auto& variant = *sit;
// Elev + offset
double cut_z = std::numeric_limits::infinity();
// Elev .. Elev(next)
std::pair range;
gp_Vec projection_direction;
gp_Pln projection_plane;
IfcUtil::IfcBaseEntity* storey = nullptr;
std::string drawing_name;
bool use_hlr = always_project_;
// @todo use visitor
// horizontal_plan, horizontal_plan_at_element, vertical_section
if (variant.which() == 0) {
const auto& plan = boost::get(variant);
storey = plan.storey;
cut_z = plan.elevation + plan.offset;
range = { plan.elevation, plan.next_elevation };
if (sit == section_heights_used->begin()) {
range.first = -std::numeric_limits::infinity();
}
projection_direction = gp::DZ();
projection_plane = gp_Pln(gp_Ax3(gp_Pnt(0, 0, cut_z), gp_Dir(0, 0, 1), gp_Dir(1, 0, 0)));
} else if (variant.which() == 1) {
projection_direction = gp::DZ();
projection_plane = gp_Pln(gp_Ax3(gp_Pnt(0, 0, cut_z), gp_Dir(0, 0, 1), gp_Dir(1, 0, 0)));
} else if (variant.which() == 2) {
const auto& section = boost::get(variant);
projection_direction = section.plane.Axis().Direction();
projection_plane = section.plane;
drawing_name = section.name;
use_hlr = section.with_projection;
}
auto& compound_to_use = is_floor_plan_ ? compound : compound_unmirrored;
if (use_hlr) { // && (hlr.which())) {
// Check if any of the bounding box points is on the correct side of the plane
Bnd_Box bb;
try {
BRepBndLib::Add(compound_to_use, bb);
}
catch (const Standard_Failure&) {}
if (bb.IsVoid()) {
continue;
}
double xs[2], ys[2], zs[2];
bb.Get(xs[0], ys[0], zs[0], xs[1], ys[1], zs[1]);
bool any_in_front = false, any_behind = false;
// See if any of the vertices is in the negative Z-axis of the projection plane
for (int i = 0; i < 8; ++i) {
gp_Pnt p(xs[(i & 1) == 1], ys[(i & 2) == 2], zs[(i & 4) == 4]);
auto d = (p.XYZ() - projection_plane.Location().XYZ()).Dot(projection_plane.Axis().Direction().XYZ());
int state;
if (std::abs(d) < 1.e-5) {
state = 0;
} else {
state = d < 0. ? -1 : 1;
}
if (state == -1) {
any_in_front = true;
} else if (state == +1) {
any_behind = true;
}
}
// Exclude annotations, spaces and grids from HLR
if (any_in_front && !data.product->declaration().is("IfcAnnotation") && !data.product->declaration().is("IfcSpace") && !data.product->declaration().is("IfcGrid")) {
TopoDS_Shape* compound_to_hlr = &compound_to_use;
TopoDS_Shape subtracted_shape;
if (any_in_front && any_behind && data.product->declaration().is("IfcSlab") && is_floor_plan_) {
// This is currently ony for slanted roof slabs on floor plans
bool should_cut = false;
TopExp_Explorer exp(compound_to_use, TopAbs_FACE);
for (; exp.More(); exp.Next()) {
const TopoDS_Face& face = TopoDS::Face(exp.Current());
BRepGProp_Face prop(face);
gp_Pnt p;
gp_Vec normal_direction;
double u0, u1, v0, v1;
BRepTools::UVBounds(face, u0, u1, v0, v1);
prop.Normal((u0 + u1) / 2., (v0 + v1) / 2., p, normal_direction);
const double dx = std::fabs(normal_direction.X());
const double dy = std::fabs(normal_direction.Y());
const double dz = std::fabs(normal_direction.Z());
auto largest = dx > dy ? dx : dy;
largest = largest > dz ? largest : dz;
if (largest < (1. - 1.e-5)) {
bool any_in_front_face = false, any_behind_face = false;
TopExp_Explorer exp2(face, TopAbs_VERTEX);
for (; exp2.More(); exp2.Next()) {
gp_Pnt p = BRep_Tool::Pnt(TopoDS::Vertex(exp2.Current()));
auto d = (p.XYZ() - projection_plane.Location().XYZ()).Dot(projection_plane.Axis().Direction().XYZ());
int state;
if (std::abs(d) < 1.e-5) {
state = 0;
} else {
state = d < 0. ? -1 : 1;
}
if (state == -1) {
any_in_front_face = true;
} else if (state == +1) {
any_behind_face = true;
}
}
should_cut = any_in_front_face && any_behind_face;
if (should_cut) {
break;
}
}
}
if (should_cut) {
gp_Pnt points[4] = {
gp_Pnt(xs[0] - 1., ys[0] - 1., cut_z),
gp_Pnt(xs[1] + 1., ys[0] - 1., cut_z),
gp_Pnt(xs[1] + 1., ys[1] + 1., cut_z),
gp_Pnt(xs[0] - 1., ys[1] + 1., cut_z)
};
try {
BRepBuilderAPI_MakePolygon mp(points[0], points[1], points[2], points[3], true);
auto w = mp.Wire();
BRepBuilderAPI_MakeFace mf(w);
auto f = mf.Face();
gp_Pnt ref = projection_plane.Position().Location().XYZ() + projection_plane.Position().Direction().XYZ();
BRepPrimAPI_MakeHalfSpace mhs(f, ref);
auto s = mhs.Solid();
subtracted_shape = BRepAlgoAPI_Cut(compound_to_use, s).Shape();
compound_to_hlr = &subtracted_shape;
} catch (...) {
Logger::Error("Failed to cut element for HLR", data.product);
}
}
}
if (is_floor_plan_ && storey) {
if (storey_hlr.find(storey) == storey_hlr.end()) {
if (use_hlr_poly_) {
storey_hlr[storey] = new HLRBRep_PolyAlgo;
} else {
storey_hlr[storey] = new HLRBRep_Algo;
}
}
hlr_writer vis(*compound_to_hlr);
boost::apply_visitor(vis, storey_hlr[storey]);
}
else {
hlr_writer vis(*compound_to_hlr);
boost::apply_visitor(vis, hlr);
}
}
}
TopoDS_Iterator it(compound_to_use);
auto dash_it = data.dash_arrays.begin();
TopoDS_Face largest_closed_wire_face;
double largest_closed_wire_area = 0.;
path_object* po = nullptr;
// Iterate over components of compound to have better chance of matching section edges to closed wires
for (; it.More(); it.Next(), ++dash_it) {
const TopoDS_Shape& subshape = it.Value();
Bnd_Box bb;
try {
BRepBndLib::Add(it.Value(), bb);
} catch (const Standard_Failure&) {}
// Empty geometry
if (bb.IsVoid()) {
continue;
}
double x1, y1, zmin, x2, y2, zmax;
bb.Get(x1, y1, zmin, x2, y2, zmax);
// Determine slicing plane z coordinate, priority:
// 1) explicitly set global section height
// 2) containing building storey elevation + 1m
// 3) zmin (from geometry bounding box) + 1m
if (variant.which() == 1) {
cut_z = zmin + 1.;
}
gp_Pln pln;
if (variant.which() < 2) {
pln = gp_Pln(gp_Pnt(0, 0, cut_z), gp::DZ());
}
else {
const auto& section = boost::get(variant);
pln = section.plane;
}
gp_Vec bbmin(x1, y1, zmin);
gp_Vec bbmax(x2, y2, zmax);
auto bbdif = bbmax - bbmin;
auto proj = projection_direction ^ bbdif ^ projection_direction;
std::string object_type;
auto ot_arg = data.product->get("ObjectType");
if (!ot_arg->isNull()) {
object_type = (std::string) *ot_arg;
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 - 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;
if (object_type.size()) {
// postfix the object_type for CSS matching
boost::replace_all(svg_name, "class=\"IfcAnnotation\"", "class=\"IfcAnnotation " + object_type + "\"");
}
if (po == nullptr) {
if (storey) {
po = &start_path(pln, storey, svg_name);
} else {
po = &start_path(pln, drawing_name, svg_name);
}
}
if (object_type == "Dimension") {
TopExp_Explorer exp(subshape, TopAbs_EDGE, TopAbs_FACE);
for (; exp.More(); exp.Next()) {
const auto& e = TopoDS::Edge(exp.Current());
TopoDS_Vertex v0, v1;
TopExp::Vertices(e, v0, v1);
gp_Pnt p0 = BRep_Tool::Pnt(v0);
gp_Pnt p1 = BRep_Tool::Pnt(v1);
BRep_Builder B;
TopoDS_Wire W;
B.MakeWire(W);
B.Add(W, e);
write(*po, W);
// @todo should we take the average parameter value instead?
gp_XYZ center = (p0.XYZ() + p1.XYZ()) / 2.;
double z_rotation = gp_Dir(p0.XYZ() - p1.XYZ()).AngleWithRef(gp_Dir(1., 0., 0.), gp_Dir(0., 0., 1.));
z_rotation *= 180. / M_PI;
if (z_rotation < -88) {
z_rotation += 180;
}
if (z_rotation > +90) {
z_rotation -= 180;
}
std::string text_offset = "8";
if (scale_) {
text_offset = "1";
}
util::string_buffer path;
// dominant-baseline="central" is not well supported in IE.
// so we add a 0.35 offset to the dy of the tspans
path.add(" ");
std::vector labels{};
GProp_GProps prop;
BRepGProp::LinearProperties(e, prop);
const double area = prop.Mass();
std::stringstream ss;
ss << std::setprecision(2) << std::fixed << std::showpoint << area;
labels.push_back(ss.str() + "m");
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);
}
} else if (object_type == "Symbol") {
TopExp_Explorer exp(subshape, TopAbs_WIRE, TopAbs_FACE);
for (; exp.More(); exp.Next()) {
const auto& W = TopoDS::Wire(exp.Current());
write(*po, W, *dash_it);
}
}
// We're finished processing IfcAnnotation instances
continue;
}
if (subshape.ShapeType() > TopAbs_FACE) {
// Except for annotations we only emit solids and surfaces to SVG.
emitted = true;
continue;
}
// No intersection with bounding box, fail early
if (variant.which() < 2) {
if (zmin > cut_z || zmax < cut_z) continue;
}
emitted = true;
if (po == nullptr) {
if (storey) {
po = &start_path(pln, storey, data.svg_name);
} else {
po = &start_path(pln, drawing_name, data.svg_name);
}
}
TopoDS_Shape result = BRepAlgoAPI_Section(subshape, pln);
if (variant.which() == 2) {
gp_Trsf trsf;
trsf.SetTransformation(gp::XOY(), pln.Position());
result.Move(trsf);
gp_Trsf trsf_mirror;
trsf_mirror.SetMirror(gp_Ax2(gp::Origin(), gp::DY()));
BRepBuilderAPI_Transform make_transform_mirror(result, trsf_mirror, true);
make_transform_mirror.Build();
result = make_transform_mirror.Shape();
}
Handle(TopTools_HSequenceOfShape) edges = new TopTools_HSequenceOfShape();
Handle(TopTools_HSequenceOfShape) wires = new TopTools_HSequenceOfShape();
{
TopExp_Explorer exp(result, TopAbs_EDGE);
for (; exp.More(); exp.Next()) {
edges->Append(exp.Current());
}
}
ShapeAnalysis_FreeBounds::ConnectEdgesToWires(edges, 1e-5, false, wires);
gp_Pnt prev;
for (int i = 1; i <= wires->Length(); ++i) {
// @nb not const, because in case of storey annotations we might
// generate a new wire with fixed length
TopoDS_Wire wire = TopoDS::Wire(wires->Value(i));
if (wire.Closed() && (print_space_names_ || print_space_areas_) && data.product->declaration().is("IfcSpace")) {
// we explicitly specify the surface here, to later on
// simplify the projection from {x,y,z} to {u, v} because
// we know we can simply discard z.
BRepBuilderAPI_MakeFace mf(pln, wire);
if (mf.IsDone()) {
TopoDS_Face f = mf.Face();
GProp_GProps prop;
BRepGProp::SurfaceProperties(f, prop);
const double area = prop.Mass();
if (area > largest_closed_wire_area) {
largest_closed_wire_face = f;
largest_closed_wire_area = area;
}
}
}
if (data.product->declaration().is("IfcBuildingStorey") && storey_height_display_ != SH_NONE && 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;
// @nb we don't actually factor in the length unit.
// 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);
}
// @todo these settings are getting out of hand, how can we
// streamline this?
std::string anchor;
gp_Pnt* anchor_pt;
if (storey_height_display_ == SH_FULL) {
anchor = "end";
anchor_pt = &p1;
} else {
anchor = "start";
anchor_pt = &p0;
auto d = (p1.XYZ() - p0.XYZ());
d.Normalize();
const double shll = storey_height_line_length_.get_value_or(2.);
d *= shll;
gp_Pnt p1x(p0.XYZ() + d);
wire = BRepBuilderAPI_MakePolygon(p0, p1x).Wire();
}
// dominant-baseline="central" is not well supported in IE.
// so we add a 0.35 offset to the dy of the tspans
path.add(" X()));
path.add("\" y=\"");
ycoords.push_back(path.add(anchor_pt->Y()));
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("X()));
path.add("\" dy=\"");
path.add(boost::lexical_cast(dy));
path.add("em\">");
path.add(l);
path.add("");
}
path.add("");
po->second.push_back(path);
}
write(*po, wire);
}
}
if (!largest_closed_wire_face.IsNull()) {
std::vector points;
TopExp_Explorer exp(largest_closed_wire_face, TopAbs_VERTEX);
for (; exp.More(); exp.Next()) {
if (exp.Current().Orientation() == TopAbs_FORWARD) {
const TopoDS_Vertex& v = TopoDS::Vertex(exp.Current());
points.push_back(BRep_Tool::Pnt(v));
}
}
// we brute force the largest distance between pairs of points where
// the center is contained in the face.
std::pair furthest_points = { nullptr, nullptr };
double furthest_points_distance = 0.;
boost::optional center_point;
BRepTopAdaptor_FClass2d fcls(largest_closed_wire_face, BRep_Tool::Tolerance(largest_closed_wire_face));
for (size_t i = 0; i < points.size(); ++i) {
for (size_t j = 0; j < i; ++j) {
const gp_Pnt& pa = points[i];
const gp_Pnt& pb = points[j];
// Since the text is always displayed horizontally,
// the distance is not simply euclidian, but we
// favour the x-component;
const double d = std::sqrt(
10 * ((pa.X() - pb.X()) * (pa.X() - pb.X())) +
1 * ((pa.Y() - pb.Y()) * (pa.Y() - pb.Y()))
);
if (d > furthest_points_distance) {
// Sample some points on the line and assure it's inside.
bool all_inside = true;
for (int i = 5; i < 95; ++i) {
gp_Pnt p3d((pa.XYZ() + (pb.XYZ() - pa.XYZ()) * i / 100.));
gp_Pnt2d p2d(p3d.X(), p3d.Y());
if (fcls.Perform(p2d) != TopAbs_IN) {
all_inside = false;
}
}
if (all_inside) {
gp_Pnt p3d((pa.XYZ() + pb.XYZ()) * 0.5);
gp_Pnt2d p2d(p3d.X(), p3d.Y());
furthest_points = { &pa, &pb };
furthest_points_distance = d;
center_point = p3d;
}
}
}
}
if (center_point) {
std::vector labels;
if (print_space_names_) {
labels.push_back(data.ifc_name);
}
if (print_space_names_ && data.product->declaration().is("IfcSpace")) {
auto attr = data.product->get("LongName");
if (!attr->isNull()) {
std::string long_name = *attr;
if (!long_name.empty()) {
labels.insert(labels.begin(), long_name);
}
}
}
if (print_space_areas_) {
GProp_GProps prop;
BRepGProp::SurfaceProperties(largest_closed_wire_face, prop);
const double area = prop.Mass();
std::stringstream ss;
ss << std::setprecision(2) << std::fixed << std::showpoint << area;
labels.push_back(ss.str() + "m²");
}
util::string_buffer path;
// dominant-baseline="central" is not well supported in IE.
// so we add a 0.35 offset to the dy of the tspans
path.add(" X()));
path.add("\" y=\"");
ycoords.push_back(path.add(center_point->Y()));
path.add("\"");
if (space_name_transform_) {
path.add(" transform=\"" + *space_name_transform_ + "\"");
}
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("X()));
path.add("\" dy=\"");
path.add(boost::lexical_cast(dy));
path.add("em\">");
path.add(l);
path.add("");
}
path.add("");
po->second.push_back(path);
}
}
if (po && !annotation.IsNull()) {
write(*po, annotation);
}
}
if (!emitted) {
Logger::Warning("Element not written to SVG due to section heights", data.product);
}
}
void SvgSerializer::setBoundingRectangle(double width, double height) {
size_ = std::make_pair(width, height);
}
std::array, 3> SvgSerializer::resize() {
// identity matrix;
std::array, 3> m = {{ {{1,0,0}},{{0,1,0}},{{0,0,1}} }};
if (size_) {
// Scale the resulting image to a bounding rectangle specified by command line arguments
// or specified by IfcAnnotation[ObjectType=DRAWING]
const double dx = xmax - xmin;
const double dy = ymax - ymin;
double sc, cx, cy;
if (offset_2d_ && scale_) {
// offset_2d is the offset in plane u,v coordinates as we want to keep the
// plane coordinates used for HLR close to the model origin.
sc = (*scale_) * 1000;
cx = offset_2d_->first;
cy = offset_2d_->second;
} else if (scale_) {
sc = (*scale_) * 1000;
cx = (xmax + xmin) / 2. * sc - size_->first * center_x_.get_value_or(0.5);
cy = (ymax + ymin) / 2. * sc - size_->second * center_y_.get_value_or(0.5);
} else {
if (calculated_scale_) {
sc = *calculated_scale_;
}
else {
if (dx / size_->first > dy / size_->second) {
sc = size_->first / dx;
}
else {
sc = size_->second / dy;
}
calculated_scale_ = sc;
}
cx = xmin * sc;
cy = ymin * sc;
}
m = {{ {{sc,0,-cx}},{{0,sc,-cy}},{{0,0,1}} }};
float_item_list::const_iterator it;
for (it = xcoords.begin() + xcoords_begin; it != xcoords.end(); ++it, ++xcoords_begin) {
double& v = (*it)->value();
v = v * sc - cx;
}
for (it = ycoords.begin() + ycoords_begin; it != ycoords.end(); ++it, ++ycoords_begin) {
double& v = (*it)->value();
v = v * sc - cy;
}
for (it = radii.begin() + radii_begin; it != radii.end(); ++it, ++radii_begin) {
(*it)->value() *= sc;
}
}
return m;
}
namespace {
template
TopoDS_Compound occt_join(T t) {
BRep_Builder B;
TopoDS_Compound C;
B.MakeCompound(C);
if (!t.IsNull()) {
TopoDS_Iterator it(t);
for (; it.More(); it.Next()) {
B.Add(C, it.Value());
}
}
return C;
}
template
TopoDS_Compound occt_join(T t, Ts... tss) {
BRep_Builder B;
TopoDS_Compound C;
B.MakeCompound(C);
if (!t.IsNull()) {
TopoDS_Iterator it(t);
for (; it.More(); it.Next()) {
B.Add(C, it.Value());
}
}
auto rest = occt_join(tss...);
if (!rest.IsNull()) {
TopoDS_Iterator it(rest);
for (; it.More(); it.Next()) {
B.Add(C, it.Value());
}
}
return C;
}
class hlr_calc {
private:
const HLRAlgo_Projector& projector_;
public:
typedef TopoDS_Shape result_type;
hlr_calc(const HLRAlgo_Projector& projector) : projector_(projector)
{}
TopoDS_Shape operator()(boost::blank&) const {
throw std::runtime_error("");
}
TopoDS_Shape operator()(Handle(HLRBRep_Algo)& algo) {
algo->Projector(projector_);
algo->Update();
algo->Hide();
HLRBRep_HLRToShape hlr_shapes(algo);
return occt_join(hlr_shapes.OutLineVCompound(), hlr_shapes.VCompound());
}
TopoDS_Shape operator()(Handle(HLRBRep_PolyAlgo)& algo) {
algo->Projector(projector_);
algo->Update();
HLRBRep_PolyHLRToShape hlr_shapes;
hlr_shapes.Update(algo);
return occt_join(hlr_shapes.OutLineVCompound(), hlr_shapes.VCompound());
}
};
}
void SvgSerializer::draw_hlr(const gp_Pln& pln, const drawing_key& drawing_name) {
gp_Trsf trsf;
trsf.SetTransformation(pln.Position());
HLRAlgo_Projector projector(trsf, false, 1.);
hlr_calc vis(projector);
TopoDS_Shape hlr_compound_unmirrored = boost::apply_visitor(vis, drawing_name.first ? this->storey_hlr[drawing_name.first] : hlr);
if (!hlr_compound_unmirrored.IsNull()) {
// Compound 3D curves for mirroring to work
ShapeFix_Edge sfe;
TopExp_Explorer exp(hlr_compound_unmirrored, TopAbs_EDGE);
for (; exp.More(); exp.Next()) {
sfe.FixAddCurve3d(TopoDS::Edge(exp.Current()));
}
// Mirror to match SVG coord system.
// @todo this is very wasteful. We better do the Y-mirror in the SVG writing and
// not on the TopoDS_Shape input.
TopoDS_Shape hlr_compound;
if (drawing_name.first == nullptr) {
gp_Trsf trsf_mirror;
trsf_mirror.SetMirror(gp_Ax2(gp::Origin(), gp::DY()));
BRepBuilderAPI_Transform make_transform_mirror(hlr_compound_unmirrored, trsf_mirror, true);
make_transform_mirror.Build();
hlr_compound = make_transform_mirror.Shape();
} else {
// In case of building storey-based floor plan the mirroring has already
// been taken into account before projection.
hlr_compound = hlr_compound_unmirrored;
}
exp.Init(hlr_compound, TopAbs_EDGE);
BRep_Builder B;
path_object* po;
if (drawing_name.first) {
po = &start_path(pln, drawing_name.first, "class=\"projection\"");
} else {
po = &start_path(pln, drawing_name.second, "class=\"projection\"");
}
for (; exp.More(); exp.Next()) {
TopoDS_Wire w;
B.MakeWire(w);
B.Add(w, exp.Current());
write(*po, w);
}
}
}
void SvgSerializer::resetScale() {
// reset the bounding box, as a subsequent drawing (elevation, section) will be centered, but use the same scale.
// this is a separate call now as we first need to read drawing extents for automatically positioning sections and
// elevations
xmin = +std::numeric_limits::infinity();
ymin = +std::numeric_limits::infinity();
xmax = -std::numeric_limits::infinity();
ymax = -std::numeric_limits::infinity();
}
void SvgSerializer::addTextAnnotations(const drawing_key& k) {
auto& meta = drawing_metadata[k];
boost::optional> range;
if (k.first && section_data_) {
for (auto& sd : *section_data_) {
if (sd.which() == 0) {
const auto& plan = boost::get(sd);
if (k.first == plan.storey) {
range = std::make_pair(plan.elevation, plan.next_elevation);
}
}
}
}
auto annotations = file->instances_by_type("IfcAnnotation");
if (annotations) {
for (auto& ann_ : *annotations) {
auto ann = (IfcUtil::IfcBaseEntity*) ann_;
auto ot = ann->get("ObjectType");
auto nm = ann->get("Name");
auto ds = ann->get("Description");
auto pl = ann->get("ObjectPlacement");
if (!ot->isNull() && !nm->isNull() && !ds->isNull() && !pl->isNull()) {
auto object_type = (std::string) *ot;
auto name = (std::string) *nm;
auto desc = (std::string) *ds;
if (object_type == "Text") {
IfcGeom::Kernel kernel(file);
gp_Trsf trsf;
if (kernel.convert_placement(*pl, trsf)) {
auto v = trsf.TranslationPart();
if (k.first) {
v.ChangeCoord(1) *= -1.;
trsf.SetTranslationPart(v);
}
if (!range || (v.Z() >= range->first && v.Z() < range->second)) {
if (meta.pln_3d.Position().Direction().Dot(gp_Dir(trsf.HVectorialPart().Column(3))) > 0.99) {
auto svg_name = nameElement(ann);
path_object* po;
if (k.first) {
po = &start_path(meta.pln_3d, k.first, svg_name);
}
else {
po = &start_path(meta.pln_3d, k.second, svg_name);
}
if (object_type.size()) {
// postfix the object_type for CSS matching
boost::replace_all(svg_name, "class=\"IfcAnnotation\"", "class=\"IfcAnnotation " + object_type + "\"");
}
boost::optional font_size;
std::vector tokens;
boost::split(tokens, name, boost::is_any_of("_"));
if (tokens.size() == 2) {
try {
font_size = boost::lexical_cast(tokens.back());
}
catch (...) {}
}
// @todo column or row?
double z_rotation = gp_Dir(trsf.HVectorialPart().Column(1)).AngleWithRef(gp_Dir(1., 0., 0.), gp_Dir(0., 0., 1.));
z_rotation *= 180. / M_PI;
util::string_buffer path;
// dominant-baseline="central" is not well supported in IE.
// so we add a 0.35 offset to the dy of the tspans
path.add(" ");
std::vector labels{ desc };
for (auto lit = labels.begin(); lit != labels.end(); ++lit) {
const auto& l = *lit;
double dy = labels.begin() == lit
? 0.0 // align bottom
: 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);
}
}
}
}
}
}
}
}
void SvgSerializer::finalize() {
doWriteHeader();
for (auto& p : drawing_metadata) {
addTextAnnotations(p.first);
}
for (auto& p : storey_hlr) {
draw_hlr(drawing_metadata[{p.first, ""}].pln_3d, { p.first, "" });
}
auto m = resize();
// Update the paper space scale matrices
for (auto& p : paths) {
drawing_metadata[p.first].matrix_3 = m;
}
if (!deferred_section_data_.is_initialized() && (auto_section_ || auto_elevation_)) {
deferred_section_data_.emplace();
}
// @nb keep in mind Y-axis is negated in these 6 definitions to account
// for coordinate system differences.
if (auto_section_) {
{
gp_Pln pln(gp_Ax3(
gp_Pnt((xmin + xmax) / 2., (ymin + ymax) / 2., 0.),
gp_Dir(-1, 0, 0),
gp_Dir(0, -1, 0)));
deferred_section_data_->push_back(vertical_section{ pln , "Section North South", false });
}
{
gp_Pln pln(gp_Ax3(
gp_Pnt((xmin + xmax) / 2., (ymin + ymax) / -2., 0.),
gp_Dir(0, -1, 0),
gp_Dir(-1, 0, 0)));
deferred_section_data_->push_back(vertical_section{ pln , "Section East West", true });
}
}
if (auto_elevation_) {
{
gp_Pln pln(gp_Ax3(
gp_Pnt(0., -(ymin - 10.), 0.),
gp_Dir(0, 1, 0),
gp_Dir(1, 0, 0)));
deferred_section_data_->push_back(vertical_section{ pln , "Elevation South", true });
}
{
gp_Pln pln(gp_Ax3(
gp_Pnt(xmax + 10., 0., 0.),
gp_Dir(1, 0, 0),
gp_Dir(0, 1, 0)));
deferred_section_data_->push_back(vertical_section{ pln , "Elevation East", true });
}
{
gp_Pln pln(gp_Ax3(
gp_Pnt(0., -(ymax + 10.), 0.),
gp_Dir(0, -1, 0),
gp_Dir(-1, 0, 0)));
deferred_section_data_->push_back(vertical_section{ pln , "Elevation North", true });
}
{
gp_Pln pln(gp_Ax3(
gp_Pnt(xmin - 10., 0., 0.),
gp_Dir(-1, 0, 0),
gp_Dir(0, -1, 0)));
deferred_section_data_->push_back(vertical_section{ pln , "Elevation West", true });
}
}
resetScale();
if (deferred_section_data_ && deferred_section_data_->size() && element_buffer_.size()) {
// Draw door arcs only on floor plans.
is_floor_plan_ = false;
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) {
if (use_hlr_poly_) {
hlr = new HLRBRep_PolyAlgo;
} else {
hlr = new HLRBRep_Algo;
}
}
section_data_ = std::vector{ sd };
for (auto& e : element_buffer_) {
write(e);
}
if (use_hlr) {
const auto& section = boost::get(sd);
const auto& ax = section.plane.Position();
draw_hlr(ax, { nullptr, drawing_name });
}
if (storey_height_display_ != SH_NONE && 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);
gp_Pln elev_pln(gp_Ax3(gp_Pnt(0, 0, elev), gp::DZ(), gp::DX()));
//, 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);
const double shll = storey_height_line_length_.get_value_or(2.);
BRepBuilderAPI_MakeFace mf(elev_pln, x0 - shll, x1 + shll, y0 - shll, y1 + shll);
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 = (std::string) *a2;
}
write(geometry_data{
C,{boost::none},trsf,storey,storey,elev,name,nameElement(storey)
});
}
}
}
}
auto m3 = resize();
auto k = std::make_pair(nullptr, drawing_name);
drawing_metadata[k].matrix_3 = m3;
resetScale();
// @todo does this probably call Nullify()
hlr = boost::blank();
}
}
std::multimap::const_iterator it;
boost::optional previous;
for (it = paths.begin(); it != paths.end(); ++it) {
if (!previous || it->first != *previous) {
if (previous) {
svg_file << " \n";
}
std::ostringstream oss;
if (it->first.first) {
svg_file << " first.first) << " " << writeMetadata(drawing_metadata[it->first]) << ">\n";
} else {
auto n = it->first.second;
IfcUtil::escape_xml(n);
svg_file << " first]) << ">\n";
}
}
svg_file << " second.first << ">\n";
std::vector::const_iterator jt;
for (jt = it->second.second.begin(); jt != it->second.second.end(); ++jt) {
svg_file << jt->str();
}
svg_file << " \n";
previous = it->first;
}
if (previous) {
svg_file << " \n";
}
svg_file << "" << std::endl;
}
void SvgSerializer::writeHeader() {
// This doesn't do anything anymore because there is now the option that an
// IfcAnnotation[ObjectType=DRAWING] defines the SVG viewBox and dimensions
}
void SvgSerializer::doWriteHeader() {
svg_file << "