/********************************************************************************
* *
* 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 "../ifcparse/IfcGlobalId.h"
#include "SvgSerializer.h"
const double PI2 = M_PI * 2.;
bool SvgSerializer::ready() {
return true;
}
void SvgSerializer::write(path_object& p, const TopoDS_Wire& wire) {
/* 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 (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("\"/>\n");
p.second.push_back(path);
}
SvgSerializer::path_object& SvgSerializer::start_path(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;
p.first = id;
return p;
}
SvgSerializer::path_object& SvgSerializer::start_path(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;
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;
}
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;
}
}
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"));
}
TopoDS_Shape compound_local = brep_obj->geometry().as_compound();
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) {
auto e = edge_from_compound(compound_local);
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 = gp::DZ().Crossed(V);
gp_Pln pln(gp_Ax3(P, N, V));
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 });
}
if (is_elevation) {
deferred_section_data_->push_back(vertical_section{ pln , "Elevation " + name, true });
}
}
}
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();
geometry_data data{ compound_local, trsf, brep_obj->product(), storey, elev, brep_obj->name(), nameElement(storey, brep_obj) };
if (buffer_elements_) {
element_buffer_.push_back(data);
}
write(data);
}
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();
// 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;
IfcUtil::IfcBaseEntity* storey = nullptr;
std::string drawing_name;
bool use_hlr = false;
// @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();
} else if (variant.which() == 1) {
projection_direction = gp::DZ();
} else if (variant.which() == 2) {
const auto& section = boost::get(variant);
projection_direction = section.plane.Axis().Direction();
drawing_name = section.name;
use_hlr = section.with_projection;
}
if (use_hlr && hlr) {
hlr->Add(compound_unmirrored);
}
TopoDS_Iterator it(compound);
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()) {
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_Vec bbmin(x1, y1, zmin);
gp_Vec bbmax(x2, y2, zmax);
auto bbdif = bbmax - bbmin;
auto proj = projection_direction ^ bbdif ^ projection_direction;
if (data.product->declaration().is("IfcAnnotation") && (proj.Magnitude() > 1.e-5) && zmin >= range.first && zmin <= range.second) {
if (po == nullptr) {
if (storey) {
po = &start_path(storey, data.svg_name);
} else {
po = &start_path(drawing_name, data.svg_name);
}
}
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);
// @todo should we take the average parameter value instead?
gp_XYZ center = (p0.XYZ() + p1.XYZ()) / 2.;
BRep_Builder B;
TopoDS_Wire W;
B.MakeWire(W);
B.Add(W, e);
write(*po, W);
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);
}
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) {
po = &start_path(storey, data.svg_name);
}
// Create a horizontal cross section 1 meter above the bottom point of the shape
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;
}
TopoDS_Shape result = BRepAlgoAPI_Section(subshape, pln);
if (variant.which() == 2) {
gp_Trsf trsf;
trsf.SetTransformation(gp::XOY(), pln.Position());
result.Move(trsf);
}
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) {
const 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;
}
}
}
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) {
gp_Pnt p3d((pa.XYZ() + pb.XYZ()) / 2.);
gp_Pnt2d p2d(p3d.X(), p3d.Y());
if (fcls.Perform(p2d) == TopAbs_IN) {
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("\">");
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) {
this->width = width;
this->height = height;
this->rescale = true;
}
void SvgSerializer::resize() {
if (rescale) {
// Scale the resulting image to a bounding rectangle specified by command line arguments
const double dx = xmax - xmin;
const double dy = ymax - ymin;
double sc, cx, cy;
if (scale_) {
sc = (*scale_) * 1000;
cx = (xmax + xmin) / 2. * sc - width / 2.;
cy = (ymax + ymin) / 2. * sc - height / 2.;
} else {
if (calculated_scale_) {
sc = *calculated_scale_;
} else {
if (dx / width > dy / height) {
sc = width / dx;
} else {
sc = height / dy;
}
calculated_scale_ = sc;
}
cx = xmin * sc;
cy = ymin * sc;
}
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;
}
}
// reset the bounding box, as a subsequent drawing (elevation, section) will be centered, but use the same scale.
xmin = +std::numeric_limits::infinity();
ymin = +std::numeric_limits::infinity();
xmax = -std::numeric_limits::infinity();
ymax = -std::numeric_limits::infinity();
}
void SvgSerializer::finalize() {
resize();
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 = false;
std::string drawing_name;
if (sd.which() == 2) {
const auto& section = boost::get(sd);
use_hlr = section.with_projection;
drawing_name = section.name;
}
if (use_hlr) {
hlr = new HLRBRep_Algo;
}
*section_data_ = { sd };
for (auto& e : element_buffer_) {
write(e);
}
if (use_hlr) {
const auto& section = boost::get(sd);
gp_Ax2 transform = section.plane.Position().Ax2();
HLRAlgo_Projector projector(transform);
hlr->Projector(projector);
hlr->Update();
hlr->Hide();
HLRBRep_HLRToShape hlr_shapes(hlr);
auto compound = hlr_shapes.VCompound();
TopExp_Explorer exp(compound, TopAbs_EDGE);
BRep_Builder B;
auto& po = start_path(drawing_name, "class=\"projection\"");
for (; exp.More(); exp.Next()) {
TopoDS_Wire w;
B.MakeWire(w);
B.Add(w, exp.Current());
write(po, w);
}
}
resize();
if (use_hlr) {
hlr.Nullify();
}
}
}
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) << ">\n";
} else {
svg_file << " first.second << "\" class=\"section\">\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() {
svg_file << "